Java 反射:反射概念、Class类生成内存分析、类加载过程、通过反射机制加载对象、反射获取泛型和注解


反射

Java不是动态语言,但Java可称之为“准动态语言”。即Java有一定的动态性,可利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活,同时也增加了不安全性。


1、Reflection 反射概念

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存中就产生了一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息。可通过这个给对象看到类的结构。这个对象就如一面镜子,透过这个镜子看到类的结构,所以形象称之为:反射

优点:

  • 可实现动态创建对象和编译,体现出很大的灵活性
  • 可以解耦,提高程序的可扩展性

缺点:

  • 对性能有影响。

反射相关的主要API

  • java.lang.Class 代表一个类
  • java.lang.reflect.Method 代表类的方法
  • java.lang.reflect.Field 代表类对成员变量
  • java.lang.reflect.Constructor 代表类的构造器

比如:

IDEA软件就用了反射机制,当使用一个类,调用方法时,IDEA会弹出很多方法的提示,这些方法都是通过反射机制实现的,会去找到该类通过class类对象封装了的Method对象,找到反射结果的方法。
在这里插入图片描述


2、Class类 JDK 1.1

对象照镜子后可以得到信息:某个类的属性、方法、构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息。

  • Class本身也是一个类
  • Class对象只能由系统建立
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

常用方法

  • static ClassforName(String name) 返回指定类名name的Class对象
  • Object newInstance() 调用缺省构造函数,返回Class对象的一个实例
  • getName() 返回此Class对象所表示的实体(类,接口,数组类或void)的名称
  • Class getSuperClass() 返回当前Class对象的父类的Class对象
  • Class[] getinterface() 获取当前Class对象的接口
  • ClassLoader getClassLoader() 返回该类的类加载器
  • Constructor[] getConstructors() 返回该类的构造器
  • Method[] getMethods() 返回一个方法类对象数组,获得本类以及父类的所有方法,只有public
    • Method[] getDeclaredMethods() 返回一个方法类对象数组,获得本类的所有方法
  • Method getMethod(String name, Class... T) 返回指定的方法类对象
  • Field[] getDeclaredFields() 返回类变量对象的一个数组,可找到全部属性
  • Field[] getFields() 只能找到public属性

获取Class类实例的方法

  1. 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。

    Class clazz = Preson.class

  2. 已知某个类的实例,调用该实例的getClass()方法获取Class对象

    Class clazz = person.getClass()

  3. 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常

    Classs clazz = Class.forName(“全路径”)

  4. Java中基本内置对象的包装类都有一个Type属性,可通Type属性获得Class对象

    Class clazz = Integer.TYPE

  5. 利用ClassLoader方式获取Class对象

public class GetClassObj {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println(person.name);

        // 1.
        Class c1 = Student.class;
        System.out.println(c1.hashCode());

        // 2.
        Class c2 = person.getClass();
        System.out.println(c2.hashCode());

        // 3.
        Class c3 = Class.forName("com.lfjava.refleciton.Student");
        System.out.println(c3.hashCode());

        // 4.
        Class c4 = Integer.TYPE;
        System.out.println(c4.hashCode());

        // 获取父类的Class对象实例
        Class c5 = c1.getSuperclass();
        System.out.println(c5.hashCode());
        System.out.println(c5);


    }
}

class Person{
    public String name;

    public Person() {
    }

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

class Student extends Person{
    public Student() {
        this.name = "我是学生";
    }
}

class Teacher extends Person{
    public Teacher() {
        this.name = "我是老师";
    }
}
我是学生
2133927002
2133927002
2133927002
1836019240
325040804
class com.lfjava.refleciton.Person

有Class对象的类型

