IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java反射机制 -> 正文阅读

[Java知识库]Java反射机制

反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。

简而言之,我们可以通过反射机制,获取到类的一些属性,包括类里面有哪些字段,有哪些方法,继承自哪个类,甚至还能获取到泛型!它的权限非常高,慎重使用!

Java类加载机制

img

在Java程序启动时,JVM会将一部分类(class文件)先加载(并不是所有的类都会在一开始加载),通过ClassLoader将类加载,在加载过程中,会将类的信息提取出来(存放在元空间中,JDK1.8之前存放在永久代),同时也会生成一个Class对象存放在内存(堆内存),注意此Class对象只会存在一个,与加载的类唯一对应!

**思考:**既然说和与加载的类唯一对应,那如果我们手动创建一个与JDK包名一样,同时类名也保持一致,那么JVM会加载这个类吗?
image-20220224115455125

明显,ClassLoader加载的事 自带的String类,这是因为ClassLoader的双亲委派机制在保护jav程序的正常运作

img

实际上我们的类最开始是由BootstarpClassLoader进行加载,BootstarpClassLoader用于加载JDK提供的类,而我们自己编写的类实际上是AppClassLoader,只有BootstarpClassLoader都没有加载的类,才会让AppClassLoader来加载,因此我们自己编写的同名包同名类不会被加载,而实际要去启动的是真正的String类,也就自然找不到main方法了!

import java.util.*;
public class Main {
    public static void main(String[] args) {
        System.out.println(Main.class.getClassLoader());   //查看当前类的类加载器
        System.out.println(Main.class.getClassLoader().getParent());  //父加载器
        System.out.println(Main.class.getClassLoader().getParent().getParent());  //爷爷加载器
        System.out.println(String.class.getClassLoader());   //String类的加载器


        /*
        sun.misc.Launcher$AppClassLoader@18b4aac2    自己创建的类通过AppClassLoader加载
        sun.misc.Launcher$ExtClassLoader@1b6d3586    父类是ExtClassLoader加载
        null                                   因为BootStrapClassLoader是C++写的,加载不出来
        null                                java自带的各个类如String   List   Map  都为null,因为是jdk自带的
        */
    }
}


Class对象

通过前面,我们了解了类的加载,同时会提取一个类的信息生成Class对象存放在内存中,而反射机制其实就是利用这些存放的类信息,来获取类的信息和操作类。那么如何获取到每个类对应的Class对象呢,我们可以通过以下方式:

import java.util.*;
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<String> stringClass = String.class;   //使用class关键字
        Class<?> aClass = Class.forName("java.lang.Thread");  //使用Class类的静态forName方法  通过包名.类名  进行获取
        Class<?> cpdd = new String("Cpdd").getClass();  //通过实例对象的getCLass()方法获得
    }
}

注意Class类也是一个泛型类,只有第一种方法,能够直接获取到对应类型的Class对象,而以下两种方法使用了?通配符作为返回值,但是实际上都和第一个返回的是同一个对象:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<String> stringClass = String.class;   //使用class关键字
        Class<?> cpdd = new String("Cpdd").getClass();  //通过实例对象的getCLass()方法获得
        Class<?> aClass = Class.forName("java.lang.Thread");  //使用Class类的静态forName方法  通过包名.类名  进行获取
        Class<?> bClass = new Thread().getClass();
        final Class<Thread> cClass = Thread.class;
        System.out.println(aClass==bClass);  //true
        System.out.println(aClass.equals(bClass));   //true
        System.out.println(aClass.equals(cClass));   //true

        
        System.out.println(int.class);    //int
        System.out.println(Integer.TYPE==int.class);  //true
        System.out.println(double.class);    //double
        System.out.println(Double.TYPE==double.class);  //true
    }
}

迷了,不是每个类才有Class对象吗,基本数据类型又不是类,这也行吗?实际上,基本数据类型也有对应的Class对象(反射操作可能需要用到),而且我们不仅可以通过class关键字获取,其实本质上是定义在对应的包装类中的:

image-20220224123334245

image-20220224123532592

每个包装类中(包括Void),都有一个获取原始类型Class方法,注意,getPrimitiveClass获取的是原始类型,并不是包装类型,只是可以使用包装类来表示。

通过对比,我们发现实际上包装类型都有一个TYPE,其实也就是基本类型的Class,那么包装类的Class和基本类的Class一样吗?不同

public class Main {
    public static void main(String[] args)  {
        System.out.println(Integer.TYPE==Integer.class);  //false
        System.out.println(Integer.TYPE);  //int
        System.out.println(Integer.class);  //class java.lang.Integer
    }
}

