Java泛型原理笔记

简介: T 到底是什么东东Java泛型的语法相当的别扭,看到一个这样的写法,感觉到很神奇,正好研究下Java泛型是怎么实现的。

<T> T 到底是什么东东

Java泛型的语法相当的别扭,看到一个这样的写法,感觉到很神奇,正好研究下Java泛型是怎么实现的。

public class A{
	public static void main(String[] args) {
		A a = new A();
		a.test();
		
		String r = a.test();
	}
	
	public <T> T test() {
		return (T) new Object();
	}
}


刚开始时,我看到那个"<T> T“ 感觉很神奇,但没什么意义。

查看下test()函数生成的字节码:

public test()Ljava/lang/Object;
   L0
    LINENUMBER 14 L0
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init>()V
    ARETURN
可以发现,这个函数实际上的返回类型是Object,和T没什么关系。

再看下调用test()函数的地方对应的字节码:

		a.test();
		
		String r = a.test();

字节码:

   L1
    LINENUMBER 5 L1
    ALOAD 1
    INVOKEVIRTUAL A.test()Ljava/lang/Object;
    POP
   L2
    LINENUMBER 7 L2
    ALOAD 1
    INVOKEVIRTUAL A.test()Ljava/lang/Object;
    CHECKCAST java/lang/String
    ASTORE 2
可以看到a.test() 实际上只是调用了下test()函数,返回值直接被pop掉了,没有那个T什么事。

String r = a.test()处,则有个CHECKCAST指令,检查类型转换有没有成功。

所以我们可以看到<T> T这种写法实际上是一个语法糖,它和下面这种写法从本质上来说没有区别。

public class A{
	public static void main(String[] args) {
		A a = new A();
		a.test();
		
		String r = (String) a.test();
	}
	public Object test() {
		return new Object();
	}
}

extends的情况

下面再来看个复杂点的例子:

public class A{
	interface interface1{
		public String interfaceOne ();
	}
	public <T extends Date & interface1> T test1(T t) {
		t.interfaceOne();
		t.toLocaleString();
		return null;
	}
}

对应的字节码分析:

  public test1(Date) : Date
   L0
    LINENUMBER 21 L0
    ALOAD 1: t
    CHECKCAST A$interface1
    INVOKEINTERFACE A$interface1.interfaceOne() : String
    POP
   L1
    LINENUMBER 22 L1
    ALOAD 1: t
    INVOKEVIRTUAL Date.toLocaleString() : String
    POP
   L2
可以看到,用了extends来限定参数的类型后,函数传进来的参数直接是Date类型的了。

不过,当中间调用到interface1.interfaceOne()时,还是需要一个CHECKCAST来进行类型转换。

关于CHECKCAST指令

http://www.vmth.ucdavis.edu/incoming/Jasmin/ref--7.html

checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type. For example, if you write in Java:


return ((String)obj);
then the Java compiler will generate something like:

aload_1                        ; push -obj- onto the stack
checkcast java/lang/String     ; check its a String
areturn                        ; return it
checkcast is actually a shortand for writing Java code like:

if (! (obj == null  ||  obj instanceof <class>)) {
    throw new ClassCastException();
}
// if this point is reached, then object is either null, or an instance of
// <class> or one of its superclasses.
所以CHECKCAST指令实际上和INSTANCEOF指令是很像的,不同的是CHECKCAST如果失败,会抛出一个异常,INSTANCEOF是返回一个值。

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof

The instanceof instruction is very similar to the checkcast instruction (§checkcast). It differs in its treatment of null, its behavior when its test fails (checkcast throws an exception,instanceof pushes a result code), and its effect on the operand stack.


另外,据虚拟机专家RednaxelaFX的说法,JVM有可能在运行时优化掉一些CHECKCAST指令。

http://hllvm.group.iteye.com/group/topic/25910


总结:

JAVA的泛型只是一个语法糖,实际上在运行时还是有类型转换的过程,从JVM生成的代码来看,和传递一个Object(或者extends的类型)没什么区别。当然泛型的最大好处是编绎期的类型错误检查。

明白JAVA泛型的大致实现原理之后,看很多泛型代码都比较清晰了:)

和C++的泛型比较,C++的泛型是在编绎期实现的,为每一个类型都生成一份代码,所以C++的泛型容易让编绎后的代码出现膨胀。

C++不会保证在运行时,你硬塞一个什么东东进去函数里去执行的结果(极有可能程序挂掉了)。

但是Java代码是跑在JVM里的,要保证程序无论如何都能正常跑,所以泛型肯定会有CHECKCAST这样的消耗。

实际上CHECKCAST算是一个运行时的输入检查了,而C++没有这种检查,Java则要求要这种检查。而JVM则有可能优化掉这些检查,比如前面已经确认过对象的类型了,那么CHECKCAST就有可能被优化掉。

相关文章
|
5天前
|
JavaScript Java 编译器
Java包装类和泛型的知识点详解
Java包装类和泛型的知识点的深度理解
|
6天前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
9 0
|
6天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
13 0
|
10天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
18天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
57 0
|
20天前
|
Java
软件工程设计原理里氏替换原则 ,具体实现及JAVA代码举例
里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计的基本原则之一,由Barbara Liskov提出。这个原则指出,如果类 S 是类 T 的子类型,则程序中使用 T 的对象的地方都可以不经修改地使用 S 的对象。换句话说,子类的对象应该能够替换掉它们的父类对象,而不影响程序的正确性。这个原则强调了继承关系中的行为兼容性,保证了基类和派生类之间的正确抽象和继承关系。
22 3
|
4天前
|
存储 监控 安全
泛型魔法:解码Java中的类型参数
泛型魔法:解码Java中的类型参数
26 0
泛型魔法:解码Java中的类型参数
|
5天前
|
运维 NoSQL 算法
Java开发-深入理解Redis Cluster的工作原理
综上所述,Redis Cluster通过数据分片、节点发现、主从复制、数据迁移、故障检测和客户端路由等机制,实现了一个分布式的、高可用的Redis解决方案。它允许数据分布在多个节点上,提供了自动故障转移和读写分离的功能,适用于需要大规模、高性能、高可用性的应用场景。
15 0
|
6天前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
9 0
|
14天前
|
Java 开发者
软件工程设计原理接口隔离原则 ,具体实现及JAVA代码举例
【4月更文挑战第7天】接口隔离原则(Interface Segregation Principle, ISP)是面向对象设计原则之一,旨在减少不必要的依赖关系,通过拆分庞大且臃肿的接口为更小、更具体的接口来实现。这个原则强调“客户端不应该被迫依赖于它不使用的接口”,意味着一个类不应该被迫实现它不使用的方法。
16 1