`
kulinglei
  • 浏览: 99446 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java和C#的泛型比较

阅读更多
C#的泛型:

C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:

泛型类的不同的封闭类是分别不同的数据类型。


java的泛型

对java虚拟机来说是没有泛型类的。java编译器在编译泛型类是会用泛型类的左边第一个限定类去替换。eg: ArrayList<T extend a & b>
编译后ArrayList的所以T会被a 去替换。但put数据是。编译器会检查put的数据是不是a类型的。但get数据时。他会返回a类型的数据.并添加类型转换代码。(这样效率就低了)
eg: ArrayList<News> arrayList=new ...; arrayList.get(0);
编译器会返回 a类型的数据。然后把 数据转换成News数据。
总之java没有泛型类。在编译后都是基本的类型。并加类类型转换。


这是我的看法。希望有人交流交流
分享到:
评论
33 楼 RednaxelaFX 2009-03-25  
之前我是直接C&P了你给的这段代码过来编译:
ray_linn 写道
我想这个例子应该更能说明我谈到的泛型带来的性能损耗
public class TestGenericCollection {
	public static void main(String[] args) {
		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		mergeG(genericCol,genericOtherCol);

		MyCollectionNG nonGenericCol = new MyCollectionNG();
		MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
		mergeNG(nonGenericCol,nonGenericOtherCol);
	}
	
	public static void mergeG(MyCollectionG<Integer> co1,MyCollectionG<Integer> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
}


ray_linn 写道
略有区别


	public static void mergeG(MyCollectionG<Nothing> co1,MyCollectionG<Nothing> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 Nothing n=co2.get(i);
			 co1.add(n);
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 Object o=co2.get(i);
			 co1.add(o);
		}
	}



如果我们把MyCollectionG<E> 修改称MyCollectionG<E extends Demo>的话,情况就变得更糟糕,compiler会加入2个checkcast:
第一次checkcast object --> Demo
第二次将Demo在 checkcast成Nothing,(nothing implements Demo)

嗯,有了。呵呵,确实是在显式赋值给局部变量的地方有插入checkcast。Sun的javac果然还是不怎么做优化的……咳咳
这个地方确实非泛型代码相对泛型代码在执行中会略占优势。
32 楼 ray_linn 2009-03-25  
略有区别


	public static void mergeG(MyCollectionG<Nothing> co1,MyCollectionG<Nothing> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 Nothing n=co2.get(i);
			 co1.add(n);
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 Object o=co2.get(i);
			 co1.add(o);
		}
	}



如果我们把MyCollectionG<E> 修改称MyCollectionG<E extends Demo>的话,情况就变得更糟糕,compiler会加入2个checkcast:
第一次checkcast object --> Demo
第二次将Demo在 checkcast成Nothing,(nothing implements Demo)
31 楼 RednaxelaFX 2009-03-25  
真神奇。那贴一下我们的代码吧,看看是不是因为代码不同所以编译出来的东西不同。
我的是:
import java.util.*;

public class TestGenericCollection {
  public static void main(String[] args) {
		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		mergeG(genericCol,genericOtherCol);

		MyCollectionNG nonGenericCol = new MyCollectionNG();
		MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
		mergeNG(nonGenericCol,nonGenericOtherCol);
	}
	
	public static void mergeG(MyCollectionG<Integer> co1,MyCollectionG<Integer> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
}

class MyCollectionG<E> {
  public void add(E elem) {
    E obj = elem;
  }
  
  public void addAll(MyCollectionG<E> otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      E elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public E get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

class MyCollectionNG {
  public void add(Object elem) {
    Object obj = elem;
  }
  
  public void addAll(MyCollectionNG otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      Object elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public Object get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

不要用Eclipse的编译器来编译,如果你在用的话。它编译出来的东西跟Sun的编译器出来的不一样。相对来说Eclipse编译器在好些地方都表现得不好(规范兼容性)。
30 楼 ray_linn 2009-03-25  
RednaxelaFX 写道
很明显我们用的编译器不一样。ray_linn大大用的是什么版本的?我用的是Sun javac, 1.6.0u10。不过根据规范,“能静态判定的转换就不用插入动态检查”,编译器如果偷懒不去做足够的静态推导而是都emit出运行时检查,也并不违反规范;只是它们emit了多余的代码而已。
说来Sun的javac自从1.4还是1.5开始,-o开关就没作用了;Sun的javac几乎不对生成的字节码做优化。很难想像一个对自己的JVM如此有信心的公司会花时间优化类型检查而不把代码里的常量给折叠起来 XDD


我用过java 6.0 update 0和 update 12,(compilance level 选5.0 , 6.0) 结果都是一样的。

checkcast总是存在的,或cast称String,或cast成test.Nothing.
29 楼 RednaxelaFX 2009-03-25  
很明显我们用的编译器不一样。ray_linn大大用的是什么版本的?我用的是Sun javac, 1.6.0u10。不过根据规范,“能静态判定的转换就不用插入动态检查”,编译器如果偷懒不去做足够的静态推导而是都emit出运行时检查,也并不违反规范;只是它们emit了多余的代码而已。
说来Sun的javac自从1.4还是1.5开始,-O开关就没作用了;Sun的javac几乎不对生成的字节码做优化。很难想像一个对自己的JVM如此有信心的公司会花时间优化类型检查而不把代码里的常量给折叠起来 XDD
28 楼 ray_linn 2009-03-25  
似乎我们得到的il有所区别

public static void mergeG(test.MyCollectionG, test.MyCollectionG);
  Code:
   0:	iconst_0
   1:	istore_2
   2:	goto	20
   5:	aload_0
   6:	aload_1
   7:	iload_2
   8:	invokevirtual	#53; //Method test/MyCollectionG.get:(I)Ljava/lang/Object;
   11:	checkcast	#20; //class java/lang/Integer
   14:	invokevirtual	#25; //Method test/MyCollectionG.add:(Ljava/lang/Object;)V
   17:	iinc	2, 1
   20:	iload_2
   21:	aload_1
   22:	invokevirtual	#57; //Method test/MyCollectionG.size:()I
   25:	if_icmplt	5
   28:	return

public static void mergeNG(test.MyCollectionNG, test.MyCollectionNG);
  Code:
   0:	iconst_0
   1:	istore_2
   2:	goto	17
   5:	aload_0
   6:	aload_1
   7:	iload_2
   8:	invokevirtual	#65; //Method test/MyCollectionNG.get:(I)Ljava/lang/Object;
   11:	invokevirtual	#36; //Method test/MyCollectionNG.add:(Ljava/lang/Object;)V
   14:	iinc	2, 1
   17:	iload_2
   18:	aload_1
   19:	invokevirtual	#66; //Method test/MyCollectionNG.size:()I
   22:	if_icmplt	5
   25:	return

}


在mergeG的11:处有一checkcast. 我个人推断一旦是在一个class内部,无需造型的时候基本不会加于checkcast(挺废话的吧)。

因为E get 和 add E都被擦拭成object,因此 一旦是get-->add,也不会产生造型,甚至你使用了中间变量i,由于compiler会认为该中间变量是无用变量而加以忽视,这时候不会有任何checkcast.

但是只要对i进行一个小操作,比如i++, 编译器再也无能为力,必须加于造型。

又比如这个例子

class MyCollectionG<E extends Demo> {
	private List<E> inner = new ArrayList<E>();

	public void add(E elem) {
		this.inner.add(elem);
	}

	public void addAll(MyCollectionG<E> otherCol) {
		for (int i = 0; i < otherCol.size(); ++i) {
			E item=otherCol.get(i);
			otherCol.add(item);
			
		}
	}

	public E get(int index) {
		return this.inner.get(index);
	}

	public int size() {
		return this.inner.size();
	}
}



这时候无论什么时候 get的时候都必须拆箱,add的时候必须装箱,因为擦除后不再object而是 test.demo...
27 楼 kulinglei 2009-03-25  
to:RednaxelaFX
看看这个帖子:
http://kulinglei.iteye.com/admin/blogs/354586
26 楼 kulinglei 2009-03-25  
25 楼 RednaxelaFX 2009-03-25  
ray_linn 写道
我想这个例子应该更能说明我谈到的泛型带来的性能损耗
public class TestGenericCollection {
	public static void main(String[] args) {
		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		mergeG(genericCol,genericOtherCol);

		MyCollectionNG nonGenericCol = new MyCollectionNG();
		MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
		mergeNG(nonGenericCol,nonGenericOtherCol);
	}
	
	public static void mergeG(MyCollectionG<Integer> co1,MyCollectionG<Integer> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
}


将addall从generic中移出来,虽然代码是一样的,但结果是不一样的。在泛型内部,编译器做了相应的优化,因此checkcast被自动移除了,但是将起移动外部,就必须得进行checkcast.

移到外部……嘛,先看这个例子对应的代码:
public static void mergeG(MyCollectionG, MyCollectionG);
  Code:
   Stack=3, Locals=3, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#8; //Method MyCollectionG.size:()I
   7:	if_icmpge	25
   10:	aload_0
   11:	aload_1
   12:	iload_2
   13:	invokevirtual	#9; //Method MyCollectionG.get:(I)Ljava/lang/Object;
   16:	invokevirtual	#10; //Method MyCollectionG.add:(Ljava/lang/Object;)V
   19:	iinc	2, 1
   22:	goto	2
   25:	return

public static void mergeNG(MyCollectionNG, MyCollectionNG);
  Code:
   Stack=3, Locals=3, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#11; //Method MyCollectionNG.size:()I
   7:	if_icmpge	25
   10:	aload_0
   11:	aload_1
   12:	iload_2
   13:	invokevirtual	#12; //Method MyCollectionNG.get:(I)Ljava/lang/Object;
   16:	invokevirtual	#13; //Method MyCollectionNG.add:(Ljava/lang/Object;)V
   19:	iinc	2, 1
   22:	goto	2
   25:	return

这个例子或许还是不够好,没有checkcast(JVM指令里没有专门的box/unbox指令,就靠这个checkcast了)。移动到外部并不总是需要类型转换的。编译器只在没有静态保证的时候才会插入必要的checkcast指令。

ray_linn 写道
从某种意义上,是编译器把本来应该有的checkcast移走了。

呵呵,这个不好说。Java泛型太难了,我也还不懂。具体规定是要在怎样的情况下插入checkcast的呢?
先看JLS3rd,5.5节关于Conversion的定义;规范说“能静态判定为正确的类型转换在运行时不需要任何检查”。所以这个“本来应该有”该从什么角度看,不好说嗯。我觉得上面这例子中的类型参数都是Integer所以编译器能静态证明类型是兼容的,因而没有任何运行时cast。

ray_linn 写道
		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		genericOtherCol.add(2);


这里必须装箱,这应该是java比不上C#的地方。

嗯对,这里是没错。涉及到原始类型到引用类型的转换时是必须插入checkcast来达到装箱的目的。

kulinglei 写道
把RednaxelaFX的代码贴出来

呃呵呵,如果能把我原来的quote和code标签保留的话看得或许更清晰些~
24 楼 kulinglei 2009-03-25  
把RednaxelaFX的代码贴出来
引用

Java里泛型的出现不是为了源码“更安全”么?执行效率应该没有多少区别才对。你看对这样的一个测试代码:
Java代码
引用
import java.util.ArrayList;  
 
public class TestGeneric {  
    public static void main(String[] args) {  
        foo1();  
        foo2();  
    }  
 
    private static void foo1() {  
        ArrayList list = new ArrayList();  
        MyObject obj = new MyObject();  
        list.add(obj);  
        MyObject objref = (MyObject)list.get(0);  
    }  
 
    private static void foo2() {  
        ArrayList<MyObject> list = new ArrayList<MyObject>();  
        MyObject obj = new MyObject();  
        list.add(obj);  
        MyObject objref = list.get(0);  
    }  
}  
 
class MyObject { } 

import java.util.ArrayList;

public class TestGeneric {
    public static void main(String[] args) {
        foo1();
        foo2();
    }

    private static void foo1() {
        ArrayList list = new ArrayList();
        MyObject obj = new MyObject();
        list.add(obj);
        MyObject objref = (MyObject)list.get(0);
    }

    private static void foo2() {
        ArrayList<MyObject> list = new ArrayList<MyObject>();
        MyObject obj = new MyObject();
        list.add(obj);
        MyObject objref = list.get(0);
    }
}

class MyObject { }
其中的foo1()和foo2()编译出来是完全一样的,都是这样:
引用
Jvm bytecode代码
0:  new #4; //class java/util/ArrayList  
3:  dup  
4:  invokespecial   #5; //Method java/util/ArrayList."<init>":()V  
7:  astore_0  
8:  new #6; //class MyObject  
11: dup  
12: invokespecial   #7; //Method MyObject."<init>":()V  
15: astore_1  
16: aload_0  
17: aload_1  
18: invokevirtual   #8; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z  
21: pop  
22: aload_0  
23: iconst_0  
24: invokevirtual   #9; //Method java/util/ArrayList.get:(I)Ljava/lang/Object;  
27: checkcast   #6; //class MyObject  
30: astore_2  
31: return 
  注意在标号为27的那条指令,那个就对应着foo1()的强制类型转换,而foo2()使用了泛型之后隐式的包含了这个操作。

我也觉得干脆不返回数组比较好。其实很多时候数组用起来都很尴尬,与Iterable<E>也不兼容。
23 楼 ray_linn 2009-03-25  
我想这个例子应该更能说明我谈到的泛型带来的性能损耗
public class TestGenericCollection {
	public static void main(String[] args) {
		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		mergeG(genericCol,genericOtherCol);

		MyCollectionNG nonGenericCol = new MyCollectionNG();
		MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
		mergeNG(nonGenericCol,nonGenericOtherCol);
	}
	
	public static void mergeG(MyCollectionG<Integer> co1,MyCollectionG<Integer> co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
	
	public static void mergeNG(MyCollectionNG co1,MyCollectionNG co2){
		for (int i = 0; i < co2.size(); ++i) {
			 co1.add(co2.get(i));
		}
	}
}


将addall从generic中移出来,虽然代码是一样的,但结果是不一样的,一旦离开泛型内部co1.add(co2.get(i)),就无法进行任何优化,不得不强行做cast



在泛型内部,编译器做了相应的优化,因此checkcast被自动移除了,但是将起移动外部,就必须得进行checkcast.

从某种意义上,是编译器把本来应该有的checkcast移走了。

		MyCollectionG<Integer> genericCol = new MyCollectionG<Integer>();
		
		MyCollectionG<Integer> genericOtherCol = new MyCollectionG<Integer>();
		genericOtherCol.add(2);



这里必须装箱,这应该是java比不上C#的地方。

  IL_000e:  ldc.i4.2
  IL_000f:  callvirt   instance void class Generic.MyCollectionG`1<int32>::'add'(!0)
  IL_0014:  nop
  IL_0015:  ldloc.0
  IL_0016:  ldloc.1
  IL_0017:  call       void Generic.Program::mergeG(class Generic.MyCollectionG`1<int32>,
                                                    class Generic.MyCollectionG`1<int32>)
  IL_002a:  ldc.i4.2
  [color=red]IL_002b:  box        [mscorlib]System.Int32[/red]
  IL_0030:  callvirt   instance void Generic.MyCollectionNG::'add'(object)
