Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

时间:2021-06-16 00:41:42

(目录)


Class类

基本介绍

Class类图:

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例生成
  5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
  6. Class对象存放在堆内存
  7. 类的字节码二进制数据存放在方法区中,有的地方称为类的元数据(包括:方法,变量名,方法名,访问权限等)

哪些类型有Class对象

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. 接口
  3. 数组
  4. 枚举
  5. 注解
  6. 基本数据类型
  7. void
import java.io.Serializable;
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 = long.class; //基本数据类型
        Class<Void> cls8 = void.class; //void 数据类型
        Class<Class> cls9 = Class.class;

        System.out.println(cls1);
        System.out.println(cls2);
        System.out.println(cls3);
        System.out.println(cls4);
        System.out.println(cls5);
        System.out.println(cls6);
        System.out.println(cls7);
        System.out.println(cls8);
        System.out.println(cls9);
    }
}

获取Class对象的方式

① forName()

第一种方法:通过forName()方法获取

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        //前提: 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取。
        //应用场景: 多用于配置文件,读取类全路径,加载类.
        Class cls = Class.forName("com.Demo.Cat");
    }
}

② 类名.class

第二种方法:通过类名.class获取

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        //前提:若已知具体的类,通过类的class 获取,该方式最为安全可靠,程序性能最高。
        //应用场景:多用于参数传递,比如通过反射得到对应构造器对象.
        Class cls2 = Cat.class;
    }
}

③ 对象.getClass()

第三种方法:通过对象.getClass()获取

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        //前提: 已知某个类的实例,调用该实例的getClass()方法获取Class对象,
        //应用场景: 通过创建好的对象,获取Class对象
        Cat cat = new Cat();
        Class cl3 = cat.getClass();
    }
}

④ classLoader.loadClass()

第四种方法:通过 类加载器 获取Class对象

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        Cat cat = new Cat();
        //(1)先获取的是类加载器
        ClassLoader classLoader = cat.getClass().getClassLoader();
        //(2)通过类加载器获取Class对象
        Class cls4 = classLoader.loadClass("com.Demo.Cat");
    }
}

⑤ 基本数据.class

第五种方法:基本数据(int,char , boolean, float , double,byte, long , short)基本类型.class 得到Class类对象

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        Class<Integer> integerClass = int.class;
        Class<Float> floatClass = float.class;
        Class<Double> doubleClass = double.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
    }
}

⑥ 包装类.TYPE

第六方法:基本数据类型对应的包装类,可以通过.TYPE 得到Class类对象

public class Reflection11 {
    public static void main(String[] args) throws Exception {
        Class<Integer> type = Integer.TYPE;
        Class<Float> type1 = Float.TYPE;
        Class<Double> type2 = Double.TYPE;
        Class<Character> type3 = Character.TYPE;
        Class<Boolean> type4 = Boolean.TYPE;
    }
}

Class类的常用方法

public class Reflection06 {
    public static void main(String[] args) throws Exception {
        //使用reflection.properties类,读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String catName = properties.get("catName").toString();
 
        //获取CLass类
        //<?> 表示不确定的Java类型
        Class<?> clazz = Class.forName(catName);
 
        // (2) 通过 cls 得到加载类的对象实例
        Object obj = clazz.newInstance();
 
        //获取类名
        String name = clazz.getName();
        System.out.println(name);
 
        //获取类的包名
        String packName = clazz.getPackage().getName();
        System.out.println(packName);
 
        //获取类的父类
        Class<?> superclass = clazz.getSuperclass();
        System.out.println(superclass);
 
        //获取类的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> c : interfaces) {
            System.out.println(c);
        }
 
        //获取注解
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        for (Annotation a : annotations) {
            System.out.println(a);
        }
 
        //获取类的构造方法
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> c : constructors) {
            System.out.println(c);
        }
 
        //获取类的属性
        Field[] fields = clazz.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
 
        //获取类的方法
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
    }
}

类加载

