Java中容易被你忽略的细节(一)

1.Java中的条件运算符
条件运算符是”? : “是Java中唯一一个三目运算符,格式为:
表达式1 ?表达式2 : 表达式3
表达式1的类型必须是boolean类型(或者是可以通过拆箱转换为boolean类型的Boolean类型)
,当表达式1的值为true时,条件表达式的结果就是表达式2的值,为false时,结果为表
达式3的值。表达式2与表达式3的类型可以是任意类型。根据表达式2与表达式3的类型
不同,条件表达式的类型也会随之不同。具体的判断如下:
1.如果二者(表达式2与表达式3)的类型形同,条件表达式的类型为表达式2(表达式3)的类型
2.如果二者之一类型为boolean,另外一个类型为Boolean,”?:”的类型为boolean
3.如果二者之一为引用常量null,则:
1)如果另外一个为引用类型,条件表达式的类型为引用类型
2)如果另外一个为基本数据类型,条件表达式的类型为基本数据类型对应的包装类型,
即基本数据类型装箱后的类型。
4.如果二者为除boolean外的基本数据类型(或者基本数据类型对应的包装类型),则条件
表达式的类型为基本数据类型中的一种(不包括boolean)
总结:根据表达式2与表达式3的类型不同,条件表达式的类型也会不同。

2.在Java运算中,存在一些关系到顺序的计算,这些计算顺序在C/C++语言中是不确定的,
并且最后的结果也没有保障。而Java不同,Java经过编译之后,生成的是与平台无关的
字节码,并且交互对象是Java虚拟机,与底层的硬件环境无关,这些运算在Java中是
确定的。在Java中,操作数的计算顺序是从左向右的,也就是首先计算左侧的操作数,
然后再计算其右侧的操作数。操作数从左向右的计算规则与运算符的结合性无关,就算
运算符是由右向左结合的,也会在运算之前确定左侧的操作数。
符合运算符可以自动将右侧运算结果类型转换为左侧操作数的类型。
如:byte b=1;
b=b+1; //错误 因为1是int类型的
b+=1; //正确,相当于b=(byte)(b+1);

3.在Java中交换变量的三种方式:
1).int temp;
temp=a;
a=b;
b=temp;
2).a=a+b;
b=a-b;
a=a-b;
3).a=a^b;
b=a^b;
a=a^b;
/**

  • 位异或(^)运算有这样的性质,就是两个整型数据x与y,有:
  • (x^y^y)=x
  • 就是如果一个变量x异或另外一个变量y两次,结果为x.
  • */

4.Java中的Switch语句:
switch表达式可以是byte,short,char,int,以及它们对应的包装类Byte,Short,Character,
Integer类型,也可以是枚举类型,String类型。
switch(表达式)
{
case 常量表达式或枚举常量名1:
语句;
……
default:
语句;
}
case语句可以有0-n个,default语句可以有0个或1个。另外任意两个case表达式的值
不允许相同,case常量表达式或枚举常量的值必须可赋值给switch表达式的类型。
switch语句处理包装类:都是将包装类型间接转换为基本数据类型处理,然后进行
case相等的比较。(包装类型拆箱为基本数据类型)
switch语句处理枚举类型:对于枚举类型,首先会在内部生成一个匿名类,该匿名类
含有一个int[]类型的静态final成员变量,数组的长度为枚举常量的个数,数组元素
的值为枚举常量的序数(ordinal方法返回的值),然后通过该数组元素的值来进行选择。
switch对枚举进行处理时,辅助的静态匿名类(含有静态成员)是由编译器生成。如果
我们自己在程序中声明匿名类,类中不允许有静态成员。
枚举类的Values方法返回该枚举类型的数组,数组包含该枚举类所有的枚举常量。每个
枚举常量都有一个序数,从0开始,依次递增,顺序与声明枚举类时枚举常量在枚举类
中出现的顺序一致。可以使用ordinal方法获得该序数,并且为数组元素依次赋值,
从1开始。
switch语句对String的处理:当switch表达式是String类型的时候,会将switch语句拆分
成两个switch语句来处理,第一个switch语句根据对象的哈希码来对一个临时变量赋值,
第二个switch语句根据该变量的值来匹配case表达式的值。
当switch类型为String类型时,将switch语句拆分为两个switch语句,分别对String对象
的哈希码及临时变量来辅助完成。

