java5特性
拆箱和装箱
装箱(autoboxing)和拆箱(unboxing)
autoboxing: 将基本数据类型用他们响应的引用类包装起来,使其具有对象的性质
unboxing:和装箱相反,将引用数据类型的对象简化为值类型数据
在表达式中,基本类型的数据值可以和基本数据对象进行运算
基本数据类型的数组不能实现自动装箱和拆箱,即int[]不能当成Integer[]使用
示例
// jdk5.0之前
Integer i = new Integer(200);
// jdk5.0-自动装箱
Integer j = 200;
// jdk5.0之前
int ii = i.intValue();
// jdk5.0-自动拆箱
int jj = j;
//进行计算时隐含的有自动拆箱
Integer num = 1;
System.out.print(num--);其它类型类似,就不演示了。
上述语句是正确的,1 会被自动装箱为Integer类型,赋值给Object对象。
String 也有自动装箱和拆箱
原理
自动装箱时编译器调用 valueOf() 将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似 intValue(), doubleValue() 这类的方法将对象转换成原始类型值。
即:
证明
使用 javap -c Test 对上述代码的字节码进行反编译。

自动装箱的设计
这是因为Java自动装箱/拆箱使用 享元模式:
享元模式为了加大对简单数字的重用,所以定义:在自动装箱对于值-128到127内的值,他们被装箱为Integer(也包括Long、Character)对象后,会在内存中重用(指向同一个对象),始终指存在一个对象。
如果超过了这个范围(-128~127),被装箱后的Integer对象则不会被重用,所以每次装箱都会创建一个对象。
注意:只有 Long,Integer, Character有享元模式。Double,Float,Byte都没有享元模式
查看Integer源码:
CacheInteger:
foreach循环
JDK1.5提供了foreach循环,冒号左边为当前遍历到的元素,右边为遍历的集合/数组。
foreach遍历不能修改数组/集合
foreach每次loop的时候,获取的都是对象的值,而不是对象的句柄,所以数组永远不会改变。
ConcurrentModificationException 并发修改异常
遍历集合时,修改集合同样会出现ConcurrentModificationException异常:
foreach的原理
foreach遍历的原理是基于java.lang.Iterable接口,只要继承该接口,就可以使用foreach对对象进行遍历。
下面是反编译后:
可以清楚的看到整个步骤:
加载this对象到栈
加载字段包含integers
调用integers接口iteratable方法iterator,获取Iterator对象
使用Iterator.hasNext()判断是否有下一个值,如果有调用Iterator.next()获取值,并赋值给变量i
注意,数组并没有实现Iterale接口,数组使用foreach遍历的字节码如下:

