0%

【Java基础】反射

学习反射的学习笔记

一、Java反射机制概述

1.1 反射概述

  • Reflection(反射)是被视为动态语言的关键。反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并直接操作任意对象的内部属性及方法
  • 加载完类之后,在堆内存的方法去中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构
  • 补充:
    • (1)动态语言:运行时可以改变其结构的语言
    • (2)静态语言:运行时结构不可变的语言就是静态语言

1.2 实例理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class ReflectionTest {
//反射前,对Person类的操作
@Test
public void test1(){
//1.创建Person类的对象
Person p1 = new Person("Tom", 12);

//2.调用其内部属性和方法
p1.toString();
p1.show();

//在Person类外部,不能通过Person类的对象调用其内部私有结构
//比如showNation(),私有构造器
}

//反射之后,对于Person的操作
@Test
public void test2() throws Exception {
Class clazz = Person.class;
//1.通过反射,创建Person类的对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Tom", 12);
Person p = (Person)obj;
System.out.println(p.toString());


//2.通过反射,调用对象指定的属性、方法
Field age = clazz.getDeclaredField("age");//私有属性
age.set(p, 10);
System.out.println(p.toString());

Method show = clazz.getDeclaredMethod("show");
show.invoke(p);

//************************************************************
//通过反射,可以调用Person类私有结构。比如:私有的构造器、方法、属性
//调用私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person)cons1.newInstance("Jerry");
System.out.println(p1);

//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "frankile");
System.out.println(p1);

//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
showNation.invoke(p1, "中国");
}
}

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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public void show(){
System.out.println("我是一个人");
}

private String showNation(String nation){//用于体现反射
System.out.println("我的国籍是" + nation);
return nation;
}
}
  • 疑问1:通过直接new的方式或反射都可以调用公共的结构,开发中到底用哪个?

    • 建议:在建直接new的方法是
    • 运用到代码动态性才使用比较好
  • 疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术

    • 不矛盾(封装性,私有不能用;反射,还是能使用私有)
    • 封装性只是建议如何使用,反射是能不能使用,是两码事

二、理解Class类并获取Class实例

2.1 类的加载过程

  • (1)类的加载过程:
    • 程序警经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),然后使用java.exe命令对某个字节码文件进行解释运行。相当于对某个字节码文件加载到内存中。此过程就称为类的加载。加载内存中的类,我们就称为运行时类。此运行时类,就作为Class的一个实例。
    • (相当于把编译成功的xxx.class直接调入到内存中)
    • 万事万物皆对象(类本身也是对象)
  • (2)换句话说,Class的实例就对应着一个运行时类

2.2 Class类的四种实例化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    @Test
public void test1() throws ClassNotFoundException {
//方式一:调用运行时类的属性:xxx.class(编译时已经写死,体现不出动态性)
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);

//方式二:通过运行时类的对象
Person p = new Person();
Class clazz2 = p.getClass();
System.out.println(clazz2);

//方式三:调用Class的静态方法 forName(String classPath)//需要异常处理,防止文件寻找不了
//(在运行时才知道是否出错,更能体现动态性)
Class clazz3 = Class.forName("ClassUnderstand.Person");
System.out.println(clazz3);

System.out.println(clazz1 == clazz2);//true
System.out.println(clazz1 == clazz3);//true
System.out.println(clazz2 == clazz3);//true
//加载到内存中的运行类,会缓存一定的时间,在此事件之类,我们可以通过不同的方式类获取此运行时类

//方式四:使用类的加载器:ClassLoader(了解,使用频率不高)
ClassLoader classLoader = Instantiation.class.getClassLoader();
Class clazz4 = classLoader.loadClass("ClassUnderstand.Person");
System.out.println(clazz4);

System.out.println(clazz4 == clazz1);//true
}

2.3 有Class对象的类型

  • (1)外部类、成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • (2)interface:接口 — Comparable.class
  • (3)[]:数组 — int[].class
  • (4)enum:枚举 — ElementType.class
  • (5)annotation:注解@interface — Override.class
  • (6)primitive type:基本数据类型 — int.class
  • (7)void — void.class
1
2
3
4
5
6
7
注意:
int[] a = new int[10];
int[] b = new int[100];
Class clazz1 = a.getClass();
Class clazz2 = b.getClass();
System.out.println(clazz1 == clazz2); // true
//只要数组的元素类型与围堵一样,就是同一个Class

三、类的加载和ClassLoader的理解

3.1 类的加载过程(了解)

  • 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
    • 类的加载(Load) –> 类的连接(Link) –> 类的初始化(Initialize)
      • 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。
      • 类的链接:将类的二进制数据合并到JRE中
      • 类的初始化:JVM负责对类进行初始化

3.2 类的加载器

  • (1)引导类加载器:用C++编写的,是JVM自带的类的加载器,负责Java平台核心库。用来装载核心类库。该加载无法直接获取。(例:String)
  • (2)扩展类加载器:扶着“jre/lib/ex”目录下的jar包或 “—D java.exit.dirs” 指定目录下的jar包装入工作库
  • (3)系统类加载器:负责java-classpath 或 -D java.class.path 所指示的目录下类与jar包装入工作,是最常用的类加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
代码举例理解:
@Test
public void classLoadTest(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = LoadingProcess.class.getClassLoader();
System.out.println(classLoader);
//jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d(系统加载器)

ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//jdk.internal.loader.ClassLoaders$PlatformClassLoader@23223dd8(扩展加载器)

ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//null(引导类加载器无法获取)
}

四、创建运行时类的对象

