目录
1.类加载器
1.类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载:
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象
连接:
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化:
初始化阶段,虚拟机负责对类的变量进行初始化
- 声明类变量是指定初始值
- 使用静态初始化块为类变量指定初始值
使用:正常使用
卸载:GC(垃圾回收机制)把无用对象从内存中卸载。
2.Class类的初始化时机
Java虚拟机只有在程序首次主动使用一个类或接口的时候才会初始化它。只有6种活动被看作是程序对类和接口的主动使用:
- 创建类的实例
- 调用类的静态方法
- 访问类或接口的静态变量,或者为静态变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
直接使用java.exe命令来运行某个主类
3.类加载器
类加载器主要负责将.class文件加载到内存中,并为之生成对应的Class对象。
类加载器的组成
- Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如System、String等。在JDK中JRE的lib目录下rt.jar文件中
2.Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
3.Sysetm ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
2.反射
JAVA反射机制是在运行状态中:
对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
2. 1 Class类
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
类也是一种类型
2.1.1 获取Class对象三种方式
- 方式一:调用Object类的getClass():任何类都会继承此方法
Person p = new Person();
Class c = p.getClass()
2. 方式二:调用某个类的class属性来获取该类对应的Class对象
Class s = Student.class
3.方式三(主要方式):调用Class类的静态方法forName(String clazzName),参数的值是某个类的全限定类名(必须添加完整包名)
Class c3 = Class.forName("com.jf.weidong.Person");
用到方法:
Class<?>
getClass()
返回此Object
的运行时类。
static Class<?>
forName(String className)
返回与带有给定字符串名的类或接口相关联的Class
对象。
forName()
方法需要处理ClassNotFoundException异常通过这个Class对象,可以获取Student类内部的成员属性、构造方法、成员方法的一些信息,并能够调用它们
代码示例:
学生类:(下面的学生类都指此类)
public class Student { // 成员变量 public String name; public int age; private String address; // 构造方法 public Student() { System.out.println("空参数构造方法"); } public Student(String name) { this.name = name; System.out.println("带有String的构造方法"); } // 私有的构造方法 private Student(String name, int age) { this.name = name; this.age = age; System.out.println("私有-------带有String,int的构造方法"); } public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; System.out.println("带有String, int, String的构造方法"); } // 成员方法 // 没有返回值没有参数的方法 public void m1() { System.out.println("没有返回值没有参数的方法"); } // 没有返回值,有参数的方法 public void m2(String name) { System.out.println("没有返回值,有参数的方法 name= " + name); } // 有返回值,没有参数 public int m3() { System.out.println("有返回值,没有参数的方法"); return 123; } // 有返回值,有参数的方法 public String m4(String name) { System.out.println("有返回值,有参数的方法"); return "哈哈" + name; } // 私有方法 private void m5() { System.out.println("私有方法"); } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", address=" + address + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
获取Class对象:
public class Text { public static void main(String[] args) throws ClassNotFoundException { //方式一:创建对象,调用Object类的getClass():任何类都会继承此方法 System.out.println("方式一:调用Object类的getClass()方法:"); Student student = new Student(); Class aClass1 = student.getClass(); //打印class对象的地址值 System.out.println(aClass1); //方式二:调用某个类的class属性来获取该类对应的Class对象 System.out.println("方式二:调用类的class属性来获取该类对应的Class对象:"); Class aClass2 = Student.class; System.out.println(aClass2); //方法三:调用Class类的静态方法forName(String clazzName),参数的值是某个类的全限定类名(必须添加完整包名) //该方法需要处理ClassNotFoundException异常 System.out.println("方法三:调用Class类的静态方法forName():"); Class aClass3 = Class.forName("笔记7.类的加载与反射.Student"); System.out.println(aClass3); } }
运行结果:
2.2 反射操作构造方法
2.2.1 通过反射获取构造方法
在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor表示。可通过Class类中提供的方法获取构造方法:
暴力访问:如果想要访问私有成员,需要设置暴力访问
Constructor的setAccessible(true):不进行权限检查
1.获取单个
Constructor getConstructor(Class ... parameterTypes):获取指定的"公有构造方法
暴力访问:Constructor getDeclaredConstructor(Class ... parameterTypes):获取指定的构造方法(包括私有的)
Constructor<T>
getConstructor(Class<?>... parameterTypes)
返回一个Constructor
对象,它反映此Class
对象所表示的类的指定公共构造方法。Constructor<T>
getDeclaredConstructor(Class<?>... parameterTypes)
返回一个Constructor
对象,该对象反映此Class
对象所表示的类或接口的指定构造方法。2.批量获取构造方法
Constructor[] getConstructors() :获取所有的"公有构造方法"
暴力访问:Constructor[] getDeclaredConstructors() :获取全部(包括私有)的构造方法
Constructor<?>[]
getConstructors()
返回一个包含某些Constructor
对象的数组,这些对象反映此Class
对象所表示的类的所有公共构造方法。Constructor<?>[]
getDeclaredConstructors()
返回Constructor
对象的一个数组,这些对象反映此Class
对象表示的类声明的所有构造方法。代码示例:
import 笔记7.类的加载与反射.Student; import java.lang.reflect.Constructor; /** * 通过Class类,获取类的成员、构造方法 * 反射类的构造方法 */ public class Text { public static void main(String[] args) throws NoSuchMethodException { //获取去Class类对象 Class<Student> aClass = Student.class; //获取单个公共构造方法 //1.获取无参的构造方法 System.out.println("获取单个公共构造方法"); System.out.println("1.获取无参的构造方法"); Constructor<Student> c1 = aClass.getConstructor(); System.out.println(c1); //2.获取有参的构造方法 System.out.println("2.获取指定参数的构造方法"); Constructor<Student> c2 = aClass.getConstructor(String.class); Constructor<Student> c3 = aClass.getConstructor(String.class, int.class,String.class); System.out.println(c2); System.out.println(c3); //获取所有的公共构造方法 System.out.println("获取所有的公共构造方法"); Constructor<?>[] cs1 = aClass.getConstructors(); //遍历所有的公共构造方法 for (Constructor<?> c : cs1) { System.out.println(c); } //暴力破解 //1.执行指定的单个构造方法(包括私有的构造方法) System.out.println(); System.out.println("暴力破解"); System.out.println("1.执行指定的单个构造方法(包括私有的构造方法):"); Constructor<Student> c4 = aClass.getDeclaredConstructor(String.class, int.class); System.out.println(c4); //2.获取所有的构造方法(包括私有的构造方法) System.out.println("2.获取所有的构造方法(包括私有的构造方法):"); Constructor<?>[] cs2 = aClass.getDeclaredConstructors(); for (Constructor<?> c : cs2) { System.out.println(c); } } }
运行结果:
2.2.2 通过反射方式创建对象
步骤如下:
1. 获取到Class对象
2. 获取指定的构造方法
3.若构造方法为私有,必须暴力访问, 通过setAccessible(boolean flag)方法
AccessibleObject 类是 Field、Method 和 Constructor 对象的父类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。常用方法如下:
void
setAccessible(boolean flag)
将此对象的 accessible 标志设置为指示的布尔值。参数值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。参数值
为 false 则指示反射的对象应该实施 Java 语言访问检查。
4. 通过构造方法类Constructor中的方法,创建对象,通过public T newInstance(Object... initargs)方法
T
newInstance(Object... initargs)
使用此Constructor
对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。代码示例:
import 笔记7.类的加载与反射.Student; import java.lang.reflect.Constructor; /** * 通过反射方式创建对象 * 步骤如下: * 1. 获取到Class对象 * 2. 获取指定的构造方法 * 3. 若构造方法为私有,必须暴力访问, 通过setAccessible(boolean flag)方法 * 4. 通过构造方法类Constructor中的方法,创建对象,通过public T newInstance(Object... initargs)方法 */ public class Demo03 { public static void main(String[] args) throws Exception { //通过反射方式创建对象(公共构造方法) //1.获取到Class对象 Class clazz1 = Class.forName("笔记7.类的加载与反射.Student"); //2. 获取指定的构造方法 Constructor<Student> c1 = clazz1.getConstructor(); Constructor<Student> c2 = clazz1.getConstructor(String.class, int.class, String.class); //3. 通过构造方法类Constructor中的方法,创建对象,通过public T newInstance(Object... initargs)方法 Student student1 = c1.newInstance(); student1.setName("张三"); System.out.println(student1.getName()); Student student2 = c2.newInstance("李四", 19, "地球"); System.out.println(student2.toString()); //通过反射方式创建对象(私有构造方法) //1.获取到Class对象 Class clazz2 = Class.forName("笔记7.类的加载与反射.Student"); //2. 获取指定的构造方法 Constructor<Student> c3 = clazz1.getDeclaredConstructor(String.class, int.class); //3. 若构造方法为私有,必须暴力访问, 通过setAccessible(boolean flag)方法 c3.setAccessible(true); //4. 通过构造方法类Constructor中的方法,创建对象,通过public T newInstance(Object... initargs)方法 Student student3 = c3.newInstance("王五", 20); System.out.println(student3.toString()); } }
运行结果:
2.3 反射操作属性
2.3.1 通过反射获取成员变量并使用
在反射机制中,把类中的成员变量使用类Field表示。可通过Class类中提供的方法获取成员变量:
1.获取单个成员变量
Field
getDeclaredField(String name)
获取指定的任意变量。返回一个Field
对象,该对象反映此Class
对象所表示的类或接口的指定已声明字段。Field
getField(String name)
获取指定的 public修饰的变量。 返回一个Field
对象,它反映此Class
对象所表示的类或接口的指定公共成员字段。2.获取多个成员变量
Field[]
getDeclaredFields()
获取所有的变量 (包含私有),返回Field
对象的一个数组,这些对象反映此Class
对象所表示的类或接口所声明的所有字段。Field[]
getFields()
获取所有public 修饰的变量, 返回一个包含某些Field
对象的数组,这些对象反映此Class
对象所表示的类或接口的所有可访问公共字段。代码示例:
import java.lang.reflect.Field; public class Demo04 { public static void main(String[] args) throws Exception { //创建对象 Class<?> forName = Class.forName("笔记7.类的加载与反射.Student"); //获取单个属性 //1.获取单个共有属性 System.out.println("获取单个属性"); System.out.println("1.获取单个共有属性"); Field name = forName.getField("name"); Field age = forName.getField("age"); System.out.println(name); System.out.println(age); //2.获取单个私有属性 System.out.println("2.获取单个私有属性"); Field address = forName.getDeclaredField("address"); System.out.println(address); //批量获取属性 //1.获取全部共有属性 System.out.println(); System.out.println("批量获取属性"); System.out.println("1.获取全部共有属性"); Field[] fields1 = forName.getFields(); for (Field field : fields1) { System.out.println(field); } //2.获取全部属性(包括私有) System.out.println("2.获取全部属性(包括私有)"); Field[] fields2 = forName.getDeclaredFields(); for (Field field : fields2) { System.out.println(field); } } }
运行结果:
2.3.2 通过反射,获取指定的成员变量,并赋值
获取成员变量,步骤如下:
1. 获取Class对象
2. 获取构造方法
3. 通过构造方法,创建对象
4. 获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问)
5. 通过方法,给指定对象的指定成员变量赋值(
set
)或者获取值(get
)方法介绍
void
set(Object obj, Object value)
在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值
Object
get(Object obj)
返回指定对象obj中,此 Field 对象表示的成员变量的值代码示例:
import 笔记7.类的加载与反射.Student; import java.lang.reflect.Constructor; import java.lang.reflect.Field; /** * 方式一 * 获取成员变量,步骤如下: * 1. 获取Class对象 * 2. 获取构造方法 * 3. 通过构造方法,创建对象 * 4. 获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问) * 5. 通过方法,给指定对象的指定成员变量赋值或者获取值 * 方式二: * 1. 获取类的构造方法 * 2. 获取Class对象 * 3.获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问) * 4. 通过方法,给指定对象的指定成员变量赋值或者获取值 */ public class Demo05 { public static void main(String[] args) throws Exception { //方式一: //1. 获取Class对象 Class<Student> clazz1 = Student.class; //2.获取构造方法 Constructor<Student> c1 = clazz1.getConstructor(); //3.通过构造方法,创建对象 Student student1 = c1.newInstance(); //4. 获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问) Field name1 = clazz1.getField("name"); Field age1 = clazz1.getField("age"); Field address1 = clazz1.getDeclaredField("address"); //私有成员变量,需通过setAccessible(boolean flag)方法暴力访问 address1.setAccessible(true); //5. 通过方法,给指定对象的指定成员变量赋值或者获取值 name1.set(student1, "张三"); age1.set(student1, 19); address1.set(student1, "中国"); System.out.println("name:" + name1.get(student1)); System.out.println("age:" + age1.get(student1)); System.out.println("address =" + address1.get(student1)); System.out.println("========================================="); //方式二: //1. 获取类的构造方法 Student student2 = new Student(); //2. 获取Class对象 Class clazz2 = student2.getClass(); //3.获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问) Field name2 = clazz2.getField("name"); Field age2 = clazz2.getField("age"); Field address2 = clazz2.getDeclaredField("address"); address2.setAccessible(true); //4. 通过方法,给指定对象的指定成员变量赋值或者获取值 name2.set(student2, "李四"); age2.set(student2, 20); address2.set(student2, "地球"); System.out.println("name:" + name2.get(student2)); System.out.println("age:" + age2.get(student2)); System.out.println("address =" + address2.get(student2)); } }
运行结果:
2.4 反射操作成员方法
在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员
执行步骤:
1. 获取Class对象
2. 获取构造方法
3. 通过构造方法,创建对象
4. 获取指定的方法
1.获取单个成员方法
Method
getDeclaredMethod(String name, Class<?>... parameterTypes)
获取指定的方法(单个),包括私有的。返回一个Method
对象,该对象反映此Class
对象所表示的类或接口的指定已声明方法。Method
getMethod(String name, Class<?>... parameterTypes)
获取指定的"公有方法"(单个)。返回一个Method
对象,它反映此Class
对象所表示的类或接口的指定公共成员方法。2.获取多个成员方法
Method[]
getDeclaredMethods()
获取所有成员方法(包括私有).返回Method
对象的一个数组,这些对象反映此Class
对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。Method[]
getMethods()
获取所有"公有方法"(包括继承的).返回一个包含某些Method
对象的数组,这些对象反映此Class
对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。5. 执行找到的方法
调用方法(执行找到的方法)
Object
invoke(Object obj, Object... args)
执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定
对带有指定参数的指定对象调用由此Method
对象表示的底层方法。代码示例:
import 笔记7.类的加载与反射.Student; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * 执行步骤: * 1. 获取Class对象 * 2. 获取构造方法 * 3. 通过构造方法,创建对象 * 4. 获取成员方法 * 5. 执行找到的方法 */ public class Demo06 { public static void main(String[] args) throws Exception { //1. 获取Class对象 Class<Student> clazz = Student.class; //2. 获取构造方法 Constructor<Student> c = clazz.getConstructor(); //3. 通过构造方法,创建对象 Student student = c.newInstance(); //4. 获取成员方法 //5. 执行找到的方法 //获取单个(指定)方法 //获取 共有 没有返回值 没有参数 的方法 System.out.println("获取 共有 没有返回值 没有参数 的方法"); Method m1 = clazz.getMethod("m1"); System.out.println(m1); //调用方法 m1.invoke(student); System.out.println(); //获取 共有 没有返回值 有参数 的方法 System.out.println("获取 共有 没有返回值 有参数 的方法"); Method m2 = clazz.getMethod("m2", String.class); System.out.println(m2); m2.invoke(student,"张三"); System.out.println(); //获取 共有 有返回值 没有参数 的方法 System.out.println("获取 共有 有返回值 没有参数 的方法"); Method m3 = clazz.getMethod("m3"); System.out.println(m3); System.out.println(m3.invoke(student)); //获取 共有 有返回值 有参数 的方法 System.out.println(); System.out.println("获取 共有 有返回值 有参数 的方法"); Method m4 = clazz.getMethod("m4",String.class); System.out.println(m4); System.out.println(m4.invoke(student,"李四")); //获取 私有 没有返回值 没有参数 的方法 System.out.println(); System.out.println("获取 私有 没有返回值 没有参数 的方法"); Method m5 = clazz.getDeclaredMethod("m5"); //私有方法需要暴力访问 m5.setAccessible(true); System.out.println(m5); m5.invoke(student); System.out.println(); //获取所有共有方法 System.out.println("-------------获取所有共有方法(包括从父类继承的)----------------"); Method[] methods1 = clazz.getMethods(); for (Method method : methods1) { System.out.println(method); } System.out.println(); //获取Student类所有方法(包括私有) System.out.println("-------------获取Student类所有方法----------------"); Method[] methods2 = clazz.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method); } } }
运行结果:
3. 案例
3.1 反射运行配置文件内容
Properties介绍
Properties继承自Hashtable,说明其是以键与值的方式存在的,只是Properties的键与值都是必须是字符串类型的数据。Properties常被用于配置文件的写入与读取,配置文件中记录着程序的各项参数信息,使用程序的用户可以自定义某些参数,以达到软件的个性化。
案例需求
通过反射读取配置文件,可以不改变主程序代码,只要修改配置文件就可以操作不同的对象执行不同的功能。
案例代码:
import java.io.FileReader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Properties; public class Demo07 { public static void main(String[] args) throws Exception { Properties p = new Properties(); FileReader fr = new FileReader("properties.ini"); //加载配置文件 p.load(fr); fr.close(); String className = p.getProperty("className"); String methodName = p.getProperty("methodName"); //创建Class对象 Class clazz = Class.forName(className); //创建构造方法对象 Constructor c = clazz.getConstructor(); //实例化类对象 Object o = c.newInstance(); //获取类的方法 Method method = clazz.getMethod(methodName); //调用方法 method.invoke(o); } }
运行结果:
更改配置文件的值:
再次运行:
3.2 通过反射越过泛型类型检查
案例需求:通过反射越过泛型检查,定义一个具有String泛型的集合,要求成功向集合中添加一个int数据
案例代码:
import java.lang.reflect.Method; import java.util.ArrayList; /** * 泛型安全检查 * 检查时机:在编译期是检查 * java --> Class * class中是没有泛型概念的,可以来通过放射来跳过安全检查 */ public class Demo08 { public static void main(String[] args) throws Exception { ArrayList<String> list = new ArrayList<>(); list.add("aaa"); //list.add(111); 报错 Class clazz = list.getClass(); Method add = clazz.getMethod("add", Object.class); add.invoke(list,111); System.out.println(list); } }
运行结果:
3.3 通过反射设置任意类的属性值
通过反射写一个通用的方法,能够设置某个对象的某个属性为指定的值
import java.lang.reflect.Field; public class Demo09 { public static void main(String[] args) throws Exception { //测试一 Cat cat = new Cat(); setValue(cat, "name", "小花"); System.out.println(cat.getName()); //测试二 Dog dog = new Dog(); setValue(dog,"name","哈士奇"); setValue(dog,"age",5); System.out.println(dog.getName()); System.out.println(dog.getAge()); } /** * 设置任意类的属性值 的通用方法 * * @param object 设置类的对象 * @param fieldName 设置属性名 * @param fieldValue 设置属性名 */ private static void setValue(Object object, String fieldName, Object fieldValue) throws Exception { //获取Class对象 Class clazz = object.getClass(); //获取属性 Field field = clazz.getDeclaredField(fieldName); //设置暴力访问 field.setAccessible(true); //设置值 field.set(object, fieldValue); } }
运行结果: