Java学习(十九)--反射

发布时间:2024年01月16日

介绍

1、动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

2、静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!


放射机制

Java 语言的反射机制:

  • 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;
  • 指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;即动态获取信息以及动态调用对象方法的功能。

反射的使用步骤:

  • ? ? ? ? ? ? ? ? (1) ?导入java.lang.reflect包中的相关类
  • ? ? ? ? ? ? ? ? (2) ?获得需要操作的类的Class实例
  • ? ? ? ? ? ? ? ? (3) ?调用Class的实例的方法获取Field,Method等实例
  • ? ? ? ? ? ? ? ? (4) ?使用反射API操作实例成员

反射相关的主要API

  • ?? ?java.lang.Class:代表一个类;?? ??? ?Class对象表示某个类加载后在堆中的Class对象;
  • ?? ?java.lang.reflect.Method:代表类的方法;?? ??? ?Method对象表示某个类的方法
  • ?? ?java.lang.reflect.Field:代表类的成员变量;?? ??? ?Field对象表示某个类的成员变量
  • ?? ?java.lang.reflect.Constructor:代表类的构造器;?? ??? ?Constructor对象表示构造器

实现功能

  • ?? ?在运行时判断任意一个对象所属的类
  • ?? ?在运行时构造任意一个类的对象
  • ?? ?在运行时判断任意一个类所具有的成员变量和方法
  • ?? ?在运行时调用任意一个对象的成员变量和方法
  • ?? ?在运行时获取泛型信息
  • ?? ?在运行时处理注解
  • ?? ?生成动态代理
  • ?? ?.......

优点:

  • ?? ??? ?能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性,降低耦合性,提高自适应能力。
  • ?? ??? ?它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • ? ? ? ? (1) 性能问题。Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题;
  • ? ? ? ?(2) 安全限制。使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
  • ? ? ? (3) 程序健壮性。反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。

Class类

介绍

  • 本身也是一个类,则继承Object类;
  • 一种特殊的类,将我们定义普通类的共同的部分进行抽象,保存类的属性,方法,构造方法,类名、包名、父类,注解等和类相关的信息。
  • 每个java类运行时都在JVM里表现为一个且唯一的Class对象,可通过类名.class类型.getClass()Class.forName("类名")等方法获取Class对象;
  • Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。
  •     private Class(ClassLoader loader) {
            classLoader = loader;
        }
  • Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法;
  • 数组同样也被映射为为Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
  • 基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 Class 对象

获取Class实例(.class字节码文件)的方法

说明:

所有 Java 类(包括Class类)均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。

包装类源码:
 public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

常用方法

  • toString()

    public String toString() {     return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))        + getName();}

    toString()方法能够将对象转换为字符串,toString()首先判断Class类型是否是接口类型,也就是说普通类和接口都能用Class对象表示,然后在判断是否是基本数据类型,这里判断的都是基本数据类型和包装类,还有void类型。

  • getName()

    获取类的全限定名称。(包括包名)即类的完整名称。

    • 如果是引用类型。比如 String.class.getName()→java.lang.String
    • 如果是基本数据类型。比如 byte.class.getName()→byte
    • 如果是数组类型。比如 new Object[3].getClass().getName()→[Ljava.lang.Object;
  • getSimpleName()

    获取类名(不包括包名)。

  • getCanonicalName()

    获取全限定的类名(包括包名)。

  • toGenericString()

    返回类的全限定名称,而且包括类的修饰符和类型参数信息。

  • forName()

    根据类名获得一个Class对象的引用,这个方法会使类对象进行初始化。

    例如:Class t = Class.forName("java.lang.Thread")就能够初始化一个Thread线程对象。

    在Java中,一共有三种获取类实例的方式:

    • Class.forName(java.lang.Thread)
    • Thread.class
    • thread.getClass()
  • newInstance()
    创建一个类的实例,代表着这个类的对象。上面forName()方法对类进行初始化,newInstance方法对类进行实例化。使用该方法创建的类,必须带有无参的构造器。

  • getClassLoader()

    获取类加载器对象。

  • getInterfaces()

    获取当前类实现的类或是接口,可能是多个,所以返回的是Class数组。

  • isInterface()

    判断Class对象是否是表示一个接口。

  • getFields()

    获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethodsgetConstructors

  • getDeclaredFields

    获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethodsgetDeclaredConstructors


Class实例对应的结构


import java.io.Serializable;

//哪些类型有Class对象
public class AllTypeClass {
    public static void main(String[] args) {

        Class<String> cls1 = String.class;//外部类

        Class<Serializable> cls2 = Serializable.class;//接口

        Class<Integer[]> cls3 = Integer[].class;//数组

        Class<float[][]> cls4 = float[][].class;//二维数组

        Class<Deprecated> cls5 = Deprecated.class;//注解

        //枚举
        Class<Thread.State> cls6 = Thread.State.class;

        Class<Long> cls7 = int.class;//基本数据类型

        Class<Void> cls8 = void.class;//void数据类型

        Class<Class> cls9 = Class.class;//

		System.out.println(cls1); //class java.lang.String
		System.out.println(cls2); //interface java.io.Serializable
		System.out.println(cls3); //class [Ljava.lang.Integer;
		System.out.println(cls4); //class [[F
		System.out.println(cls5); //interface java.lang.Deprecated
		System.out.println(cls6); //class java.lang.Thread$State
		System.out.println(cls7); //int
		System.out.println(cls8); //void
		System.out.println(cls9); //class java.lang.Class
    }
}

反射其他API

Constructor类

Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法;

获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

关于Constructor类本身一些常用方法如下(仅部分,其他可查API):

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//通过反射机制创建实例
public class ReflecCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        //1. 先获取到User类的Class对象
        Class<?> userClass = Class.forName("com.hspedu.reflection.User");

        //2. 通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);

        //3. 通过public的有参构造器创建实例
        /*
            constructor 对象就是
            public User(String name) {//public的有参构造器
                this.name = name;
            }
         */
        //3.1 先得到对应构造器
        Constructor<?> constructor = userClass.getConstructor(String.class);

        //3.2 创建实例,并传入实参
        Object hsp = constructor.newInstance("hsp");
        System.out.println("hsp=" + hsp);

        //4. 通过非public的有参构造器创建实例
        //4.1 得到private的构造器对象
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
        //4.2 创建实例
        //暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性
        constructor1.setAccessible(true);

        Object user2 = constructor1.newInstance(100, "张三丰");
        System.out.println("user2=" + user2);
    }
}


class User { //User类
    private int age = 10;
    private String name = "java教育";
    public User() {//无参 public
    }
    public User(String name) {//public的有参构造器
        this.name = name;
    }

    private User(int age, String name) {//private 有参构造器
        this.age = age;
        this.name = name;
    }
    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

Field类

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。

同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:

关于Field类本身一些常用方法如下(仅部分,其他可查API):

特别注意:被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

import java.lang.reflect.Field;

//反射操作属性
public class ReflecAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

        //1. 得到Student类对应的 Class对象
        Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");

        //2. 创建对象
        Object o = stuClass.newInstance();//o 的运行类型就是Student
        System.out.println(o.getClass());//Student

        //3. 使用反射得到age 属性对象
        Field age = stuClass.getField("age");

        age.set(o, 88);//通过反射来操作属性

        System.out.println(o);//
        System.out.println(age.get(o));//返回age属性的值

        //4. 使用反射操作name 属性
        Field name = stuClass.getDeclaredField("name");

        //对name 进行暴破, 可以操作private 属性
        name.setAccessible(true);

        //name.set(o, "jack");
        name.set(null, "jack");//因为name是static属性,因此 o 也可以写出null

        System.out.println(o);
        System.out.println(name.get(o)); //获取属性值
        System.out.println(name.get(null));//获取属性值, 要求name是static


        //5.getDeclaredFields:获取本类中所有属性
//规定 说明: 默认修饰符 是0 , public  是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
        Field[] declaredFields = stuClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("本类中所有属性=" + declaredField.getName()
                    + " 该属性的修饰符值=" + declaredField.getModifiers()
                    + " 该属性的类型=" + declaredField.getType());
        }
    }
}

class Student {//类
    public int age;
    private static String name;
    public Student() {//构造器
    }

    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}


Method类

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。

下面是Class类获取Method对象相关的方法:

关于Method类本身一些常用方法如下(仅部分,其他可查API):

Method类的invoke(Object obj,Object… args)?方法:第一个参数代表调用的对象,第二个参数传递的调用方法的参数,从而实现类方法的动态调用。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//通过反射调用方法

