1. 枚举
JDK1.5引入了新的类型——枚举,在枚举类型中定义的常量是该枚举类型的实例,枚举类的存在就是为了帮助我们管理一些设置好了的常量。
1.1 枚举类的格式
权限修饰符 enum 枚举名称 {
实例1,实例2,实例3,实例4,……;
}
1.2 枚举抽象类的常见方法
Enum是枚举类公共的抽象父类,所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。Enum的常见方法如下:
-
int compareTo(E o)
将此枚举与指定的订单对象进行比较,比较的是设置的相对顺序。
public enum Level { LOW, MEDIUM, HIGH, URGENT; } public class test{ public static void main(String[] args){ System.out.println(Level.LOW.compareTo(Level.HIGH));//返回的是-2 } }
-
boolean equals(Object other)
如果指定的对象等于此枚举常量,则返回true。
-
Class<?> getDeclaringClass()
返回与此枚举常量的枚举类型对应的Class对象。
-
String name()
返回此枚举常量的名称,与其枚举声明中声明的完全相同。
-
int ordinal()
返回此枚举常量的序数(它在枚举声明中的位置,其中初始常量的序数为零)。
-
String toString()
返回声明中包含的此枚举常量的名称。
-
static <T extends Enum> T valueOf(Class enumType, String name)
返回具有指定名称的指定枚举类型的枚举常量。
1.3 枚举类实现接口
每个枚举对象,都可以实现自己的抽象方法。
public interface LShow{
void show();
}
public enum Level implements LShow{
LOW{
@Override
public void show(){
//...
}
}, MEDIUM{
@Override
public void show(){
//...
}
},HIGH{
@Override
public void show(){
//...
}
};
1.4 使用枚举的注意事项
- 一旦定义了枚举,最好不要轻易修改里面的值,除非修改是必要的;
- 枚举类默认继承的是java.lang.Enum类,而不是Object类;
- 枚举类不能有子类,因为其枚举类默认被final修饰;
- 只能有private构造方法;
- switch中使用枚举时,直接使用常量名,不用携带类名;
- 不能定义name属性,因为自带name属性;
- 不要为枚举类中的属性提供set方法,不符合枚举最初设计初衷。
2. 注解
2.1 注解的定义
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注,相当于给他们添加了额外的辅助信息。
首先注解不是程序本身 , 可以对程序作出解释,这一点和注释(comment)没什么区别。
但是和注释不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
2.2 注解的格式
注解是以"@注释名"在代码中存在的,甚至可以添加一些参数值,例如:
@SuppressWarnings(value="unchecked")
2.3 注解的作用
- 编译格式检查
- 反射中解析
- 生成帮助文档
- 跟踪代码依赖
- ……
2.4 内置注解
-
@Override
定义在 java.lang.Override 中 ,用于编译格式检查,此注释只适用于修辞方法 , 表示一个方法声明打算重写超类中的另一个方法声明;
-
@Deprecated(已过时)
定义在java.lang.Deprecated中 ,此注释可以用于修辞方法、 属性、类 , 用于标注其被废弃了,表示不鼓励程序员使用这样的元素 , 通常是因为它很危险或者存在更好的选择;
-
@SafeVarargs
Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告;
-
@FunctionalInterface
Java 8 开始支持,标识一个匿名函数或函数式接口;
-
@Repeatable
Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
-
@SuppressWarnings
定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息, 与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们 选择性的使用就好了:
-
@SuppressWarnings(“all”)
抑制所有类型的警告;
-
@SuppressWarnings(“unchecked”)
抑制单类型的警告;
-
@SuppressWarnings(value={“unchecked”,“deprecation”})
抑制多类型的警告;
-
…
-
2.5 元注解
- ElementType(枚举类,注解的用途类型)
- RetentionPolicy(枚举类,注解作用域策略)
元注解的作用就是负责注解其他注解。Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到:
-
@Target
用于描述注解的使用范围(即:被描述的注解可以用在什么地方) ;
package java.lang.annotation; public enum ElementType { TYPE, /* 类、接口(包括注释类型)或枚举声明 */ FIELD, /* 字段声明(包括枚举常量) */ METHOD, /* 方法声明 */ PARAMETER, /* 参数声明 */ CONSTRUCTOR, /* 构造方法声明 */ LOCAL_VARIABLE, /* 局部变量声明 */ ANNOTATION_TYPE, /* 注释类型声明 */ PACKAGE /* 包声明 */ }
-
@Retention
表示需要在什么级别保存该注释信息 , 用于描述注解的生命周期 (SOURCE < CLASS < RUNTIME)
package java.lang.annotation; public enum RetentionPolicy { SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */ CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */ RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */ }
-
@Document
说明该注解将被包含在javadoc中 ;
-
@Inherited
标记这个注解是自动继承的。
- 子类会继承父类使用的注解中被@Inherited修饰的注解;
- 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰;
- 类实现接口时不会继承任何接口中定义的注解。
思路:
- ElementType、RetentionPolicy两个枚举类分别声明好:注解的作用的地方(类、方法、字段……)、作用的时间段(源文件、class文件、运行期);
- 四个元注解中,@Target和@Retention分别去管理这两个枚举类,去给其他注解做注解,@Inherited和@Document也是元注解;
- 元注解给其他注解做注解。
2.6 自定义注解
2.6.1 自定义注解格式
-
使用 @interface
定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
@interface 自定义注解名{}
-
使用@Documented
类和方法的 Annotation 默认在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。 定义 Annotation 时,@Documented 可有可无。
@Documented
-
@Target(ElementType.TYPE)
ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
@Target(ElementType.TYPE)
-
@Retention(RetentionPolicy.RUNTIME)
RetentionPolicy 是 Annotation 的策略属性,而 @Retention 的作用,就是指定 Annotation 的策略属性,确定这个注解什么时候有效(编译器、class文件、运行期)。定义 Annotation 时,@Retention 可有可无。若没有 @Retention,则默认是运行期。
@Retention(RetentionPolicy.RUNTIME)
2.7 注意事项
- 注解中的每一个方法,实际是声明的注解配置参数;
- 方法的名称就是配置参数的名称(类同于调用了一个有参方法一样,只用value属性是可以直接传的);
- 方法的返回值类型,就是配置参数的类型。只能是:基本类型/Class/String/enum;
- 可以通过default来声明参数的默认值,那么这个有默认值的参数就可以不传,所有没有默认值的参数都得传;
- 如果只有一个参数成员,一般参数名为value;
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串、0作为默认值。
//示例
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
@interface SuppressWarnings {
String[] value();
}
@SuppressWarnings("all")//value = "all"
public class Demo{
}
3. 反射
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类(已存在或者还没存在的)的内部信息,并能直接操作任意对象的内部属性及方法。
3.1 反射的定义
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象,进入方法区的入口),这 个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子, 透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
3.2 类加载器
Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分, 负责动态加载Java类到Java虚拟机的内存空间中。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。
java默认有三种类加载器,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader。它们之间不是继承关系上的子父类,而是引用上的子父类。
3.2.1 BootstrapClassLoader(引导启动类加载器)
嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负责加载JAVA_HOME/lib下的类库,引导启动类加载器无法被应用程序直接使用。
3.2.2 ExtensionClassLoader(扩展类加载器)
ExtensionClassLoader是用JAVA编写,且它的父类加载器是BootstrapClassLoader。 是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。
3.2.3 AppClassLoader(应用类加载器或系统类加载器)
App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。
它的父加载器为ExtensionClassLoader类,通常是按需加载,即第一次使用该类时才加载。获取方法:
某个类.Class.getClassLoader();
3.2.4 类加载过程
类加载的过程有七个部分,加载到初始化都是在程序的运行期间完成的。验证,准备,解析也叫连接过程,Java的特性是依赖在运行期动态加载和动态连接。(类加载并不是顺序执行,“并发” 执行)
3.2.4.1 加载阶段
类的加载指的是将类的.class文件的二进制数据读入内存中,加载完毕后将Class信息放在运行时的方法区内(Class常量池)。
-
通过一个类的全限定名来获取其定义的二进制字节流
-
将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
-
在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。
3.2.4.2 连接-验证阶段
类被加载后就进入了连接阶段。连接就是将已经读入到内存的类的二进制数据(class常量池中)合并到虚拟机的运行时环境中(运行时常量池中)。
所谓的数据合并,因为编译后的每个class文件在硬盘中都是独立的,但是每个class之间可能存在引用关系以及方法之间存在调用关系,
此时就需要根据它们之间的关系将这些class数据合并在一起放入运行时环境中。
目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全。主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。
-
文件格式验证(文件格式是否正确)
主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
-
元数据验证(文件内容是否符合要求)
确保Class的语义描述符合Java的Class规范。如:该Class是否有父类、是否错误继承了final类、是否一个合法的抽象类等。
-
字节码验证(文件内容是否能够合理运行)
最重要的验证环节,通过分析数据流和控制流,确保程序语义符合逻辑主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。如:验证类型转换是合法的。
-
符号引用验证
发生于符号引用转换为直接引用的时候(转换发生在解析阶段,主要去确定访问类型等涉及到引用的情况)。如:验证引用的类、成员变量、方法的是否可以被访问,当前类是否存在相应的方法、成员等。
3.2.4.3 连接-准备
在准备阶段,虚拟机会在方法区中为Class分配内存,并设置static成员变量的初始值为默认值。注意这里仅仅会为static变量分配内存(static变量在方法区中),并且初始化static变量的值为其所属类型的默认值。如:int类型初始化为0,引用类型初始化为null。即使声明了这样一个static变量:
public static int a = 123;
在准备阶段后,a在内存中的值仍然是0, 赋值123这个操作会在中初始化阶段执行,因此在初始化阶段产生了对应的Class对象之后a的值才是123 。为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。
3.2.4.4 连接-解析
-
符号引用
符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。
-
直接引用
是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
解析阶段,虚拟机会将常量池中的符号引用替换为直接引用,解析主要针对的是类、接口、方法、成员变量等符号引用。在转换成直接引用后,会触发校验阶段的符号引用验证,验证转换之后的直接引用是否能找到对应的类、方法、成员变量等。
3.2.4.5 初始化
这里是类加载的最后阶段(真正开始执行类中定义的java程序代码),如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)
3.2.4.6 初始化时机
类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类(一些内置类)。
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射:Class.forName(“com.lijinghua.pojo.Xxx”)
- 初始化一个类的子类(会首先初始化子类的父类),但是这个规则不适用于接口。
- 在初始化一个类时,并不会先初始化它所实现的接口;初始化一个接口时,并不会先初始化它所继承的父接口。只有当程序访问的静态变量或静态方法确实在当前接口中定义时,才会认为是对接口的主动使用。
- JVM启动时标明的启动类,即文件名和类名相同的那个类。
3.3 双亲委派机制
首先需要知道的是,java类加载遵循一个机制——Parents Delegation Model
(双亲委派模型)
3.3.1 双亲委派机制的过程
某个特定的类加载器在接收到加载类的请求时,除非显示的要求使用某一个类加载器,不然都会将加载任务委托给父类加载器,父类加载器又将加载任务继续向上委托,直到最终父类加载器,如果最终父类加载器可以完成此类的加载任务,就由其完成加载,如果不行就依次向下传递任务,由其子类加载器进行加载。
3.3.2 双亲委派机制的好处
- 出于安全考虑,这么做保证了java核心库的安全性,确保基础类永远都是由java提供的类加载器来加载;
- 可以避免重复加载,当父加载器已经加载了该类后,子类就没有必要再加载一次。
- 如果有人恶意篡改了基础类的代码(例如:java.lang.string)那他自己定义的java.lang.string将永远不会被加载进来,因为原始的String类已经在启动的时候就被加载进来了。
3.4 Class类
要想了解一个类,必须先要获取到该类的字节码文件对象。在Java中,每一个字节码文件,被加在到内存后,都存在一个对应的Class类型的对象,此类是Java反射的源头。
3.4.1 Class类的特点
- Class 本身也是一个类;
- Class 对象只能由系统建立对象;
- 一个加载的类在 JVM 中只会有一个Class实例 ;
- 一个Class对象对应的是一个加载到 JVM中的一个.class文件;
- 每个类的实例都会记得自己是由哪个 Class 实例所生成;
- 通过Class可以完整地得到一个类中的所有被加载的结构;
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。
3.4.2 哪些类型可以有Class对象
- class:外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类。
- interface:接口、数组、枚举类。
- annotation:注解@interface。
- primitive type:基本数据类型 。
- void
3.4.3 获取Class对象的几种方式
-
通过对象获得;(对象都创建了,获得的类肯定被加载过了)
Person p = new Student();//Student extends Person Class c1 = p.getClass();
-
通过字符串获得(包名+类名);(获得的Class对象所指的类不一定被加载过,动态加载)
Class c2 = Class.forName("com.reflection.Student");
-
通过类的静态成员class获得;
Class c3 = Person.class;
-
针对内置的基本数据类型,通过包装类.type、包装类.class和基本数据类型.class获得;
Class c4 = int.class; Class c5 = Integer.TYPE; Class c6 = Integer.class;
-
通过子类实例化对象获取父类的Class对象
Class c7 = c2.getSuperclass();
3.4.4 Class类的常用方法
-
static Class forName(String name)
返回指定类名name的Class对象。
-
Object newInstance()
调用缺省构造函数,返回Class对象的一个实例。
-
getName()
返回此Class对象所表示的实体(类,接口,数组类或 void)的名称。 -
Class getSuperClass()
返回当前Class对象的父类的Class对象。
-
Class[] getinterfaces()
获取当前Class对象的接口。
-
ClassLoader getClassLoader()
返回该类的类加载器。
-
通过Class对象,获取一个类的构造方法
-
Constructor getConstructor(参数类型的class对象数组)
返回这个类的构造器。
//举例 Person(String name,int age){……}//假设构造方法 Constructor c = p.getClass().getConstructor(String.class,int.class);
-
Constructor[] getConstructors()
返回一个包含某些Constructor对象的数组。
-
Constructor getDeclaredConstructor(参数类型的class对象数组)
获取所有权限的单个构造方法
-
Constructor[] getDeclaredConstructors()
获取所有权限的构造方法数组。
-
-
通过Class对象,获取一个类的方法
-
Method getMethod(String methodName, Class… T)
返回一个Method对象,此对象的形参类型为paramType。 -
Method[] getMethods()
得到一个类的所有方法 (public修饰的)
-
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)
-
Method getDeclaredMethod(String methodName , class… clss)
根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)。
-
Method[] getDeclaredMethods()
得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
-
invoke(Object o,Object… para)
调用方法 , 参数1: 要调用方法的对象 参数2:要传递的参数列表。
-
-
通过Class对象,获取一个类的属性
-
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性)
-
getDeclaredField(String filedName)
根据属性的名称, 获取一个属性对象 (所有属性)
-
getDeclaredFields()
获取所有属性
-
getField(String filedName)
根据属性的名称, 获取一个属性对象 (public属性)
-
getFields()
获取所有属性 (public)
-
属性的常用操作方法
-
get(Object o )
参数: 要获取属性的对象 获取指定对象的此属性值
-
set(Object o , Object value)
参数1:要设置属性值的对象,参数2:要设置的值 设置指定对象的属性的值。
-
getName()
获取属性的名称
-
-
3.5 通过反射操作注解
3.5.1 获取类/属性/方法的全部注解对象
Annotation[] annotations01 = Class/Field/Method.getAnnotations();
3.5.2 根据类型获取类/属性/方法的注解对象
注解类型 对象名 = (注解类型) c.getAnnotation(注解类型.class);
4.内省
基于反射衍生出来的, java所提供的一套应用到JavaBean的API,处于java.beans包下。
4.1 什么是Bean类
Bean类是没有业务逻辑的类,其定义就是:
- 一个定义在包中的类:
- 拥有无参构造器;
- 所有属性私有,且提供get/set方法;
- 实现了序列化接口。
4.2 Introspector内省
获取Bean类信息。
4.2.1 Introspector的常用方法
-
BeanInfo getBeanInfo(Class cls)
通过传入的类信息, 得到这个Bean类的BeanInfo封装对象。
Class c = Express.class;//假设有个快递类 BeanInfo b = Introspector.getBeanInfo(c);
4.3 BeanInfo
对应Bean类的信息封装类。
4.3.1 BeanInfo的常用方法
-
MethodDescriptor[] getPropertyDescriptors()
获取bean类的 get/set方法,返回一个数组。
PropertyDescriptor[] ms = b.getPropertyDescriptors();
4.4 MethodDescriptor
BeanInfo对象中一个属性的get/set方法,有可能返回null。
4.4.1 MethodDescriptor的常用方法
-
Method getReadMethod()
获取一个get方法。
-
Method getWriteMethod()
获取一个set方法。
for(PropertyDescriptor[] m : ms){//遍历获取
Method get = m.getReadMethod();
Method set = m.getWriteMethod();
}