Java——注解与反射

反射(Reflaction)

  • 动态语言:运行的时候可以根据某些条件改变自身结构

    ​ eg:Object-C、C#、JavaScript、PHP

  • 静态语言:运行时结构不可改变的语言

    ​ eg:Java、C、C++

概述

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

Java的反射机制和Java类实例对象的创建刚好相反

对象的创建:

引入需要的“包类”名称->通过new实例化->取得实例化对象

反射方式:

实例化对象->通过getClass方法->得到完整的“包类”名称

  • 实体类:

    在Java中,实体类就是一个拥有Set和Get方法的类。实体类通来常总是和数据库之类的(所谓持久层数据)联系在一起。这种联系是借由框架(如自Hibernate)来建立的。


一个简单的通过反射机制获取类的Class对象的实例

package Demo.test1;

public class test {

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("Demo.test1.User");
        Class c2 = Class.forName("Demo.test1.User");
        Class c3 = Class.forName("Demo.test1.User");
        Class c4 = Class.forName("Demo.test1.User");

        System.out.println(c1);
        //一个类在内存中只有一个Class对象
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }

}

//实体类
class User{
    private String name;
    private int age;

    public User(){

    }

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

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

    public String getName(){
        return name;
    }

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

    public int getAge(){
        return age;
    }

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

获得Class类实例的方法


  • 方法一:通过实例化对象的getClass()方法
  • 方法二:通过class类的forName()方法
  • 方法三:通过类的class属性
  • 方法四(部分内置内部类才可以):通过其TYPE属性
package Demo.test1;

public class test2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();

        //方法一: 实例对象的getClass()方法
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        //方法二:class类的forName()方法
        Class<?> c2 = Class.forName("Demo.test1.Student");
        System.out.println(c2.hashCode());

        //方法三:类对象的class属性
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        //方法四(部分内置累不累才有的):通过TYPE属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);

        //获得父类的class对象
        Class c5 = c1.getSuperclass();
        System.out.println(c5);

    }
}

class Person{
    public String name;

    public Person() {
    }

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

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

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

class Teacher extends Person{
    public Teacher(){
        super.name = "老师";
    }
}

哪些类型可以拥有Class对象


  • Class
  • interface
  • []数组
  • enum枚举
  • annotation注解@interface
  • primitive type 基本类型
  • void
package Demo.test1;

import java.lang.annotation.ElementType;

public class Test3 {
    public static void main(String[] args) {
        Class c1 = Class.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = Integer.class;
        Class c8 = void.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);

    }
}

类的加载和ClassLoader的理解

  1. 加载:将class字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据,然后生成一个代表这个类的java.lang.Class对象

    所以Class对象是一开始加载的时候就存在的,Java反射机制是对Class类的获取

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

    • 验证:确保加载的类信息符合JVM规范,无安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类默认初始值(在方法区中进行分配)
    • 解析:虚拟机常量池中的符号引用替换成直接引用
  3. 初始化:执行类构造器方法的过程,类构造器方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的

    (类构造器是构造类信息的,不是构造该类对象的构造器)

package Demo.test1;

public class Test4 {

    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);
    }

}

class A{
    static{
        System.out.println("A类的静态代码块");
        m = 300;
    }

    
    static int m = 100;

    public A() {
        System.out.println("A类的无参构造函数");
        //m = 200;
    }
}

static int m = 100语句在static静态代码块之后,所以在初始化的时候m先被赋值为300然后赋值为100,最后打印结果为100

如果把static int m = 100语句放在static静态代码块之前,则m先被赋值为100再赋值为300,最后打印结果为300

静态代码块永远比构造函数先一步被初始化


类初始化

  • 主动引用(一定会发生类的初始化)

    1. 虚拟机启动,main方法所在的类初始化
    2. new一个类的对象时
    3. 调用类的静态成员和静态方法(final常量除外)
    4. 反射使用java.lang.reflect包的方法对类进行反射调用
    5. 初始化一个类,如果其父类没有被初始化,就会初始化其父类
  • 被动引用(不会发生类的初始化)

    1. 当通过子类引用父类的静态变量,不会导致子类的初始化

    2. 通过数组定义类引用,不会触发类的初始化

    3. 引用常量不会触发类的初始化

      (常量在链接阶段就存入调用类的常量池中)


类加载器

  • 引导类加载器(根加载器)

    C++编写,JVM自带的类加载器,负责Java平台核心库,用来装载核心类

    该类无法直接获取

  • 扩展类加载器

    负责jre/lib/ext目录下的jar包或 -D java.ext.dirs指定目录下的jar包装如工作库

  • 系统类加载器

    负责java -classpath 或 -D java.class.path所指目录下的类和jar包装入工作,是最常用的类加载器