5.在Java中,使用”+”来连接两个字符串。当”+”两边的操作数是String类型时(如果只有
一个操作数是String类型,则系统也会将另外一个操作数转换为String类型),就会执行
字符串连接运算。
当使用”+”对字符串进行连接时,会创建一个临时的StringBuilder对象,该对象调用
append方法负责字符串的连接操作,然后再调用StringBuilder类的toString方法转换
成String对象。
String s1=”Hello”;
String s2=”World!”;
System.out.println(s1+s2);
可以认为实际的代码是这样的:
System.out.println((new StringBuilder()).append(s1).append(s2).toString());
当使用运算符”+”连接字符串时,如果两个操作数都是编译时常量,则在编译时期就会
计算出该字符串的值,而不会在运行的时候创建StringBuilder对象。
“+”的性能:如果只是简单几个字符串的连接,使用”+”运算符是非常方便的,但是
在循环中反复执行String对象的连接操作,性能就会变差,因此建议直接使用
StringBuilder来代替”+”的连接,这样可以省去每次系统创建StringBuilder类的开销。

6.System类的nanoTime方法:
long start=System.nanoTime;
long end=System.nanoTime();
long time=end-start;
System.out.println(time);
System类的nanoTime方法返回从某个时间开始所经历的纳秒数,这个时间可以是过去,
也可以是未来。如果是未来,则返回负数值。该方法不是系统时钟,但是可以用来
计算所经历的时间长度。

7.String类型是引用类型,String对象一经修改,就不能修改。String类是final类型的,
我们无法继承该类,并且其所有的成员变量都是私有的(private),没有提供修改私有
成员变量的公有(public)方法。
String的所有数据成员都是私有的,并且没有提供修改数据成员的方法,String内部使用
一个char类型的数组来维护字符序列,所有对字符序列的操作都是在一个新创建的String
对象上面进行的,而不是在该对象本身维护的数组上进行的。当对String对象调用某些
方法时,就会创建一个新的String对象。
String类那些看似修改字符序列的方法实际上都是返回新创建的String对象,而不是
修改自身对象。由于String对象是不可改变的,因此其具有线程安全性,可以自由地
实现共享。

8.在Java中的String类中,是使用一个字符数组来维护字符序列的,其声明如下:
private final char value[];
String的最大长度取决于字符数组的最大长度,在指定数组长度时,可以使用byte,short,
char,int类型,而不能够使用long类型,数组的最大长度就是int类型的最大值,
即0x7fffffff,十进制就是2147483647,这也就是String所能容纳的最大字符数量,获得
String对象长度的length方法返回的是int类型的,而不是long类型的。而这个最大值
只是理论上能够到达的值。
Java中的对象是分配在堆上的,堆的大小直接决定我们所能创建对象的多少,堆越大,
所能创建的对象越多。因此,我们只要改变堆的大小,就能改变所能创建对象的多少。
在Java中,默认的堆空间的最大值是256MB。

9.String字面常量的最大长度与String在内存中的最大长度是不一样的,后者的
最大长度为int类型的最大值,即2147483647,而前者根据字符(字符Unicode值)的不同,
最大长度也不同,最大值为65534(可手动修改class文件,令输出结果为65535)。
String字面常量的最大长度是由CONSTANT_Utf8_info表来决定的,该长度在编译时确定
如果超过CONSTANT_Utf8_info表bytes数组所能表示的上限,就会产生编译错误。

