Java反射机制

反射:很容易想到“正射”,其实对于我们通过new 类名()创建对象的方式就相当于是正射,我们在编译期就已知对象的类型的情况下创建出该类型的对象。那么“反射”就是在编译期我们不确定要创建的对象类型,还要创建出该对象的操作。反射创建对象方式的类型只有在运行期才能确定。

首先我们要了解到反射机制中的一个类Class,Class类封装了相应类的类型信息,诸如(类名、保命、父类、方法、实现的接口、成员变量等),它也是我们开启后续反射操作的一个入口。

一、Class类

Class类是由final修饰,并私有了构造方法,我们无法通过new关键字调用构造方法完成Class对象的创建。那我们如何得到一个类的Class对象,一共有3种方式:

  1. 类名.class(访问类中的静态成员变量class)
  2. 实例对象.getClass()
  3. Class.forName("类的全限定名")【该方式用的较多】

通过对两次获取到的String类的Class对象进行内存地址的比较结果为true,也就是我们拿的是同一个Class对象,这是因为Class对象是通过JVM创建出来的,在某个类第一次被使用时会被JVM将其字节码文件从磁盘移到内存中运行,并为该类创建出它的Class对象,后续再次使用到这个类时就无需再进行加载,因此两次拿到的Class对象是同一个。

Class类中封装了大量的类型信息,我们可以通过Class对象调用相应的方法获取信息

下面是通过Class对象获取String类型的一部分信息:

Class对象是进行反射的基础,Constructor,Field,Method这些对象的创建都以Class对为基础

我们在正常的程序中创建对象:例如Person p=new Person();

通过new关键字调用Person类的无参构造完成Person对象的创建

下面我们了解Constructor类是如何通过反射的方式创建对象

二、Constructor类

Constructor类是对构造方法进行封装,通过反射创建对象我们首先需要获取构造方法

Class cls=String.class;

1、Constructor[ ] getConstructors():获取所有public修饰的构造方法(包含父类)

Constructor[]cons=cls.getConstructors();

2、Constructor[ ] getDeclaredConstructors():获取当前类所有的构造方法(不包含继承来的)

Constructor[]cons=cls.getDeclaredConstructors();

3、Constructor getConstructor():通过传入的参数个数、参数类型对应不同的构造方法

以String为例,不传参获取的是它的无参构造方法

Constructor con=cls.getConstructor();

传入一个String.class,获取的是一个为String类型参数的构造方法

Constructor con=cls.getConstructor(String.class);

我们已经得到了Construcror对象,再调用newInstance()即可创建String类型的对象,该方法的参数是一个动态参数,该参数表示传入构造方法的值,可以是0个或多个。

只有当要用某个无参构造方法创建该类对象时,可以省略创建Constructor类对象的这个过程:

        直接使用Class对象.newInstance()

public class Demo04 {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class cls=Example.class;

//        Constructor[]cons=cls.getConstructors();
//        System.out.println(cls.getSimpleName()+"所有public修饰的构造函数:");
        Constructor[]cons=cls.getDeclaredConstructors();
        System.out.println(cls.getSimpleName()+"所有的构造函数:");
        for(Constructor con:cons){
            System.out.println(con);
        }
        System.out.println("----------------------------");
       
        Example ep1=(Example) cls.newInstance();
        System.out.println(ep1);
        Example ep2=(Example) cls.getConstructor().newInstance();
        System.out.println(ep2);

        Example ep3=(Example) cls.getConstructor(int.class).newInstance(7);
        System.out.println(ep3);

        Example ep4=(Example) cls.getConstructor(int.class,double.class).newInstance(7,9);
        System.out.println(ep4);

        Constructor con1=cls.getDeclaredConstructor(float.class);
        con1.setAccessible(true);
        Example ep5=(Example) con1.newInstance(5.6f);
        System.out.println(ep5);

        Constructor con2=cls.getDeclaredConstructor(String.class);
        Example ep6=(Example) con2.newInstance("basiguangnian");
        System.out.println(ep6);

    }
}

class Example{

    private Example(float n){
        System.out.printf("调用(private)Example的构造 n=%f\n",n);
    }

    protected Example(String str){
        System.out.printf("调用(protected)Example的构造方法 str=%s\n",str);
    }

    public Example(){
        System.out.println("调用Example的无参构造");
    }

    public Example(int a){
        System.out.printf("调用Example的有参构造 a=%d\n",a);
    }

