1. 反射
java反射是在运行状态中,对于java任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;或者说是将类的各个组成部分封装为其他对象。
作用
在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
优缺点
- 能够运行时动态获取类的实例,提高灵活性
- 使用反射性能较低,反射相当于一系列解释操作,通知JVM要做的事情,这类操作总是慢于直接执行java代码。
package com.linln.boot;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Student stu = new Student();
long start=System.currentTimeMillis();
for(int i=0;i<=100000000;i++)
stu.eat();
long end=System.currentTimeMillis();
System.out.println("普通方式创建对象耗时:"+(end-start));//3
long start1=System.currentTimeMillis();
Class stu2=Class.forName("com.linln.boot.Student");
Object o=stu2.newInstance();
Method md=stu2.getMethod("eat");
for(int i=0;i<=100000000;i++)
md.invoke(o);
long end1=System.currentTimeMillis();
System.out.println("反射方式创建对象耗时:"+(end1-start1));//180
long start2=System.currentTimeMillis();
//关闭安全访问检查能够一定程度上优化反射机制
md.setAccessible(true);
for(int i=0;i<=100000000;i++)
md.invoke(o);
long end2=System.currentTimeMillis();
System.out.println("反射方式优化创建对象耗时:"+(end2-start2));//144
}
}
AccessibleObject类是Field,Method和Constructor对象的基类。
setAccessible()作用是启动和禁用安全访问检查的开关。true的值表示反射对象应该在使用时抑制Java语言访问检查, false的值表示反映对象执行Java语言访问检查。
场景
- 使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
- 反射是框架设计的灵魂
- 模块化的开发,通过反射去调用对应的字节码
- 动态代理设计模式也采用了反射机制
- 日常使用的 Spring、Hibernate 等框架也大量使用到了反射机制
2.获取 Class 对象
- Class.forName(“全类名”) 将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中
- 类名.class 通过类名的属性class获取。多用于参数的传递
- 对象名.getClass() getClass()方法是定义在Objec类中的方法。多用于对象的获取字节码的方式
- 基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象 包装类型.TYPE
- 基本数据类型.class
package com.linln.boot;
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
package com.linln.boot;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
com.linln.boot.Student
System.out.println(classobj1.getName());
//方式二
Class classobj2 = Class.forName("com.linln.boot.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
System.out.println(classobj1 == classobj2);//true
System.out.println(classobj1 == classobj3);//true
Class<Character> char1=char.class;
Class<Boolean> bool=Boolean.TYPE;
System.out.println(char1);//char
System.out.println(bool);//boolean
}
}
3.反射原理
-
Source源代码阶段:.java被编译成.class字节码文件
-
Class类对象阶段:.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多
静态加载:编译时加载
动态加载:运行时加载
public class Main{ public static void main(String args[]){ if("A".equals(args[0])){ A a=new A(); a.start(); } if("B".equals(args[0])){ B b=new B(); b.start(); } } }
我们并不一定用到A功能或B功能,可是编译却不能通过,new 是静态加载类,在编译时刻就需要加载所有可能使用到的功能。
public class Main{ public static void main(String args[]){ try{ Class c=Class.forName(args[0]); All a=(All)c.newInstance(); a.start(); }catch(Exception e){ e.printStackTrace(); } } class A implements All{ public void start(){ System.out.println("A....START"); } } class B implements All{ public void start(){ System.out.println("B....START"); } } //接口 interface All{ public void start(); }
使用动态加载类时,我们不用定义多种功能,只需要通过实现某种标准(实现某个接口)。希望用到哪个就加载哪个,不用不加载,就需要动态加载类。
-
RunTime运行时阶段:创建对象的过程new。
4.API
Class 类
反射的核心类,可以获取类的属性,方法等信息。
- Class也是类,继承Object类,存放在堆
- Class类的二进制数据放在方法区,成为类的元数据
- Class类对象不是new出的,而是系统创建的,ClassLoader的loadClass方法创建
- 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//同步标志
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
常用方法
- getFields()获取本类公共属性,包括继承的public属性
- getDeclaredMethods() 获取本类所有属性,不包含继承属性
- getMethods()获取本类公共方法,包括继承的public方法
- getDeclaredMethods() 获取本类所有方法,但不包括继承的方法
- getConstructors()获取本类公共构造器,不包括继承的
- getDeclaredConstructors() 获取本类所有构造器,但不包括继承的
- getSuperCalss()返回父类类对象、getInterfaces()返回所有接口类对象
package com.linln.boot;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class stu=Student.class;
//获取的类名是全类名 com.linln.boot.Student
System.out.println(stu.getName());
//getClass()方法是Object类的方法 class java.lang.Class
System.out.println(stu.getClass());
//获取包名 com.linln.boot
System.out.println(stu.getPackage().getName());
//创建由此类对象表示的类的新实例。 Student [id=0, name=null, sex=false, score=0.0]
Object o=stu.newInstance();
System.out.println(o);
//获取指定名称的 public修饰的成员变量
/*
Field idField=stu.getField("id");
idField.setAccessible(true);//暴力反射
//私有的,即使暴力反射,也会报错java.lang.NoSuchFieldException: id
System.out.println(idField.get(o));
*/
Field scoreField=stu.getField("score");
System.out.println(scoreField.get(o));//0.0
scoreField.set(o, 90.0f);
System.out.println(scoreField.get(o));//90.0
//获取所有public修饰的成员变量
Field[] fileds=stu.getFields();
for (Field field : fileds) {
System.out.println(field.getName());//scor age
}
//获取指定名称修饰的成员变量,不考虑修饰符
Field idField=stu.getDeclaredField("id");
//对于私有变量虽然能会获取到,但不能直接set和get
idField.setAccessible(true);//暴力反射
System.out.println(idField.get(o));//0
//获取所有的成员变量,不考虑修饰符
Field[] filed1s=stu.getDeclaredFields();
for (Field field : filed1s) {
System.out.println(field.getName());//id、name、sex、score、age
}
//构造器、方法都类似
}
}
Field 类
Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- getModifiers获取本类属性修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
- getType返回该属性类型的类对象
- getName返回属性名、get获取属性值、set设置属性值
- 如果是静态属性,set、get参数可以是null
Method 类
Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- getModifiers获取本类方法修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
- getReturnType是返回类型的类对象,getParameterTypes是返回方法参数类型的类对象
- getName返回方法名、invoke执行对象方法
Constructor类
Java.lang.reflec 包中的类,表示类的构造方法。
- Constructor类内提供了初始化方法newInstance()方法
- 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
- getModifiers获取本类构造器修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
- getParameterTypes是返回构造器参数类型的类对象、getName返回构造器名
5.那些类型有Class对象
- 外部类、内部类
- 接口、数组、枚举
- 注解、基本数据类型、包装类
- void、Class
package com.lymn;
import java.util.List;
public class Test {
public static void main(String[] args) {
Class<String> stringClass = String.class;//外部类
Class<List> listClass = List.class;//接口
Class<int[]> aClass = int[].class;//数组
Class<Thread.State> stateClass = Thread.State.class;//枚举
Class<Override> overrideClass = Override.class;//注解
Class<Integer> integerClass = int.class;//基本数据类型
Class<Integer> integerClass1 = Integer.class;//包装类
Class<Void> voidClass = void.class;//void
Class<Class> classClass = Class.class;//class类对象
System.out.println(stringClass);//class java.lang.String
System.out.println(listClass);//interface java.util.List
System.out.println(aClass);//class [I
System.out.println(stateClass);//class java.lang.Thread$State
System.out.println(overrideClass);//interface java.lang.Override
System.out.println(integerClass);//int
System.out.println(integerClass1);//class java.lang.Integer
System.out.println(voidClass);//void
System.out.println(classClass);//class java.lang.Class
}
}
6.Class.forName和ClassLoader
Class.forName和ClassLoader都可以对类进行加载。
ClassLoader只负责加载 Java 类的字节代码到 Java 虚拟机中,不会执行初始化,只有在newInstance才会去执行。
Class.forName其实是调用了ClassLoader,如下:Class<?> forName0(String name, boolean initialize, ClassLoader loader)
这里面,forName0的第二个参数为true,表示对加载的类进行初始化。
所以Class.forName和ClassLoader的区别是在类加载的时候,class.forName有参数控制是否对类进行初始化。
7.反射案例
不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
package com.linln.boot;
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
public void eat(){
System.out.println("吃");
}
}
package com.linln.boot;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
//1获取class目录下的配置文件 使用类加载器加载配置文件
ClassLoader classLoader = Test.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("set.properties");
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);//吃
}
}