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就有可能被优化掉。

相关文章
|
2月前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
57 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
24天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
24天前
|
存储 算法 Java
【JAVA】生成accessToken原理
在Java中,生成accessToken用于身份验证和授权,确保合法用户访问受保护资源。流程包括:1. 身份验证(如用户名密码、OAuth 2.0);2. 生成唯一且安全的令牌;3. 设置令牌有效期并存储;4. 客户端传递令牌,服务器验证其有效性。常见场景为OAuth 2.0协议,涉及客户端注册、用户授权、获取授权码和换取accessToken。示例代码展示了使用Apache HttpClient库模拟OAuth 2.0获取accessToken的过程。
|
2月前
|
前端开发 JavaScript Java
Java构建工具-maven的复习笔记【适用于复习】
这篇文档由「潜意识Java」创作,主要介绍Maven的相关知识。内容涵盖Maven的基本概念、作用、项目导入步骤、依赖管理(包括依赖配置、代码示例、总结)、依赖传递、依赖范围以及依赖的生命周期等七个方面。作者擅长前端开发,秉持“得之坦然,失之淡然”的座右铭。期待您的点赞、关注和收藏,这将是作者持续创作的动力! [个人主页](https://blog.csdn.net/weixin_73355603?spm=1000.2115.3001.5343)
45 3
|
3月前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
65 3
|
3月前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
98 2
|
4月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
4月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
3月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)

热门文章

最新文章