反射入门
概念
1)反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性和方法。
2)加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。
这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射。
快速入门案例
public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.ma.Cat"); //我们可以通过forName()找到想要找到的那个类,然后调用newinstance()创建出对象 Object o = cls.newInstance(); System.out.println(o); System.out.println("o的运行类型="+o.getClass()); Method hi = cls.getMethod("aowu"); //可以通过反转的方式,找到对应的方法 System.out.println(hi); hi.invoke(o); //然后调用invoke,来执行该方法 } } public class Cat { private String name; public void hi(){ System.out.println("hahaha"); } public void aowu(){ System.out.println("aowu~"); } }
反射原理图
反射机制可以完成
1)在运行时判断任意一个对象所属的类
2)在运行时构造任意一个类的对象
3)在运行时得到任意一个类所具有的成员变量和方法
4)在运行时调用任意一个对象的成员变量和方法
5)生成动态代理
放射相关的主要类 (上面的例子有class跟method的演示)
1)java.lang.Class Class对象表示某个类加载后在堆中的对象
2)java.lang.reflect.Method 代表类的方法
3)java.lang.reflect.Field 代表类的成员变量
Field age = cls.getField("age");
System.out.println(age.get(o));
4)java.lang.reflect.Constructor 代表类的构造方法
Constructor constructor1 = cls.getConstructor(); System.out.println(constructor1); Constructor constructor2 = cls.getConstructor(String.class); System.out.println(constructor2);
结果如下:
反射的优点
可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就会失去底层支撑
放射的缺点
使用反射基本是解释执行,对执行速度有影响,例子如下:
public class SpeedTest { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { m1(); m2(); } static void m1(){ Cat cat = new Cat(); long start = System.currentTimeMillis(); for(int i=1;i<=1000000000;i++){ cat.hi(); } long end = System.currentTimeMillis(); System.out.println("time:"+(end-start)); } static void m2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { Class cls = Class.forName("com.ma.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); long start = System.currentTimeMillis(); for(int i=1;i<=1000000000;i++){ hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("time:"+(end-start)); } } class Cat { private String name; public String age="10"; public Cat(String name) { this.name = name; } public Cat() { } public void hi(){ // System.out.println("hahaha"); } public void aowu(){ System.out.println("aowu~"); } }
反射机制因为速度慢的一些优化
1)Method和Field、Constructor对象都有SetAccessible()方法
2)setAccessible作用是启动和禁用访问安全检查的开关
3)参数值为true时,表示反射的对象在使用
分析继承关系,可以知道,以上提及的三个对象,继承了AccessibleObject
Accessible使用方法
static void m2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { Class cls = Class.forName("com.ma.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); hi.setAccessible(true); //直接用即可 true表示取消访问检测 long start = System.currentTimeMillis(); for(int i=1;i<=1000000000;i++){ hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("time:"+(end-start)); }
关于Class类的基本介绍
1)Class也是类,因此也继承Object类
2)Class类对象不是new出来的,而是系统创建的
3)对于某个类的Class对象,在内存中只有一份,因为类只加载一次(例子如下)
可以观察到,结果是一样的
4)每个类的实例都会记得自己是由哪个Class实例所生成
5)通过Class可以完整的得到一个类的完整结构,通过一系列API
6)Class对象是存放在堆的
7)类的字节码二进制数据是放在方法区的,有的地方成为类的元数据
Class的常用方法
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { //1.获取类对象 Class cls = Class.forName("com.ma.Cat"); //2.输出cls System.out.println(cls); //显示cls是哪个类的Class对象 com.ma.Car System.out.println(cls.getClass()); //输出cls运行类型 java.lang.Class //3.得到包名 System.out.println(cls.getPackage().getName()); //包名 //4.得到全类名 System.out.println(cls.getName()); //5.通过cls创建实例对象 Object o = cls.newInstance(); System.out.println(o.toString()); //6.通过反射获取age属性 Field age = cls.getField("age"); System.out.println(age.get(o)); //7.通过反射给age改变值 age.set(o,"18"); System.out.println(age.get(o)); //8.获取所有字段 Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field); } }
获取Class对象的六种方式
1)Class.forName()
前提:已知一个类的全类名,且该类在类路径下
实例: Class cls = Class.forName("com.ma.Cat");
应用场景:多用于配置文件,读取类全路径,加载类
2)
前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
实例:Class cls = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
3)
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class cls = 对象.class;
应用场景:通过创建好的对象,获取Class对象
4)通过类加载器获取对象
public static void main(String[] args) throws ClassNotFoundException { Cat cat = new Cat(); ClassLoader classLoader = cat.getClass().getClassLoader(); Class cls = classLoader.loadClass("com.ma.Cat"); System.out.println(cls); }
5)基本数据类型
Class cls = 基本数据类型.class
Class<Integer> integerClass = int.class; Class<Character> characterClass = char.class; Class<Boolean> booleanClass = boolean.class;
6)基本数据类型对应的包装类,可以通过.TYPE 得到Class类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE;
在Java中哪些类有class对象?
1)外部类、成员内部类、静态内部类、局部内部类、匿名内部类
2)interface 接口
3)数组
4)enum 枚举
5)annotation 注解
6)基本数据类型
7)void
类加载的一些情况
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
何为动态?运行的时候才去加载所需要的类,如果运行时不用该类,则不会去加载,如果运行时用到该类并且该类不存在,就会报错
何为静态?编译时加载相关的类,如果没有则报错,依赖性太强。如:我们直接new Cat(),这个时候如果没有Cat类,是没办法成功运行的
类何时被加载
1)当创建一个对象时 (静态加载)
2)当子类被加载,父类也会被加载 (静态加载)
3)调用类中的静态成员 (静态加载)
4)通过反射 (动态加载)
类加载过程(加载、连接、初始化,面试可能会问类的加载过程)
1)加载阶段(如上所示,将类的class文件读入内存,并为之创建一个java.lang.Class对象)
2)连接阶段
验证:目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
除此之外,还会有文件格式验证、元数据验证、字节码验证、符号引用验证
我们可以 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
准备:JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0,0L,null,false)
解析:虚拟机将常量池内的符号引用替换为直接引用的过程
3)初始化
到初始化阶段,才开始真正执行类中定义的Java程序代码,此阶段是执行<Clinit>()方法的过程
<clinit>() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句进行合并
虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的
<clinit>方法,其他线程都必须阻塞等待,直到活动线程执行<clinit>() 方法完毕
通过反射获取类的结构信息
1)getName 获取全类名
2)getSimpleName 获取简单类名
3)getFields 获取所有public修饰的属性,包含本类以及父类
4)getDeclareFields 获取本类中所有属性
5)getMethods 获取所有public修饰的方法,包含本类以及父类的
6)getDeclareMethods 获取本类中所有方法
7)getConstructors 获取所有public修饰的构造器,包含本类,没有父类
8)getDeclareConstructors 获取本类中所有的构造器
9)getPackage 以Package形式返回包信息
10)GetSuperClass 以class形式返回父类信息
11)getInterfaces 以class[]形式返回接口信息
12)getAnnotations 返回注解信息
通过反射创建对象
1)调用类中的public修饰的无参构造器
2)调用类中指定构造器(可以通过得到对应的构造器来完成)
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> cls = Class.forName("com.ma.Cat"); //无参构造 Object o1 = cls.newInstance(); System.out.println(o1); //有参构造(一个参数) Constructor<?> constructor = cls.getConstructor(String.class); Object o2 = constructor.newInstance("金丝雀"); System.out.println(o2.toString()); //有参构造(两个参数) Constructor<?> constructor1 = cls.getConstructor(String.class, String.class); constructor1.setAccessible(true); Object o3 = constructor1.newInstance("哈哈", "heihei"); System.out.println(o3); //访问私有的有参构造 Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class); declaredConstructor.setAccessible(true); Object o4 = declaredConstructor.newInstance("100"); System.out.println(o4); }
注意:反射是可以访问到私有的构造器的,只要加上setAccessible(true)即可
通过反射调用Field
//访问public属性 Field age = cls.getField("age"); age.set(o4,"999"); System.out.println(age.get(o4)); //访问私有属性 Field name = cls.getDeclaredField("name"); name.setAccessible(true); name.set(o4,"张无忌"); System.out.println(name.get(o4));
通过反射调用method(和上面类似)
//访问public方法 Method hi = cls.getMethod("hi"); hi.invoke(o4); //访问私有方法 Method aowu = cls.getDeclaredMethod("aowu"); aowu.setAccessible(true); aowu.invoke(o4); //访问有参数的公有方法 Method hi2 = cls.getMethod("hi2", String.class); hi2.invoke(o4,"haha"); //访问静态方法,可以不指定对象,因为静态方法即时有很多对象也只会创建一个 Method helleworld = cls.getMethod("helleworld"); helleworld.invoke(null);