我们发现,包装类型的Class对象并不是基本类型Class对象。数组类型也是一种类型,只是编程不可见,因此我们可以直接获取数组的Class对象:

import java.util.*;

public class Main {
    public static void main(String[] args)  {
        Class<Map[]> aClass = Map[].class;

        System.out.println(aClass.getName());  //类名称,
        System.out.println(aClass.getSimpleName());
        System.out.println(aClass.getTypeName());
        System.out.println(aClass.getClassLoader()); //类加载器
        //exception  Cannot cast java.lang.Double to [Ljava.util.Map;
        System.out.println(aClass.cast(new Double(521.1314)));
    }
}

image-20220224125222987

再谈instanceof

回顾

instanceof 关键字用来对比左边的对象是否属于右边的对象

  • instanceof 的左右两边必须是引用类型,java 八大基本数据类型无法使用 instanceof 来进行对比
  • instanceof 用来判定左边的引用类型是否与右边的引用类型的类型是否相同,或左边引用类型是右边引用类型的子类或实现类(右边引用类型可以是类、抽象类、接口)
  • instanceof 的对比结果为 boolean 类型,如果左右两边比对成功,返回 true ;否则返回 flase
  • null 与任何引用类型进行 instanceof 对比的结果都是 flase,null 不属于任何类型,更不属于 object 基类的派生类(子类),需要特别注意

进行类型比较:

public class Main {
    public static void main(String[] args) {
        String str = "";
        System.out.println(str instanceof String);   //通过instanceof判断   true
        System.out.println(str.getClass()==String.class);   //通过类Class判断   true
    }
}

如果需要判断是否为子类或是接口/抽象类的实现,我们可以使用asSubClass()方法:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Integer i = 10;
        //a为实例化对象  B为类或者接口   a.getClass.asSubclass(B.class)
        // 如果a是b的子类或实现类,那么就正常运行,不然会报错  返回的是a的class
        System.out.println(i.getClass().asSubclass(Number.class));


        ArrayList<String> list = new ArrayList<>();
        LinkedList<String> linkedList = new LinkedList<>();
        System.out.println(list instanceof Collection);
        System.out.println(linkedList.getClass().asSubclass(Deque.class));
    }
}	

image-20220224135623309

不匹配,报错

image-20220224135812202

获取父类Class

通过Class对象的getSuperclass方法可以获得该class的父类

import java.lang.reflect.Type;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Integer i = 10;
        System.out.println(i.getClass().getSuperclass());   //获取到父类的Class对象
        //System.out.println(i.getClass().getSuperclass().getTypeName());  //java.lang.Number   获取此类的类型名称  包名.类名
        System.out.println(i.getClass().getGenericSuperclass());   //获得父类的原始类型的Type

        System.out.println("================================================");

        List<String> list = new ArrayList<>();
        for (Class<?> anInterface : list.getClass().getInterfaces()) {  //获得list的父类接口
            System.out.println(anInterface);
        }
        System.out.println("================================================");
        for (Type genericInterface : list.getClass().getGenericInterfaces()) {
            System.out.println(genericInterface);    //用于返回一个数组,该数组表示由类实现的接口或由此对象表示的接口
            //System.out.println(genericInterface.getTypeName());  //String 获取此类的类型名称
        }
    }
}

image-20220224142341474

反射创建类对象

既然我们拿到了类的定义,那么是否可以通过Class对象来创建对象、调用方法、修改变量呢?当然是可以的,那我们首先来探讨一下如何创建一个类的对象:

public class Main {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {

        Class<Student> studentClass = Student.class;
        Student student = studentClass.newInstance();  //通过反射  Class类的newInstance()方法实例化对象
        //并且通过反射newInstance()方法只能够通过无参构造来创建对象不然会报错 InstantiationException
        //构造函数不能为private  不然会报错 IllegalAccessException
        System.out.println(student.setName("lzw").setAge(22));
        student.test();
    }

    static class Student {
        private String name;
        private Integer age;

        public void test() {
            System.out.println("Study!");
        }

        Student() {
        }   //提供给反射创建对象

        Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public Student setName(String name) {
            this.name = name;
            return this;
        }

        public Integer getAge() {
            return age;
        }

        public Student setAge(Integer age) {
            this.age = age;
            return this;
        }

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

image-20220224143303192

通过class对象获得构造器来创建对象 getConstructor()

import java.lang.reflect.Constructor;

public class Main {

   public static void main(String[] args) throws ReflectiveOperationException {   //反射异常

       Class<Student> sc = Student.class;

       Student lzw = sc
               .getConstructor(String.class, Integer.class)//可以选择获得对应参数列表的构造器 ,,选择的构造器必须为public 才能直接访问
               .newInstance("lzw", 22);//在通过构造器的newInstance来实例化对象
       System.out.println(lzw);
   }

   static class Student {
       private String name;
       private Integer age;

       public void test() {
           System.out.println("Study!");
       }

       public Student() {
       }   //提供给反射创建对象

       public Student(String name, Integer age) {
           this.name = name;
           this.age = age;
       }

       public Student(String name) {
           this.name = name;
       }

       public Student(Integer age) {
           this.age = age;
       }

       public String getName() {
           return name;
       }

       public Student setName(String name) {
           this.name = name;
           return this;
       }

       public Integer getAge() {
           return age;
       }

       public Student setAge(Integer age) {
           this.age = age;
           return this;
       }

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

image-20220224145910443

通过getConstructors()获得所有的构造器

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {   //反射异常
        Class<Student> sc = Student.class;
        for (Constructor<?> constructor : sc.getConstructors()) System.out.println(constructor);

        System.out.println("=================================================");
        Student lzw = (Student) sc.getConstructors()[1].newInstance("lzw", 22);
        System.out.println(lzw);
    }
    
    static class Student {.....}  //同上
}

image-20220224145824573

如果选择的构造器访问权限为不是public呢? 会爆错 NoSuchMethodException

        Student() {
        }   //提供给反射创建对象

        Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

image-20220224150514948

这个时候通过class对象的 getDeclearedConstructor() 来获得已经声明的所有构造方法, 不管是private protected default public

Student lzw = sc.getDeclaredConstructor(String.class, Integer.class)

这个时候,只要不是private 那么都可以成功通过构造函数来实例化对象

但是如果是private 会报错 IllegalAccessException 没有访问权限

        private Student() {
        }   //提供给反射创建对象

        private Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

image-20220224150901183

这是可以通过设置 Constructor.setAccessible();

        Class<Student> sc = Student.class;
        Constructor<Student> constructor = sc.getDeclaredConstructor(String.class, Integer.class); //获取所有已经声明的构造方法
        constructor.setAccessible(true);  //设置构造器的访问权限为true
        Student hyl = constructor.newInstance("hyl", 21);   //这样就可以通过private的构造器实例化对象了
        System.out.println(hyl);   //Student{name='hyl', age=21}

从这个例子我们可以看出,使用**getDeclaredConstructor()**方法可以找到类中的非public构造方法,但仅仅是得到,只有其中的public protect 和 default修饰的方法才可以使用,想要使用private修饰的方法,我们需要先修改访问权限 setAccessible(),在修改访问权限之后,就可以使用非public方法了(这意味着,反射可以无视权限修饰符访问类的内容),可以看出反射是十分强大的

调用类的方法

我们可以通过反射来调用类的方法(本质上还是类的实例进行调用)只是利用反射机制实现了方法的调用,

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> dog = Class.forName("Dog");    //通过包名.类名  加载Dog
        Object o = dog.newInstance();
        Method t1 = dog.getMethod("t1");  //通过普通的get  只能get到public的方法
        Method t3 = dog.getDeclaredMethod("t3");  //getDeclaredMethod  获取所有声明的方法
        Method t4 = dog.getDeclaredMethod("t4",String.class);//第一个参数为方法名,第二个参数为方法参数的class对象
        t1.invoke(o);
        t3.invoke(o);
        t4.setAccessible(true);   //没有这个会报错   IllegalAccessException
        t4.invoke(o,"奥利给");
    }
}
public class Dog {
    private String name;

    Dog() {
    }

    Dog(String name) {
        this.name = name;
    }

    public void t1() {
        System.out.println("Dog.t1");
    }

    void t2() {
        System.out.println("Dog.t2");
    }

    protected void t3() {
        System.out.println("Dog.t3");
    }

