String类不可变以及不可变类总结

      String类在java中是immutable(不可变),因为它被关键字final修饰。当String实例创建时就会被初始化,并且以后无法修改实例信息。String类是工程师精心设计的艺术品。

一、String为什么不可变?

       要了解String类创建的实例为什么不可变,首先要知道final关键字的作用:final的意思是“最终,最后”。final关键字可以修饰类、方法、字段。修饰类时,这个类不可以被继承;修饰方法时,这个方法就不可以被覆盖(重写),在JVM中也就只有一个版本的方法--实方法;修饰字段时,这个字段就是一个常量。

        查看java.lang.String方法时,可以看到:

/**
 * The String class represents character strings. All string literals in Java programs, such as "abc",
 * are implemented as instances of this class.
*/
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
   ...
}

"The String class represents character strings"意思是String类表示字符串;String类被关键字final修饰,可以说明它不可被继承;从"private final char value[]"可知,String本质上是一个char数组

        String类的成员字段value是一个char[]数组,而且也是用final关键字修饰,被final关键字修饰的字段在创建后其值是不可变的,但也只是value这个引用地址不可变,可是Array数组的值却是可变的,Array数组数据结构如下图所示:

从图中可以看出,Array变量只是栈上(stack)的一个引用,数组中的数据存储在堆上(heap)。String类里的value是用final修饰,只能说在栈上(stack)这个value的引用地址不可变,可没说堆里的Array本身数据不可变。看下面这个例子:

final int[] value = {1,2,3,4,5};
int otherValue = {6,7,8,9,10};
value = otherValue;//编译报错

value是被final关键字修饰的,编译器不允许把value指向堆另外一个地址;但如果直接对数组元素进行赋值,就允许;如下面这个例子:

final int[] value  = {1,2,3,4,5};
value[0] = 0;

        所以说String是不可变,在后面所有的String方法里没有去动Array中的元素,也没有暴露内部成员字段。private  final char value[],private的私有访问权限的作用都比final大。所以String是不可变的关键都是在底层实现的,而不是一个简单的final关键字。

二、String类实例不可变的内存结构图

        String类实例不可变很简单,如下图所示是String类实例不可变的内存结构图:

 

有一个字符串s的值为"abcd",它再次被赋值为"abcdef",不是在原堆的地址上修改数据,而是重新指向一个新的对象,新的地址。

三、字符串常量池

        字符串常量池是方法区(Method Area)中一个特殊的存储区域。当一个字符串被创建时,如果这个字符串的值已经存在String pool中,就返回这个已经存在的字符串引用,而不是创建一个新的对象。下面的代码只会在堆中创建一个对象:

String name="abcd";
String userName="abcd";

这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基础的一个必要条件。

四、String类不可变有什么好处?

    最简单的就是为了安全和效率。从安全上讲,因为不可变的对象不能被改变,他们可以在多个线程之间进行自由共享,这消除了进行同步的要求;从效率上讲,设计成final,JVM才不用对相关方法在虚函数表中查询,而是直接定位到String类的相关方法上,提高执行效率;总之,由于效率和安全问题,String被设计成不可变的,这也是一般情况下,不可变的类是首选的原因。

五、不可变类

        不可变类只是它的实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例时就提供,并在对象 的整个生命周期内固定不变。String、基本类型的包装类、BigInteger和BigDecimal就是不可变得类。

        为了使类成为不可变,必须遵循以下5条规则:①不要提供任何会修改对象状态的方法②保证类不会被扩展③使所有的域都是final④使所有的域都成为私有的⑤确保 对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

六、不可变类的优点和缺点

        不可变类实例不可变性,具有很多优点。①不可变类对象比较简单。不可变对象可以只有一种状态,即被创建时的状态。②不可变对象本质上是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时,它们不会遭到破坏。实际上,没有任何线程会注意到其他线程对于不可变对象的影响。所以,不可变对象可以被自由地分配。“不可变对象可以被自由地分配”导致的结果是:永远不需要进行保护性拷贝。③不仅可以共享不可变对象,甚至也可以共享它们的内部信息。④不可变对象为其他对象提供了大量的构件。如果知道一个复杂对象内部的组件不会改变,要维护它的不变性约束是比较容易的。

        不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。创建这种对象的代价很高。

七、如何构建不可变类?

        构建不可变类有两种方式:用关键字final修饰类让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来替代公有的构造器

        为了具体说明用静态工厂方法来替代公有的构造器,下面以Complex为例:

//复数类
public class Complex{
    //实数部分
    private final double re;
    //虚数部分
    private final double im;
    //私有构造器
    private Complex(double re,double im){
        this.re = re;
        this.im = im;
    }
    //静态工厂方法,返回对象唯一实例
    public static Complex valueOf(double re,double im){
        return new Complex(re,im);
    }
    ...
}

      不可变的类提供一些静态工厂方法,它们把频繁请求的实例缓存起来,从而当现在实例符合请求的时候,就不必创建新的实例。使用这样的静态工厂方法使得客户端之间可以共享现有的实例,而不是创建新的实例,从而减低内存占用和垃圾回收的成本。

       总之,使类的可变性最小化。不要为每个get方法编写一个相对应的set方法,除非有很好的理由要让类成为可变的类,否则就应该是不可变的。如果有些类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。不可变的类有很多优点,但唯一的缺点就是在特定的情况下存在潜在的性能问题。

 

PS:静态工厂方法是什么?

静态工厂方法只是一个返回类的实例的静态方法,如下面是一个Boolean的简单实例。这个方法将boolean基本类型值转换成一个Boolean对象引用。

public static Boolean valueOf(boolean b){
    return b?Boolean.TRUE?Boolean.FALSE;
}

静态工厂方法相对于构造器来说,具有很多优势:

 

创建的方法有名字

不必在每次调用它们的时候都创建一个新的对象

可以返回原返回类型的任何子类的对象。这样我们在选择返回对象的类时就有更大的灵活性,这种灵活性的一种应用是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁,这项技术适用于基于接口的框架。

在创建参数化类型实例时,它们使代码变得更加简洁。编译器可以替你找到类型参数,这被称为类型推导。如下面这个例子

public static<k,v> HashMap<k,v> newInstance(){
    return new HashMap<k,v>();
}

静态工厂方法也有缺点:

类如果不含公有的或者受保护的构造器,就不能被子类化。对于公有的静态工厂方法所返回的非公有类也同样如此。

它们与静态方法实际上没有什么区别

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。结合实际情况,再做选择。

 

 

参考文章:Why String is immutable in Java?

                  在java中String类为什么要设计成final?程序员--知乎

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值