10.Java中equals方法与”==”的区别:
equals比较的是两个对象的内容是否相等
==:比较的是两个对象(地址)是否是同一个对象(对象地址是否相等)
equals方法是在Object类中声明的,访问修饰符为public,而所有类(除Object自身外)
都是Object的直接或间接子类,也就是所有子类的继承了这个方法。
在Object类中,equals方法实现如下:
public boolean equals(Object obj)
{
return (this==obj);
}
在Object中,equals方法与”==”运算符是完全等价的。对于String类,之所以该类可以
比较对象的内容,那是因为String类重写了equals方法,使该方法比较的是字符序列(内容)
在Java API文档中,equals的重写规则:
1.自反性。对于任何非null的引用值x,x.equals(x)应返回true.
2.对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,
x.equals(y)才应返回true.
3.传递性。对于任何非null的引用值x,y与z,如果x.equals(y)返回true,并且y.equals(z)
返回true,那么x.equals(z)也应该返回true.
4.一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,
则多次调用x.equals(y)始终返回true或始终返回false.
5.对于任何非空引用值x,x.equals(null)应返回false.
在重写equals方法的同时,也必须重写hashCode方法,否则该类与其他类(如实现了Map
接口或其子接口的类)交互时,很可能产生不确定的运行结果。

11.在Java API文档中,关于hashCode方法的几点规定:
1).在Java应用程序执行期间,如果在对象equals方法比较中所用的信息没有被修改,
那么在同一对象上多次调用hashCode方法时,必须一致地返回相同的整数。但如果多次
执行同一个应用时,不要求该整数必须相同。
2).如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须
返回相同的整数。
3).如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法
必须返回不同的整数。
在Object类中,hashCode方法是通过Object对象地址计算出来的,因为Object对象只与
自身相等,故同一个对象的地址总是相等的,计算取得的hashCode码也必然相等。对于
不同的Object对象,地址不同,hashCode码也不会相同。

12.Java中String类中,存在一个专门的区域,用于存储String字面常量,这个区域称为
常量池。常量池由String类进行维护。
当需要将某个String对象加入常量池中时,就可以调用intern方法来完成,这个操作也
称作拘留字符串,系统会自动将String字面常量与String表达式的字符串值加入常量池
中,这也是通过调用intern方法来实现的。当String对象调用intern方法时,如果
常量池中已经含有该对象(通过equals方法来判断),则返回常量池中的String对象。如果
不存在,则将该对象加入常量池中,并返回该对象。
String对象是不可改变的,因此没必要创建两个相同的String对象。只需要将String
对象加入常量池,在需要时取出,这样既可实现String对象的共享。在程序中出现String
编译常量(String字面常量与String常量表达式)时,会自动调用intern方法,如果常量池
中含有相等的String对象,则直接返回常量池中的对象,否则将对象加入常量池中并返回
该对象。对于运行时创建的String对象(非String编译时常量),会分配到堆中,系统不会
自动调用intern方法拘留该对象,不过我们依然可以自行调用该对象的intern方法对该
对象进行拘留。

13.main方法是Java程序的入口点,由Java虚拟机自动调用。main方法的声明:
public static void main(String[] args)或者
public static void main(String args[])
main方法是公有的(public),静态的(static),返回类型为void,这样声明的原因如下:
public:main方法作为应用程序的入口,该方法是在程序启动时由Java虚拟机调用的,
所有应该声明为public.
static:如果不是静态的方法,就需要通过对象来访问。而当Java程序运行,虚拟机调用
main方法的时候,没有必要创建含有main方法类的对象。
void:main方法在退出时,并没有给系统返回退出代码,而是在需要时使用
System.exit(int status)方法来返回,所以返回类型为void.
String[] args:该字符串数组用来在运行时接收用户输入的参数,具体长度取决于用户
输入参数的个数,如果用户没有输入参数,那么args数组长度为0
(并非为null).
JDK1.5后,main方法的args参数也可以使用可变参数类型,声明如下:
public static void main(String…args).
main方法与很多方法的相似之处:
1.main方法的重载(main方法也可重载)
2.调用main方法(其他方法也可以调用main方法)
3.继承main方法(main方法也是可以由子类继承)
4.隐藏main方法(子类可以隐藏父类的main方法)
5.main方法也可以抛出异常
6.main方法也可以带有类型参数
如果子类继承类父类的main方法,并且在子类中重写声明新的main方法,也可以隐藏父
类的main方法,这个表现与隐藏其他方法是一样的。
也可以通过反射机制来调用main方法。

点赞