动态加载 和 静态加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载 : 编译时加载相关的类,如果没有则报错,依赖性太强
  2. 动态加载 : 运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

类加载时机

  1. 当创建对象时(new)——静态加载
  2. 当子类被加载时,父类也加载——静态加载
  3. 调用类中的静态成员时——静态加载
  4. 通过反射——动态加载

静态加载:

编译的时候就会加载这个类(检查类是否正确、依赖关系、父类是否ok等等),比如下面这段代码

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

用户可能输入1,也可能输入2,Dog类不一定会用到,静态加载不管那么多,它在编译的时候就会加载这个类,并且进行语法的校验

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

静态加载的依赖性较强


动态加载

执行到这里才加载

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

因为new Dog()是静态加载,因此必须编写Dog Person类是动态加载,所以,没有编写Person类也不会报错,只有当动态加载该类时,才会报错


类加载过程

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

加载和连接是JVM来完成的,我们无法控制,而初始化阶段就是我们自己可控的了(比如你在静态代码里面写什么内容是你自己可以控制的)

  • 加载:将类的class文件读入到内存中,并为之创建一个java.lang.Class对象,此过程由类加载器完成
  • 连接:将类的二进制数据合并到JRE中
  • 验证阶段:对文件进行安全性校验,比如文件格式是否正确、元数据验证是否正确、字节码是否正确、符号引用是否正确
  • 准备阶段给静态变量分配内存,给静态变量默认初始化
  • 解析阶段:虚拟机把常量池中的符号引用替换为直接引用
  • 初始化阶段静态代码块,静态变量显式赋值 初始化:JVM负责对类进行初始化,这里主要指静态成员

Class类常用方法介绍、获取Class对象的方式、通过反射实现各种骚操作

  • 连接阶段-验证
  1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  2. 包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
  3. 可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

连接阶段-准备

JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、OL、null、false等)。这些变量所使用的内存都将在方法区中进行分配。

//类加载的连接阶段——准备
public class ClassLoad02 {
    public static void main(String[] args) {
        //...
    }
}

class A {
    //属性(成员变量,字段)
    //分析类加载的连接阶段——准备,属性是如何处理:
    //1. n1是成员变量,不是静态变量,因此在准备阶段,不会分配内存
    //2. n2是静态变量,分配内存,n2是默认初始化0,而不是20
    //3. n3是static final常量,和静态变量不一样,因为一旦赋值就不变,n3 = 30
    public int n1 = 10;
    public static int n2 = 20;
    public static final int n3 = 30;
}

连接阶段-解析

虚拟机将常量池内的符号引用替换为直接引用的过程。


Initialization(初始化)

  1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>0方法的过程。
  2. <clinit>0方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
  3. 虚拟机会保证一个类的<clinit>)方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>0方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>0方法完毕[debug源码]
//类加载初始化阶段
public class ClassLoad03 {
    public static void main(String[] args) throws Exception {
        //1.加载B类,并生成对应的Class类对象
        //2.连接 num = 0;
        //3.初始化阶段:依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
        /*
            clinit(){
                System.out.println("B 静态代码块被执行");
                //num = 300;
                num = 100;
            }
            合并:num = 100;
         */

        //new B(); //类加载
        //System.out.println(B.num); //100,如果直接使用类的静态属性,也会导致类的加载

        //加载类的时候,是有同步机制控制
        /*
            protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                //正因为有这个机制,才能保证某个类在内存中,只有一个 Class 对象
                synchronized (getClassLoadingLock(name)) {
                    //...
                }
            }
         */
        B b = new B();
    }
}

class B {
    static {
        System.out.println("B 静态代码块被执行");
        num = 300;
    }

    static int num = 100;

    public B() {
        System.out.println("B 构造器被执行");
    }
}


反射 获取类的结构信息

获取类的结构信息-Class类

