暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java反射

风雪留客 2021-05-24
273

反射问题的引入

根据配置文件re.properties指定的信息,创建Cat对象,并调用方法hi

classfullpath=com.fxlk.Cat
method=hi

创建Cat对象

package com.fxlk;

public class Cat {

private String name = "小奶猫";

public void hi(){
System.out.println("hi~ "+name);
}

public void cry(){
System.out.println(name+": 喵喵喵??");
}

}

创建ReflectionQuestion对象

package com.fxlk.reflection.question;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectionQuestion {

public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 根据配置文件re.properties指定的信息,创建Cat对象,并调用方法hi
// 使用Properties类读写properties配置文件
Properties properties = new Properties();
// 读取配置文件
properties.load(new FileInputStream("C:\\XXX\\\re.properties"));
// 读取key为classfullpath的配置信息
String classfullpath = properties.get("classfullpath").toString();
// 读取key为method的配置信息
String methodName = properties.get("method").toString();
System.out.println("classfullpath="+classfullpath);
System.out.println("method="+methodName);

// 使用反射创建对象
// 1.加载类,返回class类型对象
Class<?> aClass = Class.forName(classfullpath);
// 2. 通过aClass得到加载的类 即 com.fxlk.Cat 的实例
Object obj = aClass.newInstance();
System.out.println("obj的运行类型:"+obj);
// 3. 通过 aClass 得到加载类 com.fxlk.Cat 的 methodName 的方法对象 在反射中,一个方法也可以视为对象
Method method = aClass.getMethod(methodName);
System.out.println("--------------俺是分隔符-------------------");
// 4. 通过 method方法对象中的invoke方法来调用加载类的方法
// 传统调用方法为 对象.方法() 在反射中调用方法为 方法对象.invoke(对象)
method.invoke(obj);
}

}

其输出结果为:

classfullpath=com.fxlk.Cat
method=hi
obj的运行类型:com.fxlk.Cat@4dc63996
--------------俺是分隔符-------------------
hi~ 小奶猫

更改配置文件:

method=cry

其输出结果为:

classfullpath=com.fxlk.Cat
method=cry
obj的运行类型:com.fxlk.Cat@4dc63996
--------------俺是分隔符-------------------
小奶猫: 喵喵喵??

至此,达到了不修改源码的情况下,来控制程序,符合设计模式的OCP原则(开闭原则,)

反射机制

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

反射相关类

java.lang.Class: 代表一个类,Class对象表示某个类加载后在堆中的对象java.lang.reflect.Method:类的方法java.lang.reflect.Field:类的成员变量java.lang.reflect.Constructor:类的构造方法

代码示例:

首选创建cat对象

package com.fxlk;

public class Cat {

private String name = "小奶猫";

public Integer age = 3;

public Cat(String name) {
this.name = name;
}

public Cat(String name,Integer age) {
this.name = name;
this.age = age;
}

public Cat(Integer age) {
this.age = age;
}

public Cat() {

}

public void hi(){
System.out.println("hi~ "+name);
}

public void cry(){
System.out.println(name+": 喵喵喵??");
}

}

反射代码示例:

public static void main(String[] args){
// properties 可以用来读取properties格式的配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("C:\\XXX\\\re.properties"));
String methodName = properties.get("method").toString();
Class<?> aClass = Class.forName(classfullpath);
// getField不能得到私有的属性,
Field nameField = aClass.getField("age");
System.out.println("class类对象的成员变量: "+nameField);
// 传统写法 对象.成员变量 反射: 成员变量对象.get(对象)
System.out.println("class类对象的成员变量的值: "+nameField.get(obj));
// getConstructors可以指定构造器参数类型,不指定则返回无惨构造器
Constructor<?> constructor = aClass.getConstructor();
System.out.println("class类对象的无参构造器:"+constructor);

// getConstructors可以指定构造器参数类型,
Constructor<?> constructor1 = aClass.getConstructor(String.class);
System.out.println("class类对象的有参构造器:"+constructor1);

// getConstructors可以指定构造器参数类型,
Constructor<?> constructor2 = aClass.getConstructor(String.class,Integer.class);
System.out.println("class类对象的有参,且多个参数的构造器:"+constructor2);

// getConstructors 返回所有构造器
Constructor<?>[] constructors = aClass.getConstructors();
System.out.println("class类对象的所有构造器"+constructors);
}

反射的优缺点