我们自己写的类都是系统类加载器加载的

package Demo.test1;

public class Test6 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        //2.系统类加载器的父类加载器--扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        //3.扩展类加载器的父类加载器--根加载器(用于加载系统的核心类,不能被用户轻易获取)  显示打印的结果为null
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        //测试当前类的哪个加载器加载的
        ClassLoader classLoader = Class.forName("Demo.test1.Test6").getClassLoader();
        System.out.println(classLoader);

        //测试JDK写好的类是哪个加载器加载出来的
        ClassLoader classLoader1 = Class.forName("java.lang.Override").getClassLoader();
        System.out.println(classLoader1);

        //系统类加载器可以加载的路径
        String property = System.getProperty("java.class.path");
        System.out.println(property);
    }
}

在这里插入图片描述

Class对象的方法


package Demo.test1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

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

        //获得类的名称
        String name = c1.getName();
        System.out.println(name);

        //获得类的属性
        System.out.println("-----------------------");
        Field[] fields = c1.getFields();  //获得类的public属性
        for (Field field : fields) {
            System.out.println("@"+field);
        }
        fields = c1.getDeclaredFields();//获得类的全部属性
        for (Field field : fields) {
            System.out.println("#"+field);
        }
        Field name1 = c1.getDeclaredField("name");  //获得类的指定属性
        System.out.println(name1);

        //获得类的方法
        System.out.println("-----------------------------");
        Method[] methods = c1.getMethods();  //获得类及其父类的public方法
        for (Method method : methods) {
            System.out.println(method);
        }
        methods = c1.getDeclaredMethods();  //获得类自己的所有方法
        for (Method method : methods) {
            System.out.println("###"+method);
        }

        //获得类的构造器
        System.out.println("-----------------------");
        Constructor<?>[] constructors = c1.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        Constructor constructor = c1.getConstructor(String.class, int.class);
        //获得指定的构造器  需要传入参数的class对象
        System.out.println(constructor);
    }
}

Class对象的作用


有了Class对象之后,我们可以通过Class对象创建一个类实例,可以不用通过new方法来创建类对象实例了

创建的方法:

  1. 通过上面介绍的几种创建Class对象的方法获得类的Class对象
  2. 用该Class类对象.newInstance方法创建一个类对象实例**(这个创建方法要求目标类要有无参构造器)**
public class tet8 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //1.创建一个Class对象
        Class<?> c1 = Class.forName("Demo.test1.User");

        //2.通过该Class对象的newInstance方法创建一个类对象
        Object o = c1.newInstance();//要求目标类拥有无参构造器
        System.out.println(o);
    }
}
  1. 针对没有无参构造器的类,通过Class类对象获得其构造器(这个具体步骤看上面Class对象的方法),通过构造器来调用.newInstance方法,并传递参数
public class tet8 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //1.创建一个Class对象
        Class c1 = Class.forName("Demo.test1.User");

        //2.调用指定构造器来创建一个类对象
        Constructor<?> constructor = c1.getConstructor(String.class, int.class);
        Object person= constructor.newInstance("陈乡栎", 20);
        System.out.println(person);
    }
}

创建出类对象之后,通过类对象来调用普通方法

  1. 通过创建出来的Class对象的getDeclaredMethod()方法调用出所需方法

  2. 用该方法的invoke方法激活该方法,并传递参数

    invoke(类对象,参数);

  3. 通过类对象.方法来调用该方法

public class tet8 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //1.创建一个Class对象
        Class c1 = Class.forName("Demo.test1.User");

        //通过反射调用方法
        User o = (User)c1.newInstance();//要求目标类拥有无参构造器
        Method setName = c1.getDeclaredMethod("setName", String.class);
        setName.invoke(o, "chenxiangli");
        System.out.println(o.getName());
    }
}

通过创建出来的类对象,来操作属性

  1. 通过创建出来的Class对象的getDeclaredField()方法调用出所需属性

  2. 用该属性的set()方法给该属性赋值

    • 如果需要操作的属性是私有的,可以用属性的setAccessible()方法将安全检查开关打开**(这个针对私有方法的调用也适用)**

      setAccessible(true)

public class tet8 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.创建一个Class对象
        Class c1 = Class.forName("Demo.test1.User");

        //通过反射操作属性
        System.out.println("--------------------");
        User user2 = (User)c1.newInstance();
        Field name = c1.getDeclaredField("name");

        name.setAccessible(true);//允许访问

        name.set(user2,"chenxiangli");
        System.out.println(user2.getName());
    }
}

通过反射创建类对象实例和直接new的性能比较测试


public class test10 {
    //普通方法
    public static void test1(){
        User user = new User();
        int startTime = (int) System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        int EndTime = (int) System.currentTimeMillis();

        System.out.println("普通方法:"+ (EndTime-startTime));
    }

