网上找了很久,但是基本都是在说string为什么不可变,以及不可变的好处。
但是final修饰和不可变(immutable )没有必然联系吧。所以想问为什么要设计成final的?
网上还找到了一种说法:“final修饰string类,表示所有string类的方法就是final的,所以在调用string方法的时候就会被jvm优化为内联函数,可以提高效率。”这种说法是正确的吗?
添加评论
分享
按投票排序
按时间排序
26 个回答
140赞同
反对,不会显示你的姓名
Jaskey Lam
Product-Driven Programmer
140 人赞同
String基本约定中最重要的一条是immutable。
的确声明String为final 和immutable是没有必然关系,但是假如String没有声明为final, 那么你的StringChilld就有可能是被复写为mutable的,这样就打破了成为共识的基本约定。
举个例子:一个方法可能本来接受String类型并返回其大写方式
public static String uppperString(String s){
return s.toUpperCase();
}
你传入String 的s="test", 他不会修改字符串池中"test", 而是直接新建立一个实例"TEST"返回。但如果你的StringChild的toUpperCase()被你重写(override)为mutable的方式,然后你调用这个方法的时候传入的是StringChild实例, 那么整体(依赖于(过)方法uppperString的所有类)的行为就有可能出现错乱。
要知道,String是几乎每个类都会使用的类,特别是作为Hashmap之类的集合的key值时候,mutable的String有非常大的风险。而且一旦发生,非常难发现。
声明String为final一劳永逸。
传送门:
Why String is Immutable or Final in Java
Why is String class declared final in Java?
编辑于 2015-06-16
3 条评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
104赞同
反对,不会显示你的姓名
胖胖
趁还能跑能唱能笑的时候赶快死
104 人赞同
大白话解释就是: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类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,
final int[] value={1,2,3}
int[] another={4,5,6};
value=another; //编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。
final int[] value={1,2,3};
value[2]=100; //这时候数组里已经是{1,2,100}
所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以
String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。
3. 不可变有什么好处?
这个最简单地原因,就是为了
安全。看下面这个场景(有评论反应例子不够清楚,现在完整地写出来),一个函数appendStr( )在不可变的String参数后面加上一段“bbb”后返回。appendSb( )负责在可变的StringBuilder后面加“bbb”。
class Test{
//不可变的String
public static String appendStr(String s){
s+="bbb";
return s;
}
//可变的StringBuilder
public static StringBuilder appendSb(StringBuilder sb){
return sb.append("bbb");
}
public static void main(String[] args){
//String做参数
String s=new String("aaa");
String ns=Test.appendStr(s);
System.out.println("String aaa >>> "+s.toString());
//StringBuilder做参数
StringBuilder sb=new StringBuilder("aaa");
StringBuilder nsb=Test.appendSb(sb);
System.out.println("StringBuilder aaa >>> "+sb.toString());
}
}
//Output:
//String aaa >>> aaa
//StringBuilder aaa >>> aaabbb
如果程序员不小心像上面例子里,直接在传进来的参数上加"bbb",因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了"aaabbb"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。
再看下面这个
HashSet用StringBuilder做元素的场景,问题就更严重了,而且更隐蔽。
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键值的唯一性。所以
千万不要用可变类型做HashMap和HashSet键值。
还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以
线程安全。
最后别忘了String另外一个
字符串常量池的属性。像下面这样字符串one和two都用字面量"something"赋值。它们其实都指向同一个内存地址。
String one = "someString";
String two = "someString";
这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。
------------------------------------------------------
我的笔记栈
http://ciaoshen.com (笔记向,非教程)
编辑于 2016-08-05
24 条评论
感谢
分享
收藏
•
没有帮助
•
举报
•
禁止转载
11赞同
反对,不会显示你的姓名
健身是为了穿衣服更好看~
11 人赞同
将方法或类声明为final主要目的是:确保它们不会再子类中改变语义。String类是final类,这意味着不允许任何人定义String的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。
——《Java核心技术 卷I》
发布于 2015-06-17
添加评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
3赞同
反对,不会显示你的姓名
雷博
技术含量最重要!
3 人赞同
我个人的理解是,因为String是immutable的,所以不能允许其他类继承String。否则如下方法:
public void foo(String){
//...
}
可以接收String的子类。而你可以轻易的把子类实现成mutable的。这就违反了String immutable的约定。
因此String 干脆禁止被继承,这样可以确保使用String的代码用的是没有被改变过的String。
发布于 2015-06-16
1 条评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
2赞同
反对,不会显示你的姓名
Ashin
喝最烈的酒,日最野的狗。
2 人赞同
我只是觉得Java想用这种方式让String在形式上成为一种基本数据类型。而不是一个普通的类。
只是猜测。。。
发布于 2015-06-16
1 条评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
0赞同
反对,不会显示你的姓名
张宇
iOS developer
String属于不变模式的典型实现
JVM很容易优化
发布于 2015-06-18
添加评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
15赞同
反对,不会显示你的姓名
Intopass
程序员,近期沉迷于动漫ING
15 人赞同
String 类是 final 的,这样你就没法继承这个类了,也就没法修改这个类的实现了。
假设String 类是非 final 的,你可以这样:
public class FuckString extends String {
private char[] chars;
public FuckString(String string) {
chars = string.toCharArray();
}
public void setChar(int index, char ch) {
chars[index] = ch;
}
public String toString() {
return new String(chars);
}
// other fucking method
}
你瞬间就改变了String类的一些基本约定,如String一经创建就不会被改变等特性。
编辑于 2016-07-15
3 条评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
0赞同
反对,不会显示你的姓名
蠡测
坚强到胸口碎大石
为了安全,有时会依靠它算hash值作为地址,如果String是可变的那就全乱套了。为了弥补每次都要生成新的String,java又提供了StringBuffer给你用。
发布于 2016-08-02
添加评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
1赞同
反对,不会显示你的姓名
张峻
学而时习之
1 人赞同
我认为关键的地方在于安全性,如果string等基础类型可以被继承,那么调用者完全可以把基础类型的方法替换为自己的攻击代码。
虽然说把数据传回去可能有难度,但是通过不停的申请内存让被攻击的服务down掉是很简单的。
发布于 2015-06-17
添加评论
感谢
分享
收藏
•
没有帮助
•
举报
•
作者保留权利
转载请注明原文地址: https://ju.6miu.com/read-1299242.html