1.泛型与类型擦除

泛型的本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。在泛型没有出现之前,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化,由于java语言所有的类型都继承自Object,因此Object转型成任何对象都是有可能的,但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转换是否成功,只能寄托于程序员不会出错,许多ClassCatException的风险就会转嫁到程序运行期之中

C#的泛型在程序源码在、编译后的IL中,或是运行期的CLR中,都是切实存在的,List<int>和List<String>就是两种不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型

java语言中的泛型不一样,只在源码中存在,在编译后的字节码文件中,已经替换成了原来的原生类型(也称为裸类型)了,并且在相应的地方插入了强制转化代码,因此,对于运行期的Java语言来说,ArrayList<int>和ArrayList<String>就是同一个类,所以泛型技术实际上是java语言的一颗语法糖,java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

  1. public static void main(String[] args){
  2.  
  3. Map<String,String> map=new HashMap<String,String>();
  4. map.put("hello","nihao");
  5. map.put("how are you","chifan");
  6. System.out.println(map.get("hello"));
  7. System.out.println(map.get("how are you"));
  8.  
  9. }

这段代码编译成Class文件,然后用字节码反编译工具进行反编译后,泛型类型都变回了原生类型。

  1. public static void main(String[] args){
  2.  
  3. Map map=new HashMap();
  4. map.put("hello","nihao");
  5. map.put("how are you","chifan");
  6. System.out.println((String)map.get("hello"));
  7. System.out.println((String)map.get("how are you"));
  8.  
  9. }

通过擦除法来实现泛型丧失了一些泛型思想应有的优雅。

  1. public class G{
  2.  
  3. public static void method(List<String> list){
  4.  
  5. //输出1
  6. }
  7. public static void method(List<Integer> list){
  8.  
  9. //输出2
  10.  
  11. }
  12.  
  13. }

很明显不能编译通过,因为擦除以后两种方法的方法签名变得一模一样。好像不能重载的原因找到了?只能说泛型擦除成相同的原生类型这时无法重载的一部分原因,继续看

  1. public class G{
  2.  
  3. public static String method(List<String> list){
  4.  
  5. //输出1
    return "";
  6.  
  7. }
  8. public static int method(List<Integer> list){
  9.  
  10. //输出2
  11. return 1;
  12. }
  13. public static void main(String[] args){
  14.  
  15. method(new ArrayList<String>()):
  16. method(new ArrayList<Integer>()):
  17. }
  18.  
  19. }

执行结果

  1.  

因为两个返回值的加入,方法重载居然成功了。这是对java语言中返回值不参与重载选择的基本认知的挑战吗?

之所以能够编译和执行成功,是因为两个method()方法中加入了不同的返回值后才能够共存在一个Class文件之中。Class文件方法表中提到过,方法重载要求方法具备不同的特征签名,返回值并不包括在方法的特征签名里,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存,也就是说,两个方法如果相同的名称和特征签名,但返回值不同,那他们也是可以合法地共存于一个Cass文件中的!

!!!擦拭法所谓的擦除,仅仅是对方法的code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能够通过反射手段取得参数化类型的根本依据。

既然所有的泛型都会被擦拭,为什么不能往List<String>里面加int类型的数据呢?在Myeclipse里面这样会直接报错,说明是在编译之前就进行的,我想不是应该发生在语义分析里面的标注检查那个阶段么??解除语法糖发生在语义分析之后,说明之前会发生标注检查,应该就是这样吧。

②自动装箱、拆箱和遍历循环

java语言里面使用最多的语法糖。

  1. public static void main(String[] args){
  2.  
  3. List<Integer> list=Arrays.asList(,,,);
  4. int sum=;
  5. for(int i:list){
  6. sum+=i;
  7.  
  8. }
  9.  
  10. }