    public Example(int a,double b){
        System.out.printf("调用Example的有参构造 a=%d,b=%f\n",a,b);
    }

}

Constructor对象.newInstance()完成对象的创建,但创建出的对象为Object类型,若需要获取指定类型的对象,做向下转型即可。

如果构造方式是private修饰,直接调用newInstance()会抛出IllegalAccessException,因为类中定义为private的方法仅限于该类的内部使用,这体现了类的封装性,要想通过反射的方式调用私有构造方法创建对象,Constructor对象.setAccessiable(true),设置该方法为可访问的,但这样破坏了封装性。

三、Field类

Field类:对类中的成员变量进行了封装,每一个成员变量都会被封装为一个Field对象

Field类中的方法:

        getModifiers():获取字段的访问权限(以整型数字展示)

为了获取访问修饰符的名称,可以使用Modifier类的静态方法toString()进行转换

        getType():字段的类型

getName():字段的名称 获得所有public修饰的字段(包括父类)

Field[] fields = cls.getFields(); 获得所有定义的字段(当前类)

Field[] fields = cls.getDeclaredFields();

通过字段名获取指定名称的公共成员字段Field对象 Field field=cls.getField("bookName");

以上方法都可以获取Filed对象,我们可以对Field对象进行setXXX()或getXXX()操作

field.set(对象,值):设置对象的该字段值

filed.get(对象):获取对象的该字段值为了方便观察,以下类中只展示了成员变量

class Book extends Product{
    public String bookName;//图书名称
    public int stock;//库存
    private float sale;//折扣
    private boolean isShelf;//是否上架
}
class Product{
    public double price;
}
public class Demo07 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        Class cls=Book.class;
//        获得所有public修饰的字段(包括父类)
        Field[] fields = cls.getFields();
//        获得所有定义的字段(当前类)
//        Field[] fields = cls.getDeclaredFields();
        for(Field f:fields){
            System.out.println(f.getModifiers());
            System.out.println("成员变量的访问修饰符:"+ Modifier.toString(f.getModifiers()));
            System.out.println("成员变量的类型:"+f.getType());
            System.out.println("成员变量的名称:"+f.getName());
            System.out.println();
        }
        Object obj=cls.newInstance();
        Field bookName=cls.getField("bookName");
        bookName.set(obj,"三毛");
        System.out.println(bookName.get(obj));
        System.out.println(obj);

    }
}

四、Method类

Method类:对类中的方法进行了封装,也就是每一个方法都会被封装为一个Method对象。

以下方法可以获取类中的Method对象

Method []getMethods():得到所有public修饰的方法(包括从父类)

Method []getDeclaredMethods():得到本类中所有的方法(不包括继承的)

Method getMethod(方法名,方法参数字节码【无参数时null】):

        按名称得到某个特定的public方法(包括从父类或接口继承的方法)

Method getDeclaredMethod(方法名,方法参数字节码):

        按名称得到某个特定的方法(不包括继承的方法)

得到某个方法对应的Method对象后,需要调用invoke()来在某个对象上执行该方法

Method对象.invoke(方法作用对象,具体实际参数)

public class Demo11 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //计算指定数值的位数
        int r = (int)Math.log10(16457894)+1;
        System.out.println(r);
        Class cls=Math.class;
        Method method=cls.getMethod("log10",double.class);
        double ret=(Double) method.invoke(null,16457894);
        int res=(int)ret+1;
        System.out.println(res);
    }
}

Math.log10()+1用于判断当前数值的位数,两种方法的调用的运行结果均为8

当该Method对象表示的方法是static修饰时,作用对象为null

在执行一个私有访问权限的方法时,调用invoke前要执行setAccessible设置为true

了解了反射后,我们将正常的操作方式与反射机制进行对比,总结反射机制的优缺点:

优点 :在不知道具体类型的情况下,就可以对该类进行某些操作,使程序的灵活性更高,可扩展性更强

缺点 : 1、JVM在解释执行字节码文件时才能知道相关信息,效率较低

            2、通过反射机制穿透了程序原本的封装性,能够操作私有的属性或方法,对于程序来说是不安全的

小结:

  •      要进行反射操作必须先拿到这个类对应的Class对象
  •      反射方式创建对象的过程:Class--->Constructor--->某个类的对象
  •      对于private的数据需要使用getDeclaredXXX()获取返回相应的类型对象,要通过该对象进行某些操作,需设置当前对象的可访问性,setAccessible(true)

        

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值