改变一个不可变的类-反射

目录

1、不可变类 String对象是否真的不可变?

2、什么是反射?

3、反射能做什么?

4、反射的使用步骤

4.1、获取Class对象的三种方式 

4.2、实例化实例对象

4.3、访问或执行

5、反射的api应用实例

1、不可变类 String对象是否真的不可变?

不可变类的特性:
  • (1). String类被 final修饰,不可继承;
  • (2). String内部所有成员都设置为不可变 final和变为 私有变量private修饰;
  • (3). 不存在value的 setter;
  • (4). 当传入可变数组value[]时,进行深拷贝copy而不是直接将 value[]复制给内部变量.
  • (5). 获取value时不是直接返回对象引用,而是返回 对象的copy.
虽然String对象通过各种机制保证其成员变量不可改变,但也不是说一定就不可变,我们依然可以使用 反射来改变,看一个实例:
public class StringTestDemo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        //创建字符串"Hello World", 并赋给引用s
        String s = "Hello World";
        System.out.println("修改前:s = " + s); //Hello World

        //获取String类中的value字段
        Field valueFieldOfString = String.class.getDeclaredField("value");
        //改变value属性的访问权限
        valueFieldOfString.setAccessible(true);

        //获取s对象上的value属性的值
        char[] value = (char[]) valueFieldOfString.get(s);
        //改变value所引用的数组中的第6个字符
        value[5] = '_';
        System.out.println("反射修改后:s = " + s);  //Hello_World
    }
}

打印结果为:

修改前:s = Hello World
反射修改后:s = Hello_World

发现String的值已经发生了改变。也就是说,可以通过反射是可以修改所谓的“不可变”对象的。

2、什么是反射?

    java的反射机制是在运行状态中,对于任意一个类首先获取其字节码,然后通过构造函数生成一个java对象实例,对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制这也是Java被视为动态语言的关键特性,源码中用的非常多。

3、反射能做什么?

    我们知道反射机制允许程序在运行时获取class字节码文件,进而访问或者改变fields值或执行调用method方法。
  • class字节码文件:得到任何一个已知名称的class类的内部信息;
  • constructor:得到构造函数;
  • instance:生成类的实例对象;
  • fields(属性);包括包括其modifiers(修饰符);
  • methods(方法);
  • method.invoke() 执行目标方法;

4、反射的使用步骤

  • (1). 获得Class对象,就是获取到指定的名称的字节码文件对象;
  • (2). 实例化对象,获得类的属性、方法及方法参数或构造函数;
  • (3). 访问改变属性值、执行调用目标方法;

4.1、获取Class对象的三种方式 

    当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,类的.class信息对于JVM来说必须是可获取的,可以是本地机器上,网络或者磁盘。
(1)、 getClass() 通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
Class<?> class = object.getClass();

(2)、Object.class 每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。

Class<?> class = Student.class;

ps: 前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。  

(3)、 forName() 使用的Class类中的方法,静态的 forName方法。指定类名,获取类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
Class clazz = Class.forName("com.makaruina.reflect.Person");

4.2、实例化实例对象

  获取了字节码文件对象后,然后就需要创建类的实例对象。
4.2.1. 调用指定带参数的构造函数,然后通过该构造器对象的newInstance(实际参数) 进行对象的初始化。
Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class, String.class);
Object obj = constructor.newInstance("king",1,"java");

4.2.2. 直接使用了Class类调用无参构造函数生成实例对象;

//1、获取字节码;
Class clazz = Class.forName("com.king.Reflection.Person");
//2、生成实例对象;
Object obj = clazz.newInstance();

4.3、访问或执行

获取类中的方法,属性,构造函数,进行函数的调用。
classType.getName()    //获取类的完成名字,全路径名:
getFields():          //获得类的public类型的属性。
getDeclaredFields():  //获得类的所有属性。
getMethods():获得类的public类型的方法。
getDeclaredMethods(): //获得类的所有方法
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。

5、反射的api应用实例

/*
* 反射的基本步骤:
*   1. 获得Class对象,就是获取到指定的名称的字节码文件对象。
*   2. 实例化对象,获得类的属性、方法或构造函数。
*   3. 访问属性、调用方法、调用构造函数创建对象。
*/
public class ReflectionTest {