22 楼 RednaxelaFX 2009-03-25  
kulinglei 写道
也就是说java中的泛型是在运行时动态添加类型检查和类型转换的。
那java的jvm是怎么添加的啊,

不是的,涉及泛型而插入的类型检查/类型转换是由Java编译器来做的,而不是由JVM。执行运行时类型检查是由JVM来完成的,对编译器而言要让JVM做检查的方法就是用checkcast指令。

kulinglei 写道
那java的虚拟机是怎样编译泛型的啊?????是不是给个标记,jvm看到这个标记就转换吗????????

JVM不编译泛型,Java的编译器才涉及泛型。你看看之前的另一个例子就能看到checkcast指令的插入:这里最底下我的回帖

前面我对ray_linn大大的回应是想说明Java编译器不是看到泛型就给插入类型转换的,而是在有必要的时候才插入。
21 楼 kulinglei 2009-03-25  
RednaxelaFX 写道
我觉得可能得先把box/unbox和cast区分开,嗯。
box/unbox在Java里指的是将原始类型包装为一个Object放到堆上,与将一个被包装的原始类型从堆上取回到栈上的这两个过程。在.NET里把上述“原始类型”换为“值类型”就是了。
而cast更为宽泛,只是指用类型B的变量去指向类型A的变量所指向的值所需的过程。这其中可能涉及box/unbox(假如将int变量赋值给Object变量之类);但一般说的是原始类型到原始类型(值类型到值类型)、引用类型到引用类型之间的转换,此时是不涉及box/unbox的。
ray_linn 写道
public void add(MyCollection<T> sub){ 
 for(int i=0;i<sub.size();i++){
    T item=sub.get(i); //装箱
     this.add(item);//拆箱
  }
}

