javaJava

java的反射机制与通过反射创建对象的四种方式

2018-10-16  本文已影响222人  程er狗

导读

java的反射在框架中运用十分广泛,最常用的有java的动态代理、注解==,所以掌握java的反射机制是十分必要的

目的:

1. 理解Class类并实例化Class对象
2. 运行时创建类对象并获取类的完整结构
3. 通过反射调用类的指定方法、指定属性

一、要想更好地理解反射,就要先知道java类的加载过程

每个类都会产生一个对应的Class对象,也就是保存在.class文件。所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static修饰的属性和代码块初始化是在类加载时进行的。

1.类加载的三个步骤

加载:由类加载器完成,找到对应的字节码,创建一个Class对象
链接:验证类中的字节码,为静态域分配空间
初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块

类的加载器小tpis:



类加载器的一个重要运用:获取配置文件里的属性值

 /**
     * 类加载器:ClassLoader
     */
    @Test
    public void classLoaderTest() throws ClassNotFoundException, IOException {
        //1.引导类加载器
        ClassLoader loader1 = ClassLoader.getSystemClassLoader();
        System.out.println(loader1);//sun.misc.Launcher$AppClassLoader@18b4aac2

        ClassLoader loader2 = loader1.getParent();
        System.out.println(loader2);//sun.misc.Launcher$ExtClassLoader@6433a2

        ClassLoader loader3 = loader2.getParent();
        System.out.println(loader3);//null

        //2.扩展类加载器(自定义类加载器)
        Class clazz = Student.class;
        ClassLoader loader4 = clazz.getClassLoader();
        System.out.println(loader4);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //3.系统类加载器
        String className = "java.lang.String";
        Class clazz1 = Class.forName(className);
        ClassLoader loader5 = clazz1.getClassLoader();
        System.out.println(loader5);//null

        //类加载器的一个重要运用如下:
        ClassLoader loaderImportant = this.getClass().getClassLoader();
        InputStream is = loaderImportant.getResourceAsStream("templates/jdbc.properties");
        Properties pro = new Properties();
        pro.load(is);
        System.out.println(pro.getProperty("username"));//chengergou
        System.out.println(pro.getProperty("password"));//chengergou123
    }

Class小tpis:

  • Class本身是一个类
  • Class对象只能由系统创建对象
  • 一个类在JVM中只会有一个Class实例
  • 一个Calss对象对应的是一个加载在JVM中.class文件
  • 每个类的实例都会记得由哪个Class实例生成
  • 通过Class能够得到一个类中的完整结构
理解:与对象的抽取与封装进行对比
Car这个类是对所有汽车的静态特征进行描述与抽取,那么不管是宝马、大众还是其它汽车,只
要是Car都拥有了name这个属性。Car的行为是运行,那么不管什么Car,都有run()这个方法
class Car {
    private String name;
    public void run(){
    System.out.println("我的功能是run"):
}
这就是对对象的共同特征与行为进行抽取从而封装成了类。

同理,对所有的class对象进行向上抽取与封装,那么他们都有(Filed)、Constructor(构造器)、Method(方法),你可以看做是这样的类:
class Class{
    Filed field;
    Constructor constructor;
    Method method;
    。。。
}

具体参考Class本类的结构:

二、什么是反射(Java Reflection)

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

三、反射的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

四、有了反射我们能做什么???

通常,我们创建对象的时候都是new出来,但现在我们是反过来知道类的信息,从而创建出对象;类的信息我们可以通过获取.class文件(字节码文件)来得到,包括类的属性、方法、构造器==

4.1 在没有反射前,是先创建类的对象,从而调用类的方法和属性

  /**
     * 在没有反射前,是先创建类的对象,从而调用类的方法和属性
     */
    @Test
    public void testNoRefleTest() {
        //对象的属性我们都设置了public修饰的getter和setter,所以都能
        // 调用和赋值
        Student s=new Student();
        s.setAge(18);
        s.setName("程二狗");
        s.setGender("男");
        System.out.println(s);//Student{age=18, name='程二狗', gender='男'}

        //只能调用public权限修饰的方法(我这儿是不同包下的)
        String  name=s.getPublicMessagePublicHaveParam("陈雪峰");
        System.out.println(name);//陈雪峰, hello!
        s.showMygenderPublicNoParam();//程二狗的性别为男
    }

访问权限控制小tpis:
注意:本文中的代码都是写在不同包下的(也是根据实际情况开发而写),你可以依据以下权限的进行相应访问权限匹配,代码中注释”只能“都是针对不同包下来说的

Java 中一共有四种访问权限控制,其权限控制的大小情况是这样的:public > protected > default(包访问权限) > private

访问权限      本类    本包的类    子类  非子类的外包类
public        是       是         是        是
protected     是       是         是        否
default       是       是         否        否
private       是       否         否        否

4.2 通过反射能调用类的完整结构
Filed、Method、Constructor、Superclass、Interface、Annotation

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法

4.2.1 为了方便测试,我们先创建出必要的类:

image.png

自定义注解类

package com.ergou.springboot.entity;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,METHOD,FIELD,PARAMETER,CONSTRUCTOR})
public @interface MyAnnotation {
    String  value();
}