    //构造器获得对象方法
    public static void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        User user = new User();
        Class c1 = user.getClass();
        Constructor constructor = c1.getConstructor(String.class, int.class);
        User person = (User)constructor.newInstance( "chenxiangli", 20);


        int startTime = (int) System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            person.getName();
        }

        int EndTime = (int) System.currentTimeMillis();

        System.out.println("构造器方法:"+ (EndTime-startTime));
    }

    //反射方法
    public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);


        int startTime = (int) System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }

        int EndTime = (int) System.currentTimeMillis();

        System.out.println("反射方法:"+ (EndTime-startTime));
    }

    //反射方法  关闭权限检查
    public static void test4() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);

        getName.setAccessible(true);

        int startTime = (int) System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }

        int EndTime = (int) System.currentTimeMillis();

        System.out.println("反射关闭方法:"+ (EndTime-startTime));
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        test1();
        test2();
        test3();
        test4();
    }
}

在这里插入图片描述

通过结果可以看出,反射方法获得类对象实例的速度要比普通方法慢很多,如果关掉权限检查的话,速度可以提高一点,所以如果在确保要调用的方法是public方法的情况下,可以关闭权限检查,这样可以提高代码的性能

  • new和newInstance的区别

    当我们使用new关键字创建类实例化对象时,这个类可以没有被加载,而在使用newInstance的时候,要保证这个类已经被加载并且被链接了,也就是说,通过反射创建类实例化对象的时候,相当于把new方式创建实例化对象拆分成为了两步骤

    第一步:Class.forName() 获得类的class对象,这一步是实现了目标类的加载和链接

    第二步:newInstance() 创建出类实例化对象

    因此,通过反射创建实例化对象相比于通过new关键字创建实例化对象,要绕的弯子大一点,因而速度要慢

newInstance: 弱类型。低效率。只能调用无参构造
new: 强类型。相对高效。能调用任何public构造

反射操作泛型


Java泛型的出现是为了保证数据的安全性并且免去强制类型转换问题而出现的一种机制,但是一旦编译完成,所有与反省相关的类型都会被擦去

为了获得这些类型,Java反射机制新增了一些类型,这些类型不能被归到Class类中但是又和原类型齐名

  • ParameterizedType:参数化类型 Collection
  • GenericArrayType:表示元素为参数化类型或者类型变量的数组类型
  • TypeVariable:各种类型变量的公共父接口
  • WildcardType:通配符类型表达式
package Demo.test1;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

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


    public static Map<String,Integer> test2(){
        System.out.println("test2");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        //通过反射获得类的方法
        Method method = test11.class.getMethod("test1",Map.class,List.class);

        //获得方法的泛型参数
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("###"+genericParameterType);
            //进一步展开,如果genericParameterType属于参数化类型
            if(genericParameterType instanceof ParameterizedType){
                //进一步获取真实的类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("%%%"+actualTypeArgument);
                }
            }
        }

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


        Method s = test11.class.getMethod("test2");
        
        //获得方法的返回值类型
        Type genericReturnType = s.getGenericReturnType();
        System.out.println(genericReturnType);
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("000"+actualTypeArgument);
            }
        }
    }
}

反射获取注解

  • getAnnotation()方法
  1. 通过上面介绍的各种方式先获得类的class对象
  2. 通过class对象获得其方法或者属性,然后调用getAnnotation()方法获得注解,或者直接通过class对象调用getAnnotation()方法获得注解
  3. 如果想要进一步获得注解的具体内容,必须指定具体的注解名称,而不能笼统的用Annotation来定义
package Demo.test1;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Type;

//通过反射获取注解
public class test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //通过反射获得类class对象
        Class c1 = Class.forName("Demo.test1.Student2");

        //获取类的注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的value值
        classChen annotations1 = (classChen)c1.getAnnotation(classChen.class);  //准确定位到是哪个注解
        System.out.println(annotations1.value());

        //获得指定属性的注解
        Field name = c1.getDeclaredField("name");  //获得指定的属性
        /**
         * //准确定位到哪个注解
         *   只有准确定位到注解才能调用其方法去读取具体属性或者方法注解的值
         */
        field annotation = name.getAnnotation(field.class);
        System.out.println(annotation.name());
        System.out.println(annotation.length());
    }
}

@classChen("Student_table")
class Student2{
    @field(name = "age",length = 5)
    private int age;
    @field(name = "id",length = 5)
    private int id;
    @field(name = "name",length = 5)
    private String name;

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

    public Student2() {
    }

    public int getAge() {
        return age;
    }

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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

//对类的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface classChen{
    String value();
}

//对属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface field{
    String name(); //表格的名称
    int length();  //表格的长度
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值