一、泛型与类型擦除
1.1 泛型与类型擦除
Java中的泛型与C#中的泛型区别
Java中的泛型是伪泛型,而C#中的泛型是真泛型。Java中的泛型类型在编译成字节码后,是不区分类型的,都被替换为原生类型了(泛型擦除),并且在相应地方插入了强制类型转换。而C#中的泛型在编译后是真正事实存在的(泛型膨胀)。
Java中泛型例子
public static void main(String argc[]) {
Map<String, String> map = new HashMap<String, String>();
map.put("hello", "你好");
map.put("how are you", "吃了没?");
System.out.println(map.get("hello"));
System.out.println(map.get("how are you"));
}
使用反编译工具后
public static void main(String[] var0) {
HashMap var1 = new HashMap();//泛型不见了,被替换为原生类型了
var1.put("hello", "你好");
var1.put("how are you", "吃了没?");
System.out.println((String)var1.get("hello"));//这儿的强制类型转换
System.out.println((String)var1.get("how are you"));//这儿的强制类型转换
}
JDK设计团队为什么要用类型擦除实现Java泛型呢?
可能是实现简单、兼容性吧!!!
Java泛型丧失的优雅
import java.util.ArrayList;
public class Test {
public static String method(ArrayList<String> list) {
System.out.println("invoke method(ArrayList<String> list)");
return "";
}
public static int method(ArrayList<Integer> list) {
System.out.println("invoke method(ArrayList<Integer> list)");
return 1;
}
public static void main(String argc[]) {
method(new ArrayList<String>());
method(new ArrayList<Integer>());
}
}
它竟然可以运行,这不是它违法了重载规范。而是在字节码层面特征标签包括方法返回类型和受查异常表。并且,在Java层面,ArrayList与ArrayList是两个不同的类型(如果是一样类型的话,那就会报’method(ArrayList)’ is already defined in ‘Test’)。不过这样的话会报’method(ArrayList)’ clashes with ‘method(ArrayList)’; both methods have same erasure错误(有相同的泛型擦除),此时我们使得返回类型不同就行了。
二、自动装箱、拆箱与遍历循环
例子:
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String argc[]) {
List<Integer> list= Arrays.asList(1,2,3,4);//语法糖:变长参数、自动装箱、泛型
int sum=0;
for(int i:list){//语法糖:自动拆箱、循环遍历
sum+=i;
}
System.out.println(sum);
}
}
用jd-gui反编译的结果:
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class Test
{
public static void main(String[] argc)
{
List list = Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
int sum = 0;
for (Iterator i$ = list.iterator(); i$.hasNext(); ) { int i = ((Integer)i$.next()).intValue();
sum += i;
}
System.out.println(sum);
}
}
当然,语法糖虽甜,但是会长蛀牙哦!!!
例子:
public class Test {
public static void main(String argc[]) {
Integer a=1;
Integer b=2;
Integer c=3;
Integer d=3;
Integer e=321;
Integer f=321;
Long g=3L;
System.out.println(c==d);//1.true
System.out.println(e==f);//2.false
System.out.println(c==(a+b));//3.true
System.out.println(c.equals(a+b));//4.true
System.out.println(g==(a+b));//5.ture
System.out.println(g.equals(a+b));//6.false,因为a+b后结果为int,在找重载函数时,自动封装为Integer去了
}
}
至于1~3和5为什么这样,参考Java自动封装和自动拆箱。至于4和6,要知道一点——重载方法的匹配顺序:
byte–>short–>int–>long–>float–>double–>对应的装箱类–>(现在可以上转型了)。所以a+b后被封装为Integer了。现在又要看equals源码了:
public boolean equals(Object obj) {
if (obj instanceof Long) {//很显然Integer不是Long
return value == ((Long)obj).longValue();
}
return false;
}
由上面代码就知道了,4为ture,6为false.
当然,顺手粘贴一下反编译结果:
import java.io.PrintStream;
public class Test
{
public static void main(String[] argc)
{
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(2);
Integer c = Integer.valueOf(3);
Integer d = Integer.valueOf(3);
Integer e = Integer.valueOf(321);
Integer f = Integer.valueOf(321);
Long g = Long.valueOf(3L);
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c.intValue() == a.intValue() + b.intValue());
System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
System.out.println(g.longValue() == a.intValue() + b.intValue());
System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
}
}
三、条件编译
C++中提供了预编译(如#include)。java语言并没有使用预处理器(因为java编译器并非一个个地编译Java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后在进行编译,因此各个文件之间能够互相提供符号信息)。但是,java语言也是可以实现条件编译的——使用条件为常量的if语句。
如:
public class Test {
public static void main(String argc[]) {
if(true){
System.out.println("执行true分支");
}else{
System.out.println("执行false分支");
}
}
}
反编译之后的结果:
import java.io.PrintStream;
public class Test
{
public static void main(String[] argc)
{
System.out.println("执行true分支");
}
}
需要注意的是只能使用条件为常量的if语句才能达到上述效果,如果使用常量与其他带有条件判断能力的语句搭配,则可能拒绝编译。