优点: 可以动态的创建和使用对象,缺点: 反射基于解释执行,对执行速度有影响

反射调用时间:

    public static void main(String[] args) throws Exception {
m1();
m2();
}

public static void m1(){
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
Cat cat = new Cat();
cat.cry();
}
long end = System.currentTimeMillis();
System.out.println("普通调用时间: "+(end-start));
}

public static void m2() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
Class<?> aClass = Class.forName("com.fxlk.Cat");
Object obj = aClass.newInstance();
Method cay = aClass.getMethod("cry");
cay.invoke(obj);
}
long end = System.currentTimeMillis();
System.out.println("反射调用时间: "+(end-start));
}
普通调用时间: 6
反射调用时间: 4206

反射调用优化:

我们可以关闭访问检查来提交反射效率,Field,Method和constructor都有setAccessible()方法,setAccessible作用是启用和禁用访问安全检查,参数为true时表示反射对象在使用时取消访问检查,提高反射的效率,false则表示反射的对象执行访问检查

public static void m2() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
Class<?> aClass = Class.forName("com.fxlk.Cat");
Object obj = aClass.newInstance();
Method cay = aClass.getMethod("cry");
cay.setAccessible(true);
cay.invoke(obj);
}
long end = System.currentTimeMillis();
System.out.println("反射调用时间: "+(end-start));
}
反射调用时间: 4168

可以看出4168是比4206快了那么一丢丢,其实有更好的方法,等下篇文章详说.

Class类

基本介绍

1. Class类也是类,因此也继承Object

在这里插入图片描述

2. Class类对象不是new出来的,而是系统创建的

我们可以将断点设置在new对象这句代码上

在这里插入图片描述

进入之后我们可以看到loadClass()这个方法,对此类进行了加载

在这里插入图片描述

3. 对于某个类的Class类对象,在内存中只有一份,类只加载一次.

 public static void main(String[] args) throws Exception {
Cat cat = new Cat();
Class<? extends Cat> catClass = cat.getClass();
Class<?> aClass = Class.forName("com.fxlk.Cat");
System.out.println(catClass.hashCode());
System.out.println(aClass.hashCode());
}

我们可以从输出结果结果中看到,无论是new出来的类,还是从反射中创建的类,它的hashCode值是一样的,也就是说,对于某个类的Class类对象,在内存中只有一份,类只加载一次.

1304836502
1304836502

4. 每个类的实例都会记录自己是有哪个Class实例所生成

5. 通过Class对象可以完成得到一个类的完整结构,通过一系列API

6. Class对象是存放在内存的堆中

7. 类的字节码二进制数据,是放在方法区的,有个地方称之为类的元数据(包括方法代码,变量名,方法名,访问权限等),

Class类常用方法

方法作用
forName获取指定的class对象
getPackage()获取类的包名
getName()获取类的全路径
newInstance()创建对象实例
getFields()获取类中以public修饰的全部属性

代码示例

    public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.fxlk.Cat");
// 输出aClass类对象,是哪个类的class对象
System.out.println(aClass);
// 输出aClass的运行类型
System.out.println(aClass.getClass());
// 获取包名
System.out.println(aClass.getPackage().getName());
// 获取全类名,即类的包路径.类名
System.out.println(aClass.getName());
// 通过aClass创建对象实例
Object obj = aClass.newInstance();
// 通过反射获取全部属性,此方法要求属性需要使用public修饰
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.err.println(field);
}
// 通过反射获取属性名为age的成员变量,此方法要求成员变量需要使用public修饰
Field field = aClass.getField("age");
// 返回指定对象上此Field表示的字段的值。
System.err.println(field.get(obj));
// 将此Field对象表示的字段设置为指定的新值。
field.set(obj,10);
System.err.println(field.get(obj));
}

获取Class类对象的六种方式

1. 使用Class类的静态方法Class.forName(String className)获取Class类对象

前提: 已知一个类的全类名,且该类在类路径下.可能抛出ClassNotFoundException(类未找到)异常,**应用场景:**多用于配置文件,读取类全路径,加载类示例:

Class<?> aClass = Class.forName("com.fxlk.Cat");


2. 通过类的class获取,该方式最为安全可靠,程序性能最高

前提: 已知具体的类应用场景: 多用于参数传递,比如通过反射得到对应构造器对象示例:

Class<Cat> catClass = Cat.class;


3. 调用该实例的getClass()方法获取Class对象