Class类:

  1. getName:获取全类名
  2. getSimpleName:获取简单类名
  3. getFields:获取所有public修饰的属性,包含本类以及父类的
  4. getDeclaredFields:获取本类中所有属性
  5. getMethods:获取所有public修饰的方法,包含本类以及父类的
  6. getDeclaredMethods:获取本类中所有方法
  7. getConstructors:获取本类所有public修饰的构造器
  8. getDeclaredConstructors:获取本类中所有构造器
  9. getPackage:以Package形式返回包信息
  10. getSuperClass:以Class形式返回父类信息
  11. getInterfaces:以Class0形式返回接口信息
  12. getAnnotations:以Annotation门形式返回注解信息
public class Reflection07 {
    public static void main(String[] args) throws Exception{
        String catName = "reflection.question.Cat";
        Class<?> cls = Class.forName(catName);
 
        //1. getName:获取全类名
        System.out.println(cls.getName());
 
        //2. getSimpleName:获取简单类名
        System.out.println(cls.getSimpleName());
 
        //3. getFields:获取所有public修饰的属性,包含本类以及父类的
        Field[] fields = cls.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
 
        //4. getDeclaredFields:获取本类中所有属性(包括私有属性)
        Field[] fields04 = cls.getDeclaredFields();
        for (Field field : fields04) {
            System.out.println(field);
        }
 
        //5. getMethods:获取所有public修饰的方法,包含本类以及父类的
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
 
        //6. getDeclaredMethods:获取本类中所有方法(包括私有方法)
        Method[] methods06 = cls.getDeclaredMethods();
        for (Method method : methods06) {
            System.out.println(method.getName());
        }
 
        //7. getConstructors:获取本类所有public修饰的构造器
        Constructor[] constructors = cls.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
 
        //8. getDeclaredConstructors:获取本类中所有构造器(包括私有的)
        Constructor[] constructors08 = cls.getDeclaredConstructors();
        for (Constructor constructor : constructors08) {
            System.out.println(constructor);
        }
 
        //9. getPackage:以Package形式返回包信息
        System.out.println(cls.getPackage());
 
        //10. getSuperClass:以Class形式返回父类信息
        Class<?> superclass = cls.getSuperclass();
        System.out.println(superclass);
 
        //11. getInterfaces:以Class0形式返回接口信息
        Class<?>[] interfaces = cls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println(anInterface);
        }
 
        //12. getAnnotations:以Annotation门形式返回注解信息
        Annotation[] annotations = cls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

获取类的结构信息-Field类

Field类:

  1. getModifiers: 以int形式返回修饰符 [说明:默认修饰符是0,public是1,private是2,protected是4, static是8, final是16] , public(1) + static (8)=9
  2. getType: 以Class形式返回类型
  3. getName: 返回属性名
public class Reflection08 {
    public static void main(String[] args) throws Exception {
        String catName = "reflection.question.Cat";
        String ageName = "age";
        Class<?> cls = Class.forName(catName);
        Field age = cls.getField(ageName);
 
        //1. getModifiers(): 以int形式返回属性修饰符
        //[说明:默认修饰符是0,public是1,private是2,protected是4, static是8, final是16], public(1) + static (8)=9]
        int modifiers = age.getModifiers();
        System.out.println(modifiers);
 
        //2. getType(): 以Class形式返回属性类型
        Class<?> type = age.getType();
        System.out.println(type);
 
        //3. getName(): 获取属性名
        String name = age.getName();
        System.out.println(name);
    }
}

获取类的结构信息-Method类

Method类

  1. getModifiers: 以lint形式返回修饰符 [说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]
  2. getReturnType:以Class形式获取返回类型
  3. getName:返回方法名
  4. getDeclaringClass(): 获取方法所属的类
  5. aetParameterTypes:以Class返回参教类型数组
  6. getParameterCount() 获取方法参数的个数
  7. getModifiers() 获取方法的访问修饰符
  8. isAnnotationPresent() 判断某个类/方法/属性/构造器有没有被某个注解标注过
public class Reflection09 {
    public static void main(String[] args) throws Exception {
        String catName = "reflection.question.Cat";
        String hiMethod = "hi";
        Class<?> cls = Class.forName(catName);
        Method method = cls.getMethod(hiMethod);
 
        //1. getModifiers: 以lint形式返回方法的修饰符
        //[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]
        int modifiers = method.getModifiers();
        System.out.println(modifiers);
 
        //2. getReturnType:以Class形式获取返回类型
        Class<?> returnType = method.getReturnType();
        System.out.println(returnType);
 
        //3. getName: 获取方法名
        String name = method.getName();
        System.out.println(name);
 
        //4. getParameterTypes:以Class返回参教类型数组
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.println(parameterType);
        }
    }
}

