Java一些容易忘的小知识点(一)

最近重新回顾了一些Java的基础知识,发现了很多容易被大家忽视或者混淆的小知识点(初级),所以用一篇文章来总结一下。

1.逻辑操作符的短路

这个相信大家都会知道,&和&&,|和||是有区别的,单个操作符会将整个表达式都执行完才得出结果,而两个操作符当可以判断出结果时就不会再去执行后面了,这就是所谓的“短路”,例如

if(3>2 | 1<3 | 2>3){
System.out.println("hello");
}

上面这个if中的条件会全部执行

if(3>2 || 1<3 || 2>3){
System.out.println("hello");
}

当这么写时我们在Eclipse中会看到警告,它提示我们条件后面的1<3 || 2>3deadcode,就是不会被执行的死代码,因为3>2已经是true了,我们知道或关系中只要有一项为true,整个表达式就是true,所以不会再去判断后面而直接执行输出。

2.float的变量

相信初级java程序员在笔试中经常会遇到这么一种题,在一些变量声明中找出有错误的选项,其中有一个选项是float fNum = 1.2; 是不是觉得这个并没有错啊,其实这种声明是不正确的,不知道大家记不记得书上说过当赋值float变量的时候需要再末尾加上F来表示,否则就需要强转,这么做的原因就是当我们赋值时,这个小数1.2会被默认为double类型,直接将double赋给float当然不行了,因为double精度高,不会隐式转换,所以需要加F或者强转才行。

float fNum = 1.2;  ×

float fNum = 1.2F;   √

float fNum = (float) 1.2F;   √

3.移位操作符

这个在我刚学java的时候是很讨厌它的,不要问我为什么。

移位操作符号有三种,左移<<, 右移>>,无符号右移>>>

这种移动是指二进制的移动,还不会二进制转换的童鞋请百度学习下,这个比较简单我就不介绍了,例如int num = 12;二进制是1100

num<<1 : 1100左移一位,低位补0,即11000,所以结果是24

num>>1 : 1100右移一位,由于num是正数,右移高位补0,低位0会消失,即变成0110,结果是6(是不是很6啊,666)

num>>>1 : 无符号右移,1100右移一位,右移高位补0,低位0会消失,即变成0110,结果是6

再来看看负数的情况 int num = -12;二进制为11111111111111111111111111110100(第一位是符号位,1代表负数),正数二进制各位取反加1就变成了对应负数的二进制,不懂的百度去吧,或者Integer.toBinaryString(int);可以直接打印出二进制

num<<1 : 左移一位,低位补0,即11111111111111111111111111101000,所以结果是取反加1后,-24

num>>1 : 右移一位,由于num是负数,右移高位补1,低位0会消失,即变成11111111111111111111111111111010,结果是-6(不要以为负数右移结果就是加负号,这里只是碰巧)

num>>>1 : 无符号右移一位,高位补0,即01111111111111111111111111111010,结果。。。2147483642

总结:<<低位补0 ,>>有符号,正数高位补0,负数高位补1 , >>>无符号,高位补0

4.自增自减

++i,I++,--i,I--这个吧。。++是自增,--是自减,那先++和后++有什么区别呢,假设i=1;

int sum = ++i; 这句相当于i=i+1;sum=i; //i=2,sum=2

int sum = i++;这句相当于sum=i;i=i+1;//sum=1,i=2

减法也是类似,其实就是执行顺序的区别

5.强转问题

int firstNum = (int)1.2;int secondNum = (int)1.7;现在这两个int值都是double隐式转成int的,他们的值是多少??答案都是1,要注意强转是没有什么四舍五入的概念的,转int只会向下去整,想要四舍五入的值可以调Math.round()方法。

6.循环

while,dowhile,for,foreach大家应该都很熟了,这里说一下for(int i=0;i<10;i++,j--),for的步进step(第三项的那些表达式)可以用逗号分隔写多个表达式,我们经常写什么i++这种,估计都忘了并不只能写一个表达式了吧,而且for里面是可以什么都不写的,但要有两个“;”for(;;)相当于死循环。Java5出现了一种更方便的遍历数组或集合的foreach,

即for(int a : int数组),这种方式更便于阅读,但它的性能效率会低一些。这里再提一下break和continue的区别,假如有一个遍历10次的循环,当我在第五次是执行continue则会跳出本次循环去执行第六次循环,continue后面的代码本次就执行不到了,如果是break那循环一共就执行了五次,break会直接停止并跳出循环。

7.重载

方法重载也是笔试题中经常出现的,我们要记住只有两个方法名字相同但参数不同的时候才叫重载(参数顺序不同的也是重载,但并不推荐这么写),返回值并不能作为区分两个方法的条件,例如与public void myMethod(int num,String str){}重载的方法

public void myMethod(int a,int b,int c){} 

public void myMethod(String str,int num){}

public int myMethod(int num,String str){} ×

重载这还有个比较特殊的问题,就是基本数据类型的自动包装,我们看看下面的代码

public static void main(String[] args){
add(1,2);
}
public static void add(int a,int b){
System.out.println("a");
}
public static void add(Integer a,Integer b){
System.out.println("b");
}
public static void add(int a,Integer b){
System.out.println("c");
}
public static void add(String a,String b){
System.out.println("d");
}

上面这段代码会输出“a”,这应该没什么说的,现在我们把①③注释掉会发现依然会执行②,把①②注释掉会执行③,这就是自动包装机制。值得注意的是,当我们只注释①你会发现编译时不通过的,为什么?因为编译器已经懵了,它不知道②③应该去执行哪个,所以不允许编译,它认为这两个是一样的。总体来说就是编译器会自动寻找最匹配的方法,当无法辨别最适合方法时编译不会通过。

