文章目录
笔记来源: 尚硅谷
一、反射的概述
Reflectlon (反射) 是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
正常方式 :
- 引入需要的包类名称
- 通过new实例化
- 取得实例化对象
反射方式:
- 实例化对象
- getClass()方法
- 取得完整的包类名称
java反射机制提供的功能:
➢ 在运行时判断任意一个对象所属的类
➢ 在运行时构造任意一个类的对象
➢ 在运行时判断任意一个类所具有的成员变量和方法
➢ 在运行时获取泛型信息
➢ 在运行时调用任意一个对象的成员变量和方法
➢ 在运行时处理注解
➢ 生成动态代理
首先我们看一下反射的运用:
- 声明一个Person实体类【有私有属性,公有属性,私有构造函数,公有构造函数,私有方法,公有方法】
public class Person {
private String name;
public int age;
private Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("我是一个人");
}
private void showNation(String nation) {
System.out.println("我来自"+nation);
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
- 不用反射使用Person(正常使用)
public class ReflectionTest {
//反射之前对于person的操作
@Test
public void Test1(){
Person person = new Person("Tom",12);
person.age = 10;
person.show();
System.out.println(person);
//在person类外部,不可通过Person类的对象调用内部私有结构
}
}
- 使用反射
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.junit.Test;
public class ReflectionTest {
//反射之后,对于person的对象
@Test
public void Test2() throws Exception{
Class clazz = Person.class;
//获取构造方法
Constructor cons = clazz.getConstructor(String.class, int.class);
//使用构造方法声明一个对象
Person person = (Person) cons.newInstance("Tom",12);
System.out.println(person.toString());
//获取对象指定的公有属性age,并赋值为10
Field ageField = clazz.getDeclaredField("age");
ageField.set(person, 10);
System.out.println(person.toString());
//获取对象的公有方法show,并调用
Method showMethod = clazz.getDeclaredMethod("show");
showMethod.invoke(person);
//通过反射调用person私有结构
//私有构造器
Constructor privateCons = clazz.getDeclaredConstructor(String.class);
privateCons.setAccessible(true);
Person person2 = (Person) cons.newInstance("Tom",12);
System.out.println(person2.toString());
//调用私有属性
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person2, "Peter");
//调用私有方法showNation
Method showNationMethod = clazz.getDeclaredMethod("showNation", String.class);
showNationMethod.setAccessible(true);
showNationMethod.invoke(person2, "中国");
System.out.println(person2.toString());
}
}
输出结果为:
Person [name=Tom, age=12]
Person [name=Tom, age=10]
我是一个人
Person [name=Tom, age=12]
我来自中国
Person [name=Peter, age=12]
综上所述,反射的强大之处在于可以调用一个类的私有结构
疑问:
- 有了反射以后,与面向对象的封装性是不是矛盾的?
答:不矛盾。封装性可以看做一种提示,提示你公有方法可以调用,私有方法最好不要调用,而反射解决的是能不能调用的问题
- 通过直接new对象,还是通过反射调用公共结构,开发中用哪个?
答:建议使用直接new的方式。
反射的特征:动态性。在动态执行代码,不确定new谁的时候,使用反射。
二、关于Class类的理解并获取Class实例
Class clazz = Person.class;
2.1 关于 java.lang.Class 类的理解
- 类的加载过程:
程序在编译后会生成一个或多个字节码文件(.class结尾),接着我们使用java.exe命令对某个字节码文件解释运行,相当于将某个字节码文件加载到内存中,此过程就叫做 类的加载 ,加载到内存的类,就成为运行时类,这个运行时类,就是Class的一个实例
- 换句话说,Class的实例就对应着一个运行时的类
- 加载到内存中的运行时类,会缓存一段时间,在此时间内,我们可以通过不同的方式获取此运行时类
2.2 获取Class实例的四种方式
- 方式一:调用运行时类的属性.class
Class<Person> clazz = Person.class;
- 方式二:通过运行时类的对象
Person person = new Person("tom", 12);
Class clazz1 = person.getClass();
- 方式三:调用class的静态方法:forName(String classPath)
Class clazz2 = Class.forName("reflection.Person");
我们判断一下三个对象的地址是否相同(是否指向同一个运行时类):
System.out.println(clazz==clazz1);
System.out.println(clazz1==clazz2);
运行结果:
true
true
- 方法四:使用类加载器ClassLoader
ClassLoader classLoader=ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("reflection.Person");
System.out.println(clazz==clazz4);
true
方法三用得最多
三、Class实例对应的结构说明
哪些类型可以有Class对象?
(1) class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2) interface:接口
(3) []:数组
(4) enum:枚举
(5) annotation:注解@interface
(6) primitive type:基本数据类型
(7) void
@Test
public void test4(){
Class c1 = object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100] ;
Class c10 = a.getClass();
Class c11 = b.getClass();
//只要数组类型与维度一样,就是同一个Class
System.out.print1n(c10 == c11);
}
四、理解类的加载过程与ClassLoader
4.1 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
加载 :将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
链接 :将Java类的二进制代码合并到JVM的运行状态之中的过程。
➢ 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
➢ 准备:正式为类变量(static )分配内存并设置类变量 默认初始值 的阶段,这些内存都将方法区中进行分配。
➢ 解析: 虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
➢ 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
➢ 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
➢ 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
我们以下面这个实例说明:
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A{
static {
m = 300;
}
static int m = 100;
}
4.2 ClassLoader的理解
类加载器的作用:
-
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
-
类缓存:标准的JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
import org.junit.Test;
public class ClassLoaderTest {
@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
//获取当前类加载器【系统类加载器】
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类:扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//引导类加载器【主要负责加载JAVA核心类库】无法用getParent获取
}
}
sun.misc.Launcher$AppClassLoader@5c647e05
sun.misc.Launcher$ExtClassLoader@330bedb4