Java 基础巩固-反射机制(四)

Java 基础巩固-反射机制(四)

1 、反射(通过镜子看到类的结构)

  • 反射可以在程序运行期间借助 RefectionApi取得任何类的内部信息(比如成员变量,构造器,成员方法等),并且能操作对象的属性和方法。反射在设计模式和框架底层都会用到
  • 加载完类之后,在堆中产生了一个Class类型的一个对象,这个对象包含了类的完整结构信息。通过这个对象得到类的结构。

1.1 反射原理图

image.png

1.2 反射快速入门

// classfullpath=com.lyq.Cat
// method=cry

Properties properties = new Properties();
properties.load(new FileInputStream("src\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();

System.out.println(classfullpath);
System.out.println(methodName);
// 反射快速入门
// 1. 加载类
Class<?> aClass = Class.forName(classfullpath);

// 2. 得到 cls 得到加载类 com.lyq.Cat 的对象实例
Object cat = aClass.newInstance();

// 3. 通过 cls 得到 Cat 里面的 hi 对象
//    即:在反射中,万物皆对象
Method method = aClass.getMethod(methodName);
// 4. 通过 method 来唤醒 cat 的对应方法,
method.invoke(cat); // 传统是 对象.方法, 反射是: 方法.invoke(对象)
复制代码

1.3 反射相关类

import java.lang.Class;  // 类对象
import java.lang.reflect.Method; // 类的方法
import java.lang.reflect.Field;  // 类的成员变量
import java.lang.reflect.Constructor; // 代表类的构造方法


Properties properties = new Properties();
properties.load(new FileInputStream("src\re.properties"));

String classfullpath = properties.getProperty("classfullpath");
String methodName = properties.getProperty("method");

Class<?> aClass = Class.forName(classfullpath);

Object o = aClass.getDeclaredConstructor().newInstance();

Method method = aClass.getMethod(methodName);

method.invoke(o);

// getField 不能得到私有属性
Field ageField = aClass.getField("age");
System.out.println(ageField.get(o));

// String.class 代表形参类型
Constructor<?> constructor = aClass.getConstructor(String.class);
System.out.println(constructor);
复制代码

1.4 反射优点和缺点

  • 优点:可以动态创建和使用对象,灵活,没有反射机制,框架技术缺少底层支撑
  • 缺点:使用反射基本是解释执行,对执行速度又影响

1.5 反射优化

  • Method 和 Field 和 Constructor 都有 setAccssible()方法
  • 默认为false,检查访问安全,可以设置为true,取消检查

1.6 Class 类

  • Class类也是类,继承 Object
  • Class 不是 new 出来的类,而是类加载器通过 loadClass()方法创建
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    复制代码
  • 类只会被加载一次
  • Class 对象存放在
  • 类的二进制字节码数据存放在方法区

1.6.1 Class 类常用方法

String s = "com.lyq.Cat";

Class<?> cls = Class.forName(s);

System.out.println(cls.getName());    // 获取全类名  com.lyq.Cat
System.out.println(cls.getClass());   // 获取类名    class java.lang.Class
System.out.println(cls.getPackage()); // 获取包名    package com.lyq

Cat o = (Cat) cls.getConstructor().newInstance();
System.out.println(o);                // Cat{name='小黄', age=15}
// 得到 Cat 类的 age 属性
Field age = cls.getField("age");
System.out.println(age.get(o));       // 15
// 通过反射给属性赋值
age.set(o, 123123123);
System.out.println(age.get(o));
// 获取 Cat 对象的所有属性
Field[] fields = cls.getFields();
for (Field o : fields) {
    System.out.println(o);
}
复制代码

1.7 获取 Class 对象的六种 方式

  • Class.forName() :多用于配置文件
  • 类.class :多用于参数传递。比如通过反射得到类对象
  • 对象.getClass()
  • 类加载器 getClassLoader() 得到 Class
  • 包装类.TYPE
// 1. Class.forName
Class<?> aClass = Class.forName("com.lyq.Cat");
System.out.println(aClass);

// 2. 类名.class
System.out.println(Cat.class);

// 3. getClass()
Cat cat = new Cat();
System.out.println(cat.getClass());

// 4. 类加载器 Classloader
ClassLoader classLoader = cat.getClass().getClassLoader();
Class<?> aClass1 = classLoader.loadClass("com.lyq.Cat");
System.out.println(aClass1);

// 5. 基本数据类型
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;

// 6. 包装类
Class<Integer> type = Integer.TYPE;
复制代码

1.8 那些类型有Class对象

  • 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • 接口
  • 枚举
  • 数组
  • 注解
  • 基本数据类型
  • void
Class<String> stringClass = String.class; // 外部类
Class<Serializable> serializableClass = Serializable.class; // 接口
Class<Integer[]> aClass = Integer[].class; // 数组
Class<Enum> enumClass = Enum.class; // enum 枚举
System.out.println(Deprecated.class); // 注解
Class<Long> longClass = long.class; // 基本数据类型
Class<Void> voidClass = void.class; // void
复制代码

1.9 静态加载和动态加载

  • 静态加载:不使用反射,在编译期间就会加载的类,如果没有则报错,依赖性强
  • 动态加载:使用反射,依赖性不强,但是时间久

1.10 类加载过程

image.png

image.png

1.10.1 类加载过程详解

  • 加载阶段:jvm在该阶段的主要目的是将字节码从不同的数据源(class、jar包等)转化为二进制字节流加载到内存中,并且生成代表该类的 java.lang.Class对象
  • 连接阶段
    • 验证:

      • 目的是为了确保Class文件的字节流中包含的信息符号JVM机的要求,并且不会危害到JVM机
      • 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码严重和符号引用验证
      • 可以考虑使用 -Xverify:none参数关闭大部门的验证措施,缩短虚拟机类加载时间
    • 准备:

      • 在该阶段会对静态变量分配内存并默认初始化(0,null,false等)。这些变量所使用的内存都将在方法区中进行分配
    • 解析:

      • 虚拟机将常量池内的符号引用替换成直接引用的过程
    • 初始化:

      1. 到该阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行<clinit>()方法的过程
      2. <clinit>()方法是由编译器语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
      3. 虚拟机会保证一个类的<clinit>()方法在多线程环境下被正确加锁、同步,如果多个线程同时去初始化一个类,那么只有一个类会初始化成功,其他将被阻塞,直到活动线程执行<clinit>()方法完毕

1.11 通过反射获取类的结构信息

  1. java.lang.Class 类

    • getName :获取全类名
    • getSimpleName :获取简单类名
    • getFields :获取所有public修饰的属性,包含本类以及父类
    • getMethods :获取所有public修饰的方法,包含本类以及父类
    • getConstructors : 获取所有public修饰的构造器
    • getDeclaredConstructors :获取本类中所有构造器
    • getPackage :以Package形式返回包信息
    • getSuperClass :以Class形式返回父类信息
    • getlnterfaces :以Class形式返回接口信息
    • getAnnotations :以Annotation形式返回注解信息
  2. java.lang.reflect.Method 类

    • getModifiers:以 int 形式返回修饰符
      1. 默认修饰符是 0
      2. public 是 1
      3. private 是 2
      4. protected 是 4
      5. static 是 8
      6. final 是 16
    • getReturnType:以 class 形式获取返回类型
    • getName :返回方法名
    • getParameterTypes:以 Class[]返回参数类型数组
  3. java.lang.reflect.Method 类

    • getModifiers:以 int 形式返回修饰符
      1. 默认修饰符是 0
      2. public 是 1
      3. private 是 2
      4. protected 是 4
      5. static 是 8
      6. final 是 16
    • getType:以 Class 形式返回类型
    • getName : 返回属性名

1.12 反射爆破创建实例

  1. 通过调用类中的 public 修饰的无参构造器

  2. 调用类中的指定构造器

  3. Class 类相关方法

    • newInstance :调用类中的无参构造器,获取对应类的对象
    • getCOnstructor(Class ... clazz) :根据参数列表,获取对应的 public 构造器对象
    • getDecalaredConstructor(Class ... class) :根据参数列表,获取全部的构造器对象
  4. Constructor 类相关方法

    • setAccessible :爆破
    • newInstance(Object...obj) :调用构造器

1.12.1 通过反射访问构造器

String path = "com.lyq.reflection_.User";

Class<?> cls = Class.forName(path);

// 访问公有构造器,并且调用
Object publicConstructor = cls.getConstructor().newInstance(); // User{age=10, name='lyq'}

Constructor<?> declaredConstructor = cls.getDeclaredConstructor(int.class, String.class);

// 通过 setAccessible 可以访问私有构造器,并且调用私有构造器
declaredConstructor.setAccessible(true);
Object lyq = declaredConstructor.newInstance(1, "lyq222");

System.out.println(lyq);  // User{age=1, name='lyq222'}
复制代码

1.12.2 通过反射操作属性

Class<?> cls = Class.forName("com.lyq.reflection_.Student");

Constructor<?> constructor = cls.getConstructor();

Object o = constructor.newInstance();

Field age = cls.getField("age");
age.set(o, 20202020);

System.out.println(age.get(o));  // 反射设置属性值 20202020
Field name = cls.getDeclaredField("name");
// 对 name 进行暴力破解
name.setAccessible(true);
name.set(o, "zzz");
System.out.println(o); // Student{age=20202020, name='zzz'}
复制代码

1.12.3 通过反射操作方法

Class<?> cls = Class.forName("com.lyq.reflection_.Boss");

Object o = cls.getConstructor().newInstance();
// 调用公有方法
Method method = cls.getMethod("hi", String.class);
method.invoke(o, "dasda"); // hi dasda

// 调用私有方法
Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
// 暴力破解
say.setAccessible(true);
Object invoke = say.invoke(o, 100, "ccccc", 'k');
System.out.println(invoke); // 100  ccccc  k
复制代码

案例:使用反射创建文件

Class<File> fileClass = File.class;

Constructor<File> declaredConstructor = fileClass.getDeclaredConstructor(String.class);

File file = declaredConstructor.newInstance("d:\aa.txt");

file.createNewFile();

System.out.println(file);
复制代码

猜你喜欢

转载自juejin.im/post/7129770154413522980