应用场景: 通过已经创建好的对象,获取Class对象示例:

// 运行类型,即此对象所关联的Class对象Class<? extends Cat> aClass = cat.getClass();
Cat cat = new Cat();



4. 通过类加载器(类加载器有四种)来获取到类的Class对象

示例:

Cat cat = new Cat();
// 首先得到类加载器
ClassLoader classLoader = cat.getClass().getClassLoader();
// 其次使用类加载器得到Class对象
Class<?> loadClass = classLoader.loadClass("com.fxlk.Cat");
// 错误的获取方式 classLoader.getClass()此方式获取的为classLoader的Class对象
Class<? extends ClassLoader> classLoaderClass = classLoader.getClass();

5. 基本数据类型(int,char,boolean,float,double,byte,long,short)可以使用基本数据类型.class获取Class类对象

示例:

// int类型获取Class会自动装箱
Class<Integer> integerClass = int.class;
// 但输出结果为int,会自动拆箱
System.out.println(integerClass);
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;\
// 省略其他代码.....

6. 基本数据类型对象的包装类,可以通过包装类.TYPE得到Class类对象

示例:

Class<Integer> type = Integer.TYPE;
// 输出结果为int
System.out.println(type);

哪些类型有Class对象

1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类2.interface接口3.数组4.enum枚举5.annotation注解6.基本数据类型7.viod

类加载

反射机制使Java实现动态语言的关键,也是通过反射实现类动态加载

1. 静态加载: 编译时加载相关的类,没有则报错,依赖性太强

示例:

    public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
int key = sc.nextInt();
switch (key) {
case 0:
// 静态加载
Dog dog = new Dog();
dog.cay();
break;
case 1:
System.out.println("ok");
break;
default:
System.out.println(".....");
}
}

上述代码中Dog此对象并不存在,不过当以上代码使用javac进行编译的时候,就会出现异常,尽管用户输入的可能不是0,可能并不会进入此代码块,但同样会抛出异常,如下图:

在这里插入图片描述

2. 动态加载: 运行时加载需要的类,如果运行时不用该类,则不需要加载,也不报错,降低了依赖性

示例:

    public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
int key = sc.nextInt();
switch (key) {
case 0:
// Dog dog = new Dog();
// dog.cay();
break;
case 1:
// 反射,动态加载
Class<?> aClass = Class.forName("com.fxlk.Dog");
Object obj = aClass.newInstance();
Method method = aClass.getMethod("cay");
method.invoke(obj);
System.out.println("ok");
break;
default:
System.out.println(".....");
}
}

此时,使用javac编译,并不会出现错误,这便是动态加载的好处,不使用并不会加载,也不会抛出异常.

类加载时机:

1.当创建对象时(new) // 静态加载2.当子类被加载时,父类也同样加载 // 静态加载3.调用类中的静态成员时 // 静态加载4.通过反射 // 动态加载

类加载流程图

一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载,连接,初始化,具体行为在 Java 虚拟机规范里有非常详细的定义。

1.加载(Loading): 它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,比如 jar 文件,class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出ClassFormatError。加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。2.连接(Linking): 是核心的步骤,简单说是把原始的类定义信息平滑地转入 JVM 运行的过程中。这里可进一步细分成三个步骤验证(Verification):这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。可以考虑使用-Xverify:none参数来关闭大部分类的验证措施,缩短虚拟机类加载的时间。准备(Pereparation):创建类或者接口中的静态变量,并初始化静态变量的初始值(如0,0L,null,false等)。这些变量所使用的内存都会在方法中进行分配。但这里的“初始化”和下面的初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。


// n1是实例属性,不是静态变量,因此在准备阶段,并不会分配内存
public int n1 =1;
// n2 是静态变量,在准备阶段会分配内存并默认初始化值为0,并不是2
public static int n2 =2;
// n3是static final 是常量,它和静态变量不一行,因为一旦赋值就不会改变,会在准备阶段分配内存,并直接赋值为3
public static final int n3 =3;


解析(Resolution):在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在 Java 虚拟机规范中,详细介绍了类,接口,方法和字段等各方面的解析。3.初始化阶段(initialization):这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。   其实此阶段是执行clinit()方法的过程,次方法是由编译器按照语句在源文件出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,依次进行合并,虚拟机会保证一个类的clinit()方法在多线程环境中被正确的加锁,同步.如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕.


