反射机制

发布时间 2023-09-12 18:44:42作者: 新至所向

第17章_反射机制1

1.反射(Reflection)的概念

1.1反射的出现背景

Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。

   //使用反射完成
    public void test1() throws Exception {
        //1.创建Person类的实例public Person()
        Class<Person> personClass = Person.class;
        Person p1 = personClass.newInstance();
        System.out.println(p1);

        //2.调用属性
        Field field = personClass.getField("age");
        field.set(p1,88);
        System.out.println(field.get(p1));

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

    //出了Person类之后,就不能直接调用Person类中声明的private权限修饰的结构
    // 但是,我们可以通过反射的方式,调用Person类中私有的结构
    public void test2() throws Exception {
        //1.调用私有的构造器,创建Person的实例
        //private Person(String name, int age)
        Class<Person> personClass = Person.class;
        Constructor<Person> cons = personClass.getDeclaredConstructor(String.class, int.class);
        cons.setAccessible(true);
        Person p1 = cons.newInstance("tom", 12);
        System.out.println(p1);

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

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

    }

通过使用反射前后的例子的对比,回答:

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

  • 不使用反射,我们需要考虑封装性。比如:出了Person类之后,就不能调用Person类中私有的结构
  • 使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器。

2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?
场景是什么?

  • 从我们作为程序员开发者的角度来讲,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,我们使用非反射的方式多一些。
  • 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架的时候,会大量的使用反射。意味着,如果大家需要学习框架源码,那么就需要学习反射。
  • 框架=注解+反射+设计模式

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

是的!

4.通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是Java语言设计存在Bug?

  • 不存在bug!
  • 封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用
  • 反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用

1.5反射的优缺点

优点:

  • 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力。
  • 允许程序创建和控制任何类的对象,无需提前硬编码目标类

缺点:

  • 反射的性能较低。

    反射机制主要应用在对灵活性和扩展性要求很高的系统框架上。

  • 反射会模糊程序内部逻辑,可读性较差。


02-Class的理解与类的加载.

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;

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

3.获取Class实例的几种方式(掌握前三种)

  //获取Class实例的几种方式(掌握前三种)
    public void test3() throws ClassNotFoundException {
        //1.调用运行时类的静态属性: class
        Class<Person> personClass = Person.class;
        System.out.println(personClass);//class com.xin.reflection.demo01.Person

        //2.调用运行时类的对象的getclass()
        Person person = new Person();
        Class<? extends Person> personClass1 = person.getClass();

        //3.调用Class的静态方法forName (String className)
        String className="com.xin.reflection.demo01.Person";//全类名
        Class<?> personClass2 = Class.forName(className);

        //4.使用类的加载器的方式〔了解)
        Class<?> personClass3 = ClassLoader.getSystemClassLoader().loadClass("com.xin.reflection.demo01.Person");

        System.out.println(personClass1==personClass);//true
        System.out.println(personClass1==personClass2);//true
        System.out.println(personClass1==personClass3);//true
    }
  1. class的实例都可以指向哪些结构呢?(熟悉)简言之,所有Java类型!
  2. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

  3. interface:接口

  4. []:数组

  5. enum:枚举

  6. annotation:注解@interface

  7. primitive type:基本数据类型

  8. void


5.类的加载过程(了解)

过程1:类的装载(loading)

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

过程2链接(linking)

  • 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题
  • 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程3:初始化(initialization)

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

5.关于类的加载器(了解、JDK8版本为例)

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

5.2分类(分为两种):

  • BootstrapclassLoader:引导类加载器、启动类加载器

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

    • ExtensionClassLoader:扩展类加载器

    • SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器

      我们自定义的类,默认使用的类的加载器。

    • 用户自定义类的加载器

      实现应用的隔离(同一个类在一个应用程序中可以加载多份)

//用户自定义的类使用的是系统类加载器加载的。
Class clazz1 = User.class;
classLoader classLoader = clazz1.getClassLoader();
system.out.println(classLoader);

//对于Java的核心api使用引导类加载器加载
class clazz2 = class.forName("java.lang.string");
classLoader classLoader1 = clazz2.getclassLoader();
system.out.println(classLoader1) ; l/null

    //需求:通过ClassLoader加载指定的配置文件
    public void test4() throws IOException {
        Properties pro = new Properties();
        ///通过类的加载器读取的文件的默认的路径为:当前module下的src
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties");

        pro.load(is);
        String name = pro.getProperty("name");
        String password = pro.getProperty("password");
        System.out.println(name+":"+password);
    }

    //Properties:处理属性文件
    public void test5() throws IOException {
        Properties pro = new Properties();
        //读取的文件的默认路径为:当前的module
        FileInputStream is = new FileInputStream(new File("info1.properties"));
        pro.load(is);
        String name = pro.getProperty("name");
        String password = pro.getProperty("password");
        System.out.println(name+":"+password);
    }

03-反射的应用

1.(掌握)反射的应用1:创建运行时类的对象

1.1如何实现?
通过Class的实例调用newInstance()方法即可。

1.2要想创建对象成功,需要满足:

  • 条件1。要求运行时类中必须提供一个空参的构造器
  • 条件2:要求提供的空参的构造器的权限要足够。

1.3回忆:JavaBean中要求给当前类提供一个公共的空参的构造器。有什么用?

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

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

通过Constructor类调用newInstance( .. .)

2反射应用2:获取运行时类的内部结构

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

2.2(熟悉)获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等

1/5.获取运行时类的父类的泛型〔难)aTest
public void test5() throws ClassNotFoundException {
class clazz = Class.forName( " com.atguigu03.reflectapply.data .Person");
   //获取带泛型的父类(Type是一个接口,Class实现了此接口
Type superclass = clazz.getGenericSuperclass();/如果父类是带泛型的,则可以强转为ParameterizedType
ParameterizedType paramType = (ParameterizedType) superclass;
//调用getActualTypeArguments()获取泛型的参数,结果是一个数组,因为可能有多个泛型参数。
                                                                                              Type[] arguments = paramType.getActualTypeArguments();
//获取泛型参数的名称
system.out.println(((class)arguments[0]).getName());
}

