1.什么是反射
反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。
当类加载完后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只能有一个Class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构。这个对象就像透过一面镜子一样,透过照镜子来看类的结构一样,所以形象的称之为反射。
1.1反射机制的功能
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
1.2.实现反射机制的类
Java中主要由以下的类来实现Java反射机制(这些类都位于java.lang.reflect包中):
-
Class类:代表一个类。 Field类:代表类的成员变量(成员变量也称为类的属性)。
-
Method类:代表类的方法。
-
Constructor类:代表类的构造方法。
-
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
通过反射调用指定类的属性及方法
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestReflection {
public static void main(String[] args) throws Exception {
//尝试反射Person类
//反射之前对于Person类的操作
System.out.println("===========反射之前对于Person类的操作==========");
Person person1 = new Person();
Person person2 = new Person("张三",12);
//调用内部属性及方法
System.out.println(person2.age);
person2.show();
//在Person类外部,不可以调用其内部的私用方法或属性
System.out.println("===========反射之前对于Person类的操作==========");
System.out.println("===========================================");
System.out.println("===========运用反射对于Person类的操作==========");
//得到Person类
Class<Person> personClass = Person.class;
//得到指定的构造器
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
//通过构造器类获取Person类的构造器,从而创建Person类实例
Person person = constructor.newInstance("李四", 15);
System.out.println("输出李四:"+person.toString());
//通过反射,调整对象指定的属性及方法
Field age = personClass.getDeclaredField("age");
age.set(person,25);
System.out.println("输出李四,但年龄成了25:"+person.toString());
//通过反射,调用方法
Method show = personClass.getDeclaredMethod("show");
//调用person实例的show方法
show.invoke(person);
//通过反射调用Person类的私有方法
//调用私有构造器
Constructor<Person> privateConstructor = personClass.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);
Person privatePerson = privateConstructor.newInstance("王五");
System.out.println("输出王五,反射利用了私有构造器创建了实例: :"+privatePerson.toString());
//调用私有属性
Field name = personClass.getDeclaredField("name");
name.setAccessible(true);
System.out.println("直接通过反射的方式调用私有属性: "+name.get(privatePerson));
System.out.println("还可以再修改一下");
name.set(privatePerson,"羊羊");
System.out.println("直接通过反射的方式调用私有属性(修改后): "+name.get(privatePerson));
}
}
class Person{
private String name;
public int age;
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 Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public Person() {
}
public void show(){
System.out.println("我是人");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
疑问:如何看待反射和封装两种技术?
首先二者并不完全矛盾。
封装也是给方法或属性加上“不建议直接使用”的意思,但不是不准去调用该方法或属性,只是不建议直接使用。
疑问:通过直接new的方式或反射的方式调用公有结构,开发中用哪个?
建议直接用new
疑问:什么时候使用反射的方式创建对象?
在编译的时候无法确认会创建哪个对象时,使用反射的方式
2.Class类
2.1 理解
2.1.1 类的加载过程
程序经过Javac.exe命令后会生成一个或多个字节码文件(.class),接着使用java.exe命令对某个字节码文件进行解释运行。相当于某个字节码文件加载到内存中了。加载到内存的过程,就称为类的加载。
加载到内存中的类,我们就称为运行时类。此运行时类就做为Class的一个实例。
例如说Person类,在编译成字节码文件后就变成Person.class,在使用java.exe命令运行后,.class文件对应的类就会加载到内存中,这整个过程就称为类的加载,而内存里的Person类本身就充当了Person的实例,只是这个实例是相对于Person.class是一个对象或称实例。
2.1.2 运行时类会缓存
加载到内存中的运行时类,会缓存一定时间,在此时间内通过不同方式获取到的运行时类都是同一个实例。
2.1.3 类的加载过程深入
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤对类进行初始化。
- 1.类的加载(Load):将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成。
- 2.类的链接(Link): 将类的二进制数据合并到JRE中
- 3.类的初始化(initialize):JVM负责对类进行初始化。
1.Load:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中磊数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
2. Link:
将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:cafe开头,没有安全方面的问题。
准备:正式为类变量(static关键字修饰)分配内存并设置类变量默认初始值的阶段,这些内存都在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
3.initialize:
执行类构造器<clinit>()方法的过程。
当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的<clinit>()方法在多线程环境下会正确加锁和同步。
2.1.4 哪些类型可以有Class对象
每个实例对应了一个运行时类,那除了类以外还有什么结构可以作为大的Class的实例呢?
- class:外部累,成员类(成员内部类,静态内部类),局部内部类,匿名内部类
- 接口interface
- 数组[]
- 枚举enum
- annotation注解
- primitive type 基本数据类型
- void
2.1.5 类的加载器(ClassLoader)
主要作用:用来把泪加载到内存当中
加载器类型:引导类加载器,扩展类加载器,系统类加载器
- 引导类加载器:C++编写,是JVM自带的类加载器,用来加载Java平台核心库(例如String类),该加载器无法直接获取。
- 扩展累加载器:负责jre/lib/ext目录下的jar包或者java.ext.dirs指定目录下的jar包装入工作库。
- 系统类加载器:负责java -classpath或 java.class.path所指目录下的泪与jar包装入工作库(比如自己创建的Person类),是最常用的加载器。
package 反射;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 了解类的加载器
*/
public class ClassLoaderTest {
public static void main(String[] args) throws IOException {
//获取当前自定义类的 类的加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//输出结果 sun.misc.Launcher$AppClassLoader@18b4aac2 表示为系统类加载器
//获取上一层类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent); //输出结果 sun.misc.Launcher$ExtClassLoader@5cad8086 表示为扩展类加载器
//获取上一层类加载器
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent); //输出结果 null 但是引导类加载器是无法获取的,null不代表没有引导类加载器
//使用ClassLoader加载配置文件
//先利用Properties类
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream("test.properties");
properties.load(fileInputStream);
String user = properties.getProperty("user");
String pass = properties.getProperty("pass");
System.out.println(user+pass); //输出结果 hwh123
//利用ClassLoader类
Properties properties2 = new Properties();
ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader1.getResourceAsStream("/Users/hwh/Desktop/学习资料/NettyStudy/src/main/resources/test1.properties");
properties2.load(is);
String user2 = properties2.getProperty("user");
String pass2 = properties2.getProperty("pass");
System.out.println(user2+pass2); //输出结果 hwh123
}
}
类加载器的作用:
类加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为在方法区中类数据的访问入口。
类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间(即缓存)。不过JVM垃圾回收器可以回收这些class对象。
2.2 获取Class实例的4种方式
- 调用运行时类的.class属性
- 调用实例的getClass方法
- 使用Class类的静态方法forName
- 利用类加载器ClassLoader
package 反射;
public class TestClass {
public static void main(String[] args) throws Exception{
//获取Class实例的4种方式
//方式一:调用运行时类的属性 .class
Class p1Class = Person.class;
System.out.println(p1Class);
//方式二:通过运行时类的对象,调用getClass方法
//Person是运行时类
Person p2 = new Person();
Class p2Class = p2.getClass();
System.out.println(p2Class);
//方式三:调用Class的静态方法:forName(String classPath)
Class p3Class = Class.forName("反射.Person");
System.out.println(p3Class);
//方式四:使用类加载器ClassLoader
ClassLoader classLoader = TestClass.class.getClassLoader();
Class p4Class = classLoader.loadClass("反射.Person");
System.out.println(p4Class);
//测试这几个Class是否是同一个实例
System.out.println("p1是否和p2相等 = "+(p1Class == p2Class));
System.out.println("p1是否和p3相等 = "+(p1Class == p3Class));
System.out.println("p1是否和p4相等 = "+(p1Class == p4Class));
}
}
2.3 通过反射创建运行时类的实例
public class NewInstanceTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Person> personClass = Person.class;
//创建一个运行时类的实例
Person person = personClass.newInstance();
System.out.println(person);
}
}
newInstance():调用此方法,创建对应的运行时类的实例。实际上它还是调的Person类的无参构造器。只要是创建实例,那就都是用构造器来创建,只有构造器才能创建实例!
要想此方法正常的创建运行时类的实例,有以下要求:
- 运行时类必须提供空参的构造器
- 空参构造器的访问权限得够,最好是public
在JavaBean中要求提供一个public的空参构造器有以下原因:
- 便于通过反射创建运行时类的实例
- 便于子类继承此运行时类时,默认调用super()时,保证父类有次构造器
举例:感受下反射的动态性
说明:在运行时代码可以根据条件改变自身结构是反射的特征。
public class TestDynamicGetInstance {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
int i = new Random().nextInt(3);
String[] classPath = {"java.util.Date","java.lang.Object","反射.Person"};
Object instance = getInstance(classPath[i]);
System.out.println("i="+i+instance.toString());
}
/**
* 创建一个指定类的对象
* @param classPath 指定类的全类名
* @return
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
}