解除语法糖之后

  1. public static void main(String[] args){
  2.  
  3. List list=Arrays.asList(new Integer[]{
  4. Integet.valueOf();
  5. Integet.valueOf();
  6. Integet.valueOf();
  7. Integet.valueOf();
  8. }
  9. int sum=;
  10. for(Iterator l=list.iterator();l.hasNext();){
  11.  
  12. int i=((Ingeter)l.next()).intValue();
  13. sum+=;
  14. }
  15.  
  16. }

变长参数调用的时候变成了一个数组类型的参数,Integer.valueOf()与Integer.intValue()为包装和还原方法。

自动装箱的错误用法

  1. public static void main(String[] args){
  2. Integer a=;
  3. Integer b=;
  4. Integer c=;
  5. Integer d=;
  6. Integer e=;
  7. Integer f=;
  8. Long g=3L;
  9. System.out.println(c==d);
  10. System.out.println(e==f);
  11. System.out.println(c==(a+b));
  12. System.out.println(c.equals(a+b));
  13. System.out.println(g==(a+b));
  14. System.out.println(g.equals(a+b));
  15. }
  1. true
  2. false
  3. true
  4. true
  5. true
  6. false

包装类的“==”运算在不遇到算术运算的情况下不会自动装箱,以及它们的equals()方法不处理数据类型转型的关系。

1. 首先我们明确一下"=="和equals方法的作用。

  "==":如果是基本数据类型,则直接对值进行比较,如果是引用数据类型,则是对他们的地址进行比较(但是只能比较相同类型的对象,或者比较父类对象和子类对象。类型不同的两个对象不能使用==)

  equals方法继承自Object类,在具体实现时可以覆盖父类中的实现。看一下Object中qeuals的源码发现,它的实现也是对对象的地址进行比较,此时它和"=="的作用相同。而JDK类中有一些类覆盖了Object类的equals()方法,比较规则为:如果两个对象的类型一致,并且内容一致,则返回true,这些类有:
java.io.file,java.util.Date,java.lang.string,包装类(Integer,Double等)。

2. Java的包装类实现细节。观察源码会发现Integer包装类中定义了一个私有的静态内部类如下:

  1. 1 private static class IntegerCache {
  2. 2 static final int low = -128;
  3. 3 static final int high;
  4. 4 static final Integer cache[];
  5. 5
  6. 6 static {
  7. 7 // high value may be configured by property
  8. 8 int h = 127;
  9. 9 String integerCacheHighPropValue =
  10. 10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
  11. 11 if (integerCacheHighPropValue != null) {
  12. 12 try {
  13. 13 int i = parseInt(integerCacheHighPropValue);
  14. 14 i = Math.max(i, 127);
  15. 15 // Maximum array size is Integer.MAX_VALUE
  16. 16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
  17. 17 } catch( NumberFormatException nfe) {
  18. 18 // If the property cannot be parsed into an int, ignore it.
  19. 19 }
  20. 20 }
  21. 21 high = h;
  22. 22
  23. 23 cache = new Integer[(high - low) + 1];
  24. 24 int j = low;
  25. 25 for(int k = 0; k < cache.length; k++)
  26. 26 cache[k] = new Integer(j++);
  27. 27
  28. 28 // range [-128, 127] must be interned (JLS7 5.1.7)
  29. 29 assert IntegerCache.high >= 127;
  30. 30 }
  31. 31
  32. 32 private IntegerCache() {}
  33. 33 }

而Integer的自动装箱代码:

  1. 1 public static Integer valueOf(int i) {
  2. 2 if (i >= IntegerCache.low && i <= IntegerCache.high)
  3. 3 return IntegerCache.cache[i + (-IntegerCache.low)];
  4. 4 return new Integer(i);
  5. 5 }

通过观察上面的代码我们可以发现,Integer使用一个内部静态类中的一个静态数组保存了-128-127范围内的数据,静态数组在类加载以后是存在方法区的,并不是什么常量池。在自动装箱的时候,首先判断要装箱的数字的范围,如果在-128-127的范围则直接返回缓存中已有的对象,否则new一个新的对象。其他的包装类也有类似的实现方式,可以通过源码观察一下。

3. "=="在遇到非算术运算符的情况下不会自动拆箱,以及他们的equals方法不处理数据类型转换的关系。

因此,对于 System.out.println(c == d); 他们指向同一个对象,返回True。

对于 System.out.println(e == f); 他们的值大于127,即使值相同,但是对应不同的内存地址,返回false。

对于 System.out.println(c == (a+b)); 自动拆箱后他们的值是相等的,返回True。

对于 System.out.println(c.equals(a+b)); 他们的值相同,而且类型相同,返回true。

对于 System.out.println(g == (a+b)); 自动拆箱后他们的值相等,返回True。

对于 System.out.println(g.equals(a+b)); 他们的值相同但是类型不同,返回false。

③条件编译

(—般情况下,C语言源程序中的每一行代码.都要参加编译。但有时候出于对程序代码优化的考虑.希望只对其中一部分内容进行编译.此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译)

C、C++使用预处理器指示符(#ifdef)来完成条件编译。而在java预言之中没有使用预处理器,因为java语言天然的编译方式(编译器并非一个个地编译java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息)无须使用预处理器。

java想实现条件编译,方法就是使用条件为常量的if语句。这时if语句不通气其他java代码,它在编译阶段就会被“运行”,生成字节码之中只包括if里面的内容,不包括它分支else里面的内容

  1. public static void main(String[] args){
  2.  
  3. if(true){
  4. System.out.println("bolck 1");
  5. }else{
  6. System.out.println("bolck 2");
  7. }
  8.  
  9. }

编译后会变成

  1. public static void main(String[] args){
  2. System.out.println("bolck 1"):
  3.  
  4. }

早期(编译器)优化--Java语法糖的味道的更多相关文章

  1. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  2. Java语法糖设计

    语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这 ...

  3. java语法糖---枚举

    java语法糖---枚举   在JDK5.0中提供了大量的语法糖,例如:自动装箱拆箱.增强for循环.枚举.泛型等.所谓“语法糖”就是指提供更便利的语法供程序员使用,只是在编译器上做了手脚,却没有提供 ...

  4. Java语法糖(二)

    语法糖之四:内部类 内部类:顾名思义,在类的内部在定义一个类.内部类仅仅是编译时的概念,编译成字节码后,内部类会生成单独的Class文件. 四种:成员内部类.局部内部类.匿名内部类.静态内部类. 1. ...

  5. Java语法糖(一)

    概述 语法糖(Syntactic Sugar):主要作用是提高编码效率,减少编码出错的机会. 解语法糖发生在Java源码被编译成Class字节码的过程中,还原回简单的基础语法结构. 语法糖之一:泛型( ...

  6. JVM总结-Java语法糖与Java编译器

    自动装箱与自动拆箱 首先要提到的便是 Java 的自动装箱(auto-boxing)和自动拆箱(auto-unboxing). 我们知道,Java 语言拥有 8 个基本类型,每个基本类型都有对应的包装 ...

  7. 深入理解java虚拟机(十二) Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  8. Java语法糖4:内部类

    内部类 最后一个语法糖,讲讲内部类,内部类指的就是在一个类的内部再定义一个类. 内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功 ...

  9. 转:【深入Java虚拟机】之六:Java语法糖

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18011009 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家P ...

随机推荐

  1. convertToNodeSpace和convertToWorldSpace ---实际应用

    游戏中经常会用到一些变换: 游戏中武器和角色在一个layer上,为了效率,会考虑将bullet, effect和 PhysicsParticle分别放到不用的层上,对应的层上使用batchnode来提 ...

  2. Python-GIL 进程池 线程池

    5.GIL vs 互斥锁(*****) 1.什么是GIL(Global Interpreter Lock) GIL是全局解释器锁,是加到解释器身上的,保护的就是解释器级别的数据 (比如垃圾回收的数据) ...

  3. Vue源码

    参考文章:http://hcysun.me/2017/03/03/Vue%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/?utm_source=qq&utm_medi ...

  4. --save-dev和--save的区别

    使用npm来进行前端包管理的时候,我们会用到npm install或者cnpm install命令来安装需要用到的包资源 1: npm install *** --save-dev 2: npm in ...

  5. HTTP协议 (1)

    HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议. HTT ...

  6. Adam优化算法

    Question? Adam 算法是什么,它为优化深度学习模型带来了哪些优势? Adam 算法的原理机制是怎么样的,它与相关的 AdaGrad 和 RMSProp 方法有什么区别. Adam 算法应该 ...

  7. openj 4004 01背包问题求方案数

    #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define ...

  8. pytest二:setup和teardown

    用例运行级别 模块级(setup_module/teardown_module)开始于模块始末,全局的 函数级(setup_function/teardown_function)只对函数用例生效(不在 ...

  9. python 全栈开发,Day69(Django的视图层,Django的模板层)

    昨日内容回顾 相关命令: 1 创建项目 django-admin startproject 项目名称 2 创建应用 python manage.py startapp app名称 3 启动项目 pyt ...

  10. python 全栈开发,Day61(库的操作,表的操作,数据类型,数据类型(2),完整性约束)

    昨日内容回顾 一.回顾 定义:mysql就是一个基于socket编写的C / S架构的软件 包含: ---服务端软件 - socket服务端 - 本地文件操作 - 解析指令(mysql语句) ---客 ...