Java 基础巩固-反射机制(四)
Java 基础巩固-反射机制(四)
1 、反射(通过镜子看到类的结构)
- 反射可以在程序运行期间借助
Refection
Api取得任何类的内部信息(比如成员变量,构造器,成员方法等),并且能操作对象的属性和方法。反射在设计模式和框架底层都会用到 - 加载完类之后,在堆中产生了一个
Class
类型的一个对象,这个对象包含了类的完整结构信息。通过这个对象得到类的结构。
1.1 反射原理图
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 类加载过程
1.10.1 类加载过程详解
- 加载阶段:jvm在该阶段的主要目的是将字节码从不同的数据源(class、jar包等)转化为
二进制字节流加载到内存中
,并且生成代表该类的java.lang.Class
对象 - 连接阶段
-
验证:
- 目的是为了确保Class文件的字节流中包含的信息符号JVM机的要求,并且不会危害到JVM机
- 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码严重和符号引用验证
- 可以考虑使用
-Xverify:none
参数关闭大部门的验证措施,缩短虚拟机类加载时间
-
准备:
- 在该阶段会对
静态变量
分配内存并默认初始化(0,null,false等)。这些变量所使用的内存都将在方法区中进行分配
- 在该阶段会对
-
解析:
- 虚拟机将常量池内的符号引用替换成直接引用的过程
-
初始化:
- 到该阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行
<clinit>()
方法的过程 <clinit>()
方法是由编译器语句在源文件中出现的顺序,依次自动收集类中的所有静态变量
的赋值动作和静态代码块
中的语句,并进行合并- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境下被正确加锁、同步,如果多个线程同时去初始化一个类,那么只有一个类会初始化成功,其他将被阻塞,直到活动线程执行<clinit>
()方法完毕
- 到该阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行
-
1.11 通过反射获取类的结构信息
-
java.lang.Class 类
- getName :获取全类名
- getSimpleName :获取简单类名
- getFields :获取所有public修饰的属性,包含本类以及父类
- getMethods :获取所有public修饰的方法,包含本类以及父类
- getConstructors : 获取所有public修饰的构造器
- getDeclaredConstructors :获取本类中所有构造器
- getPackage :以Package形式返回包信息
- getSuperClass :以Class形式返回父类信息
- getlnterfaces :以Class形式返回接口信息
- getAnnotations :以Annotation形式返回注解信息
-
java.lang.reflect.Method 类
- getModifiers:以 int 形式返回修饰符
- 默认修饰符是 0
- public 是 1
- private 是 2
- protected 是 4
- static 是 8
- final 是 16
- getReturnType:以 class 形式获取返回类型
- getName :返回方法名
- getParameterTypes:以 Class[]返回参数类型数组
- getModifiers:以 int 形式返回修饰符
-
java.lang.reflect.Method 类
- getModifiers:以 int 形式返回修饰符
- 默认修饰符是 0
- public 是 1
- private 是 2
- protected 是 4
- static 是 8
- final 是 16
- getType:以 Class 形式返回类型
- getName : 返回属性名
- getModifiers:以 int 形式返回修饰符
1.12 反射爆破创建实例
-
通过调用类中的 public 修饰的无参构造器
-
调用类中的指定构造器
-
Class 类相关方法
- newInstance :调用类中的无参构造器,获取对应类的对象
- getCOnstructor(Class ... clazz) :根据参数列表,获取对应的 public 构造器对象
- getDecalaredConstructor(Class ... class) :根据参数列表,获取全部的构造器对象
-
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);
转载自:https://juejin.cn/post/7129770154413522980