在这里插入图片描述

获取类的结构信息

准备class类

public class Cat {
private String name;
public Integer age;
protected String gender;
String colour;

public void method1() {

}

private void method2() {

}

protected void method3() {

}

void method4() {

}
}

常用方法

方法返回值作用
getName()String获取全类名
getSimpleName()String获取类的简单名称
getFields()Field[]获取所有public修饰的属性,包含本类与父类,返回值为数组
getField(String name)Field获取类或接口的指定的public属性,可获取父类属性
getDeclaredFieldsField[]获取类或接口声明的所有属性。(private,public,protected,以及没有用访问控制符修饰的属性)
getDeclaredField(String name)Field获取类或接口的指定已声明的属性(private,public,protected,以及没有用访问控制符修饰的属性)
getMethods()Method[]获取所有public修饰的方法,包含本类与父类以及超类的方法
getMethod(String name)Method获取类或接口的指定公共方法,可获取到父类以及超类方法
getDeclaredMethods()Method[]获取本类或接口中所有的方法,包含父类与超类
getDeclaredMethod(String name)Method获取类或接口的指定声明的方法
getConstructors()Constructor<?>[]获取类的所有public构造器
getConstructor(Class<?>... parameterTypes)Constructor获取类中所指定的public构造器
getDeclaredConstructors()Constructor<?>[]获取类中所有的构造器
getDeclaredConstructor(Class<?>... parameterTypes)Constructor获取类中所指定的构造器
getPackage()String获取此类的包信息
getSuperclass()Class<? super T>获取父类信息
getInterfaces()Class<?>[]获取此类的接口信息
getAnnotations()Annotation[]获取此类的注解信息

获取Class类的Field信息

方法返回值作用
getModifiers()int返回此类或接口的Java语言修饰符,以整数编码。
不加访问修饰符为 0 , public 1 ,private 2 ,protected 4,static 8 ,final 16。
如果是 public static final 三个修饰的 就是3 个的加和 为 25
getType()Class<?>返回一个Class对象,该对象标识由此Field对象表示的字段的声明的类型。
getName()String返回此对象表示的字段的名称。
    public static void main(String[] args) throws Exception {

Class<?> catClass = Class.forName("com.fxlk.Cat");

for (Field field : catClass.getFields()) {
// 不加访问修饰符为 0 , public 1 ,private 2 ,protected 4,static 8 ,final 16。
//如果是 public static final 三个修饰的 就是3 个的加和 为 25 。
System.out.println("本类中的属性: "+field.getName()+",其访问修饰符为: "+field.getModifiers()+"该属性的类型: "+field.getType());
}

}
本类中的属性: age,其访问修饰符为: 1该属性的类型: class java.lang.Integer
本类中的属性: a,其访问修饰符为: 9该属性的类型: class java.lang.String

获取Class类的Method信息

方法返回值作用
getModifiers()int返回此类或接口的Java语言修饰符,以整数编码。
不加访问修饰符为 0 , public 1 ,private 2 ,protected 4,static 8 ,final 16。
如果是 public static final 三个修饰的 就是3 个的加和 为 25
getName()String获取方法名
getReturnType()Class<?>获取该方法的返回值类型,以Class形式表示
getParameterTypes()Class<?>[]获取该方法形参类型,以Class[]形式表示
    public static void main(String[] args) throws Exception {

Class<?> catClass = Class.forName("com.fxlk.Cat");

for (Method method : catClass.getMethods()) {

System.out.println("该方法名: "+method.getName()+",该方法的访问修饰符"+method.getModifiers()+",该方法的返回值类型: "+method.getReturnType()+",该方法的参数类型: "+ Arrays.toString(method.getParameterTypes()));

}

}
该方法名: equals,该方法的访问修饰符1,该方法的返回值类型: boolean,该方法的参数类型: [class java.lang.Object]
该方法名: toString,该方法的访问修饰符1,该方法的返回值类型: class java.lang.String,该方法的参数类型: []
该方法名: hashCode,该方法的访问修饰符1,该方法的返回值类型: int,该方法的参数类型: []
该方法名: getName,该方法的访问修饰符1,该方法的返回值类型: class java.lang.String,该方法的参数类型: []
该方法名: setName,该方法的访问修饰符1,该方法的返回值类型: void,该方法的参数类型: [class java.lang.String]

获取Class类的Constructor信息