获取类的结构信息-Constructor类

Constructor类:

  1. getModifiers: 以int形式返回修饰符
  2. getName:返回构造器名(全类名)
  3. getParameterTypes:以Class[]返回参数类型数组
public class Reflection10 {
    public static void main(String[] args) throws Exception {
        String catName = "reflection.question.Cat";
        Class<?> cls = Class.forName(catName);
        Constructor<?> constructor = cls.getConstructor(String.class,int.class,String.class);
 
        //1. getModifiers: 以int形式返回构造器修饰符
        //[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]
        int modifiers = constructor.getModifiers();
        System.out.println(modifiers);
 
        //2. getName:返回构造器名(全类名)
        String name = constructor.getName();
        System.out.println(name);
 
        //3. getParameterTypes:以Class[]返回参数类型数组
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.println(parameterType);
        }
    }
}

通过反射创建对象

Class类相关方法:

newInstance:同用类中的无参构造器,获取对应类的对象 getConstructor(Class..clazz):根据参数列表,获取对应的public构造器对象 getDecalaredConstructor(Class..clazz):根据参数列表,获取对应的所有构造器 对象

Constructor类相关方法

setAccessible():暴力破解

newInstance(Object...obj):调用构造器

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.panyujie.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("zhangsan");
        System.out.println("zhangsan=" + 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 = "hhh";

    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 + "]";
    }
}

通过反射访问类中的成员

访问属性

1.根据属性名获取Field对象

Field f=clazz对象.getDeclaredField(属性名):

2.暴破:

f.setAccessible(true);//f是Field

3.访间

f.set(o,值);//o表示对像
syso(f.get(o));//o表示对象

注意:

如果是静态属性,则set和get中的参数o,可以写成null

应用:

import java.lang.reflect.Field;

//反射操作属性
public class ReflectAccessProperty {
    public static void main(String[] args) throws Exception {
        //1. 得到Student类对应的 Class对象
        Class<?> stuClass = Class.forName("com.panyujie.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, "hhh");
        name.set(null, "hhh~");//因为name是static属性,因此 o 也可以写出null
        System.out.println(o);
        System.out.println(name.get(o)); //获取属性值
        System.out.println(name.get(null));//获取属性值, 要求name是static
    }
}

class Student {
    public int age;
    private static String name;

    public Student() {
    }

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


访问方法

1.根据方法名和参数列表获取Method方法对象:

Method m = clazz.getDeclaredMethod(方法名,XX.class);//得到本类的所有方法

2.获取对象:

Object o = clazz.newlnstance():

3.暴破:

m.setAccessible(true);

4.访问:

Object returnValue=m.invoke(o,实参列表)://o就是对象

注意:

如果是静态方法,则invoke的参数o,可以写成null!

应用:

import java.lang.reflect.Method;

//通过反射调用方法
public class ReflectAccessMethod {
    public static void main(String[] args) throws Exception {
        //1. 得到Boss类对应的Class对象
        Class<?> bossCls = Class.forName("com.panyujie.reflection.Boss");
        //2. 创建对象
        Object o = bossCls.newInstance();
        //3. 调用public的hi方法
        //Method hi = bossCls.getMethod("hi", String.class);//OK
        //3.1 得到hi方法对象
        Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
        //3.2 调用
        hi.invoke(o, "hhh~");
        //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) {
        System.out.println("hi " + s);
    }
}


@