    //getClass
    public static void getClassTest() throws Exception {
        Son son = new Son("king", 29, "hust");
        //todo 得到class的三种方式
        //1、通过每个对象都具备的方法getClass来获取。通过对象调用来getClass来获取,通常用于传过来的是一个Object,而我不知道你具体的是什么类
        //弊端:必须要创建该类对象,才可以调用getClass方法。
        Class aClass = son.getClass();
        //2、直接通过类名来获取class,任何一个类都有一个隐含的静态成员变量class,该方法安全可靠,程序性能更高;
        // 弊端:必须先明确类
        Class bClass = Son.class;
        //3、使用Class类中的方法,forName(),指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名传入即可。
        Class cClass = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
        //todo 一个jvm只有一个class实例,我们对上面的aclass ,bclass, 和cClass进行equal对比,其地址都是一样的
        System.out.println(aClass.equals(bClass) ? bClass.equals(cClass) : "not equal"); //true
        //获得类完整的名字
        System.out.println(cClass.getName());
    }
    //获取类中所有的方法。
    public static void getMethodAll() throws Exception {
        Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
        //获取的是该类中所有的公有方法,静态的,包含继承和实现的方法,不包含私有的。
        Method[] methodsAll = clazz.getMethods();
        for (Method method : methodsAll) {
            System.out.println("所有的方法,包含继承和实现的方法:" + method.getName());
        }
        //获取的是该类中的所有方法,包含私有方法,但不包含继承的方法。
        Method[] methodsNotContainExtent = clazz.getDeclaredMethods();
        methodsNotContainExtent = clazz.getDeclaredMethods();
        for (Method method : methodsNotContainExtent) {
            System.out.println("所有的方法,不包含继承的方法:" + method.getName());
        }
    }
    //获取指定方法;
    public static void getMethodAssign() throws Exception {
        //1、加载类;
        Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");

        //2、获取指定名称的方法。
        Method method = clazz.getMethod("goToSchool", String.class);

        //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
        //3、 创建实例
        Son obj = (Son) clazz.newInstance();
        //4、调用
        method.invoke(obj, "#############king");//执行一个方法
    }

    //想要运行私有方法。
    public static void getMethodPrivate() throws Exception {
        //1、获取字节码
        Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");

        //todo 想要获取私有方法。必须用getDeclearMethod();
        //2、获取指定方法
        Method method = clazz.getDeclaredMethod("play", String.class);
        // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
        method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
        //3、创建实例,调用默认的构造函数
        //Object obj = clazz.newInstance();
        //TODO 3、调用带参数的构造函数
        Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class, String.class);
        Object obj = constructor.newInstance("kongyin",30,"HUST");
        //4、调用方法
        method.invoke(obj, "带参数的构造函数 #############king");//执行私有方法
    }

    //反射静态方法。
    public static void getMethodStatic() throws Exception {
        //1、获取字节码
        Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
        //2、得到方法
        Method method = clazz.getMethod("staticMethod", null);
        //3、获取实例
        Object obj = clazz.newInstance();
        //4、执行方法
        method.invoke(obj);//执行静态方法
    }

    //todo 获取字段属性
    public static void getField() throws Exception {
        Class c2 = Class.forName("com.beijing.king.springtest.service.Reflection.Son");

        //获得指定的属性
        Field f1 = c2.getField("course");
        System.out.println("指定的son public属性:" + f1);

        //获得指定的私有属性
        Field f2 = c2.getDeclaredField("school");
        //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
        f2.setAccessible(true);
        System.out.println("指定的私有属性school: " + f2);

        //获得类的public类型的属性。
        Field[] fields = c2.getFields();
        for (Field field : fields) {
            System.out.println("public 的属性字段:" + field.getName());//course
        }

        //获得类的所有属性。包括私有的
        Field[] allFields = c2.getDeclaredFields();
        for (Field field : allFields) {
            System.out.println("包括私有的字段:" + field.getName());//course school
        }
    }

    //todo 获取构造方法
    public static void getConstructor() throws Exception {

        Class c2 = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
        Constructor[] constructors = c2.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("包含共有的构造函数:" + constructor.toString());//public com.ys.reflex.Person()
        }
        //获取私有的构造方法
        Constructor[] constructorAlls = c2.getDeclaredConstructors();
        for (Constructor constructor : constructorAlls) {
            System.out.println("包含私有的构造函数:" + constructor.toString());//public com.ys.reflex.Person()
        }
    }
 
 
 
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
 
 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值