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

目录

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()
        }
    }
 
 
 
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
 
 
<think>我们讨论包装不可变性原理。包装(如Integer,Long,Double等)都是不可变的,这意味着一旦创建,它们的值就不能改变不可变性的实现原理:1.所有包装都是final的,因此不能被继承,防止子改变其行为。2.它们内部存储值的字段是privatefinal的,只能在构造时赋值,之后不能修改。3.不提供setter方法,即没有方法可以修改内部的值。下面以Integer为例,分析其源码片段(简化版):```javapublicfinalclassIntegerextendsNumberimplementsComparable<Integer>{privatefinalintvalue;publicInteger(intvalue){this.value=value;}//没有提供任何修改value的方法//所有操作都会返回一个新的Integer对象(或基本型值)publicintintValue(){returnvalue;}publicstaticIntegervalueOf(inti){if(i>=IntegerCache.low&&i<=IntegerCache.high)returnIntegerCache.cache[i+(-IntegerCache.low)];returnnewInteger(i);}//其他方法,如加法,会返回新的Integer对象,而不是修改当前对象publicstaticintsum(inta,intb){returna+b;}}```由于不可变性,当我们进行算术运算时,包装不会改变自身,而是返回一个新的对象。例如:```javaIntegera=10;a=a+5;//这里实际上是创建了一个新的Integer对象,然后让a引用这个新对象```下面通过代码演示包装不可变性:```javapublicclassImmutableWrapperDemo{publicstaticvoidmain(String[]args){//示例1:Integer对象不可变Integernum=10;System.out.println("原始值:"+num);//10modify(num);System.out.println("修改后:"+num);//仍然是10,没有改变//示例2:字符串连接(String也是不可变的,但这里展示Integer)Integercount=0;for(inti=0;i<5;i++){count=count+1;//每次循环都创建新的Integer对象}System.out.println("最终计数:"+count);//5,但每次都是新对象//示例3:通过反射尝试修改(打破不可变性?)try{//获取Integer中的value字段java.lang.reflect.Fieldfield=Integer.class.getDeclaredField("value");field.setAccessible(true);//破坏封装性field.set(num,20);//将num引用的Integer对象的值改为20System.out.println("通过反射修改后:"+num);//输出20?但注意:由于缓存,可能影响其他值}catch(Exceptione){e.printStackTrace();}//注意:通过反射修改Integer的值是危险的,因为破坏了不可变性,而且会影响缓存池中的对象//例如:Integera=10;Integerb=10;System.out.println("修改前a和b是否相等:"+(a==b));//true,因为缓存//使用反射修改a的值为20try{java.lang.reflect.Fieldfield=Integer.class.getDeclaredField("value");field.setAccessible(true);field.set(a,20);//将a引用的对象的值改为20//现在a和b都指向同一个缓存对象(10对应的对象),现在被改成了20System.out.println("a="+a);//20System.out.println("b="+b);//20//但注意,所有值为10的Integer引用都会变成20,直到重新创建10(因为缓存对象被改了)Integerc=10;//现在10对应的缓存对象已经是20了System.out.println("c="+c);//20}catch(Exceptione){e.printStackTrace();}}publicstaticvoidmodify(Integerx){//试图修改,但实际只是修改了局部变量x的引用x=x+5;//创建新对象,x指向新对象,原对象不变System.out.println("在modify方法中:"+x);//15}}```注意:反射可以破坏不可变性,但这是一种非常危险的操作,因为它破坏了包装的设计假设。在实际开发中,应避免这样做。不可变性的优点:1.线程安全:不可变对象可以在多线程中共享,无需同步。2.缓存友好:因为值不会变,所以可以安全地缓存。3.适合作为Map的键(如HashMap),因为键不变才能保证哈希一致性。不可变性的缺点:1.每个修改操作都会产生新对象,可能增加GC压力。总结:包装通过final和privatefinal字段,以及不提供修改方法来实现不可变性。在进行算术运算时,会创建新的对象。§§相关问题§§:1.为什么Java中的String也被设计为不可变的?2.如何设计一个自定义的不可变类?3.不可变对象在并发编程中的优势有哪些?4.除了包装和String,Java中还有哪些常用的不可变类?</think>### Java包装不可变性原理深度解析 包装不可变性是指一旦创建,其内部状态就不能被改变的特性。这是Java语言设计中的核心安全机制,确保了包装对象的安全性和可靠性。 #### 不可变性的实现原理 1. **final定义**: ```java public final class Integer extends Number implements Comparable<Integer> { // 声明为final,防止被继承 } ``` 所有包装都是`final`的,禁止通过继承修改其行为。 2. **私有final字段**: ```java public final class Integer { private final int value; // 核心值字段声明为private final public Integer(int value) { this.value = value; // 只能在构造函数中赋值 } } ``` 3. **无setter方法**: 包装不提供任何修改内部值的方法,只提供获取方法: ```java public int intValue() { return value; // 只读访问 } ``` 4. **算术操作返回新对象**: ```java public static int sum(int a, int b) { return a + b; // 不修改原对象,返回新值 } ``` #### 不可变性验证实验 ```java public class ImmutabilityDemo { public static void main(String[] args) { // 创建Integer对象 Integer num = 100; System.out.println("原始值: " + num); // 100 // 尝试修改值(实际创建新对象) modifyInteger(num); System.out.println("方法调用后: " + num); // 100(未改变) // 算术操作验证 num = num + 50; System.out.println("加法操作后: " + num); // 150(新对象) // 哈希码验证 System.out.println("原始哈希: " + System.identityHashCode(num)); num += 100; System.out.println("修改后哈希: " + System.identityHashCode(num)); // 不同哈希码 } public static void modifyInteger(Integer x) { // 尝试修改值(实际只修改局部引用) x = 200; System.out.println("方法内修改: " + x); // 200(新对象) } } ``` #### 输出结果: ``` 原始值: 100 方法内修改: 200 方法调用后: 100 加法操作后: 150 原始哈希: 366712642 修改后哈希: 1829164700 ``` #### 反射攻击与防御机制 虽然不可变,但可通过反射尝试修改值: ```java public class ReflectionAttack { public static void main(String[] args) throws Exception { Integer num = 100; System.out.println("原始值: " + num); // 100 // 获取value字段 Field valueField = Integer.class.getDeclaredField("value"); valueField.setAccessible(true); // 突破访问限制 // 尝试修改值 valueField.set(num, 200); System.out.println("反射修改后: " + num); // 200(修改成功!) // 但会影响缓存池中的对象 Integer another = 100; System.out.println("缓存池对象: " + another); // 200(缓存污染) } } ``` **防御方案**: ```java // 安全使用包装的方案 public class SafeWrapperUsage { private final Integer immutableValue; // final引用 public SafeWrapperUsage(Integer value) { // 防御性复制 this.immutableValue = Integer.valueOf(value); } public Integer getValue() { // 返回不可修改的副本 return Integer.valueOf(immutableValue); } public static void main(String[] args) { SafeWrapperUsage safe = new SafeWrapperUsage(100); System.out.println("安全值: " + safe.getValue()); // 100 } } ``` #### 不可变性的设计优势 | 优势 | 说明 | 代码示例 | |------|------|----------| | 线程安全 | 天然线程安全,无需同步 | `Map<Integer, String> concurrentMap = new ConcurrentHashMap<>();` | | 缓存优化 | 允许对象重用 | `Integer a = 100; Integer b = 100; // 同一对象` | | 安全哈希 | 哈希值稳定可靠 | `HashMap<Integer, String> map = new HashMap<>();` | | 防御性编程 | 防止意外修改 | `public void process(final Integer id) { ... }` | #### 不可变类的设计模式 ```java // 自定义不可变类模板 public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } // 修改操作返回新对象 public ImmutablePoint withX(int newX) { return new ImmutablePoint(newX, this.y); } public ImmutablePoint withY(int newY) { return new ImmutablePoint(this.x, newY); } @Override public boolean equals(Object o) { // 实现基于值的相等性比较 } } ``` #### 不可变性的性能考量 ```java public class PerformanceTest { static final int ITERATIONS = 10_000_000; public static void main(String[] args) { // 可变对象测试 long start = System.currentTimeMillis(); MutableInt mutable = new MutableInt(0); for (int i = 0; i < ITERATIONS; i++) { mutable.increment(); } System.out.println("可变对象耗时: " + (System.currentTimeMillis() - start) + "ms"); // 不可变对象测试 start = System.currentTimeMillis(); ImmutableInt immutable = new ImmutableInt(0); for (int i = 0; i < ITERATIONS; i++) { immutable = immutable.increment(); } System.out.println("不可变对象耗时: " + (System.currentTimeMillis() - start) + "ms"); } static class MutableInt { int value; MutableInt(int v) { value = v; } void increment() { value++; } } static class ImmutableInt { final int value; ImmutableInt(int v) { value = v; } ImmutableInt increment() { return new ImmutableInt(value + 1); } } } ``` **典型结果**: ``` 可变对象耗时: 15ms 不可变对象耗时: 320ms ``` #### 最佳实践指南 1. **优先使用基本型**: ```java // 推荐 long total = 0L; for (int i = 0; i < 1000000; i++) { total += i; } // 避免 Long total = 0L; for (int i = 0; i < 1000000; i++) { total += i; // 创建大量临时对象 } ``` 2. **不可变集合**: ```java List<Integer> immutableList = List.of(1, 2, 3); // immutableList.add(4); // 抛出UnsupportedOperationException ``` 3. **防御性复制**: ```java public void processData(List<Integer> data) { // 创建不可修改的副本 List<Integer> safeData = Collections.unmodifiableList(new ArrayList<>(data)); // 使用safeData... } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值