在java中String类为什么要设计成final?

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

我进行了重新排版,并且更换了其中的一个例子,让我们更好理解。

String很多实用的特性,比如说不可变性,是工程师精心设计的艺术品!艺术品易碎!用final就是拒绝继承,防止世界被熊孩子破坏,维护世界和平!

1. 什么是不可变?

String不可变很简单,如下图,给一个已有字符串"abcd"第二次赋值成"abcedl",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。


2. String为什么不可变?

翻开JDK源码,java.lang.String类起手前三行,是这样写的:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** String本质是个char数组. 而且用final关键字修饰.*/
    private final char value[];
	...
	...
}

首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。Array的数据结构看下图

也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的valuefinal修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,

final int[] value={1,2,3}
int[] another={4,5,6};
value=another;    //编译器报错,final不可变

valuefinal修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。

final int[] value={1,2,3};
value[2]=100;  //这时候数组里已经是{1,2,100}

或者更粗暴的反射直接改,也是可以的。

final int[] array={1,2,3};
Array.set(array,2,100); //数组也被改成{1,2,100}

所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。

private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final考验的是工程师构造数据类型,封装数据的功力。


3. 不可变有什么好处?

这个最简单的原因,就是为了安全

示例1

package _12_01字符串;

public class 为什么String要设计成不可变类你 {

	public static void main(String[] args) {

		String a, b, c;
		a = "test";
		b = a;
		c = b;
		String processA = processA(a);
		String processB = processB(b);
		String processC = processC(c);
		System.out.println(processA);
		System.out.println(processB);
		System.out.println(processC);
	}
	
	static String processA(String str){
		return str + "A";
	}
	
	static String processB(String str){
		return str + "B";
	}
	
	static String processC(String str){
		return str + "C";
	}

}
//OUTPUT
// testA
//testB
//testC

String支持非可变性的时候,它们的值很好确定,不管调用哪个方法,都互不影响。

如果String是可变的,就可能如下例,我们使用StringBuffer来模拟String是可变的

package _12_01字符串;

public class 为什么String要设计成不可变类2 {

	public static void main(String[] args) {

		StringBuffer a, b, c;
		a = new StringBuffer("test");
		b = a;
		c = b;
		String processA = processA(a);
		String processB = processB(b);
		String processC = processC(c);
		System.out.println(processA);
		System.out.println(processB);
		System.out.println(processC);
	}
	
	static String processA(StringBuffer str){
		return str.append("A").toString();
	}
	
	static String processB(StringBuffer str){
		return str.append("B").toString();
	}
	
	static String processC(StringBuffer str){
		return str.append("C").toString();
	}

}
//OUTPUT
// testA
//testAB
//testABC

能看出b=a,c=b;程序员的本意是希望变量是不变的。所以String不可变的安全性就体现在这里。实际上StringBuffer的作用就是起到了String的可变配套类角色。

示例2

再看下面这个HashSetStringBuilder做元素的场景,问题就更严重了,而且更隐蔽。

class Test{
    public static void main(String[] args){
        HashSet<StringBuilder> hs=new HashSet<StringBuilder>();
        StringBuilder sb1=new StringBuilder("aaa");
        StringBuilder sb2=new StringBuilder("aaabbb");
        hs.add(sb1);
        hs.add(sb2);    //这时候HashSet里是{"aaa","aaabbb"}

        StringBuilder sb3=sb1;
        sb3.append("bbb");  //这时候HashSet里是{"aaabbb","aaabbb"}
        System.out.println(hs);
    }
}
//Output:
//[aaabbb, aaabbb]
StringBuilder 型变量 sb1 sb2 分别指向了堆内的字面量 "aaa" "aaabbb" 。把他们都插入一个 HashSet 。到这一步没问题。但如果后面我把变量 sb3 也指向 sb1 的地址,再改变 sb3 的值,因为 StringBuilder 没有不可变性的保护, sb3 直接在原先 "aaa" 的地址上改。导致 sb1 的值也变了。这时候, HashSet 上就出现了两个相等的键值 "aaabbb" 破坏了 HashSet键值的唯一性 。所以千万不要用可变类型做 HashMapHashSet键值。

不可变性支持线程安全

还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。

不可变性支持字符串常量池

最后别忘了 String 另外一个字符串常量池的属性。像下面这样字符串 one two 都用字面量 "something" 赋值。它们其实都指向同一个内存地址。

String one = "someString";
String two = "someString";


这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。






  • 45
    点赞
  • 154
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
### 回答1: "final" 在 Java 有多种用途,其一种是用来修饰方法和变量。 - 修饰:表示该不能被继承。 - 修饰方法:表示该方法不能被子重写。 - 修饰变量:表示该变量是常量,不能被修改。 另外,final 也可用于在编译时确定值的局部变量和员变量,这种用法称为"final变量"。 ### 回答2: 在Javafinal关键字具有以下几种作用: 1. 定义final变量:通过使用final关键字进行修饰的变量表示常量,即它的值在定义之后不能被修改。一旦被赋值,即使在后续的程序执行也不能再对其进行修改。这样可以确保变量的值在使用过程不会被意外修改,增加代码的可靠性和安全性。 2. 定义final方法:通过将方法声明为final,可以防止子对该方法进行覆写。这是为了确保方法在继承时的一致性和完整性。当我们希望某个方法具有最终的实现,并且不允许子进行修改时,可以使用final关键字修饰该方法。 3. 定义final:通过将一个声明为final,可以防止其他继承该。这样的是最终的实现,不可被继承或修改。一些,如String和Math,就是使用final关键字修饰的,防止被继承或修改,以确保其功能和安全性。 总的来说,final关键字可以用于修饰变量、方法,用于指定某个数据或实现的最终状态,禁止对其进行修改或继承。通过使用final关键字,可以提高代码的可读性、安全性和性能,避免潜在的错误或问题。 ### 回答3: 在Java,"final"关键字用于标识一个特定的实体不可改变。它可以应用于方法和变量。 1. 当用于时,"final"关键字表示该是不可继承的,即不能有子。这可以用来防止其他开发者修改或扩展该的功能,从而保护核心逻辑和数据的完整性。 2. 当用于方法时,"final"关键字表示该方法是不可覆盖的,即子不能重写该方法。这可以用于确保某些在父被定义的方法在子保持一致性,避免子意外修改父逻辑的情况。 3. 当用于变量时,"final"关键字表示该变量是一个常量,即它的值在初始化后不能再改变。一旦为final变量赋值后,它将为一个只读变量,不能再被重新赋值。这可以用于定义不希望被修改的常量,如数学常数、数据库连接信息等。 使用final关键字有以下几个好处: 首先,它提供了更安全的编程方式。通过将方法或变量声明为final,可以避免其他人对其进行意外或恶意的修改,确保它们的行为和值的稳定性。 其次,final关键字可以提高程序的性能。在某些情况下,编译器可以对final实体进行优化,例如内联方法、常量折叠等,从而减少方法调用或运行时计算的开销。 最后,final关键字可以用于设计模式和约定。例如,在单例模式,将声明为final可以确保它只有一个实例;在模板方法模式,将某些关键方法声明为final可以提供一个固定的算法框架,允许子只对其一部分进行实现。 总之,final关键字在Java有重要的作用,它允许开发者限制和保护方法和变量,提供更安全和高效的编程方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值