反射
反射
1.问题:根据配置文件re.properties指定信息,创建cat对象并调用方法hi
re.properties配置文件
classpath=entiy.cat
method=hi
具体代码实现:
public class question { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根据配置文件re.properties指定信息,创建cat并调用方法hi //1.传统方法 cat c= new cat(); c.hi(); //2.使用Properties类读写配置文件 Properties properties=new Properties(); properties.load(new FileInputStream("src/main/resources/re.properties")); String classpath= properties.get("classpath").toString(); String method= properties.get("method").toString(); //3.使用反射机制解决 //加载类,返回class类型对象 Class cls= Class.forName(classpath); Object o=cls.newInstance(); System.out.println("o的运行类型"+o.getClass()); Method method1= cls.getMethod(method); method1.invoke(o);//传统方式 对象.方法() 反射机制 方法.invoke(对象) } }
如果调用的是cat的其他方法,不需要修改源码,只需要更改配置文件re.properties中的value值即可,method=value,这符合设计模式的OCP原则(开闭原则:不修改源码,扩容功能)
2.反射机制
反射优点和缺点:
1.优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活没有反射机制,框架技术就失去底层支撑。
2.缺点:使用反射基本是解释执行,对执行速度有影响.
public class Reflection01 { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { m1(); m2(); } //用传统方式调用hi方法 public static void m1(){ cat c = new cat(); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { c.hi(); } long end= System.currentTimeMillis(); System.out.println("用传统方式调用hi方法 耗时="+(end-start)); } //用反射方式调用hi方法 public static void m2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> cls = Class.forName("entiy.cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查 耗时=2293, long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o); } long end= System.currentTimeMillis(); System.out.println("用反射方式调用hi方法 耗时="+(end-start)); } }
反射调用优化关闭访问检查
1.Method和Field、Constructor对象都有setAccessible(方法
2. setAccessible作用是启动和禁用访问安全检查的开关
3.参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
3.Class类
3.1.基本介绍
1. Class也是类,因此也继承Object类
2. Class类对象不是new出来的,而是系统创建的
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪个Class实例所生成
5.通过Class对象可以完整地得到一个类的完整结构,通过一系列API
6. Class对象是存放在堆的
7.类的字节码进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)
3.2.获取Class对象
1.在代码阶段(编译阶段),可以通过Class类的静态方法forName(String className)获取,例如:Class cls = Class.forName("entiy.cat");
应用场景:多用于配置文件,读取类全路径,加载类
2.Class类阶段(加载阶段),通过类名.class获取,该方式最为安全可靠,程序性能最高。例如:Class cls = cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
3.Runtime(运行阶段),前提是已知某个类的实例,通过调用该实例的getClass(),实例:
Class<?> aClass = c.getClass();
应用场景:不经常用,这种是通过创建好的对象,来获取Class对象,一般有对象就可以直接通过对象调用对应的成员方法,成员变量等,没必要再通过反射调用。
4.通过类加载器来获取类的class对象
cat c = new cat(); //得到类加载器 ClassLoader loader = c.getClass().getClassLoader(); //通过加载器得到Class对象 Class<?> aClass = loader.loadClass("entiy.cat");
5.基本数据类型(int,char,boolean,float,double,byte,long,short),可以按以下方式获得class对象 例如Class<Integer> integerClass = int.class;
6.基本类型对应的包装类,可以通过.Type得到Class对象,例如:Class<Integer> type = Integer.TYPE;
3.3.有Class对象的类型
1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2.interface :接口
3.数组
4. enum :枚举
5.annotation :注解
6.基本数据类型
7.void
4.类加载
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
1.静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
2.动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性
4.1类加载时机
1.当创建对象时(new) //静态加载
2.当子类被加载时,父类也加载//静态加载
3.调用类中的静态成员时//静态加载
4.通过反射//动态加载
4.2.类加载的五个阶段
加载阶段
JVM在该阶段主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至是网络)转化为二进制字节流加载到内存中,并生成一个代表该类的Java.lang.Class对象。
连接阶段—验证
1.目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
3.可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段—准备
JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、OL、null、 false等) 这些变量所使用的内存都将在方法区中进行分配。
static class A{ public int n1=10; public static int n2=20; public static final int n3=30; /* n1是实例属性,不是静态变量,因此在准备阶段,不会分配内存 n2是静态变量,分配内存,默认初始化赋值0 n3 static final 是常量,一旦赋值就不变,分配内存赋值30, */ }
连接阶段—解析
虚拟机将常量池内的符号引用替换为直接引用的过程(相对位置--物理位置)
初始化
1.到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行< clinit> ()方法的过程。
2. <clinit> ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
3.虚拟机会保证一个类的 <clinit> 0)方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类, 那么只会有一个线程去执行这个类的<clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行<clinit> )方法完毕
5.通过反射获取类的结构信息
反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
首先先建立有继承关系两个实体类,如下:
public class animal {
public int age;
public animal() {
}
public animal(int age) {
this.age = age;
}
private void eat(){
System.out.println("吃饭");
}
}
public class cat extends animal {
private String name;
public String hobby;
protected String work;
//构造方法
public cat() {
System.out.println("调用了公共,无参构造方法执行了");
}
public cat(String name){
System.out.println("姓名"+name);
}
private cat(int age){
System.out.println("私有的构造方法, 年龄"+age);
}
public cat(String name, String hobby, String work) {
this.name = name;
this.hobby = hobby;
this.work = work;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void hi(){
System.out.println("你好"+name);
}
}
5.1.使用反射技术获取构造器对象并使用
Class类中用于获取构造器的方法:
获取构造器的作用依然是初始化一个对象返回。
Constructor类中用于创建对象的方法:
Class c=Class.forName("entiy.cat");
System.out.println("------------调用所有公共的构造方法--------");
final Constructor[] constructors = c.getConstructors();
for (Constructor t:
constructors) {
System.out.println(t);
}
System.out.println("------------调用所有的构造方法(包括:私有、受保护、默认、公有)-------");
final Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor t:
declaredConstructors) {
System.out.println(t);
}
System.out.println("------------调用的构造方法公共,有参的-------");
Constructor constructor = c.getConstructor(String.class);
System.out.println("constructor"+constructor);
final Object o = constructor.newInstance("招财猫");
System.out.println("------------调用的构造方法私有,有参的-------");
final Constructor declaredConstructor = c.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
//暴力访问(忽略访问修饰符)
declaredConstructor.setAccessible(true);
final Object o1 = declaredConstructor.newInstance(5);
5.2.使用反射技术获取成员变量对象并使用
Class类中用于获取成员变量的方法:
获取成员变量的作用依然是在某个对象中取值、赋值
Field类中用于取值、赋值的方法:
Class c=Class.forName("entiy.cat");
System.out.println("************获取本类和父类所有公有的字段********************");
final Field[] fields = c.getFields();
for (Field f:
fields ) {
System.out.println(f);
}
System.out.println("************获取本类所有的字段(包括私有、受保护、默认的)********************");
final Field[] declaredFields = c.getDeclaredFields();
for (Field f:
declaredFields ) {
System.out.println(f);
}
System.out.println("************获取父类单个公共字段并调用********************");
final Field f = c.getField("age");
System.out.println(f);
final Object o2 = c.getConstructor().newInstance();//产生cat对象
f.set(o2,2);
final cat cat = (cat) o2;
System.out.println("验证年龄"+cat.age);
System.out.println("************获取本类单个私有字段并调用********************");
final Field name = c.getDeclaredField("name");
name.setAccessible(true);//暴力反射,解除私有限定
name.set(o2,"幸运猫");
System.out.println("验证名字"+cat.getName());
5.3.使用反射技术获取方法对象并使用
Class类中用于获取成员方法的方法:
获取成员方法的作用依然是在某个对象中进行执行此方法
Method类中用于触发执行的方法:
public class animal {
public void eat(){
System.out.println("吃饭");
}
}
public class cat extends animal {
public void hi(){
System.out.println("你好");
}
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
Class c=Class.forName("entiy.cat");
System.out.println("*************获取本类和父类的所有公有成员方法******");//父类不仅包括animal类,还有基类object
final Method[] methods = c.getMethods();
for (Method m:
methods) {
System.out.println(m);
}
System.out.println("*************获取本类所有成员方法******");
final Method[] declaredMethods = c.getDeclaredMethods();
for (Method m:
declaredMethods) {
System.out.println(m);
}
System.out.println("*************获取父类公有单个成员方法******");
final Method m = c.getMethod("eat");
System.out.println(m);
//实例化一个对象调用eat方法
final Object o3 = c.getConstructor().newInstance();
m.invoke(o3);
System.out.println("*************获取本类私有的show4()方法******");
final Method s = c.getDeclaredMethod("show4", int.class);
System.out.println(s);
s.setAccessible(true);//解除私有限定
final Object result = s.invoke(o3, 8);
System.out.println("返回值"+result);
6.反射作用
6.1.绕过编译阶段为集合添加数据
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了。
{
final ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
// strList.add(100);
//获取ArrayList的Class对象,在运行阶段没有泛型约束,可以添加其他类型
Class listClass = list.getClass();
final Method m = listClass.getMethod("add", Object.class);
m.invoke(list,100);
m.invoke(list,23.45);
for (Object o:
list) {
System.out.println(o);
}
}
6.2.反射做通用框架
简单demo:
需求如下:给你任意一个对象,在不清楚对象字段的情况下,可以将对象的字段名称(变量名)和对应值(具体值,或默认值)存储到文件中去。
分析:
- 定义一个方法,可以接收任意类的对象。public class BatisUtil{}
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
- 使用反射获取对象的Class类对象,然后获取全部成员变量信息。
- 遍历成员变量信息,然后提取本成员变量在对象中的具体值
- 存入成员变量名称和值到文件中去即可。
public static void save(Object obj) throws FileNotFoundException, IllegalAccessException { try { PrintStream ps = new PrintStream(new FileOutputStream("src/main/resources/data.txt", true)); Class<?> c = obj.getClass(); // 表明对象的类简易名称 ps.println("--------" + c.getSimpleName() + "-----------"); final Field[] declaredFields = c.getDeclaredFields(); for (Field f : declaredFields) { final String var = f.getName(); // 记录当前的成员变量名称 f.setAccessible(true);// 注:暴力打开权限的方法,将类对象的私有成员暴露给本类使用,将获取完整的数据信息 final String value = f.get(obj) + "";//假设学生类,变量名为name,获取name在obj对象下设置的具体值 ps.println(var + "==>" + value); /*JDK1.7及以上,IO流自动关闭总结: (1)需要关闭的资源必须实现Closeable或者AutoCloseable;查看JDK源码,Closeable继承自AutoCloseable,这两个接口中只有一个方法:void close() throws Exception; (2)需要关闭的资源必须重写close()方法,方法中必须包含完整的资源关闭操作代码; (3)调用写法:自动关闭功能属于try的增强特性,此时无需显式的书写资源关闭代码,try段代码执行完毕后,系统会自动调用资源的关闭方法关闭资源:*/ } } catch (Exception e) { e.printStackTrace(); } }
public static void main(String[] args) throws FileNotFoundException, IllegalAccessException { cat c = new cat(); c.setAge(3); c.setHobby("吃鱼"); c.setName("招财猫"); c.setWork("招财"); // 输入基本信息,并送给BatisUtil类中的save()方法中进行保存 BatisUtil.save(c); }