Java—反射基础

21 篇文章 6 订阅

Java—反射

1.反射概述

  • JAVA反射机制实在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及调用对象的方法的功能称为java的反射机制
  • 要想解剖一个类,必须要先获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以要先获取到每一个字节码文件所对应的Class类型的对象

那么,反射就是把java类中的各种成分映射成一个个的java对象,如下图所示
image.png

再说到Class类,Class类的实例表示正在运行的java应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)
Class没有公共构造方法。Class对象是在加载类时由java虚拟机以及通过调用类加载器中的defineClass方法自动构造的,也就是不需要我们去创建,jvm已经创建好了


2.反射的使用

这里我们先写一个User类

public class User {
    private int id;
    private int age;
    private String name;

    public User() {
    }

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

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

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

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

    public void setName(String name) {
        this.name = name;
    }
}
  • 1> 获取Class对象的三种方式

    1. Object –> getClass();
    2. 任何数据类型(包括基本数据类型)都有一个 “静态的”class属性
    3. 通过Class类的静态方法:forName(String className)(常用)
注意:运行期间,一个类只有一个Class对象产生。

  • 2> 获取User的类名,属性,方法,构造器
public class Demo2 {
    public static void main(String[] args) {
        String path = "anno.reflection.User";
        try {
            Class<?> clazz = Class.forName(path);

            //类的名字
            System.out.println(clazz.getName()); //获得包名+类名
            System.out.println(clazz.getSimpleName()); //获得类名:User

            //获得属性信息
        //    Field[] fields = clazz.getFields(); //只能返回public属性
            Field[] fields = clazz.getDeclaredFields();//获得所有field
            try {
                Field f = clazz.getDeclaredField("name");
                System.out.println(fields.length);
                for (Field temp:fields){
                    System.out.println("属性" + temp);
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }

            //获得方法信息
            Method[] method = clazz.getDeclaredMethods();
            Method m = clazz.getDeclaredMethod("getName",null);
            Method m2 = clazz.getDeclaredMethod("setName", String.class);
            for (Method temp:method){
                System.out.println("方法" + temp);
            }

            //获得构造器信息
            Constructor[] constructors = clazz.getDeclaredConstructors();

            System.out.println();

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
首先,path指定要进行反射的类的路径,然后通过Class.forName()获取到Class对象,该对象调用getName()返回该类所在的包名+类名,getSimpleName()将光返回一个类名。
  • 通过getFields()可获得所有属性信息,但只能获得声明为public的属性,要想获得所有属性就要调用getDeclaredFields()方法,若要获得某个单独的属性,就要调用getDeclaredField(String name)方法,name为该属性的名字
  • 通过getDeclaredMethods()方法可以返回该类的所有方法,但若要获得某个特定的方法,getDeclaredMethod()参数中除了方法名,还有该方法的参数的class对象
  • 构造器信息可以通过getConstructors方法获得。

  • 3> 创建类的实例,调用类的属性与方法
public class Demo3 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        String path = "anno.reflection.User";
        try {
            Class<?> clazz = Class.forName(path);

            //动态调用构造方法,构造对象
            User u = (User) clazz.newInstance();//其实是调用了user的无参构造器
            //javabean必须有无参构造器
            System.out.println(u);

            Constructor<User> c = (Constructor<User>) clazz.getDeclaredConstructor(int.class,int.class,String.class);
            User u2 = c.newInstance(1001,18,"winter");

            System.out.println(u2.getName());

            //通过反射API调用普通方法
            User u3 = (User) clazz.newInstance();

            Method method = clazz.getDeclaredMethod("setName", String.class);
            method.invoke(u3,"wtf");                //u3.setName("wtf");
            System.out.println(u3.getName());

            //通过反射API操作属性
            User u4 = (User) clazz.newInstance();
            Field f = clazz.getDeclaredField("name");
            f.setAccessible(true);  //这个属性不用做安全检查了
            f.set(u4,"zhang");      //无法访问私有属性,通过反射直接写属性
            System.out.println(u4.getName());   //通过反射直接读属性
            System.out.println(f.get(u4));

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
在获取到User类的Class对象后,让该对象调用newInstance()方法即可返回一个User类的对象,但是,这个方法创建的对象是通过调用无参构造器创建的,所以User类中必须要有一个无参构造器,否则调用该方法就会报错。
  • 接下来可以调用getDeclaredConstructor()方法获得对应的构造器,方法的参数为对应构造器参数的Class对象,获取之后就可以利用该对象进行构造新的User对象(注意强制类型转换)
  • 同理,我们可以通过getDeclaredMethod()方法获得相对应的方法,该方法的第一个参数是该方法的方法名,之后的参数是要获取的方法的参数Class对象。在获取到方法后,该让哪个User对象调用该方法呢?这个时候该方法对象需要调用invoke()方法指定执行该方法的对象,并且指定传入参数(如上面的代码所示)
  • 调用getDeclaredField()获得相应的属性之后,要想对某对象中的该属性进行修改,可以使用set()方法,但如果该属性为私有属性,就必须先使用setAccessible()方法来避开安全检查,这样就可以随意修改对象中的属性值了

  • 4> 反射获取泛型信息
public class Demo4 {
    public void test01(Map<String,User> map, List<User> list){
        System.out.println("Demo04.test01()");
    }

    public Map<Integer,User> test02(){
        System.out.println("Demo.test02()");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {

        //获得指定方法的参数泛型信息
        Method m = Demo4.class.getMethod("test01",Map.class,List.class);
        Type[] t = m.getGenericParameterTypes();//获得参数类型
        for (Type paramType: t){
            System.out.println("#" + paramType);
            if (paramType instanceof ParameterizedType){//判断是否存在泛型参数
                Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                for (Type genericType:genericTypes){
                    System.out.println("泛型类型:" + genericType);
                }
            }
        }

        //获得指定方法返回值泛型信息
        Method m2 = Demo4.class.getMethod("test02",null);
        Type returnType = m2.getGenericReturnType();
        System.out.println(returnType);
        if (returnType instanceof ParameterizedType){
            Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type genericType:genericTypes){
                System.out.println("返回值,泛型类型" + genericType);
            }
        }

    }
}
接下来是使用反射来获取泛型信息,事实上泛型是java编译器的内容,在编译时经过泛型擦除,jvm中不存在泛型。但在类的Class对象加载到内存中后,我们任然可以获取其中的泛型信息。上面的代码分别是获取参数中的泛型信息和方法返回值的泛型信息
  • 参数中的泛型信息:在获取到相应的方法对象后,调用getGenericParameterTypes()方法获得参数类型,如果该参数对象 符合 instanceof ParameterizedType,即说明该参数是一个泛型参数,然后再调用getActualTypeArguments()方法来获得泛型类型(先将其强制转型为ParameterizedType类型)

  • 方法返回值中的泛型信息:再获取到相应的方法对象后,调用getGenericReturnType()拿到返回值的类型,再重复之前的步骤判断其是否是带有泛型的类型,再获得泛型类型。


3.反射获得注解信息

首先我们先定义两个自定义注解

@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
    String columnName();
    String type();
    int length();
}
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
    String value();
}

然后定义一个javabean类,并未其类和属性添加注解

@MyTable("tb_student")
public class MyStudent {

    @MyField(columnName = "id",type = "int",length = 10)
    private int id;
    @MyField(columnName = "sname",type = "varchar" ,length = 10)
    private String name;
    @MyField(columnName = "age",type = "int",length = 3)
    private int age;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

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

然后,我们通过反射来获取其注解中的信息。其主要目的是为了获取注解中的信息,拼出DDL语句,连接数据库,然后用JDBC执行sql语句,在数据库中生成相应的表。

public class AnnoDemo {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("anno.MyStudent");
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation a: annotations){
                System.out.println(a);
            }
            MyTable mt = (MyTable) clazz.getAnnotation(MyTable.class);
            System.out.println(mt.value());

            //获得类的属性的注解
            Field f = clazz.getDeclaredField("name");
            MyField mf = f.getAnnotation(MyField.class);
            System.out.println(mf.columnName());

            //根据获得的表名,字段的信息,拼出DDL语句,然后使用JDBC执行这个SQL,在数据库中生成相关的表
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
  • 其中最重要的方法就是getAnnotations()和getAnnotation(),获取到注解信息之后,可以进一步得到注解中的相应信息(表名,列名,类型,长度等)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值