0%

java-Day15——反射

反射

反射是动态语言的关键,反射机制运行程序在运行期间借助于Reflection API取得任何类的内部信息,并能之间操作任意对象的内部属性与方法。

反射的意义:

1.面向对象中创建对象,调用指定结果(属性、方法)等功能,可以不适用反射,也可以使用反射。有什么区别?

使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有属性、方法构造器

2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的画,那种使用的多?场景?

  • 作为程序员主要完成的是业务代码,对于相关的对象、方法的调用是确定的。因此使用非反射的情况多。

  • 因为反射提醒了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架时会大量使用反射。框架 = 注解+反射+设计模式

3.单例模式的饿汉式与懒汉式,私有化类的构造器。此时通过反射可以通过单例模式中类的多个对象吗?

是的

4.通过反射,可以调用类中私有结构,是否与面向对象的封装性有冲突?

封装性:提醒的是是否建议我们调用内部api的问题。比如,private声明的结果,意味着不建议调用。

反射:提醒的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用

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
public class PersonTest {
@Test
public void test() throws InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
//1.创建Person类的实例
Class<Person> clazz = Person.class;
Person p1 = clazz.newInstance();
System.out.println(p1);

//2.调用属性
Field ageField = clazz.getField("age");
ageField.set(p1, 10);
System.out.println(ageField.get(p1));

//3.调用方法
Method showMethod = clazz.getMethod("show");
showMethod.invoke(p1);
}

/*
* 出了Person类,就无法直接调用private修饰的结构
* 但可以通过反射的方式来调用
* */

@Test
public void test2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1.调用私有的构造器,创建Person的实例
//private Person(String name, int age)
Class clazz1 = Person.class;
Constructor cons = clazz1.getDeclaredConstructor(String.class, int.class);
cons.setAccessible(true);
Person p1 = (Person) cons.newInstance("Tom",12);
System.out.println(p1);

//2.调用私有属性
//private String name
Field nameField = clazz1.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p1,"jerry");
System.out.println(nameField.get(p1));

//3.调用私有的方法
//private String showNation(String nation)
Method showNationMethod = clazz1.getDeclaredMethod("showNation", String.class);
showNationMethod.setAccessible(true);
String info = (String) showNationMethod.invoke(p1, "CHN");
System.out.println(info);

}
}
class Person{
private String name;
public int age;

public Person() {
}

private Person(String name, int age) {
this.name = name;
this.age = age;
}

public void show(){
System.out.println("showshow");
}

private String showNation(String nation){
return nation;
}

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

Class

针对于编写好的.java源文件进行编译(使用javac.exe),会生产一个或多个.class字节码文件。接着我们使用java.exe命令对知道的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件(使用类的加载器)加载到内存中。加载到内存中(方法区)的.class文件对应的结果即为Class的一个实例。

比如:加载到内存中的Person类或String类或User类。都作为Class的一个个实例

Class clazz1 = Person.class;//运行时类

Class clazz4 = Comparable.class;

说明:运行时类在内存会缓存起来,在整个执行期间,只会加载一次。

获取Class的实例的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ClassTest {
@Test
public void test() throws ClassNotFoundException {
//1.class
Class clazz1 = Person.class;
System.out.println(clazz1);

//2.调用运行时类的对象的getclass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz1 == clazz2);//true

//3.调用Class的静态方法forName(String className)
String className= "ex2.Person";
Class clazz3 = Class.forName(className);
System.out.println(clazz1==clazz3);//true

//4.使用类加载器的方式
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("ex2.Person");
System.out.println(clazz1==clazz4);//true
}
}

Class的实例可以指向哪些结构

所有java类型

类的加载过程

过程1:类的装载Load

将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成

过程2:连接Link

  • 验证(Verify):确保加载的类信息符合JVM规范。
  • 准备(Prepare):正式为类变量份分配内存并设置类变量默认初始值的阶段,这些内存都会在方法区分配
  • 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程

过程3:初始化initialization

执行类构造器()方法的过程。

类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。

类的加载器

作用:负责类的加载,并对应于一个Class的实例

分类(分为两种):

1.BootStrapClassLoader:引导类加载器

  • 使用C/c++语言编写,不能通过Java代码获取其实例
  • 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)

2.继承于ClassLoader的类加载器

  • ExtensionClassLoader:扩展类加载器
  • SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器
  • 用户自定义类加载器

以上的类加载器是否存在继承关系

不存在。

通过classLoader加载指定配置文件

1
2
3
4
5
6
7
8
9
10
@Test
public void test2() throws IOException {
Properties pros = new Properties();
//通过类的加载器读取的文件默认路径为:当前module的src下
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("../info.properties");
pros.load(is);
String name = pros.getProperty("name");
String pwd = pros.getProperty("password");
System.out.println(name+":"+pwd);
}

应用

1.创建运行时的对象

1.1 实现方法:

通过Class的实例调用newInstance()方法即可

1
2
3
4
5
6
7
8
9
10
public class NewInstanceTest {
@Test
public void test1() throws InstantiationException, IllegalAccessException {
Class clazz = Person.class;

//创建Person类的实例
Person per = (Person) clazz.newInstance();
System.out.println(per);
}
}

1.2 创建对象成功的满足条件:

  1. 要求运行时类中必须提供一个空参的构造器

  2. 要求提供的空参构造器的权限要足够