平时写的代码:

类型1:业务逻辑代码(多关注)

类型2:算法逻辑代码(多积累)


3.(掌握)反射的应用3:调用指定的结构:指定的属性、方法、构造器

3.1 调用指定的属性(步骤)
public void test2(throws Exception {
Class clazz = Person.class;
//得到类实例
Person per = (Person) clazz.newInstance();
//1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
    Field nameField = clazz.getDeclaredField( name: "name" );
//2. setAccessible(true):确保此属性是可以访问的
nameField.setAccessible(true);
//3.通过Filed类的实例调用get(0bject obj)(获取的操作)
    //或set(0bject obj,0bject value)(设置的操作)进行操作。
    nameField.set(per , "Tom" );
system.out.println(nameField.get(per));

静态,不用调对象

public void test3() throws Exception {
Class clazz = Person.class;
//1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
    Field infoField = clazz.getDeclaredField( name: "info" );
//2. setAccessible(true):确保此属性是可以访问的
infoField.setAccessible(true);
//3.通过Filed类的实例调用get(0bject obj)(获取的操作)
    //或set(0bject obj,0bject value)(设置的操作)进行操作。
    infoField.set(Person.class,"我是一个人");
system.out.println(infoField.get(Person.class) );}

3.1 调用指定的方法(步骤)

public void test4() throws Exception {
Class clazz = Person.class;
Person per = (Person) clazz.newInstance();
//1.通过Class的实例调用getDeclaredWlethod(String methodName ,Class ... args),获取指定的方法
Wethod showNationMethod = clazz.getDeclaredRethod( name: "showNation" , String.class,int.class);
//2. setAccessible(true):确保此方法是可访问的
showNationMethod.setAccessible(true);
//3.通过Method实例调用invoke(0Object obj,0bject ... objs),即为对Wethod对应的方法的调用。
    //invoke()的返回值即为Nethod对应的方法的返回值
    //特别的:如果Nethod对应的方法的返回值类型为void,则invoke()返回值为null

0bject returnValue = showNationAethod.invoke(per,...args: "CHN",10);system.out.println(returnValue);

静态的

public void test5() throws Exception {
class clazz = Person.class;
//1.通过Class的实例调用getDeclaredWethod(String methodName ,Class ... args),获取指定的方法
    Method showInfoMethod = clazz.getDeclaredHethod( name: "showInfo");
//2. setAccessible(true):确保此方法是可访问的
showInfoMethod.setAccessible(true);
//3.通过Nethod实例调用invoke(0bject obj,0bject ... objs),即为对Method对应的方法的调用。l/invoke()的返回值即为Method对应的方法的返回值
//特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
    0bject returnValue = showInfoMethod.invoke( obj: null);
System.out.println(returnValue);
}

3.3 调用指定的构造器

public void test6() throws Exception {
Class clazz = Person.class;
//1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
    Constructor constructor = clazz.getDeclaredConstructor(String.class,
int.class) ;
//2.setAccessible(trve):确保此构造器是可以访问的
constructor.setAccessible(true);
//3.通过Constructor实例调用newInstance(0bject ... objs),返回一个运行时类的实例
    Person per = (Person) constructor.newInstance(  "Tom",12);
system.out.println(per);


4.(了解)反射的应用4:通过反射获取注解的信息(见com.atguigu04.other.annotation包的测试)
复习:自定义注解
参照@SuppressWarnings进行创建即可。
注解要想通过反射的方式获取,必须声明元注解:@Retention(RetentionPolicy.RUNTIME)

05-体会反射的动态性

//体会:静态性
public Person getInstance(){
return new Person();
}
//体会:反射的动态性
public <T> T getInstance(String className) throws Exception {
Class clazz = Class.forName(className );
Constructor con = clazz.getDeclaredConstructor();con.setAccessible(true);
return (T) con.newInstance();
}

    //体会:反射的动态性
    public Object invoke(String className ,String methodName) throws Exception{
        //1.创建全类名对应的运行时类的对象
        Class<?> clazz = Class.forName(className);
        Constructor<?> con = clazz.getDeclaredConstructor();
        con.setAccessible(true);
        Object o = con.newInstance();

        //2.获取运行时类中指定的方法,并调用
        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        return method.invoke(o);
    }

    public void test1() throws Exception {
    String className="com.xin.reflection.demo01.Person";//用. 不用/
    String methodName="show";
        Object returnVable = invoke(className, methodName);
        System.out.println(returnVable);
    }
案例:榨汁机榨水果汁,水果分别有苹果(Apple)、香蕉(Banana)、桔子(Orange)等。
public class Juicer {
    public void run(Fruit f){
        f.squeeze();
    }
}
====
  public interface Fruit {
    //榨汁
    public void squeeze();
}
===
   public class Apple implements Fruit{

    @Override
    public void squeeze() {
        System.out.println("榨一杯苹果汁");
    }
}
===
    public class Banana implements Fruit{
    public void squeeze() {
        System.out.println("榨一杯香蕉汁");
    }
}
===
    public class Orange implements Fruit{

    public void squeeze() {
        System.out.println("榨一杯橘子汁");
    }
}

====
    fruitName=com.xin.reflection.demo02.Apple
    ====
        public void test1() throws Exception {
        //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);
    }