  • class:外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解
  • primitive type:基本数据类型
  • void
public class GetClassObj {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = Override.class;
        Class c4 = int[].class;
        Class c5 = int[][].class;
        Class c6 = void.class;
        Class c7 = ElementType.class;
        Class c8 = Integer.class;
        Class c9 = Class.class;

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        int[] num1 = new int[10];
        int[] num2 = new int[100];
        System.out.println(num1.getClass().hashCode());
        System.out.println(num2.getClass().hashCode());
    }
}
class java.lang.Object
interface java.lang.Comparable
interface java.lang.Override
class [I
class [[I
void
class java.lang.annotation.ElementType
class java.lang.Integer
class java.lang.Class
2133927002
2133927002

3、Class类生成的内存分析

在这里插入图片描述

在执行程序时,会先将类里的基本数据全部加载在方法区中,然后生成对应的Class类,每一个类都对应了一个Class对象,这个过程时类加载器做的。为方便new实例化时,能够通过Class类反射机制去找到对应的数据。当在new了一个对象时,就会去找该类的Class对象,通过Class对象反射机制找到该类的所有数据,进行链接(对该类的验证,准备,解析),变量什么都先赋默认值,然后开始初始化,JVM通过调用

<clinit>(){
    System.out.println("静态代码块");
    m = 300;
    m = 100;
 }

方法,将静态代码合并在一块,进行初始化。然后再调用构造器。

public class Test05{
    public static void main(String[] args){
        A a = new A();
        System.out.println(a.m); // 输出 100
    }
}

class A{
    static{
        System.out.println("静态代码块");
        m = 300
    }
    
    static int m = 100;
    
    public A(){
        System.out.println("构造器");
    }
}

注意:Class类,是通过类加载器加载到内存中时产生的。

在这里插入图片描述

该图就是分析的将一个Person类编译成class字节码文件,通过java的类加载器,加载该类,通过class类对象对该字节码进行封装成各个组成部分,成为其他对象。


类加载器加载过程

在这里插入图片描述

1、类的加载

加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,
也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

2、类的链接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
2)准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
3)解析:将类的二进制数据中的符号引用(在编译时,有些通过字符变量赋值的,因为不知道该变量指的是哪个直接指,所有把它解析成特殊的符号)替换成直接引用。

3、类的初始化

初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,
其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样
的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准
备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值
0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

4、类的初始化时刻

在这里插入图片描述

public class ClassInit {
    static {
        System.out.println("Main被加载,初始化...");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 类被初始化
        // new Son();
        // System.out.println(Son.b);
        // System.out.println(Son.a);
        // Class<?> aClass = Class.forName("com.lfjava.refleciton.Son"); // 反射

        // 类不会初始化
        // System.out.println(Son.B);
        Son[] sons = new Son[10]; // 只是数组在内存分配空间,并不会使Son类初始化
    }
}

class Father{
    static {
        System.out.println("Father被加载,初始化...");
    }

    static int a = 1;
}

class Son extends Father{
    static {
        System.out.println("Son被加载,初始化...");
    }

    static int b = 2;
    static final int B = 3; // 类被加载的时候常量就已经在常量池中了
}

对应输出:

// 类被初始化
Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...

Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...
2

Main被加载,初始化...
Father被加载,初始化...
1

Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...

// 类不被初始化
Main被加载,初始化...
3

Main被加载,初始化...

5、类加载器

类加载的作用:

​ 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:

​ 标准的JavaSE类加载器可按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

在这里插入图片描述

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        
        System.out.println(ClassLoaderTest.class.getClassLoader());
        // 获取自己写的类,是通过用户加载器加载的
        System.out.println(Class.forName("java.lang.Math").getClassLoader());
    }
}

// 输出:
// sun.misc.Launcher$AppClassLoader@18b4aac2 用户加载器
// sun.misc.Launcher$ExtClasssLoader@7f31245a 扩展加载器
// null 根加载器(是Java的核心,用C/C++编写,该类加载器是无法直接获取的)
// sun.misc.Launcher$AppClassLoader@18b4aac2
// null 

双亲委派机制:

定义了一个类,在类加载的时候,先去用户类加载器看有没有该类,没有就去扩展类加载器查找,如还没有就去根类加载器去查找。首先使用自己的包。


6、获取运行时类的对象,使用方法