1.3 JavaBean中要给一个当前类提供一个公共的空参构造器的意义

场景1:子类对象在实例化时,子类的构造器的首行默认调用父类空参的构造器。

场景2:在反射中,经常用来创建运行时类的对象。那么我要求各个运行时类都提供一个空参的构造器,便于我们编写创建运行时类对象的代码

1.4在jdk9中标识为过时,替换成什么结构

通过Constructor类调用newInstance(…)

2.获取运行时类的内部结构

2.1获取运行时类的内部结果1:所有属性、所有方法、所有构造器

2.2获取运行时类的内部结果2:父类、接口、包、带泛型的父类、父类的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test2() throws ClassNotFoundException {
Class clazz = Class.forName("com.alugg.reflection.Person");
//运行时类的父类
Type superclass = clazz.getSuperclass();

//运行时类的接口
Class[] interfaces = clazz.getInterfaces();
for(Class c:interfaces){
System.out.println(c);
}
//运行时类的包
Package pack = clazz.getPackage();
//运行时类的父类的泛型
Type superclass1 = clazz.getGenericSuperclass();
//如果父类是带泛型的,则可以强转为Parameterized
ParameterizedType paramType = (ParameterizedType) superclass1;
//调用getActualTypeArguments()获取带泛型的参数,结果是一个数组,因为可能有多个泛型参数
Type[] arguments = paramType.getActualTypeArguments();
System.out.println(((Class)arguments[0]).getName());
}

3.调用指定的结构:指定的属性、方法、构造器

3.1调用指定属性:

实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test3() throws InstantiationException, IllegalAccessException, NoSuchFieldException {
Class clazz = Person.class;
Person per = (Person)clazz.newInstance();

//1.通过Class实例调用getDeclaredField(String fieldName)获取运行时类指定的属性
Field nameField = clazz.getDeclaredField("name");
//2.setAccessible(true),确保此属性是可以访问的
nameField.setAccessible(true);
//3.获取或设置此属性的值
nameField.set(per,"alugg");
System.out.println(nameField.get(per));

}

静态类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
//private static String info;
@Test
public void test3() throws InstantiationException, IllegalAccessException, NoSuchFieldException {
Class clazz = Person.class;

//1.通过Class实例调用getDeclaredField(String fieldName)获取运行时类指定的属性
Field infoField = clazz.getDeclaredField("info");
//2.setAccessible(true),确保此属性是可以访问的
infoField.setAccessible(true);
//3.获取或设置此属性的值
infoField.set(Person.class,"xixixhaha");
System.out.println(infoField.get(Person.class));
}

3.2调用方法

实例方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//private String showNation(String nation, int age)
@Test
public void test4() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class clazz = Person.class;
Person per = (Person) clazz.newInstance();

//1.通过Class的实例调用getDeclaredMthod(String methodName, Class ... args),获取指定的方法
Method showNationMethod = clazz.getDeclaredMethod("showNation", String.class, int.class)
//2.setAccessible(true):确保此方法是可访问的
showNationMethod.setAccessible(true);
//3.通过method实例调用invoke(Object obj, Object .., objs),即对Method对应的方法的调用
Object returnValue = showNationMethod.invoke(per,"chnxxx");
System.out.println(returnValue);
}

类静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//public static void showInfo()
@Test
public void test4() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class clazz = Person.class;
//1.通过Class的实例调用getDeclaredMthod(String methodName, Class ... args),获取指定的方法
Method showNationMethod = clazz.getDeclaredMethod("showInfo");
//2.setAccessible(true):确保此方法是可访问的
showNationMethod.setAccessible(true);
//3.通过method实例调用invoke(Object obj, Object .., objs),即对Method对应的方法的调用
//如果Method对应的方法的返回值为void,则invoke()返回值为null
Object returnValue = showNationMethod.invoke(null);
System.out.println(returnValue);
}

3.3调用构造器

1
2
3
4
5
6
7
8
9
10
11
//private Person(String name, int age)
@Test
public void test5() throws NoSuchMethodException {
Class clazz = Person.class;
//1.通过Class的实例调用getDeclaredConstructor(Class ... args) 获取指定参数类型的构造器
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
//2.setAccessible(true):确保此构造器是可以访问的
constructor.setAccessible(true);
//3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。
Person tom = (Person) constructor.newInstance("TOM", 12);
}

4.获取注解信息

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test1() throws NoSuchFieldException {
Class clazz = Customer.class;
Table annotation = (Table) clazz.getDeclaredAnnotation(Table.class);
System.out.println(annotation.value());

//获取属性声明的注解
Field nameField = clazz.getDeclaredField("name");
Column nameColumn = nameField.getDeclaredAnnotation(Column.class);
System.out.println(nameColumn.columnName());
System.out.println(nameColumn.columnType());
}

常见问题

反射的好处,为什么需要反射,使用的场合

实现反射的类有哪些?

反射是如何实现?

Class类的作用和生成Class对象有哪些

Class.forName()会调用哪些方法?会调用构造方法吗?加载的类放在哪?

Class.forName()会执行执行类构造器()方法

不会调用构造方法

加载的类放在方法区

类的加载流程

创建对象有几种方法

Java反射创建效率高还是new创建高?

new高

如何利用反射机制访问类的方法或获取私有属性

-------------本文结束感谢您的阅读-------------