1
2
3
4
5
6
7
8
@Test
public void test() throws Exception {
Class<Person> clazz = Person.class;
//Person obj = newInstance(); 方法过时(since = "9")
Person obj = clazz.getConstructor().newInstance();
//创建对应的运行时磊的对象,调用空参、非私有构造器
System.out.println(obj);
}
  • 默认调用的是类的空参构造器,需要给类提供。

五、获取运行时类的对象

5.1 获取当前类的属性

1
2
3
4
5
6
7
8
9
10
11
12
Class clazz = Person.class;
Field[] fields = clazz.getFields();
//能够获取当前运行时类,及其父类所有public权限的属性

Field[] declaredFields = clazz.getDeclaredFields();
//获取当前运行时类中所有声明的属性(但不包含父类的属性)

Filed field = clazz.getField(String name);
//获取指定名称的属性(仅限于public权限)

Filed declaredField = clazz.getDeclaredFiled(String name);
//获取指定名称的属性(当前运行类全部属性)

5.2 获取当前类的方法

1
2
3
4
5
6
7
8
9
10
11
Method[] methods = clazz.getMethods();
//获取当前运行时类,及其父类所有pulic方法

Method[] declaredMethods = clazz.getDeclaredMethods();
//获取当前运行时类中所有方法(不包括父类)

Method method =clazz.getMethod(String name, Class[] parameterTypes);
//获取当前运行类指定的方法名,参数类型的public方法

Method declaredMethod = clazz.getDeclaredMethod(String name, Class[] parameterTypes);
//获取当前运行时类指定的方法名,参数类型的方法(不受限于修饰符)

5.3 获取当前类的构造器

1
2
3
4
5
6
7
8
9
10
11
Constructor[] constructor = clazz.getConstructors();
//获取当前运行时类被声明为public的构造器

Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
//获取当前运行类中所有的构造器

Constructor constructor = clazz.getConstructor(Class[] parameterTypes);
//获取指定的构造器(仅限于public)

Constructor declaredConstructor = clazz.getDeclaredConstructor(Class[] parameterTypes);
//获取指定的构造器(不受限于修饰符)

5.4 获取当前类的父类,接口,包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class superclass = clazz.getSuperclass();
//获取当前运行类的父类

Type genericSuperclass = clazz.getGenericSuperclass();
//获取当前运行类带泛型的父类

ParameterizedType paramtype = (ParameterizedType)genericSuperclass;
Type[] actualTypeArguments = paramtype.getActualTypeArguments();
//获取泛型类型

Class[] interfaces = clazz.getInterfaces();
//获取运行时类实现的接口(不含父类)

Package package = clazz.getPackage();
//获取当前运行类所在的包

5.5 获取上面内容的参数/属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int getModifiers();
//获取权限修饰符,0:缺省, 1:public, 2:private

public Class<?> getType();
//获取数据类型

public String getName();
//获取变量名,方法名...

public Annotation[] getAnnotations();
//获取注解

public Class<?> getReturnType();
//获取返回值类型

public Class<?>[] getParameterTypes();
//获取形参列表

六、调用运行时类的指定结构

6.1 调用属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void FiledTest() throws Exception{
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.getConstructor().newInstance();

//获取指定的数据
Field id = clazz.getField("id");//只能获取类中属性的public

//设置当前的值 set():[参数1]指明设置那个对象的属性 [参数2]将次属性值设置为多少
id.set(p, 1001);
//获取当前属性值get()
System.out.println(id.get(p));

//--------------------------上面方法不常用--------------------------
Field age = clazz.getDeclaredField("age");//获取指定变量名的属性
age.setAccessible(true);//保证当前属性可访问(非public需要)

age.set(p, 12);
System.out.println(age.get(p));
}

6.2 调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void MethodTest() throws Exception{
Class clazz = Person.class;

Person p = (Person) clazz.getConstructor().newInstance();

//getDeclaredMethod():参数1:指明获取的方法名 参数2:指明获取方法的参数列表
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);//保证当前方法可访问
show.invoke(p, "中国");//invoke():调用方法,参数1--对象,参数2--形参列表
Object nation = show.invoke(p, "日本");//invoke()方法有返回值,返回值为当前调用方法的返回值
System.out.println(nation);

//**********************如何调用静态方法************************
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
Object invoke = showDesc.invoke(Person.class);
System.out.println(invoke);//调用运行时类的方法没有返回值时,返回null
}

6.3 调用构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void ConstructorTest() throws Exception{
Class clazz = Person.class;
//getDeclaredConstructor():参数--指明构造器的参数列表
Constructor constructor = clazz.getDeclaredConstructor(String.class);

//设置构造器可访问
constructor.setAccessible(true);

//利用构造器创建对象
Person p = (Person) constructor.newInstance("Tom");
System.out.println(p);
}

七、动态代理(了解)

  • 使用Proxy 和 InvocationHander创建动态代理
  • public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象,该对象实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class proxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);//创建代理类对象

//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("北京烤鸭!");
}
}


interface Human{
String getBelief();

void eat(String food);
}


class SuperMan implements Human{

@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}


class ProxyFactory{
//调用此方法,返回一个代理类的对象
public static Object getProxyInstance(Object obj){
MyInvocationHander handler = new MyInvocationHander();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}


class MyInvocationHander implements InvocationHandler {

private Object obj;//需要使用被代理类的对象进行赋值

public void bind (Object obj){
this.obj = obj;
}

//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法,invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为地阿里类对象调用的方法,此方法也作为了被代理对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
return returnValue;
}
}