
一、注解Annotation
1.1 注解入门
注解作用
对程序作出解释(Comment),可以被其他程序(比如编译器)读取。
注解格式
@注释名,还可以添加参数值
比如:@Override
1.2 内置注解和元注解
内置注解
@Override:表示方法声明旨在覆盖超类型中的方法声明。
@Deprecated的程序元素是程序员不鼓励使用的程序元素,通常是因为它是危险的,或者因为存在更好的替代方法。
@SuppressWarnings: 表示在注释元素(以及注释元素中包含的所有程序元素)中应该抑制命名的编译器警告。
元注解meta-annotation
作用:负责注解其他注解。
@Target:描述注解的适用范围(比如有的注解只能用在方法上,有的注解可以用在类上。。)
@Retention:用于描述注解生命周期(RUNTIME>CLASS>SOURCE)
@Documented:该注解是否在javadoc中
@Inherited:子类可以继承父类的注解
1.3 自定义注解
使用@interface自定义注解时候,自动继承java.lang.annotation.Annotation接口。
import java.lang.annotation.*;public class TestAnno { //测试注解 @MyAnnotion(age = 18,name = "psz") public void test(){ }}//自定义注解//注解适用范围:方法,类@Target({ElementType.METHOD,ElementType.TYPE})//生命周期:runtime@Retention(RetentionPolicy.RUNTIME)//表示是否讲注解生成在Javadoc中@Documented//子类可以继承父类的注解@Inherited@interface MyAnnotion { //注解的参数(不是方法):参数类型+参数名(); String name() default ""; //注解没有默认值的时候,使用注解时候必须赋值 int age();}二、Class
2.1 Class类
Class类是反射的源头,我们首先应该得到对应的Class对象。
Class本身也是一个类,Class对象只能由系统建立,一个类加载在JVM中只有一个Class实例,一个Class对象对应一个.class文件。
哪些类可以有Class对象呢?
类,接口,数组,枚举,反射,基本数据类型,void。。。
获得Class实例方法
Person p =new Person(); //方式一:通过具体实例得到 Class c1 = p.getClass(); //方式二:通过类得到 Class c2 = Person.class; //方式三:通过全路径得到 Class c3 = Class.forName("com.psz.Person");一个类在JVM中只有一个Class实例
System.out.println(c1.hashCode()==c2.hashCode()); //true2.2 类加载内存
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
过程:
加载:由类加载器ClassLoader执行,将字节流所代表的的静态存储结构转换为运行时数据结构,然后生成对应的java.lang.Class对象。
链接:将.Class文件中二进制代码合并到运行环境中。
验证:确保字节流包含的信息是否符合当前虚拟机的要求
准备:为static静态域分配存储空间和设置类变量的初始值
解析:将常量池中的符号引用转化为直接引用
初始化:执行类中定义的java程序代码。
2.3 类初始化
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)
类的主动引用(会发生初始化)
虚拟机启动时候,main函数所在的类被初始化
new 一个类
调用类的静态成员或静态方法
反射调用
初始化子类的时候,如果父类没有被初始化,那么父类会被初始化
类的被动引用(不会发生初始化)
访问静态域,只有通过真正声明这个域的类才会被初始化。(比如子类调用父类的静态变量,此时子类不会初始化)
通过数组定义类引用,不会初始化
引用final常量时候不会触发此类的初始化
2.4 类加载器ClassLoader
作用:负责加载类的对象。通俗的讲就是将class文件加载到jvm虚拟机中去。
分类:
C++编写,是JVM自身的一部分。将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库(rt.jar),此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用
负责加载jre\lib\ext目录下的jar包类库,用来加载java的扩展库,开发者可以直接使用这个类加载器。
系统类加载器Application ClassLoader
扩展类加载器Extendsion ClassLoader
引导类加载器Bootstrap ClassLoader
测试加载器
//1. 获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2 //2. 获取系统类加载器的父类加载器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1b6d3586 //3. 获取扩展类加载器的父类加载器-->获取不到 ClassLoader parent1 = parent.getParent(); System.out.println(parent1);//null //4.测试当前类是哪个加载器加载的 ClassLoader classLoader = Class.forName("com.psz.TestReflection").getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2三、反射Reflection
3.1 定义
因为Java是静态语言,运行时结构不可变,当我们想让编程更加灵活,想获得类似动态语言的特性时候,就可以用反射机制。
反射机制是在运行状态时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
通俗的来讲:加载完类之后,内存的方法区中就会生成一个Class类型的对象,这个对象包含了类的所有结构信息(属性,方法,私有字段等等)。我们通过这个对象看到类的结构。
缺点:影响性能,比如invoke()会对参数做封装和解封操作。项目中尽量不要使用大量的反射。
3.2 获得类的信息
通过Class获得类中的属性,方法,修饰符等等等。
Class personClass = Person.class; //1.获得类的名字 String name = personClass.getName(); //com.psz.Person 包名+类名 String simpleName = personClass.getSimpleName(); //Person //2. 获得类的属性 Field[] fields = personClass.getFields(); //得到声明为public的属性 Field[] declaredFields = personClass.getDeclaredFields();//全部属性 Field name1 = personClass.getDeclaredField("name"); //得到属性为name的值 //3.得到方法 personClass.getMethods();//public 本类+父类的public方法 personClass.getDeclaredMethods();//本类的所有方法 personClass.getMethod("方法名","参数"); //因为存在重载,所以需要参数 //4.获得构造器 personClass.getConstructors(); personClass.getDeclaredConstructor();3.4 动态创建对象
通过反射获得对象
Class personClass = Person.class;//2.创建对象,调用无参构造器Person person = (Person)personClass.newInstance();System.out.println(person); //Person{name='null', age=0}//3.通过构造器创建对象Constructor constructor = personClass.getDeclaredConstructor(String.class,int.class);Person psz = (Person)constructor.newInstance("psz", 22);System.out.println(psz); //Person{name='psz', age=22}通过反射调用方法
//通过反射调用方法,方法名+参数Method m = personClass.getMethod("setName", String.class);//invoke(对象,方法值)m.invoke(psz,"codecat");System.out.println(psz.getName()); //codecat通过放射操作属性
错误方式:
public static void main(String[] args) throws Exception { //1.获得Class对象 Class personClass = Person.class; //通过反射操作属性 Person psz = (Person)personClass.newInstance(); Field age = personClass.getDeclaredField("age"); age.set(psz,22); System.out.println(psz.getAge()); }报错:因为age字段是private,无法直接set。
Exception in thread "main" java.lang.IllegalAccessException: Class com.psz.TestReflection can not access a member of class com.psz.Person with modifiers "private"at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)at java.lang.reflect.Field.set(Field.java:761)at com.psz.TestReflection.main(TestReflection.java:59)正确方式,关闭程序的安全检测
setAccessible(true),设置成true后,就关闭了安全监测。关闭检测效率会高一些。
Person psz = (Person)personClass.newInstance();Field age = personClass.getDeclaredField("age");//=========关闭安全监测===================age.setAccessible(true);age.set(psz,22);System.out.println(psz.getAge());3.4 性能分析
我们尝试去执行2000000000次方法,来查看几种方式的执行时间。
//1. 普通方式,new对象 public static void normalTest(){ Person p1 =new Person("psz",22); long startTime = System.currentTimeMillis(); for (int i = 0; i < 2000000000; i++) { p1.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式:"+(endTime-startTime)+"ms"); } //2. 反射 public static void reflectionTest() throws Exception { Class personClass = Person.class; Constructor constructor = personClass.getDeclaredConstructor(String.class,int.class); Person p2 = (Person)constructor.newInstance("psz", 22); long startTime = System.currentTimeMillis(); for (int i = 0; i < 2000000000; i++) { p2.getName(); } long endTime = System.currentTimeMillis(); System.out.println("反射方式:"+(endTime-startTime)+"ms"); } //3. 反射,关闭检查 public static void reflectionAndCloseCheckTest() throws Exception { Class personClass = Person.class; Person p3 = (Person)personClass.newInstance(); Field name = personClass.getDeclaredField("name"); name.setAccessible(true);//关闭检查 name.set(p3,"psz"); long startTime = System.currentTimeMillis(); for (int i = 0; i < 2000000000; i++) { p3.getName(); } long endTime = System.currentTimeMillis(); System.out.println("反射+不检查方式:"+(endTime-startTime)+"ms"); }结果:普通 > 反射不检查 > 反射检查
普通方式:4ms反射方式:6ms反射+不检查方式:5ms总结:注解和反射在框架中有大量的应用,也是面试常见问题,所以学好注解和反射,对工作有很大帮助。