这里都只能称为cast,而肯定不涉及box/unbox,因为Java里的泛型参数是不能指定为原始类型的;这段代码如果存在cast,也只能是引用类型间的转换。

下面的例子可能不太好,不过还是来看看吧:
public class TestGenericCollection {
  public static void main(String[] args) {
    MyCollectionG<String> genericCol = new MyCollectionG<String>();
    MyCollectionG<String> genericOtherCol = new MyCollectionG<String>();
    genericCol.addAll(genericOtherCol);
    
    MyCollectionNG nonGenericCol = new MyCollectionNG();
    MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
    nonGenericCol.addAll(nonGenericOtherCol);
  }
}

class MyCollectionG<E> {
  public void add(E elem) {
    E obj = elem;
  }
  
  public void addAll(MyCollectionG<E> otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      E elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public E get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

class MyCollectionNG {
  public void add(Object elem) {
    Object obj = elem;
  }
  
  public void addAll(MyCollectionNG otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      Object elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public Object get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

接着ray_linn大大的例子,这里实现了两个dummy容器,一个声明为泛型,另一个声明为非泛型;里面啥也没有,只是用来看编译器插入了些什么内容。

在main()里有没有插入类型转换的指令呢?
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=5, Args_size=1
/////////////////////////////////////////////////////////
   0:	new	#2; //class MyCollectionG
   3:	dup
   4:	invokespecial	#3; //Method MyCollectionG."<init>":()V
   7:	astore_1
   8:	new	#2; //class MyCollectionG
   11:	dup
   12:	invokespecial	#3; //Method MyCollectionG."<init>":()V
   15:	astore_2
   16:	aload_1
   17:	aload_2
   18:	invokevirtual	#4; //Method MyCollectionG.addAll:(LMyCollectionG;)V
/////////////////////////////////////////////////////////
   21:	new	#5; //class MyCollectionNG
   24:	dup
   25:	invokespecial	#6; //Method MyCollectionNG."<init>":()V
   28:	astore_3
   29:	new	#5; //class MyCollectionNG
   32:	dup
   33:	invokespecial	#6; //Method MyCollectionNG."<init>":()V
   36:	astore	4
   38:	aload_3
   39:	aload	4
   41:	invokevirtual	#7; //Method MyCollectionNG.addAll:(LMyCollectionNG;)V
/////////////////////////////////////////////////////////
   44:	return

可以看到上下两半是一样的,也就是并没有插入类型转换指令。

然后看看MyCollectionG<E>里的add()和addAll():
public void add(java.lang.Object);
  Code:
   Stack=1, Locals=3, Args_size=2
   0:	aload_1
   1:	astore_2
   2:	return 

public void addAll(MyCollectionG);
  Code:
   Stack=2, Locals=4, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#2; //Method size:()I
   7:	if_icmpge	27
   10:	aload_1
   11:	iload_2
   12:	invokevirtual	#3; //Method get:(I)Ljava/lang/Object;
   15:	astore_3
   16:	aload_0
   17:	aload_3
   18:	invokevirtual	#4; //Method add:(Ljava/lang/Object;)V
   21:	iinc	2, 1
   24:	goto	2
   27:	return


与MyCollectionNG里对应的add()和addAll():
public void add(java.lang.Object);
  Code:
   Stack=1, Locals=3, Args_size=2
   0:	aload_1
   1:	astore_2
   2:	return

public void addAll(MyCollectionNG);
  Code:
   Stack=2, Locals=4, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#2; //Method size:()I
   7:	if_icmpge	27
   10:	aload_1
   11:	iload_2
   12:	invokevirtual	#3; //Method get:(I)Ljava/lang/Object;
   15:	astore_3
   16:	aload_0
   17:	aload_3
   18:	invokevirtual	#4; //Method add:(Ljava/lang/Object;)V
   21:	iinc	2, 1
   24:	goto	2
   27:	return

两边是一样的对吧?都没有插入checkcast指令来做类型转换。

就上面的代码而言,编译器是不会插入任何cast的,用不用泛型所生成的代码几乎完全一样(metadata有细微不同)。
即使把内部改为用ArrayList<E>来装内容也一样,在下面部分代码里不会出现编译器插入的cast:
import java.util.*;

public class TestGenericCollection {
  public static void main(String[] args) {
  }
}

class MyCollectionG<E> {
  private ArrayList<E> _list;
  
  public void addAll(ArrayList<E> otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      E elem = otherCol.get(i);
      _list.add(elem);
    }
  }
}

class MyCollectionNG {
  private ArrayList _list;
  
  public void addAll(ArrayList otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      Object elem = otherCol.get(i);
      _list.add(elem);
    }
  }
}


不放心的话,这里是java.util.ArrayList<E>的add()和addAll():
public boolean add(java.lang.Object);
  Code:
   Stack=5, Locals=2, Args_size=2
   0:	aload_0
   1:	aload_0
   2:	getfield	#156; //Field size:I
   5:	iconst_1
   6:	iadd
   7:	invokevirtual	#178; //Method ensureCapacity:(I)V
   10:	aload_0
   11:	getfield	#157; //Field elementData:[Ljava/lang/Object;
   14:	aload_0
   15:	dup
   16:	getfield	#156; //Field size:I
   19:	dup_x1
   20:	iconst_1
   21:	iadd
   22:	putfield	#156; //Field size:I
   25:	aload_1
   26:	aastore
   27:	iconst_1
   28:	ireturn

public boolean addAll(java.util.Collection);
  Code:
   Stack=5, Locals=4, Args_size=2
   0:	aload_1
   1:	invokeinterface	#184,  1; //InterfaceMethod java/util/Collection.toArray:()[Ljava/lang/Object;
   6:	astore_2
   7:	aload_2
   8:	arraylength
   9:	istore_3
   10:	aload_0
   11:	aload_0
   12:	getfield	#156; //Field size:I
   15:	iload_3
   16:	iadd
   17:	invokevirtual	#178; //Method ensureCapacity:(I)V
   20:	aload_2
   21:	iconst_0
   22:	aload_0
   23:	getfield	#157; //Field elementData:[Ljava/lang/Object;
   26:	aload_0
   27:	getfield	#156; //Field size:I
   30:	iload_3
   31:	invokestatic	#174; //Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
   34:	aload_0
   35:	dup
   36:	getfield	#156; //Field size:I
   39:	iload_3
   40:	iadd
   41:	putfield	#156; //Field size:I
   44:	iload_3
   45:	ifeq	52
   48:	iconst_1
   49:	goto	53
   52:	iconst_0
   53:	ireturn

里面也是没有checkcast指令的。


也就是说java中的泛型是在运行时动态添加类型检查和类型转换的。
那java的jvm是怎么添加的啊,
那java的虚拟机是怎样编译泛型的啊?????是不是给个标记,jvm看到这个标记就转换吗????????
先谢谢

20 楼 RednaxelaFX 2009-03-25  
kulinglei 写道
我要疯了,我还不知道,我看不懂il.

呃呵呵……我觉得关键是不要一开始就拒绝IL/bytecode。它们不难,特别是在Java这边,语言里的语法糖并不算多,Java源码与JVM字节码的对应关系还是很明显的。由于JVM有强制的对象模型和内存模型,并且与Java语言保持一致(只有很少的例外),阅读JVM字节码跟阅读Java源码可以说是一样容易(或者一样难,看你要从什么角度看)。也因为.class文件里保留很很多元数据,所以才能有反编译器将.class文件转换会非常可读的Java源码。

刚才在读一些关于Java语言设计的评论的时候,看到许多支持Java的人持有这样的观点:“繁琐”是件好事,帮助大家理解实现机理。Well,JVM字节码比Java语言更能精确的反映出语义,为什么没见有多少人喜欢用JVM字节码来直接编程呢?语法还是有意义的……

忍不住在这帖里发牢骚了,抱歉……
19 楼 kulinglei 2009-03-25  
RednaxelaFX 写道
ray_linn 写道
你用什么工具来查看Java的IL的?

JDK自带工具,javap……就像.NET程序员不能不知道ildasm一样,我觉得Java程序员也应该知道javap,呵呵~
我用的命令是
javap -c -private -verbose 类名

我要疯了,我还不知道,我看不懂il.
18 楼 RednaxelaFX 2009-03-25  
ray_linn 写道
你用什么工具来查看Java的IL的?

JDK自带工具,javap……就像.NET程序员不能不知道ildasm一样,我觉得Java程序员也应该知道javap,呵呵~
我用的命令是
javap -c -private -verbose 类名
17 楼 ray_linn 2009-03-25  
RednaxelaFX 写道
kulinglei 写道
 
public void add(MyCollection<T> sub){  
 
  for(int i=0;i<sub.size();i++){  
     T item=sub.get(i); //装箱  
      this.add(item);//拆箱  
 
  }  
}  
 
public void add(MyCollection sub){  
     for(int i=0;i<sub.size();i++){  
     Object item=sub.get(i); //不装箱  
      this.add(item);//不拆箱  
 
  }  
}

这段代码很好的说明了。在运行效率上非泛型比泛型快
在java中用泛型好像就是减少代码量

楼上或许可以读一读我前面的回帖……


good, 我昨天本来是想用DJCompiler是看一下il的,结果dj compiler不能工作,所以对前面的代码我不是太make sure的。

你用什么工具来查看Java的IL的?
16 楼 RednaxelaFX 2009-03-25  
kulinglei 写道
 
public void add(MyCollection<T> sub){  
 
  for(int i=0;i<sub.size();i++){  
     T item=sub.get(i); //装箱  
      this.add(item);//拆箱  
 
  }  
}  
 
public void add(MyCollection sub){  
     for(int i=0;i<sub.size();i++){  
     Object item=sub.get(i); //不装箱  
      this.add(item);//不拆箱  
 
  }  
}

这段代码很好的说明了。在运行效率上非泛型比泛型快
在java中用泛型好像就是减少代码量

楼上或许可以读一读我前面的回帖……
15 楼 kulinglei 2009-03-25  
 
public void add(MyCollection<T> sub){  
 
  for(int i=0;i<sub.size();i++){  
     T item=sub.get(i); //装箱  
      this.add(item);//拆箱  
 
  }  
}  
 
public void add(MyCollection sub){  
     for(int i=0;i<sub.size();i++){  
     Object item=sub.get(i); //不装箱  
      this.add(item);//不拆箱  
 
  }  
}

这段代码很好的说明了。在运行效率上非泛型比泛型快
在java中用泛型好像就是减少代码量
14 楼 RednaxelaFX 2009-03-25  
我觉得可能得先把box/unbox和cast区分开,嗯。
box/unbox在Java里指的是将原始类型包装为一个Object放到堆上,与将一个被包装的原始类型从堆上取回到栈上的这两个过程。在.NET里把上述“原始类型”换为“值类型”就是了。
而cast更为宽泛,只是指用类型B的变量去指向类型A的变量所指向的值所需的过程。这其中可能涉及box/unbox(假如将int变量赋值给Object变量之类);但一般说的是原始类型到原始类型(值类型到值类型)、引用类型到引用类型之间的转换,此时是不涉及box/unbox的。
ray_linn 写道
public void add(MyCollection<T> sub){ 
 for(int i=0;i<sub.size();i++){
    T item=sub.get(i); //装箱
     this.add(item);//拆箱
  }
}

这里都只能称为cast,而肯定不涉及box/unbox,因为Java里的泛型参数是不能指定为原始类型的;这段代码如果存在cast,也只能是引用类型间的转换。

下面的例子可能不太好,不过还是来看看吧:
public class TestGenericCollection {
  public static void main(String[] args) {
    MyCollectionG<String> genericCol = new MyCollectionG<String>();
    MyCollectionG<String> genericOtherCol = new MyCollectionG<String>();
    genericCol.addAll(genericOtherCol);
    
    MyCollectionNG nonGenericCol = new MyCollectionNG();
    MyCollectionNG nonGenericOtherCol = new MyCollectionNG();
    nonGenericCol.addAll(nonGenericOtherCol);
  }
}

class MyCollectionG<E> {
  public void add(E elem) {
    E obj = elem;
  }
  
  public void addAll(MyCollectionG<E> otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      E elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public E get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

class MyCollectionNG {
  public void add(Object elem) {
    Object obj = elem;
  }
  
  public void addAll(MyCollectionNG otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      Object elem = otherCol.get(i);
      this.add(elem);
    }
  }
  
  public Object get(int index) {
    return null;
  }
  
  public int size() {
    return 1; // dummy
  }
}

接着ray_linn大大的例子,这里实现了两个dummy容器,一个声明为泛型,另一个声明为非泛型;里面啥也没有,只是用来看编译器插入了些什么内容。

在main()里有没有插入类型转换的指令呢?
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=5, Args_size=1
/////////////////////////////////////////////////////////
   0:	new	#2; //class MyCollectionG
   3:	dup
   4:	invokespecial	#3; //Method MyCollectionG."<init>":()V
   7:	astore_1
   8:	new	#2; //class MyCollectionG
   11:	dup
   12:	invokespecial	#3; //Method MyCollectionG."<init>":()V
   15:	astore_2
   16:	aload_1
   17:	aload_2
   18:	invokevirtual	#4; //Method MyCollectionG.addAll:(LMyCollectionG;)V
/////////////////////////////////////////////////////////
   21:	new	#5; //class MyCollectionNG
   24:	dup
   25:	invokespecial	#6; //Method MyCollectionNG."<init>":()V
   28:	astore_3
   29:	new	#5; //class MyCollectionNG
   32:	dup
   33:	invokespecial	#6; //Method MyCollectionNG."<init>":()V
   36:	astore	4
   38:	aload_3
   39:	aload	4
   41:	invokevirtual	#7; //Method MyCollectionNG.addAll:(LMyCollectionNG;)V
/////////////////////////////////////////////////////////
   44:	return

可以看到上下两半是一样的,也就是并没有插入类型转换指令。

然后看看MyCollectionG<E>里的add()和addAll():
public void add(java.lang.Object);
  Code:
   Stack=1, Locals=3, Args_size=2
   0:	aload_1
   1:	astore_2
   2:	return 

public void addAll(MyCollectionG);
  Code:
   Stack=2, Locals=4, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#2; //Method size:()I
   7:	if_icmpge	27
   10:	aload_1
   11:	iload_2
   12:	invokevirtual	#3; //Method get:(I)Ljava/lang/Object;
   15:	astore_3
   16:	aload_0
   17:	aload_3
   18:	invokevirtual	#4; //Method add:(Ljava/lang/Object;)V
   21:	iinc	2, 1
   24:	goto	2
   27:	return


与MyCollectionNG里对应的add()和addAll():
public void add(java.lang.Object);
  Code:
   Stack=1, Locals=3, Args_size=2
   0:	aload_1
   1:	astore_2
   2:	return

public void addAll(MyCollectionNG);
  Code:
   Stack=2, Locals=4, Args_size=2
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	invokevirtual	#2; //Method size:()I
   7:	if_icmpge	27
   10:	aload_1
   11:	iload_2
   12:	invokevirtual	#3; //Method get:(I)Ljava/lang/Object;
   15:	astore_3
   16:	aload_0
   17:	aload_3
   18:	invokevirtual	#4; //Method add:(Ljava/lang/Object;)V
   21:	iinc	2, 1
   24:	goto	2
   27:	return

两边是一样的对吧?都没有插入checkcast指令来做类型转换。

就上面的代码而言,编译器是不会插入任何cast的,用不用泛型所生成的代码几乎完全一样(metadata有细微不同)。
即使把内部改为用ArrayList<E>来装内容也一样,在下面部分代码里不会出现编译器插入的cast:
import java.util.*;

public class TestGenericCollection {
  public static void main(String[] args) {
  }
}

class MyCollectionG<E> {
  private ArrayList<E> _list;
  
  public void addAll(ArrayList<E> otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      E elem = otherCol.get(i);
      _list.add(elem);
    }
  }
}

class MyCollectionNG {
  private ArrayList _list;
  
  public void addAll(ArrayList otherCol) {
    for (int i = 0; i < otherCol.size(); ++i) {
      Object elem = otherCol.get(i);
      _list.add(elem);
    }
  }
}


不放心的话,这里是java.util.ArrayList<E>的add()和addAll():
public boolean add(java.lang.Object);
  Code:
   Stack=5, Locals=2, Args_size=2
   0:	aload_0
   1:	aload_0
   2:	getfield	#156; //Field size:I
   5:	iconst_1
   6:	iadd
   7:	invokevirtual	#178; //Method ensureCapacity:(I)V
   10:	aload_0
   11:	getfield	#157; //Field elementData:[Ljava/lang/Object;
   14:	aload_0
   15:	dup
   16:	getfield	#156; //Field size:I
   19:	dup_x1
   20:	iconst_1
   21:	iadd
   22:	putfield	#156; //Field size:I
   25:	aload_1
   26:	aastore
   27:	iconst_1
   28:	ireturn

public boolean addAll(java.util.Collection);
  Code:
   Stack=5, Locals=4, Args_size=2
   0:	aload_1
   1:	invokeinterface	#184,  1; //InterfaceMethod java/util/Collection.toArray:()[Ljava/lang/Object;
   6:	astore_2
   7:	aload_2
   8:	arraylength
   9:	istore_3
   10:	aload_0
   11:	aload_0
   12:	getfield	#156; //Field size:I
   15:	iload_3
   16:	iadd
   17:	invokevirtual	#178; //Method ensureCapacity:(I)V
   20:	aload_2
   21:	iconst_0
   22:	aload_0
   23:	getfield	#157; //Field elementData:[Ljava/lang/Object;
   26:	aload_0
   27:	getfield	#156; //Field size:I
   30:	iload_3
   31:	invokestatic	#174; //Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
   34:	aload_0
   35:	dup
   36:	getfield	#156; //Field size:I
   39:	iload_3
   40:	iadd
   41:	putfield	#156; //Field size:I
   44:	iload_3
   45:	ifeq	52
   48:	iconst_1
   49:	goto	53
   52:	iconst_0
   53:	ireturn

里面也是没有checkcast指令的。

相关推荐

    关于C#、java泛型的看法

    谈谈关于Microsoft Visual Studio 2008中C#和java泛型的区别

    泛型反射例子

    JAVA中 关于泛型 以及反射的例子 欢迎下载 一起研究

    C# To JAVA Converter v17.10.6

    使用Java泛型转换C#ref参数 当代码中定义事件委托时转换C#事件,或者是System.Action或System.Func代理之一 将所有类型的数组从C#转换成Java 对于继承和接口的所有方面,从C#到Java的无瑕疵转换 允许自定义替换...

    你真的懂Java泛型吗?

    在JDK 1.5开始引入了泛型,但Java实现泛型的方式与C++或C#差异很大。在平常写代码用到泛型时,仿佛一切都来得如此理所当然。但其实Java泛型还是有挺多tricky的东西的,编译器在背后为我们做了很多事。下面我们来看看...

    c# 反射获取传入对象的属性拼接sql语句实现增、删、改、查

    利用反射动态拼接sql。 daohelper属于DAL层,objectdata类属于BLL层,BLL层引用DAL层。映射数据的表继承objectdata类。例如,数据表book,根据字段与属性一一对应的方式创建book类,插入数据库时,直接book.save()

    快速了解泛型(C#,JAVA,C++)

    泛型其实就是能够向你的类型中加入类型参数的一种能力,也称作参数化的类型或参数多态性

    开发专家·编程大讲坛:C#核心开发技术从入门到精通.tag.pdf

    依次讲解了C#开发基础、搭建C#开发环境、变量与常量、复杂的变量类型和.NET框架类、表达式和运算符、流程控制...和事件、C#泛型、C#文件操作和流文件操作、XML文件操作处理、C#的Windows编程基础、菜单、工具栏和对话框...

    Silverlight与Java通信远程调用封装

    可以转换日常使用中的任意复杂类型通信,成功解决C# DateTime和Java Date传递问题,解决java容器泛型与C#容器泛型转换问题,解决复杂对象问题(对象属性是另外对象,容器中套容器)等。 欢迎大家使用,有问题,可以...

    c#分页方案

    提供三种.net分页方案,总结既详细又简单实用

    Java泛型类型通配符和C#对比分析

    下面小编就为大家带来一篇Java泛型类型通配符和C#对比分析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    泛型约束,.NET 中越来越多的使用到泛型

    泛型,无论在Java中还是在C#中,应用都很广泛,可以解决很多关于类型的问题

    自定义的Troop&lt;T&gt;泛型类( c++, java和c#)的实现代码

    主要介绍了自定义的Troop泛型类( c++, java和c#)的实现代码的相关资料,需要的朋友可以参考下

    java笔试题算法-C5:用于C#/.NET的C5泛型集合库

    编程语言和其他支持泛型的语言的一组泛型集合类(或容器类),由 Microsoft .NET 4.6.1+、.NET Core 2.0+ 实现,和单声道。 C5 库提供了广泛的经典数据结构、丰富的功能、最佳的渐近时间复杂度、记录的性能和经过...

    Generic Programming for Scientific Computing in C++, Java, and C#

    JavaTM and C# programming languages. In this paper, we evaluate these extensions with respect to the realm of scientific computing and compare them with C++ templates. At the heart of our comparison ...

    Apla→Java程序生成系统中泛型机制实现方法研究.pdf

    在对泛型程设本质特征深入研究的基础上提出了新型泛型语言机制构想,并在Apla→java生成系统...该方法比现有的java、C++、C#等语言中泛型机制的实现方法简单,并通过经典算法实例演示实现效果,大量用例的成功测试表明...

    关于C#编程语言的一些常见基础面试题.pdf

    2. C#与其他编程语言(如Java、C++等)有哪些不同之处?请说明C#相对于其他语言的优势。 3. C#中的命名空间(Namespace)是什么?请说明在C#中命名空间的作用和用法。 4. C#语言中的面向对象编程(Object-Oriented ...

    C#中数组Array,ArrayList,泛型List详细对比

    关于数组Array,ArrayList,泛型List,简单的说数组就是值对象,它存储数据元素类型的值的一系列位置.Arraylist和list可以提供添加,删除,等操作的数据. 具体如何进行选择使用呢,我们来详细探讨下

    C#实现利用泛型将DataSet转为Model的方法

    因为网站需要用C#开发,习惯了java的泛型,所以看了一下C#下,也可以这样做,随便写了一个。 public static List&lt;T&gt; PutAllVal(T entity, DataSet ds) where T : new() { List&lt;T&gt; lists = new List(); if (ds....

    c# 5.0 解析

    C#3 0加入了一堆语法糖 lambda linq的加入 让C#变得更加优雅和灵活; C#4 0增加了动态语言的特性 另外加入的TPL 并行开发库 PLinq等降低现存并发模型的复杂性也是相当的给力 C#5 0 还会有什么奇思妙想 "&gt;C# 5 0 not...

    java数据库增删改查操作实例(hibernate原理)支持泛型集合

    写了一个通用java访问数据库操作的DAO类,非常简单,非常强大,非常方便,非常好用。绝对原版,绝对原创。把hibernate彻底扔掉。(根据C#改编的,原本我是搞C#开发的)支持泛型集合,让程序运行效率更高。

Global site tag (gtag.js) - Google Analytics