public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class<?> c1 = Class.forName("com.lfjava.refleciton.User");

        // 通过反射机制获取 类名
        System.out.println(c1.getName());
        System.out.println(c1.getSimpleName());

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

        // 通过反射机制获取 字段
        Field[] fields = c1.getFields(); // 只能获取public
        for(Field f : fields){
            System.out.println("# "+f);
        }
        Field[] dFields = c1.getDeclaredFields();
        for(Field f : dFields){
            System.out.println("$ "+f);
        }
        System.out.println(c1.getField("sex"));
        System.out.println(c1.getDeclaredField("name"));

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

        // 通过反射机制获取 方法
        Method[] methods = c1.getMethods(); // 只能获取public
        for(Method m: methods){
            System.out.println("# "+m);
        }
        Method[] dMethods = c1.getDeclaredMethods();
        for (Method m : dMethods) {
            System.out.println("$ "+m);
        }
        
        System.out.println(c1.getDeclaredMethod("getAge", null));

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

        // 通过反射机制获取 构造器
        Constructor[] constructors = c1.getConstructors(); // 只能获取public
        for (Constructor c : constructors) {
            System.out.println("# "+c);
        }
        Constructor[] dConstructors = c1.getDeclaredConstructors();
        for (Constructor c : dConstructors) {
            System.out.println("$ "+c);
        }

        System.out.println(c1.getDeclaredConstructor(String.class, String.class));
    }
}

class User{
    private String name;
    private String age;
    public String sex;

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

    public User() {
    }

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    private void method(){

    }

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

输出:

com.lfjava.refleciton.User
User
------------------------------------------------------
# public java.lang.String com.lfjava.refleciton.User.sex
$ private java.lang.String com.lfjava.refleciton.User.name
$ private java.lang.String com.lfjava.refleciton.User.age
$ public java.lang.String com.lfjava.refleciton.User.sex
public java.lang.String com.lfjava.refleciton.User.sex
private java.lang.String com.lfjava.refleciton.User.name
------------------------------------------------------
# public java.lang.String com.lfjava.refleciton.User.toString()
# public java.lang.String com.lfjava.refleciton.User.getName()
# public void com.lfjava.refleciton.User.setName(java.lang.String)
# public void com.lfjava.refleciton.User.setAge(java.lang.String)
# public java.lang.String com.lfjava.refleciton.User.getAge()
# public final void java.lang.Object.wait() throws java.lang.InterruptedException
# public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
# public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
# public boolean java.lang.Object.equals(java.lang.Object)
# public native int java.lang.Object.hashCode()
# public final native java.lang.Class java.lang.Object.getClass()
# public final native void java.lang.Object.notify()
# public final native void java.lang.Object.notifyAll()
$ public java.lang.String com.lfjava.refleciton.User.toString()
$ public java.lang.String com.lfjava.refleciton.User.getName()
$ public void com.lfjava.refleciton.User.setName(java.lang.String)
$ private void com.lfjava.refleciton.User.method()
$ public void com.lfjava.refleciton.User.setAge(java.lang.String)
$ public java.lang.String com.lfjava.refleciton.User.getAge()
public java.lang.String com.lfjava.refleciton.User.getAge()
------------------------------------------------------
# public com.lfjava.refleciton.User(java.lang.String,java.lang.String)
# public com.lfjava.refleciton.User()
$ public com.lfjava.refleciton.User(java.lang.String,java.lang.String)
$ public com.lfjava.refleciton.User()
public com.lfjava.refleciton.User(java.lang.String,java.lang.String)

7、反射机制创建对象,设置属性

首先获取类的Class对象,然后通过调用newInstance()创建该类的实例对象。

通过获取到类的set方法,通过invoke()方法,可以设置值

setAccessible(true)将安全监测机制关闭,可访问private的属性或成员

public class UseClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class clazz = Class.forName("com.lfjava.refleciton.User");

        // 如同: User user = new User();
        // User user = (User)clazz.newInstance();

        // 通过反射 创建一个User实例,无参构造器
        Constructor constructorNull = clazz.getConstructor(null);
        User user1 = (User)constructorNull.newInstance(null);
        System.out.println(user1.toString());

        // 通过反射 创建一个User实例,有参构造器
        Constructor constructor = clazz.getConstructor(String.class, String.class);
        User user2 = (User)constructor.newInstance("heroC","18");
        System.out.println(user2.toString());

        // 通过反射 获取setName方法,用于在user2实例上
        Method setName = clazz.getMethod("setName", String.class);
        setName.invoke(user2,"张还行");
        System.out.println(user2.toString());
        
        // 通过反射 获取属性age,调用setAccessible(true)将安全监测机制关闭,不然没有权限直接操作属性
        // 修改user2实例上的age
        Field name = clazz.getDeclaredField("age");
        name.setAccessible(true);
        name.set(user2,"20");
        System.out.println(user2.toString());
    }
}

输出:

User{name='null', age='null'}
User{name='heroC', age='18'}
User{name='张还行', age='18'}
User{name='张还行', age='20'}