方法返回值作用
getModifiers()int返回此类或接口的Java语言修饰符,以整数编码。
不加访问修饰符为 0 , public 1 ,private 2 ,protected 4,static 8 ,final 16。
如果是 public static final 三个修饰的 就是3 个的加和 为 25
getName()String获取构造器名称
getParameterTypes()Class<?>[]获取该构造器形参类型,以Class[]形式表示
    public static void main(String[] args) throws Exception {

Class<?> catClass = Class.forName("com.fxlk.Cat");

for (Constructor<?> constructor : catClass.getConstructors()) {

System.out.println("该构造器:"+constructor.getName()+"的访问修饰符为:"+constructor.getModifiers()+",其返回参数类型为: "+ Arrays.toString(constructor.getParameterTypes()));

}

}
该构造器:com.fxlk.Cat的访问修饰符为:1,其返回参数类型为: [class java.lang.String, class java.lang.Integer, class java.lang.String, class java.lang.String]
该构造器:com.fxlk.Cat的访问修饰符为:1,其返回参数类型为: []

通过反射创建对象

调用类中public修饰的无参构造器

    public static void main(String[] args) throws Exception {
Class<?> catClass = Class.forName("com.fxlk.Cat");
Object o = catClass.newInstance();
System.out.println(o);
}

调用类中指定的构造器

    public static void main(String[] args) throws Exception {
Class<?> catClass = Class.forName("com.fxlk.Cat");
// 获取构造函数中形参为String构造函数
Constructor<?> constructor = catClass.getConstructor(String.class);
// 使用构造器对象通过newInstance方法创建示例,并传入实参
Object o = constructor.newInstance("小奶猫");
System.out.println(o);
}

调用类中私有的的构造器

    public static void main(String[] args) throws Exception {
Class<?> catClass = Class.forName("com.fxlk.Cat");
// 获取构造函数中形参为String构造函数
Constructor<?> constructor = catClass.getDeclaredConstructor(String.class);
// 使用构造器对象通过newInstance方法并传入该构造器形参值即可创建对象
// 如果该构造的形参为私有,执行此方法会抛出IllegalAccessException(非法访问异常)异常
// Object o = constructor.newInstance("小奶猫");
// 设置其可访问性,值为true表示反射的对象在使用时应禁止Java语言访问检查。
constructor.setAccessible(true);
Object o = constructor.newInstance("小奶猫");
System.out.println(o);
}

通过反射访问类中的成员

    public static void main(String[] args) throws Exception {
// 获取class对象
Class<?> catClass = Class.forName("com.fxlk.Cat");
// 创建对象
Object o = catClass.newInstance();
Field age = catClass.getField("age");
// 通过反射为其属性设置值
age.set(o,3);
// 获取其属性设置值
System.out.println(age.get(o));
// 通过反射操作非public属性
Field name = catClass.getDeclaredField("name");
// 当属性为private时,需要禁止Java语言访问检查
name.setAccessible(true);
// name.set(o,"小奶猫");
// 如果该属性为静态,则set方法中的参数o可以为null,但不为静态写空则会报错,推荐使用上面一行注释掉的代码
name.set(null,"小奶猫");
System.out.println(name.get(o));
}

通过反射访问类中的方法

    public static void main(String[] args) throws Exception {
// 获取class对象
Class<?> catClass = Class.forName("com.fxlk.Cat");
// 创建对象
Object o = catClass.newInstance();
// 如果该方法拥有形参,需要传递对应的class对象,否则会抛出NoSuchMethodException(没有这样的方法)异常
Method method1 = catClass.getDeclaredMethod("method1", String.class);
// 如果方法没有形参,则只填写方法名即可
// Method method = catClass.getDeclaredMethod("method2");
// 使用方法对象中的invoke()执行方法
method1.invoke(o,"小奶猫");

Method method4 = catClass.getDeclaredMethod("method4", String.class);
// 如果该方法不为public,需要禁止Java语言访问检查
method4.setAccessible(true);
// method4.invoke(o,"小奶猫");
// 如果该方法为静态方法,则第一个参数可以为空,否则第一个参数不能为空,但推荐使用上面方式
// 在反射中,如果方法存在返回值,统一返回Object类型,Object结果为方法返回结果,运行类型为方法返回值类型,如果方法返回值为空,则Object为null
Object obj = method4.invoke(null, "小奶猫");
System.out.println(obj);
}


文章转载自风雪留客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论