目录
一、Java的反射机制概述
1.1 Java Reflection
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取的任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以称之为:反射。
正常方式:
引入需要的“包类“名称——>通过new实例化——>取的实例化对象
反射方式:
实例化对象——>getClass()方法——>得到完整的“包类名称”
1.2 补充:动态语言 vs 静态语言
1.2.1 动态语言
在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
1.2.2 静态语言
与动态语言相对应,运行时结构不可变的语言就是静态语言。
如Java、C、C++。
Java不是动态语言。但Java可以称之为“准动态语言”。即Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性使得编程更灵活。
1.3Java反射机制的应用
- Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
1.4反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- .....
代码实现
package com.ch.java;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
//反射之前对于Person类的操作
@Test
public void test1() {
//1.实例化对象
Person p1 = new Person("Tom", 21);
//2.通过对象调方法
p1.age = 12;
System.out.println(p1.toString());
p1.show();
//在Person类的外部,不可以通过Person类的对象调用其内部的私有结构。
//比如name、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", 21);
Person p1 = (Person) obj;
//2.通过反射,调用 对象指定的属性、方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p1, 12);
System.out.println(p1.toString());
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p1);
System.out.println(p1.toString());
System.out.println("***********************************");
//通过反射,可以调用Person类的私有结构,比如私有构造器、私有属性、私有方法
//调用私有的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p2 = (Person) cons1.newInstance("Jerry");
System.out.println(p2);
//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p2,"HanMeiMei");
System.out.println(p2);
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1, "中国");//相当于p1.showNation("中国")
System.out.println(nation);
}
}
总结:反射的强大之处在于,使用反射可以调用类的私有结构
1.5如何看待反射和封装性两个技术
1.通过直接new的方式或反射的方式都可以调用公用的结构,实际开发中使用哪种?
确定创建具体类的对象时,使用new的方式。
不确定要创建哪个类的对象时,使用反射方式。体现动态性。
2.反射机制与面向对象中的封装性是否矛盾?如何看待两个技术?
首先肯定的是不矛盾。
封装性
站在编程的角度,去让程序有可读性,层次结构能体现属性跟方法的特征,且能够按照希望的方式被调用。
比如私有化,说明不希望别人直接调用,而是使用get、set方法来间接调用。
比如单例模式,私有化构造器是为了不让外部再去创建对象。
反射
站在不知道对象的内部结构但又必须要使用他的情况下。
比如各大框架的编写。
比如为了动态生成用户需要的对象。
反射确实可以获取私有的结构并任意改变,但是实际上并没有人会去这么做。
小结
封装性跟反射并不矛盾且可以共存,两者拥有不同的使用场景跟意义。
二、理解Class类并获取Class实例
2.1 Class类的理解
1.类的加载过程
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class)。
接着使用java.exe命令对某个字节码文件进行解释运行。
相当于将某个字节码文件加载到内存中。此过程就成为类的加载。
而加载到内存中的类,就叫做运行时类,此运行时类,就作为Class类的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一段时间。在这段时间内,可以使用不同的方式来获取运行时类。
2.2 获取Class实例
获取Class实例的方法有4种,前3种要掌握,最后1种了解即可。
方式一:调用运行时类的属性:.class
方式二:通过运行时类的对象,调用getClass()方法
方式三:调用Class的静态方法:forName(String classPath) (使用频率较高)
方式四:类的加载器:ClassLoader (了解)
//获取Class实例
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()方法
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.ch.java.Person");
System.out.println(clazz3);
System.out.println(clazz==clazz2);//ture
System.out.println(clazz==clazz3);//ture
//方式四:类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.ch.java.Person");
System.out.println(clazz4);
System.out.println(clazz==clazz4);//ture
}
2.3 哪些结构可以有Class对象
- class 外部类,成员,局部内部类,匿名内部类
- interface 接口
- [] 数组
- enum 枚举类
- annotation 注解
- primitive type 基本数据类型
- void
只要元素类型和纬度一样,就是同一个Class
//哪些类可以有Class对象
@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
Class c10 = a.getClass;
Class c11 = b.getClass;
System.out.println(c10==c11);//true
}
三、类的加载与ClassLoader的理解
3.1 了解类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
3.1.1类的加载(Load)
文件内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后创建一个java.lang.Class对象没座位方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。此过程由类的加载器完成。
3.1.2类的链接(Link)
将Java类的二进制数据合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范。
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
3.1.3类的初始化(Initiallize)
JVM负责对类进行初始化。
- 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);//100
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
第一步:加载。将A.class加载到内存中,并创建一个Class对象,暂称为c。
第二步:链接。静态变量m赋值为0。
第三步:初始化。m的值由<clinit>()方法执行决定。
这个A的类构造器clinit>()方法由类变量的赋值和静态代码库中的语句按照顺序合并产生,类似于:
<clinit>(){
m = 300;
m = 100;
}
3.2 类加载器的作用
-
类加载的作用
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
-
类缓存
- 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(即缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
3.3 了解ClassLoader
类加载器作用是用来把类装载进内存的。JVM规范定义了如下类型的类的加载器。
import org.junit.Test;
public class ClassLoaderTest {
@Test
public void test1() {
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent()方法,可以获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent()方法,是无法获取引导类加载器
//引导类加载器主要负责加载Java核心类库,无法加载自定义类。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);//null
ClassLoader classLoader3 = String.class.getClass().getClassLoader();
System.out.println(classLoader3);//null
}
}
小结
- 自定义类使用系统加载器加载。
- 使用加载器的getParent()方法可以获取上一层加载器。
- 引导类加载器只负责加载Java核心类库,无法加载自定义类,也无法被获取到。
3.4 使用ClassLoader加载配置文件
- 方式一:直接使用节点流加载,此时配置文件在当前module下。
- 方式二:使用类加载器获取流,此时配置文件在当前module的src下。
为了避免在Tomcat下丢失配置文件,建议将配置文件放在module的src下。
/*
Properties:用来读取配置文件
*/
@Test
public void test2() throws IOException {
Properties pro = new Properties();
//读取配置文件
//方式一:文件在当前module下
// FileInputStream fis = new FileInputStream("jdbc.properties");
// pro.load(fis);
//方式二:类加载器,文件在当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pro.load(is);
String user = pro.getProperty("user");
String password = pro.getProperty("password");
System.out.println("user=" + user);
System.out.println("password=" + password);
}
四、创建运行时类的对象
4.1 通过反射,创建运行时类对象
调用newInstance()方法,创建对应的运行时类对象,方法内部实际调用了运行时类的空参构造器。而要想此方法被成功调用,需要满足以下要求:
- 运行时类存在空参构造器。
- 有足够的权限访问空参构造器。空参构造器通常为public。
@Test
public void test1() throws InstantiationException, IllegalAccessException {
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);
}
另外,在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象。
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器。
4.2 举例体会反射的动态性
动态的生成对象:
@Test
public void test2() {
int num = new Random().nextInt(3);//0,1,2
String className = "";
switch (num) {
case 0:
className = "java.util.Date";
break;
case 1:
className = "java.sql.Date";
break;
case 2:
className = "com.ch.java.Person";
break;
}
try {
Object obj = getInstance(className);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建一个指定类的对象
*
* @param classPath 指定类的全类名
* @return
* @throws Exception
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
五、获取运行时类的完整结构
5.1 提供结构丰富的Person类
自定义接口MyInterface
package com.ch.java1;
public interface MyInterface {
void info();
}
自定义注解MyAnnotation
package com.ch.java1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "hello";
}
父类Creature
package com.ch.java1;
import java.io.Serializable;
public class Creature<T> implements Serializable {
private char gender;
public double weight;
private void breath(){
System.out.println("Creature breath");
}
public void eat(){
System.out.println("Creature eat");
}
}
Person类
package com.ch.java1;
@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
private String name;
int age;
public int id;
public Person() {
}
@MyAnnotation(value = "abc")
private Person(String name) {
this.name = name;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
@MyAnnotation
private String show(String nation) {
System.out.println("My nation is " + nation);
return nation;
}
public String display(String interests, int age) throws NullPointerException, ClassCastException {
return interests + age;
}
@Override
public void info() {
System.out.println("I am a Person");
}
@Override
public int compareTo(String o) {
return 0;
}
private static void showDesc() {
System.out.println("I am a Good Person");
}
}
5.2 获取运行时类的属性结构及其内部结构
5.2.1 获取属性
- getFields():获取当前运行时类及其父类中权限为public的属性。
- getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类的属性)
@Test
public void test1() {
Class clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中权限为public的属性
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}
System.out.println("************************");
//getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
}
5.2.1 获取属性结构
- 获取权限修饰符:getModifiers()
- 获取数据类型:getType()
- 获取变量名:getName()
//权限修饰符、数据类型、变量名
@Test
public void test2() {
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
//1.权限修饰符:
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t");
//2.数据类型
Class type = f.getType();
System.out.print(type.getName() + "\t");
//3.变量名
String fName = f.getName();
System.out.print(fName);
System.out.println();
}
}
执行结果:
5.3 获取运行时类的方法结构
5.3.1 获取方法
- getMethods():获取当前运行时类及其所有父类中声明为public权限的方法。
- getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的)
5.3.2 获取方法的结构
- 获取注解:getAnnotations()
- 获取权限修饰符:getModifiers()
- 获取返回值类型:getReturnType()
- 获取参数列表:getParameterTypes()
- 获取抛出的异常:getExceptionTypes()
/*
@Xxx
权限修饰符、返回值类型、方法名(参数类型1 形参名1 .....) throws XxxException{}
*/
@Test
public void test2() {
Class<Person> clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
//1.获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for (Annotation a : annos) {
System.out.println(a);
}
//2.权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.返回值类型
Class returnType = m.getReturnType();
System.out.print(returnType.getName() + "\t");
//4.方法名
String name = m.getName();
System.out.print(name);
System.out.print("(");
//5.参数列表
Class[] types = m.getParameterTypes();
if (!(types == null || types.length == 0)) {
for (int i = 0; i < types.length; i++) {
if (i == types.length - 1) {
System.out.print(types[i].getName() + " args_" + i);
break;
}
System.out.print(types[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");
//6.抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if (!(exceptionTypes == null || exceptionTypes.length == 0)) {
System.out.print("throws ");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
执行结果:
5.4 获取运行时类的构造器
5.4.1 获取构造器
- getConstructors():获取当前运行时类中声明为public的构造器。
- getDeclaredConstructors():获取当前运行时类中声明的所有构造器。
/*
获取运行时类的构造器
*/
@Test
public void test1() {
Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor cons : constructors) {
System.out.println(cons);
}
System.out.println("************************");
//getDeclaredConstructors():获取当前运行时类中声明的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor cons : declaredConstructors) {
System.out.println(cons);
}
}
执行结果
5.5 获取运行时类的父类及父类的泛型
- getSuperclass():获取运行时类的父类
- getGenericSuperclass():获取运行时类的带泛型的父类
- ParameterizedType.getActualTypeArguments():获取运行时类带泛型的父类的泛型。可以用于DAO操作数据库层。
/*
获取运行时类的父类
*/
@Test
public void test2() {
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3() {
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
/*
获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4() {
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType type = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = type.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName());
}
执行结果
父类:
带泛型父类:
带泛型父类的泛型:
5.6 获取运行时类的接口
- getInterfaces():获取运行时类实现的接口
/*
获取运行时类实现的接口
*/
@Test
public void test5() {
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println();
//获取运行时类父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c : interfaces1) {
System.out.println(c);
}
}
5.7 获取运行时类的所在包
- getPackage():获取运行时类所在包
/*
获取运行时类所在的包
*/
@Test
public void test6() {
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
5.8 获取运行时类的注解
- getAnnotations():获取运行时类声明的注解
/*
获取运行时类声明的注解
*/
@Test
public void test7() {
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
System.out.println(anno);
}
}
六、调用运行时类的指定结构
6.1 调用指定的属性
实现顺序
- 1.创建运行时类对象
- 2.获取指定名的属性
- getField(String fieldName):获取指定名字的属性,但仅限public权限,故不建议。
- getDeclaredField(String fieldName):获取指定名字的属性,实际采用此方法。
- 3.保证当前属性可访问
- setAccessible(true)
- 4.设置、读取操作。
- get()、set()
/*
如何操作运行时类中指定的属性
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//1.getDeclaredField(String fieldName):获取指定名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性可访问
name.setAccessible(true);
//3.设置、获取指定对象的属性
name.set(p,"Tom");
System.out.println(name.get(p));
}
6.2 调用指定的方法
实现顺序
- 1.创建运行时类对象
- 2.获取指定名的方法
- getDeclaredMethod(String name, Class<?>... parameterTypes)
- 功能:获取指定名字的方法
- 参数1:指明获取的方法的名称
- 参数2:指明获取的方法的参数列表
- getDeclaredMethod(String name, Class<?>... parameterTypes)
- 3.保证当前方法可访问
- setAccessible(true)
- 4.方法执行
- invoke(Object obj, Object... args)
- 参数1:方法的调用者,即具体哪个对象执行此方法。若调用的为静态方法,可以写null或类.class。
- 参数2:给方法的形参赋值的实参。
- 返回值:即为对应类中方法的返回值。如果方法没有返回值,则返回null。
- invoke(Object obj, Object... args)
/*
如何操作运行时类中指定的方法
*/
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod()
参数1:指明获取的方法的名称
参数2:指明获取的方法的参数列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法可访问
show.setAccessible(true);
/*
3.调用方法
invoke():
参数1:方法的调用者
参数2:给方法的形参赋值的实参
返回值:即为对应类中方法的返回值
*/
Object returnValue = show.invoke(p, "China");
System.out.println(returnValue);
System.out.println("*************调用静态方法************");
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则返回null
Object returnV = showDesc.invoke(Person.class);
System.out.println(returnV);
}
6.3 调用指定的构造器
实现顺序
- 1.创建运行时类对象
- 2.获取指定名的构造器
- getDeclaredConstructor(Class<?>... parameterTypes)
- 参数:指明构造器的参数列表
- getDeclaredConstructor(Class<?>... parameterTypes)
- 3.保证当前构造器可访问
- setAccessible(true)
- 4.调用构造器
- newInstance(Object ... initargs)
- 参数:给构造器的形参赋值的实参
- newInstance(Object ... initargs)
/*
如何操作运行时类中指定的构造器
*/
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器可访问
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person jack = (Person) constructor.newInstance("Jack");
System.out.println(jack);
}