public class ReflecAccessMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

        //1. 得到Boss类对应的Class对象
        Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");

        //2. 创建对象
        Object o = bossCls.newInstance();

        //3. 调用public的hi方法
        //Method hi = bossCls.getMethod("hi", String.class);//OK

        //3.1 得到hi方法对象
//获取Class对象中的hi方法
        Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK

        //3.2 调用方法的invoke方法,这里的方法表示hi方法,相当于动态调用bossCls对象的hi方法并传入“韩顺平教育”参数;

        hi.invoke(o, "韩顺平教育~");

        //4. 调用private static 方法
        //4.1 得到 say 方法对象
        Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);

        //4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样

        say.setAccessible(true);

        System.out.println(say.invoke(o, 100, "张三", '男'));
        //4.3 因为say方法是static的,还可以这样调用 ,可以传入null

        System.out.println(say.invoke(null, 200, "李四", '女'));

        //5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
        Object reVal = say.invoke(null, 300, "王五", '男');
        System.out.println("reVal 的运行类型=" + reVal.getClass());//String


        //在演示一个返回的案例
        Method m1 = bossCls.getDeclaredMethod("m1");
        Object reVal2 = m1.invoke(o);
        System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster
    }
}


class Monster {}

class Boss {//类
    public int age;
    private static String name;

    public Boss() {//构造器
    }

    public Monster m1() {
        return new Monster();
    }

    private static String say(int n, String s, char c) {//静态方法
        return n + " " + s + " " + c;
    }

    public void hi(String s) {//普通public方法
        System.out.println("hi " + s);
    }
}


其他

类加载的时机

虚拟机的类加载机制(类加载的全过程)

在java语言中,类的加载、连接和初始化过程都是在程序运行期间完成。

  • 由类加载器将.class文件(源于磁盘文件、网络、数据库、内存或动态产生等)中的二进制字节流读入到JVM中。其中类加载器由JVM提供,开发者也可以通过继承 classLoader基类来创建自己的类加载器。通过使用不同的类加载器可以从不同来源加载类的二进制数据。具体完成一下3项工作:

    • 通过一个类的全限定名获取定义该类的二进制字节流;

    • 将字节流所代表的静态存储结构转化为方法区的运行时数据结构

    • 内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。

  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

    • 验证:确保加载的类信息符合JVM规范;具体校验为 文件格式验证;元数据验证(是否符合Java语言规范);字节码验证(确定程序语义合法,符合逻辑);符号引用验证(确保下一步的解析能正常执行)。例如:是否以0xCAFEBABE魔数开头等。

    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段。JDK 8及其以后类变量随着Class对象一起存在java堆中。此时初始值“通常情况”下是数据类型的零值。

    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

  • 初始化:直到初始化阶段,java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。

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

    • 虚拟机保证子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕(接口除外);【<clinit>()方法对于类或接口来说并非必需的】

    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。


ClassLoader

classLoader和forName加载类的区别

ClassLoader.loadClass(String name) 只会找对应的 class 字节码并加载(Loading)到JVM中,不会干其他的事,例如链接(Linking)、初始化(Initialization)等都不会再进一步处理。

Class.forName(String className) 不仅会找对应的 class 字节码并加载(Loading)到JVM中,还会进行链接(Linking)、初始化(Initialization),执行类的静态代码块和静态变量的初始化。


1)getName加载类
源码:
        @CallerSensitive
        public static Class<?> forName(String className)
                throws ClassNotFoundException {
            Class<?> caller = Reflection.getCallerClass();
            return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
        }

(1)className:表示我们要加载的类名
(2)true:指Class被加载后是不是必须被初始化。 不初始化就是不执行static的代码即静态代码,在这里默认为true,也就是默认实现类的初始化。
(3)ClassLoader.getClassLoader(caller):表示类加载器;forNanme底层是使用ClassLoader类加载器加载。
(4)caller:指定类加载器。


2)classLoader加载类
流程是先判断class是否已经被加载,如果被加载了那就重新加载,如果没有加载那就使用双亲委派原则加载。加载的时候并没有指定是否要进行初始化;

源码:
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }

                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
文章来源:https://blog.csdn.net/hahaha2221/article/details/135534568
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。