封装类为何是immutable 不可改变的

本文通过一个Java示例程序,探讨了基本数据类型与引用类型在方法传递过程中的区别,重点分析了AtomicInteger与Integer在共享和变更时的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 问题引入demo

package com.fqyuan.Wrapper;

import java.util.concurrent.atomic.AtomicInteger;

public class TestWrapper {

    public static void main(String[] args) {
        Foo f1 = new Foo();
        Foo f2 = new Foo();
        Foo f3 = new Foo();
        AtomicInteger i = new AtomicInteger(1);

        f1.setValue(i);
        i.incrementAndGet();
        f2.setValue(i);
        i.incrementAndGet();
        f3.setValue(i);

        Integer j = new Integer(1);
        f1.setItem(j);
        ++j;
        f2.setItem(j);
        ++j;
        f3.setItem(j);
        System.out.println(f1.getValue() + " " + f2.getValue() + " " + f3.getValue());
        System.out.println(f1.getItem() + " " + f2.getItem() + " " + f3.getItem());
    }
}

class Foo {
    /*
     * The primitive type are mutable, but they are not sharable- That's why no
     * 2 sections of code will ever be referring to the same int variable (they
     * are always pass by value). So you can change your copy and no one else
     * sees the change, and vice versa.
     */

    private AtomicInteger value;
    private Integer item;

    /*
     * If strings and wrapper classes are non-final, anybody can extend those
     * classes and write their own code to modify the wrapped primitive data.
     * So, in order to maintain data integrity, the variables which we are using
     * for data storage must be read-only. i.e., Strings and Wrapper classes
     * must be final & immutable and 'pass-by-reference' feature should not be
     * provided.
     */
    public void setValue(AtomicInteger value) {
        this.value = value;
    }

    public AtomicInteger getValue() {
        return value;
    }

    public Integer getItem() {
        return item;
    }

    public void setItem(Integer item) {
        this.item = item;
    }
}
//Running result:
3 3 3
1 2 3

为何上述结果是:对于AtomicInteger是相同的,而对于Integer类型的是不同的值呢?

2. 原因总结

下面原因部分来自于此处的总结。
1. 首先primitive数据类型本身不属于对象,故而讨论其是否mutable没有意义。
2. 其次,String不属于Java语言基本数据类型,它属于引用数据类型,即:它是属于一种Object。但它和Wrapper 类一样,都是不可改变的,所以有一些共性。
3. primitive数据类型是不可共享的,即:没有任何2块代码引用同一个int 变量值,Java语言特性:pass by value. 是以值的一份副本传递的,故而对于其所做的修改并不会对其他使用该值的函数可见。
4. 如果String和Wrapper 类 non-final的,那么,任何人都可以继承该类,并对类中数据wrapped primary data进行修改。所以,为了维持数据一致性,所以我们用于数据存储的数据类型必须是read-only的。也就是说:String & Wrapper类必须是 final & immutable的,“pass-by-reference”(注意Java中其实只有pass by value说法,但是对于引用数据的修改,其作用结果就像是传递了引用一样,其实传递的依然是值,只是这里的值是引用对象的copy) 特性不应当提供给该类数据。

<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("修改前ab是否相等:"+(a==b));//true,因为缓存//使用反射修改a的值为20try{java.lang.reflect.Fieldfield=Integer.class.getDeclaredField("value");field.setAccessible(true);field.set(a,20);//将a引用的对象的值改为20//现在ab都指向同一个缓存对象(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压力。总结:包装类通过finalprivatefinal字段,以及不提供修改方法来实现不可变性。在进行算术运算时,会创建新的对象。§§相关问题§§: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、付费专栏及课程。

余额充值