变长参数 vararg
变长参数 values 实际还是一个数组类型
静态导入 static iomport
静态导入可以导入Class中的所有static变量以及方法:
泛型
为什么需要泛型?
使用泛型
通过List,直接限定了list集合中只能含有String类型的元素,集合记住了元素的类型,无需进行强制转换
Heap Pollution
什么是堆污染:
堆污染是一种技术用语,它指的是对象的引用类型不是指向对象类型或者超类类型。
所以,当将一个不带泛型的对象赋值给带泛型的变量是,通常会引发堆污染。
什么时候会发生堆污染
把一个不带泛型的对象赋给一个带泛型的变量是,就会发生堆污染. 通常编译期间都会检测出这种情况,并且显示警告: unchecked warning.
为什么会发生堆污染
在Java中,参数化类型(泛型),都是非具体化类型(non-reifiable types)。
non-reifiable types 是指,在runtime时期是不完整的类型,在编译期间,非具体化类型会经过 “类型擦除”(编译器会删除域类型参数相关的信息),所以准确的说,泛型只存在于编译时期。
非具体化类型保证了那些使用JDK1.5(没有泛型)之前的程序的二进制兼容性。
因此,不同泛型的同一类型变量,在运行时具有相同的类或接口实现。所以,当参数化类型的变量( List<String>)指的不是该参数化类型的对象( List<Integer>)时,就发生了堆污染。
parameterized vararg type 为什么会发生堆污染警告
有如下参数化可变参数类型的方法会发生堆污染警告:
参数化可变参数类型可能发生堆污染,这是因为,可变参数在编译时期,会做出如下转换:
public static <T> void foo(List<T>... bar)函数,被转换为public static <T> void foo(List<T>[] bar),然后再转换为public static <T> void foo(List[] bar)
这样,我们如下调用:
就会发生 ClassCastException 异常,(java.lang.String; cannot be cast tojava.util.List)。所以,会出现堆污染警告。
枚举
JDK1.5引入枚举类型,他是一种特殊的数据类型,即使一种类(class)类型但是又比普通的类类型多了些特殊的约束,这些约束造就了枚举类型的简洁性,安全性以及便捷性。
定义枚举
引用枚举:
枚举的实现原理
将上述day类编译并使用反编译得到如下:
编译器在编译enum时,会生成一个final的
Day类,并且此类继承java.lang.Enum根据枚举元素,生成了七个Day类型的实例对象,并且都是
public static final修饰,所以,使用enum定义的枚举元素都是一个个Day实例此外,编译器还生成了两个静态方法,分别为
values()和valueOf()
java.lang.Enum
上面说到,使用enum修饰的枚举类型,在编译期间会自动继承Enum类,下面是Enum类的使用和源码:
首先,Enum是一个抽象类,实现了序列化接口Serializable和比较接口 Comparable,这意味着Enum可以被序列化以及被比较。
构造:
Enum只有一个包私有构造器:
protected Enum(String name, int ordinal)
从Object继承的方法:
protected Object clone()CloneNotSupportedException,enum类型不支持对象克隆protected void finalize()枚举类不能有 finalize 方法String toString()返回枚举常量的名称,它包含在声明中int hashCode()boolean equals(Object other)当指定对象等于此枚举常量时,返回 true
比较方法Compareable:
int compareTo(E o)
特有方法:
Class<E> getDeclaringClass()返回枚举常量对应的Class对象int ordinal()返回枚举常量的序数,它在枚举声明中的位置,其中初始常量序数为零,比如Day.MONDAY.ordinal() == 0String name()返回此枚举常量的名称,和toString功能类似static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)返回带指定名称的指定枚举类型的枚举常量
编译器生成的 values 方法和 valueOf 方法:
values()方法和valueOf(String name)方法是编译器生成的static方法
源码解析:
枚举的几种用法
1. 用作常量
在JDK1.5之前,定义常量需要使用 public static final
在JDK1.5,我们可以这样定义常量:
2. switch语句
JDK1.6之前switch语句只支持 int, char, enum 类型,使用枚举可以让代码可读性更强
3. 添加构造/方法/字段
因为enum本身是一个类,而元素又是enum的对象,所以我们可以添加字段记录一些值,使用构造初始化这些值:
也可以添加一些方法,作为工具类调用。
4. 覆盖toString
5. 定义抽象
6. 实现接口
我们可能对一组数据进行分类:
Java的注解
参考:
https://blog.csdn.net/sun_promise/article/details/51315032
https://docs.oracle.com/javase/tutorial/java/annotations/type_annotations.html
https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html
概念
Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。(注解就是元数据)
JDK1.5 提供注解特性。
元注解
修饰注解的注解,称为元注解,在JDK中,共有四种元注解:
@Target规定注解可以使用的位置,类,方法还是域@Retention表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义@Documented表示注解会被包含在javaapi文档中@Inherited允许子类继承父类的注解
@Target:
使用ElementType枚举定义,包含以下值:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类、接口(包括注解类型)或enum声明
ANNOTATION_TYPE:注解声明(应用于另一个注解上)
TYPE_PARAMETER:类型参数声明(1.8新加入)
TYPE_USE:类型使用声明(1.8新加入)
@Retention:
使用RetentionPolicy枚举定义,包含以下值:
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)
RUNTIME:VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息) PS:当注解未定义Retention值时,默认值是CLASS
常用注解
java.lang.SuppressWarnings抑制警告java.lang.Override方法重写java.lang.Deprecated已经过时
jdk7新增:
@SafeVarargs堆污染
定义注解
底层实现
注解的本质实际上是一个Interface,所有注解在反编译后都是继承 java.lang.annotation.Annotation 接口的。
类型注解
JDK8后,类型注解可以在任何有类型的地方加入注解(包含泛型):
作用:
在8种新增类型注释主要是为了改进Java的程序分析,配合类型检查框架做强类型检查,从而在编译期间确认运行时异常,比如 NullpointException,从而提高代码质量。
比如:
第三方工具会在编译期间自动检测my是否为null,如果为null,抛出异常或者警告
创建类型注解
JDK8 新增 ElementType.TYPE_USE 用来创建类型注解:
注意:JDK8还提供了 TYPE_PARAMETER 类型的注解,表示可以修饰类型参数(泛型)的注解。
注解的继承
首先想让注解可继承,必须增加
@Inherited元注解,如果需要反射支持,就需要@Rentention(RetentionPolicy.RUNTIME)标识JDK文档中,对于注解继承的描述为:只有类上的注解才能被继承
实际上,如果类中的成员成员变量以及成员方法没有被重写,他们就仍然是父类的那个方法,即使在子类中。故继承自父类的没有被重写的成员,也具有原注解的信息。
注解的继承不能用在接口上,任何接口上的注解都不能被继承
可重复注解(Repeating Annotations)
在8之前,一个目标只可以打一个注解:
现在JDK8提供了Repeating Annotations,可以在一个目标上打上多个可重复注解:
创建可重复注解
JDK8中提供了一个新的源注解,用于表示可重复注解:
获取可重复注解(1.8)
注意:如果想要在运行时获取注解,注解 Retention 必须为 RetentionPolicy.RUNTIME,并且,Schedules与Schedule 注解的保留时期必须都为运行时!
获取可重复注解的方式同获取普通注解没有什么太大的区别:
可重复注解的原理
使用一个注解来存储重复的注解,编译后的class为:
所谓的可重复注解, 只是编译层面的改动。
最后更新于
这有帮助吗?