8、普通方式与反射机制 性能 对比

public class ReflectionFun {
    public static void test01(){
        User user = new User();

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000_000_000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方法调用getName方法10亿次 "+(endTime-startTime) + "ms");
    }

    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000_000_000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射机制调用getName方法10亿次 "+(endTime-startTime) + "ms");
    }

    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);
        getName.setAccessible(true);

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000_000_000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射机制关闭安全监测调用getName方法10亿次   "+(endTime-startTime) + "ms");
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01(); // 普通方法
        test02(); // 反射机制
        test03(); // 反射机制 关闭监测
    }
}

输出结果:

普通方法调用getName方法10亿次 4ms
反射机制调用getName方法10亿次 4359ms
反射机制关闭安全监测调用getName方法10亿次   1794ms

可见,反射机制使用的性能很低很低。在反射机制中,关闭安全监测能够提高一些性能。


9、反射获取泛型

public class GenericParamsType {
    public static void test(Map<String, User> map, List<User> list){
        System.out.println("test");
    }

    public static Map<String,User> test01(){
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = GenericParamsType.class.getMethod("test", Map.class, List.class);
        // 获取方法的 泛型参数类型,返回一个Type数组
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {

            System.out.println("*******************************");
            System.out.println(genericParameterType); // 打印方法中每个参数的泛型类型
            System.out.println("-------------------------------");

            // genericParameterType 与 ParameterizedType 有关系,就执行
            // getActualTypeArguments()是ParameterizedType的方法
            if(genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument); // 将每个参数的真实类型点取出来
                }
            }
        }

        System.out.println();

        method = GenericParamsType.class.getMethod("test01",null);
        // 获取 泛型返回值类型
        Type genericReturnType = method.getGenericReturnType();
        System.out.println(genericReturnType); // 打印返回值类型
        if(genericReturnType instanceof ParameterizedType){
            // 获取返回值类型的每个参数类型
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument); // 得到具体的返回值类型
            }
        }

    }
}

输出:

*******************************
java.util.Map<java.lang.String, com.lfjava.refleciton.User>
-------------------------------
class java.lang.String
class com.lfjava.refleciton.User
*******************************
java.util.List<com.lfjava.refleciton.User>
-------------------------------
class com.lfjava.refleciton.User

java.util.Map<java.lang.String, com.lfjava.refleciton.User>
class java.lang.String
class com.lfjava.refleciton.User

10、反射获取注解信息

getAnnotations() 获取注解

getAnnotation(String s) 获取指定的注解

public class ReflectionAnnotation {
    public static void main(String[] args) throws NoSuchFieldException {
        // 获取Table类的注解 (作用在类上的注解)
        Annotation[] annotations = Table.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        // 获取Table类上指定的注解,并获取参数值
        ClassAnnotation annotation = Table.class.getAnnotation(ClassAnnotation.class);
        System.out.println(annotation.value());

        // 获取Table类中的指定的属性上的指定的注解,并获取注解中的参数值
        Field id = Table.class.getDeclaredField("id");
        FieldAnnotation idAnnotation = id.getAnnotation(FieldAnnotation.class);
        System.out.println(idAnnotation);
        System.out.println(idAnnotation.columnName());
        System.out.println(idAnnotation.type());
        System.out.println(idAnnotation.length());
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassAnnotation{
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
    String columnName();
    String type();
    int length();
}

@ClassAnnotation("db_student")
class Table{
    @FieldAnnotation(columnName = "id", type = "int", length = 30)
    private int id;
    @FieldAnnotation(columnName = "age", type = "int", length = 30)
    private int age;
    @FieldAnnotation(columnName = "name", type = "varchar", length = 3)
    private String name;

    public Table() {
    }

    public Table(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

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

输出:

@com.lfjava.refleciton.ClassAnnotation(value=db_student)
db_student
@com.lfjava.refleciton.FieldAnnotation(columnName=id, type=int, length=30)
id
int
30

通过操作注解,如果是将类生成一张数据表,也可以通过反射获取注解,获取注解中的参数信息,再通过sql语句,生成数据库的表。

通过操作注解,如果是将数据库表中的数据注入到类中的属性中,就可以通过反射获取注解来完成。

ORM:Object relationship Mapping (对象关系映射)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值