8.变量初始化

这个应该不算是个问题,但是有时容易被忽略。当我们定义了一个成员变量时,像int age;即便不去给它赋值,这个age也是有默认值0的,但是如果是在本地方法中定义的话就必须做初始化了,比如我们的一个方法public void check(){ int age;System.out.println(age);}这种编译时不通过的,The local variable a may not have been initialized,所以要给它一个默认值才行。

9.static

静态修饰可以用于类直接调用而不用new对象,static修饰的方法中是不允许出现this的,因为static会在类加载时执行,而this是指当前对象,static方法中用this极有可能当前对象还没在内存中。static代码块和static成员变量优先级相同,是按书写顺序执行的。如果存在父类,则根基类的static会先执行,然后在执行子类。(据说static变量会在类加载时被创建在堆内存中,然后在栈内存存引用便于快速访问)

10.引用值的改变

首先我们来看看下面的代码

int[] arr = new int[]{1,1,1};
int[] arr2 = arr;
arr2[0] = 2;
System.out.println(arr[0]);

相信大家都知道输出的结果应该是2(因为arr和arr2都指向了内存中的这个数组),那再来看一段代码

String str = "a";
String str2 = str;
str2 = "b";
System.out.println(str);

这个输出是什么呢?“b”?nonono,如果你觉得是"b"的话就被我带沟里了,这个输出应该还是"a",大家可以看看String的源码,我们所说的字符串在底层其实是用字符一个一个拼起来的,“a”其实是final的,它被初始化之后值就不可改变,所以执行前两行str和str2确实都指向"a",当执行第三句时会在内存中再开辟一块内存放"b",然后让str2指向"b",这是"a"依然存在并且str是指向它的,所以str不会变,这是比较特殊的。(基本数据类型我就不说了,那属于复制)

11.枚举

枚举我用的并不多,这里就大概总结一下比较简单的用法

public enum EnumDemo {
MON,TUS,WED,THU,FRI,SAT,SUN;
}

关键字enum就是枚举了,其里面定义的都是一些常量。感觉枚举最方便的就是配合switch

public static void main(String[] args){
EnumDemo enumDemos;
enumDemos = EnumDemo.SUN;
switch(enumDemos){
case MON:
System.out.println(enumDemos.toString());
break;
case TUS:
break;
case WED:
break;
case THU:
break;
case FRI:
break;
case SAT:
break;
case SUN:
//下标+枚举值数组集
System.out.println(enumDemos.ordinal());//ordinal方法会返回该枚举常量在枚举类中的下标,从0开始,像SUN的下标就是6
break;
default:
break;
}
for(EnumDemo demo : enumDemos.values()){//values()会返回一个数组,里面是所有定义的枚举常量
System.out.println(demo.toString());
}
}

12.权限

这个我就百度复制了

访问权限 子类其他包

public √ √ √

protect √ √ √ ×

default √ √ × ×

private × × ×

13.类命名及权限

这个问题其实我是不太想写在这里的,但发现有些东西被我们习惯性的忽略了。

我们知道java文件中只能有一个public的类并且该类名必须与文件名相同,如果没有public的类可以吗?会有什么问题?大家可能会说当然可以没有public的类了。

这是对的,那一个不加权限修饰的类,类名和文件名不同会导致什么问题?接下来我总结一下通过demo看到的结果。

首先类不可以被private和protected修饰,没有修饰(就是default)和public是可以的。

要注意的是用public修饰的类名必须和文件名一致,否则编译不通过。但是default的类名和文件名不同是可以编译的,不过你会发现在Eclipse上无法去运行,用命令执行会报错NoClassDefFoundError-ClassNotFountException,因为例如一个Demo.java中是class MyTest{}这么个类,编译之后生成的文件是MyTest.class,但虚拟机会去找Demo.class,所以找不到就异常了。

14.继承多态

笔试选择题比较容易出现

class Father{

public void myMethod(){

System.out.println("father");

}

}

public class Son{

public void myMethod(){

System.out.println("son");

}

public static void main(String[] args){ 
Father f = new Son();//父类引用指向子类对象

f.myMethod();//这个执行的是son的方法,也就是说会打印son

Son son = (Son)f;//这种情况才可以强转,否则父类对象是不可以强转成子类的

}

}

15.final

final 修饰的变量一旦初始化就不可以改变,static final 的变量必须在声明时就赋初值,而非static的final变量可以将赋值操作置于构造方法中。

final修饰的方法,final的方法不可以被子类重写

final修饰的类不可以被继承,但可以继承别的类

16.String

说到string就比较多了,这个我们太常用。由于string是引用类型对象,我们一直在玩它的引用,即使我们发现一个字符串的值变了,其实也只是改变指向了生成了一个新的字符串。举个例子,String str = "a";str = "b";首先在栈内存中创建了“a”(常量在栈内存,变量存在堆内存,堆满了会触发gc回收),让str指向“a”,接着在栈内存创建“b”,str指向“b”,这时“a”独立存在于栈内存,当方法结束后“a”就被回收了。至于为什么不马上回收“a”,估计是为了重用,因为可能下面的代码会执行例如String str2 = “a”;这样的操作,就不用在创建“a”了。还有一种很常用的形式,字符串连接,String str = "a" + "b" + "c";这种形式,这时其实编译器会自动创建一个StringBuilder对象,用它的append方法来连接字符串以达到一定的优化效果,但并不意味着我们可以不用管优化的问题,像在循环中还是创建StringBuilder比较好。

本文持续更新!!!

你可能感兴趣的:(Java)