前言:
反射与之前的知识的区别
1.面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。请问有什么区别?
- 不使用反射,我们需要考虑封装性。比如:出了自定义类之后,就不能调用自定义类中私有的结构
- 使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器
2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?
- 从开发者的角度来讲,开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,使用非反射的方式多一些。
- 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架的时候会大量的使用反射。意味着,如果大家需要学习框架源码,那么就需要学习反射。
- 框架 = 注解 +反射 +设计模式
3,通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是Java语言设计存在Bug?
- 首先不存在bug!
- 封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用。
- 反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用。
一、反射(Reflection)
1.概念
概念:
- Reflection(反射)是被视为动态语言 的关键,反射机制允许程序在 运行期间 借助于Refection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
- 这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
图示:
1.1反射的优缺点
优点:
- 提高了Java程序的灵活性和扩展性,降低了耦合性,提高 自适应 能力
- 允许程序创建和控制任何类的对象,无需提前 硬编码 目标类
缺点:
- 反射的性能较低。
- 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 反射会 模糊 程序内部逻辑,可读性较差
2.Class类
2.1 Class类的理解(掌握)
(如下以Java类的加载为例说明)
针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为 Class 的一个实例。
如:加载到内存中的Person类或String类或User类,都作为Class的一个一个的实例
//运行时类
- Class clazz1 = Person.class;
- Class clazz2 = String.class;
- Class clazz3 = User.class;
Class clazz4 = Comparable.class;(接口)
2.2 Class的实例化
1.Class类实例化的三种方式:
package exer1;import org.junit.Test;public class ClassTest {/*获取Class实例的几种方式(掌握前三种)*/@Testpublic void test4() throws ClassNotFoundException {//1.调用运行时类的静态属性:classClass clazz1 = User.class;System.out.println(clazz1);//2. 调用运行时类的对象的getclass()User u1 = new User();Class clazz2 = u1.getClass();//3.调用Class的静态方法forName(String className)String className ="exer1.User";//全类名Class clazz3 = null;try {clazz3 = Class.forName(className);} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println(clazz1 == clazz2);//trueSystem.out.println(clazz1 == clazz3);//true}
}
2.Class的实例都可以指向哪些结构呢?(熟悉)
即所有Java类型!
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
3.类的加载过程(了解)
过程1:类的装载(loading)
- 将类的class文件读入内存,并为之创建一个java.lang.class对象。此过程由类加载器完成
过程2:链接(linking)
- 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
- 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
过程3:初始化(initialization)
- 执行类构造器<clinit>()方法的过程。
- 类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
4.使用类的加载器获取流,并配置文件
需求:通过ClassLoader加载指定的配置文件
public class ClassLoaderTest {//使用ClassLoader加载配置文件@Testpublic void test(){try {Properties pros = new Properties();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");//默认当前读取的文件是module下的src中的文件pros.load(is);String name = pros.getProperty("name");String pwd = pros.getProperty("password");System.out.println(name+":"+pwd);} catch (Exception e) {throw new RuntimeException(e);}}//Properties配置文件@Testpublic void test2(){try {Properties pros = new Properties();FileInputStream fis = new FileInputStream(new File("info.properties"));//默认当前读取的饿文件是module下的pros.load(fis);String name = pros.getProperty("name");String pwd = pros.getProperty("password");System.out.println(name + ":" + pwd);} catch (IOException e) {e.printStackTrace();}}
}
2.3 创建运行时类的对象,以及获取运行时类的完整结构
(掌握)反射的应用:调用指定的结构:指定的属性、方法、构造器
1. 调用指定的属性(步骤)
- 步骤1.通过Class实例调用getDeclaredField(string fieldName),获取运行时类指定名的属性
- 步骤2.setAccessible(true):确保此属性是可以访问的
- 步骤3.通过Filed类的实例调用qet(0bject obj)(获取的操作)或 set(0bject obj,0bject value)(设置的操作)进行操作。
2. 调用指定的方法(步骤)
- 步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
- 步骤2.setAccessible(true):确保此方法是可访问的
- 步骤3.通过Method实例调用invoke(0bject obj ,0bject ... objs),即为对Method对应的方法的调用。invoke()的返回值即为Method对应的方法的返回值 //特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
3. 调用指定的构造器(步骤)
- 步骤1.通过class的实例调用getDeclaredConstructor(class...args),获取指定参数类型的构造器
- 步骤2.setAccessible(true):确保此构造器是可以访问的
- 步骤3.通过Constructor实例调用newInstance(0bject... objs),返回一个运行时类的实例。
1.2.3部分的代码实现:
package exec2;import org.junit.Test;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ClassTest {@Testpublic void test() throws Exception {//1.调用指定的属性Class clazz = Person.class;Person per = (Person) clazz.newInstance();Field nameField = clazz.getDeclaredField("name");//属性权限打开nameField.setAccessible(true);//通过Field类的实例化调用get(Object obj)(获取的属性)//或 set(Object obj, Object value)(设置的操作)进行操作nameField.set(per,"Alice");System.out.println(nameField.get(per));}@Testpublic void test2(){//2.调用指定的方法try {Class clazz = Person.class;Person per = (Person) clazz.newInstance();Method showMethod = clazz.getDeclaredMethod("show",String.class,int.class);showMethod.setAccessible(true);//通过Method实例调用invoke(Object obj, Object...obj),即为Method对应的方法的调用//invoke()的返回类型为Method对应的方法的返回值Object returnValue = showMethod.invoke(per,"China",18);System.out.println(returnValue);} catch (Exception e) {e.printStackTrace();}}@Testpublic void test3(){//3.调用指定的构造器try {Class clazz = Person.class;Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);constructor.setAccessible(true);Person per = (Person) constructor.newInstance("Tom",12);System.out.println(per);} catch (Exception e) {e.printStackTrace();}}}
4.反射的动态性
@Testpublic void test4(){// Main.javatry {// 动态加载Person类Class personClass = Person.class;// 获取构造方法并创建实例Constructor<?> constructor = personClass.getConstructor(String.class, int.class);Object personInstance = constructor.newInstance("Alice", 30);// 调用introduce方法Method introduceMethod = personClass.getMethod("introduce");introduceMethod.invoke(personInstance);// 访问私有字段namejava.lang.reflect.Field nameField = personClass.getDeclaredField("name");nameField.setAccessible(true); // 允许访问私有字段String nameValue = (String) nameField.get(personInstance);System.out.println("Name from field: " + nameValue);// 修改私有字段namenameField.set(personInstance, "Bob");introduceMethod.invoke(personInstance);} catch (Exception e) {e.printStackTrace();}}//Person类
package exec2;public class Person {private String name;public int age;private String nation;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public Person(String name, int age, String nation) {this.name = name;this.age = age;this.nation = nation;}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 String getNation() {return nation;}public void setNation(String nation) {this.nation = nation;}public static String show(String nation , int age){return "国籍:"+nation+" "+"年龄:"+age;}public void introduce() {System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age + '}';}
}
5.笔记:(重要)
3.案例练习
案例:榨汁机榨水果汁,水果分别有苹果(Apple)、香蕉(Banana)、桔子(0range)等。
提示:
1、声明(Fruit)水果接口,包含榨汁抽象方法:void squeeze();
2、声明榨汁机(Juicer),包含运行方法:public void run(Fruit f),方法体中,调用f的榨汁方法squeeze()
3、声明各种水果类,实现水果接口,并重写squeeze();
4、在src下,建立配置文件:config.properties,并在配置文件中配上fruitName=xxx(其中xx为某种水果的全类名)
5、在FruitTest测试类中,
(1)读取配置文件,获取水果类名,并用反射创建水果对象,
(2)创建榨汁机对象,并调用run()方法
代码实现:
配置的文件信息:
fruitName=reflectionTest1.Apple//可修改成想要使用的其他类的路径
三个类
package reflectionTest1;public class Apple implements Fruit{@Overridepublic void squeeze() {System.out.println("榨一杯苹果汁!");}
}package reflectionTest1;public class Banana implements Fruit{@Overridepublic void squeeze() {System.out.println("弄一碗香蕉泥!");}
}package reflectionTest1;public class Orange implements Fruit{@Overridepublic void squeeze() {System.out.println("榨一杯橙汁!");}
}
Fruit接口
package reflectionTest1;public interface Fruit {//榨汁的方法void squeeze();
}
榨汁机类
package reflectionTest1;public class Juicer {public void run(Fruit f){f.squeeze();}
}
测试方法:
package reflectionTest1;import org.junit.Test;import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.util.Properties;public class FruitTest {@Testpublic void test(){try {//1.读取配置文件中的信息,获取全类名Properties pros = new Properties();File file = new File("src/config.properties");FileInputStream fis = new FileInputStream(file);pros.load(fis);String fruitName = pros.getProperty("fruitName");//2.通过反射,创建指定全类名对应的类的实例Class clazz = Class.forName(fruitName);Constructor con = clazz.getDeclaredConstructor();//权限设置为允许con.setAccessible(true);Fruit fruit = (Fruit) con.newInstance();//3.通过对榨汁机的对象调用run()方法Juicer juicer = new Juicer();juicer.run(fruit);} catch (Exception e) {e.printStackTrace();}}
}
结果:
结果的修改只需要修改配置文件中的调用的类