    private void t4(String msg) {
        System.out.println("Dog.t4 "+msg);
    }
}

image-20220224153517912

通过调用getMethod()方法,我们可以获取到类中所有声明为public的方法,得到一个Method对象,我们可以通过Method对象的invoke()方法(返回值就是方法的返回值,因为这里是void,返回值为null)来调用已经获取到的方法,注意传参。

我们发现,利用反射之后,在一个对象从构造到方法调用,没有任何一处需要引用到对象的实际类型,我们也没有导入Student类,整个过程都是反射在代替进行操作,使得整个过程被模糊了,过多的使用反射,会极大地降低后期维护性。

同构造方法一样,当出现非public方法时,我们可以通过反射来无视权限修饰符,获取非public方法并调用,

Method和Constructor都和Class一样,他们存储了方法的信息,包括方法的形式参数列表,返回值,方法的名称等内容,我们可以直接通过Method对象来获取这些信息:

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> dog = Class.forName("Dog");    //通过包名.类名  加载Dog
        Object o = dog.newInstance();
        Method t1 = dog.getMethod("t1");
        Method t2 = dog.getDeclaredMethod("t2");
        Method t3 = dog.getDeclaredMethod("t3");
        Method t4 = dog.getDeclaredMethod("t4", String.class);
        System.out.println(t1.getName());    //获得方法名
        System.out.println(t2.getDeclaringClass());//获得所属类的Class对象
        System.out.println(t3.getReturnType());  //获得返回类型
        for (Class<?> parameterType : t4.getParameterTypes()) {    //获得方法的参数类型列表
            System.out.println(parameterType);
        }
    }
    /*Result
    t1
    class Dog
    void
    class java.lang.String
    */
}

当方法的参数为可变参数时,我们该如何获取方法呢?实际上,我们在之前就已经提到过,可变参数实际上就是一个数组,因此我们可以直接使用数组的class对象表示:

public class Dog {
    private String name;
    Dog() {}
    Dog(String name) { this.name = name;}
    private void t2(String... strings) {
        for (String string : strings) System.out.println(string);
    }
}
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> dog = Class.forName("Dog");    //通过包名.类名  加载Dog
        Object o = dog.newInstance();
        Method t2 = dog.getDeclaredMethod("t2", String[].class);
        t2.setAccessible(true);
        t2.invoke(o, (Object) new String[]{"a", "b", "c", "d"});   //必须强转(Object)
    }
}

image-20220224160120853

反射非常强大,尤其是我们提到的越权访问,但是请一定谨慎使用,别人将某个方法设置为private一定有他的理由,如果实在是需要使用别人定义为private的方法,就必须确保这样做是安全的,在没有了解别人代码的整个过程就强行越权访问,可能会出现无法预知的错误。

修改类的属性

我们还可以通过反射访问一个类中定义的成员字段也可以修改一个类的对象中的成员字段值,通过getField()方法来获取一个类定义的指定字段:

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> dog = Class.forName("Dog");    //通过包名.类名  加载Dog
        Object o = dog.newInstance();
        //因为name属性设置为了private   所有需要Declared  和  setAccessible
        Field name = dog.getDeclaredField("name");   //获得一个属性
        name.setAccessible(true);
        name.set(o, "奥利给");
        System.out.println(o);   //Dog{name='奥利给'}
        //(Dog) o.test();   //不行
        Dog o1 = (Dog) o;
        o1.test();
    }
}

现在我们已经知道,反射几乎可以把一个类的老底都给扒出来,任何属性,任何内容,都可以被反射修改,无论权限修饰符是什么,那么,如果我的字段被标记为final呢?现在在字段i前面添加final关键字,我们再来看看效果:

通过对象修改

  • 当final修饰的成员变量在定义的时候初始化值,反射就不能动态修改它的值了。

当常量是这样定义时:

image-20220224171512014

通过反射无法修改count的值

image-20220224171653897

  • 当final修饰的成员变量在定义的时候没有初始化值,就还能通过反射来动态修改它的值。

当常量是这样定义时(在构造函数里初始化):

image-20220224171757474

可以通过反射来修改private

image-20220224171827900

我们可以发现,反射非常暴力,就连被定义为final字段的值都能强行修改,几乎能够无视一切阻拦。我们来试试看修改一些其他的类型:

import java.util.*;
import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws ReflectiveOperationException {
        List<String> i = new ArrayList<>();
        Field field = ArrayList.class.getDeclaredField("size");
        field.setAccessible(true);
        field.set(i, 10);
        i.add("测试");   //只添加一个元素
        System.out.println(i.size());  //大小直接变成11
        i.remove(10);   
    }
}

实际上,整个ArrayList体系由于我们的反射操作,导致被破坏,因此它已经无法正常工作了!

再次强调,在进行反射操作时,必须注意是否安全,虽然拥有了创世主的能力,但是我们不能滥用,我们只能把它当做一个不得已才去使用的工具!

跟着B站Up主 青空の霞光 学习javaSE,感谢up主,

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:16:56  更:2022-02-26 11:21:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:58:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码