Day14-反射
反射
Java反射机制可以完成
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时得到任意一个类所具有的成员变量和方法
-
在运行时调用任意一个对象的成员变量和方法
-
生成动态代理
反射相关的主要类
-
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
-
java.lang.reflect.Method:代表类的方法
-
java.lang.reflect.Field:代表类的成员变量,getField不能得到私有的属性
-
java.lang.reflect.Constructor:代表类的构造方法
反射的优点和缺点
-
优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
-
缺点:使用反射基本时解释执行,对执行速度有影响
反射调用优化—关闭访问检查
-
Method和Field、Constructor对象都有setAccessible()方法
-
set Accessible作用是启动和禁用访问安全检查开关
-
参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查
Class类
package com.lsq.study.反射;
import java.lang.reflect.Field;
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classAllPath="com.lsq.study.反射.Car";
//1.获取到Car类对应的Class对象
//<?>表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
//2.输出cls
System.out.println(cls);//显示cls对象,是哪个类的Class对象 com.lsq.study.反射.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创建对象实例
//jdk1.9以前的写法
Car car = (Car)cls.newInstance();
System.out.println(car);
//jdk1.9后的写法
//Object o=cls.getDeclaredConstructors().newInstance();
//6.通过反射获取属性brand(如果是私有属性会报错)
Field brand = cls.getField("brand");
System.out.println(brand.get(car));
//7.通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
//8.得到所有的属性(字段)
Field[] fields = cls.getFields();
for (Field f :fields) {
System.out.println(f.getName());
}
}
}
获取Class类对象
package com.lsq.study.反射;
//演示得到Class对象的各种方式(6)
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException {
//1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
//实例: Class cls1=Class.forName("java.lang.Cat")
//应用场景:多用于配置文件,读取类全路径,加载类
// Class.forName
String classAllpath="com.lsq.study.反射.Car";//通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllpath);
System.out.println(cls1);
//2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
//实例:Class cls2=Cat.class;
//应用场景:多用于参数传递,比如通过反射得到对应构造器对象
// 类名.class
Class cls2 = Car.class;
System.out.println(cls2);
//3.前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
//实例:Class clazz=对象.getClass()
//应用场景:通过创建好的对象,获取Class对象
//对象.getClass()
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
//4.其他方式
//ClassLoader cl=对象.getClass().getClassLoader();
//Class clazz4=cl.loadClass("类的全类名");
//(1)先得到类加载器car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class<?> cl4 = classLoader.loadClass(classAllpath);
System.out.println(cl4);
//5.基本数据类型(int,char,boolean,float,double,byte,long,short)
//Class cls=基本数据类型.class
Class<Integer> integerClass = int.class;
System.out.println(integerClass);
//6.基本数据类型对应的包装类,可以通过.TYPE得到Class对象
//Class cls=包装类.TYPE
Class<Integer> type = Integer.TYPE;
System.out.println(type);
System.out.println(integerClass.hashCode());
System.out.println(type.hashCode());
}
}
哪些类型有Class对象
-
外部类,成员内部类,静态内部类,局部内部类,匿名内部类
-
interface:接口
-
数组
-
enum:枚举
-
annotation:注解
-
基本数据类型
-
void
package com.lsq.study.反射;
import java.io.Serializable;
public class Demo03 {
public static void main(String[] args) {
Class<String> cls1 = String.class; //外部类
Class<Serializable> cls2 = Serializable.class; //接口
Class<Integer[]> cls3 = Integer[].class; //数组
Class<float[][]> cls4 = float[][].class; //二维数组
Class<Deprecated> cls5 = Deprecated.class; //注解
Class<Thread.State> cls6 = Thread.State.class;//枚举
Class<Long> cls7 = long.class; //基本数据类型
Class<Void> cls8 = void.class; //void数据类型
Class<Class> cls9 = Class.class; //外部类
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
}
}
类加载
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
-
静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
-
动态加载:运行时加载需要的类,如果运行时不用该类,及时不存在该类,则不报错,降低了依赖性
类加载时机
-
当创建对象时(new) //静态加载
-
当子类被加载时,父类也加载 //静态加载
-
调用类中的静态成员时 //静态加载
-
通过反射 //动态加载
-
加载阶段
-
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
-
-
连接阶段-验证
-
目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
-
包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
-
可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
-
-
连接阶段-准备
-
JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配
-
-
连接阶段-解析
-
虚拟机将常量池内的符号引用替换为直接引用的过程
-
-
初始化
-
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程
-
<clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
-
虚拟机会保证一个类的<clinit>()方法再多线程环境中被正确的加锁、同步、如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类<clinit>()方法,其他线程都需要阻塞等待,直到线程执行<clinit>()方法完
-
通过反射创建对象
-
方式一:调用类中的public修饰的无参构造器
-
方式二:调用类中的指定构造器
-
Class类相关方法
-
newInstance:调用类中的无参构造器,获取对应类的对象
-
getConstructor(Class.....clazz):根据参数列表,获取对应的public构造器对象
-
getDecalaredConstructor(Class...clazz):根据参数列表,获取对应的所有 构造器对象
-
-
Constructor类相关方法
-
setAccessible:爆破
-
newInstance(Object...obj):调用构造器
-
package com.lsq.study.反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo04 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1.先获取到Cat类的对象
Class<?> cls = Class.forName("com.lsq.study.反射.Cat");
//2.通过public的无参构造器创建实例
Cat cat = (Cat)cls.newInstance();
System.out.println(cat);
//3.通过public的有参构造创建实例
//3.1先得到对应的构造器
Constructor<?> constructor = cls.getConstructor(String.class);
//3.2创建实例,并传入实参
Object money = constructor.newInstance("刘发财");
System.out.println(money);
//4.通过非public的有参构造器创建实例
//4.1得到private的构造器对象
Constructor<?> declaredConstructor = cls.getDeclaredConstructor(int.class, double.class);
//4.2创建实例
declaredConstructor.setAccessible(true);//暴破[暴力破解],使用反射可以访问private构造器/方法/属性
Object o = declaredConstructor.newInstance(1, 500);
System.out.println(o);
}
}
class Cat{
public String name="小米";
private int age=5;
private double sal;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
private Cat(int age,double sal){
this.age=age;
this.sal=sal;
}
通过反射访问类中的成员
-
根据属性名获取Field对象
Field f=clazz对象.getDeclaredField(属性名);
-
暴破:f.setAccessible(true); //f是Field
-
访问
-
f.set(o,值); //o表示对象
-
syso(f.get(o)); //o表示对象
-
-
注意:如果是静态属性,则set和get中的参数o,可以写成null
-
invoke是Method类下的方法,只有方法才能调用invoke方法
-
invoke方法的含义:反射调用实例方法,返回的是实例方法的返回值
-
哪个方法调用invoke方法,返回的就是哪个方法的返回值
-
invoke方法参数的含义,填入的参数代表实例对象
-
例题1
package com.lsq.study.反射;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> pCls = Class.forName("com.lsq.study.反射.PrivateTest");
Object o = pCls.newInstance();
Field name = pCls.getDeclaredField("name");
name.setAccessible(true);
name.set(o,"招财猫");
Method getName = pCls.getMethod("getName");
Object invoke = getName.invoke(o);
System.out.println("name属性的值="+invoke);
}
}
class PrivateTest{
private String name="hellokitty";
public String getName() {
return name;
}
}
例题2
package com.lsq.study.反射;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo06 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1.Class类的forName方法得到File类的class对象
Class<?> fCls = Class.forName("java.io.File");
//2.得到所有的构造器
Constructor<?>[] declaredConstructors = fCls.getDeclaredConstructors();
//遍历输出
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//3.指定的得到public java.io.File(java.lang.String)
Constructor<?> declaredConstructor = fCls.getDeclaredConstructor(String.class);
Object o = declaredConstructor.newInstance("/Users/liushaoqin/Desktop/mynew.txt");
//4.得到createNewFile的方法对象
Method createNewFile = fCls.getMethod("createNewFile");//创建文件,调用的是createNewFile
createNewFile.invoke(o);
//file的运行类型就是File
System.out.println(o.getClass());
System.out.println("文件创建成功");
}
}