注解小tpis

注解的生命周期:@Retention元注解有三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME,分别对应:java源文件<class文件<内存中的字节码。

接口

package com.ergou.springboot.entity;

import java.io.Serializable;

public interface MyInterface extends Serializable {
}

Person类

package com.ergou.springboot.entity;


public class Person<T> {
    public String job;
    private String address;
    public  void  breathing(){
        System.out.println("是人都要呼吸!");
    }

    public Person() {

    }

    public String getJob() {

        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

学生类

package com.ergou.springboot.entity;
@MyAnnotation(value = "我是类的注解")
public class Student extends Person<String> implements Comparable,MyInterface {
    //私有属性private 修饰
    private Integer age;
    //默认属性defualt修饰
    String  name;
    //公有属性
    public String  gender;
    //无参构造器
    //tips:创建类时,默认有一个无参构造器
    public Student() {
        super();
        //验证谁调用了我
        System.out.println("我是Student的无参构造,我被调用了。。。");
    }
    //有参构造器
    public  Student(Integer age, String name, String gender) {

        this.age = age;
        this.name = name;
        this.gender = gender;
    }
   //私有方法,无参数
   @MyAnnotation(value = "我是private方法的注解")
    private  void  seeMyAgePrivateNoParam(){
        System.out.println("我是程二狗,我今年18岁了");
    }
    //私有方法,有参数
     private String getMyFriendPrivateHaveParam(Integer age,String name) throws ArrayIndexOutOfBoundsException{
        return "我的朋友叫"+name+",他今年"+age+"岁了";
    }
    //公有方法,有参数
    @MyAnnotation(value = "我是public方法的注解")
    public  String  getPublicMessagePublicHaveParam(String name){
        return  name+", hello!";
    }
    //公有方法,无参数
    public  void  showMygenderPublicNoParam() throws Exception{
        System.out.println("程二狗的性别为男");
    }
    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    //重写方法
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

测试代码

/**
     * 有了反射后,可以通过Class去创建对象(字节码.class)
     * java.lang.Class:是反射的源头
     * 通过创建创建类,编译(java.exe)生成对应的.class文件,之后我们用java.exe
     * (JVM类加载器完成的)此.class文件,此.class文件加载到内存以后,就是一个运行时类
     * 存放在缓存区,那么这个运行时类本身就是一个Class的实例!
     * 注意:每一个运行时类只加载一次
     * 有了Clas类的实例以后,我们才可以进行如下操作:
     * 1)创建对应类的运行时类
     * 2)获取对应运行时类的完整结构(属性、方法、构造器、内部类、父类、所在的包、异常、注解。。。)
     * 3)调用对应的运行时类的指定结构(属性、方法、构造)
     */
    @Test
    public void testHaveRefleTest() throws Exception {

 //=====================1.获取Class的实例(字节码文件)四种方式================================================

        //1.1 通过运行时类的对象,调用其getClass()方法,返回运行时类。
        Student stuObject = new Student();
        Class stuForObject = stuObject.getClass();
        System.out.println(stuForObject);//class com.ergou.springboot.entity.Student

        //1.2获取Class类型的对象
        Class clazzforClass = Student.class;
        System.out.println(clazzforClass);//class com.ergou.springboot.entity.Student

        //1.3 通过Class的静态方法获取(在jdbc、框架阶段,这个方法用的最多)
        String className = "com.ergou.springboot.entity.Student";
        Class clazzForStaticMethod = Class.forName(className);
        System.out.println(clazzForStaticMethod);//class com.ergou.springboot.entity.Student

        //1.4 通过类的加载器获取
        ClassLoader classLoader = this.getClass().getClassLoader();
        //this 代表当前类的实例,也就是com.ergou.spirngboot.ExampleApplicationTests@1c33c17b
        System.out.println(this);
        Class clazzForClassLoader = classLoader.loadClass(className);
        System.out.println(clazzForClassLoader);//class com.ergou.springboot.entity.Student

//=====================2.获取共有单个属性、共有多个属性、私有单个属性、全部属性=======================================

        //2.通过无参构造器创建对象,怎么证明调用的是无参构造?我在无参构造器中添加一个输出
        //调用无参构造方法的必要条件:1.类中必须要有空参构造方法 2.构造器的权限修饰符权限要足够
        Student stu = (Student) clazzForStaticMethod.newInstance();//我是Student的无参构造,我被调用了。。。
        System.out.println(stu);// Student{age=null, name='null', gender='null'}
        //2.1 通过有参构造器创建对象 public修饰的getConstructor(),参数是属性类型的字节码文件, private修饰的用
        // getDeclaredConstructor(),并开启权限 havePrivateParamStudent.setAccessible(true);参数是属性类型的字节码文件
        Constructor haveParamStudent = clazzForStaticMethod.getConstructor(Integer.class,String.class,String.class);
        Student student = (Student)haveParamStudent.newInstance(18,"程二狗","男");
        System.out.println("我是有参public构造器构造的对象:"+student);
        //我是public有参构造器构造的对象:Student{age=18, name='程二狗', gender='男'}

        //把构造器的属性public 变成private 开启下面的代码
//        Constructor havePrivateParamStudent = clazzForStaticMethod.getDeclaredConstructor(Integer.class,String.class,String.class);
//        havePrivateParamStudent.setAccessible(true);
//        Student student = (Student) havePrivateParamStudent.newInstance(18,"程二狗","男");
//        System.out.println("我是有参private构造器构造的对象:"+student);
         //我是有参private构造器构造的对象:Student{age=18, name='程二狗', gender='男'}

//======================3.通过反射调用运行是类的指定对象========================================================

        //3.1 getField只能获取public修饰的属性,无需开启修改权限(单个属性)
        Field publicFileld = clazzForStaticMethod.getField("gender");
        //3.1.2 第一个参数:要赋值的对象,第二参数:“gender”这个属性需要赋什么值
        publicFileld.set(stu, "男");
        System.out.println(stu);//Student{age=null, name='null', gender='男'}
        //3.1.3 getFields只能获取public修饰的属性(多个属性)
        Field[] publicFilelds = clazzForStaticMethod.getFields();
        for (Field fileld : publicFilelds) {
            System.out.println(fileld);//tips:能够获取父类和子类的public修饰的属性,输出如下:
            //public java.lang.String com.ergou.springboot.entity.Student.gender
            //public java.lang.String com.ergou.springboot.entity.Person.job
        }

        //3.2 getDeclaredField 暴力反射,任何权限修饰的都可以获取
        Field privateFileld = clazzForStaticMethod.getDeclaredField("age");
        //3.2.1 必须开启可修改权限,才能赋值成功
        privateFileld.setAccessible(true);
        //3.2.2 第一个参数:要赋值的对象,第二参数:“age”这个属性需要赋什么值
        privateFileld.set(stu, 20);
        //3.2.2 getFields只能获取public修饰的属性(多个属性)
        Field[] allFilelds = clazzForStaticMethod.getDeclaredFields();
        for (Field allFileld : allFilelds) {//tips::能够获取父类和子类所有申明的属性,输出如下:
            System.out.println(allFileld);
            //public java.lang.String com.ergou.springboot.entity.Student.gender
            //public java.lang.String com.ergou.springboot.entity.Person.job
            //private java.lang.Integer com.ergou.springboot.entity.Student.age
            //java.lang.String com.ergou.springboot.entity.Student.name
            // public java.lang.String com.ergou.springboot.entity.Student.gender
        }
        for (Field allFileld : allFilelds) {
            System.out.println(allFileld.getName());//age name gender 只能获取子类的属性名称,
            //不能获取父类的属性名称
        }

//=============== 4.通过反射调用运行时类的指定方法===============================================================

        //4.1 公有方法,无参数
        Method publicNoPramaMethod = clazzForStaticMethod.getMethod("showMygenderPublicNoParam");
        publicNoPramaMethod.invoke(stu);//程二狗的性别为男

        //4.2 公有方法,有参数
        Method publicHavePramaMethod = clazzForStaticMethod.getMethod("getPublicMessagePublicHaveParam", String.class);
        String mess = (String) publicHavePramaMethod.invoke(stu, "陈雪峰");
        System.out.println(mess);//陈雪峰, hello!

        //4.3 私有方法,无参数
        Method privateNoPramaMethod = clazzForStaticMethod.getDeclaredMethod("seeMyAgePrivateNoParam");
        privateNoPramaMethod.setAccessible(true);
        privateNoPramaMethod.invoke(stu);//我是程二狗,我今年18岁了
        publicNoPramaMethod.invoke(stu);//程二狗的性别为男

        //4.4 私有方法,有参数
        Method privateHavePramaMethod = clazzForStaticMethod.getDeclaredMethod("getMyFriendPrivateHaveParam",Integer.class, String.class);
        privateHavePramaMethod.setAccessible(true);
        String mess1 = (String) privateHavePramaMethod.invoke(stu, 21,"陈雪峰");
        System.out.println(mess1);//我的朋友叫陈雪峰,他今年21岁了
    }

4.2.2 获取注解、返回值类型、方法名、形参名、异常

/**
     * 获取注解、返回值类型、方法名、形参名、异常
     */
    @Test
    public void  reflectionTestOther(){
        Class clazz = Student.class;
        Method[] methods1 =  clazz.getDeclaredMethods();
        for (Method method : methods1) {
            //1.获取注解
            Annotation[] anono=method.getAnnotations();
            for (Annotation annotation : anono) {
                System.out.println(annotation);
                //@com.ergou.springboot.entity.MyAnnotation(value=我是public方法的注解)
                //@com.ergou.springboot.entity.MyAnnotation(value=我是private方法的注解)
            }
            //2.返回值类型
            Class returnType = method.getReturnType();
            System.out.println(returnType.getName()+"");
            //java.lang.String
            //int
            //java.lang.String
            //void
            //java.lang.String
            //void
            //void
            //void
            //void
            //java.lang.String
            //java.lang.Integer
            //java.lang.String

            //3.获取方法名
            System.out.println(method.getName());
            //toString
            //compareTo
            //getName
            //setName
            //showMygenderPublicNoParam
            //getPublicMessagePublicHaveParam
            //setAge
            //setGender
            //seeMyAgePrivateNoParam
            //getMyFriendPrivateHaveParam
            //getGender
            //getAge

            //4.获取形参名
            Class[] paramters=method.getParameterTypes();
            for (int i = 0;i < paramters.length;++i) {
                System.out.println(paramters[i]+"args_"+i);
                //class java.lang.Objectargs_0
                //class java.lang.Stringargs_0
                //class java.lang.Integerargs_0
                //class java.lang.Stringargs_0
                //class java.lang.Stringargs_0
                //class java.lang.Integerargs_0
                //class java.lang.Stringargs_1
            }
           //5.获取异常
            Class[] exps=method.getExceptionTypes();
            for (Class exp : exps) {
                System.out.println(exp);
                //class java.lang.Exception
                //class java.lang.ArrayIndexOutOfBoundsException
            }
        }
    }

小结:

===================================以上是重点===========================================

扩展1:获取属性的各部分内容:1.权限修饰符 2.变量类型 3.变量名

  /**
     * 获取属性的各部分内容
     * 1.权限修饰符 2.变量类型 3.变量名
     */
    @Test
    public void reflectionTestFiled() throws Exception {
        Class clazz = Student.class;
        Field[] field = clazz.getDeclaredFields();
        for (Field f : field) {
           //1.获取每个权限的修饰符
            int i = f.getModifiers();
            System.out.println(i);
            System.out.println(Modifier.toString(i));
            //2.获取属性类型
            System.out.println(f.getType());
            //3.获取变量名
            System.out.println(f.getType().getName());
            //输出如下
            //----------第一次循环---------
            //2
            //private
            //class java.lang.Integer
            //java.lang.Integer

            //----------第二次循环---------
            //0
            //     (空格代表default)
            // class java.lang.String
            //java.lang.String

            //--------第三次循环-----------
            //1
            // public
            //class java.lang.String
            //java.lang.String
        }
    }

扩展2:获取运行时类的方法

/**
     * 获取运行时类的方法
     */
    @Test
    public void reflectionTestMethod(){
        Class clazz = Student.class;
        //***:getMethods() 获取运行时类及其父类中声明为public的方法
        Method[] methods =  clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("============我是分割线==========");
        //public java.lang.String com.ergou.springboot.entity.Student.toString()
        //public int com.ergou.springboot.entity.Student.compareTo(java.lang.Object)
        //public java.lang.String com.ergou.springboot.entity.Student.getName()
        //public void com.ergou.springboot.entity.Student.setName(java.lang.String)
        //public java.lang.String com.ergou.springboot.entity.Student.getPublicMessagePublicHaveParam(java.lang.String)
        //public void com.ergou.springboot.entity.Student.showMygenderPublicNoParam()
        //public void com.ergou.springboot.entity.Student.setGender(java.lang.String)
        //public void com.ergou.springboot.entity.Student.setAge(java.lang.Integer)
        //public java.lang.String com.ergou.springboot.entity.Student.getGender()
        //public java.lang.Integer com.ergou.springboot.entity.Student.getAge()
        //public void com.ergou.springboot.entity.Person.setJob(java.lang.String)
        //public void com.ergou.springboot.entity.Person.breathing()
        //public java.lang.String com.ergou.springboot.entity.Person.getJob()
        //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()

        //***:getDeclaredMethods()获取运行时类本生声明的所有方法,不能获取父类的
        Method[] methods1 =  clazz.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }
        //public java.lang.String com.ergou.springboot.entity.Student.toString()
        //public int com.ergou.springboot.entity.Student.compareTo(java.lang.Object)
        //public java.lang.String com.ergou.springboot.entity.Student.getName()
        //public void com.ergou.springboot.entity.Student.setName(java.lang.String)
        //public java.lang.String com.ergou.springboot.entity.Student.getPublicMessagePublicHaveParam(java.lang.String)
        //public void com.ergou.springboot.entity.Student.showMygenderPublicNoParam()
        //public void com.ergou.springboot.entity.Student.setGender(java.lang.String)
        //public void com.ergou.springboot.entity.Student.setAge(java.lang.Integer)
        //private void com.ergou.springboot.entity.Student.seeMyAgePrivateNoParam()
        //java.lang.String com.ergou.springboot.entity.Student.getMyFriendPrivateHaveParam(java.lang.Integer,java.lang.String)
        //public java.lang.String com.ergou.springboot.entity.Student.getGender()
        //public java.lang.Integer com.ergou.springboot.entity.Student.getAge()
    }

扩展3:获取所有的构造器

   /**
     *  获取所有的构造器
     */
    @Test
   public  void getAllConstructorTest(){
       Class clazz=Student.class;
       //clazz.getDeclaredConstructors() 只能获取本类的所有构造器
       Constructor[] constructors = clazz.getDeclaredConstructors();
       for (Constructor constructor : constructors) {
           System.out.println(constructor);
           //public com.ergou.springboot.entity.Student()
           //private com.ergou.springboot.entity.Student(java.lang.Integer,java.lang.String,java.lang.String)
       }
   }

扩展4:获取运行时的父类、获取父类的泛型、获取实现的借口

 /**
     *  获取运行时的父类、获取父类的泛型、获取实现的借口
     */
    @Test
    public  void getOthersTest() {
        //获取运行时的父类
        Class clazz=Student.class;
        Type type = clazz.getGenericSuperclass();
        System.out.println(type);//com.ergou.springboot.entity.Person<java.lang.String>

        //获取父类的泛型
        ParameterizedType param = (ParameterizedType)type;
        Type[] types = param.getActualTypeArguments();
        //先转型成一个Class类型,在调用其方法
        System.out.println(((Class)types[0]).getName());// //java.lang.String

        //获取实现的接口
        Class[] interfaces = clazz.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println(anInterface);
            //interface java.lang.Comparable
            //interface com.ergou.springboot.entity.MyInterface
        }
        //获取所在的包
        Package pack = clazz.getPackage();
        System.out.println(pack);//package com.ergou.springboot.entity

        //获取注解(类上的注解)
        Annotation[] annos = clazz.getAnnotations();
        for (Annotation anno : annos) {
            System.out.println(anno);//@com.ergou.springboot.entity.MyAnnotation(value=我是类的注解)
        }
    }

通过这一节的学习,想必你对java的反射机制有了更好的理解,下一节我会为大家带来Java反射的重要运用之一:动态代理

代码百度网盘:链接:https://pan.baidu.com/s/1vrMqoqufUXqs4NQh_XRPCw
提取码:dw93

上一篇 下一篇

猜你喜欢

热点阅读