在java中String类为什么要设计成java final 成员变量

在java中String类为什么要设计成final? - 知乎465被浏览40895分享邀请回答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一劳永逸。传送门:1624 条评论分享收藏感谢收起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}
或者 @评论说喜欢更粗暴的反射直接改,也是可以的。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. 不可变有什么好处?这个最简单地原因,就是为了安全。看下面这个场景(有评论反应例子不够清楚,现在完整地写出来),一个函数appendStr( )在不可变的String参数后面加上一段“bbb”后返回。appendSb( )负责在可变的StringBuilder后面加“bbb”。class Test{
//不可变的String
public static String appendStr(String 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());
//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);
//[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的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。------------------------------------------------------我的笔记栈
(笔记向,非教程)19741 条评论分享收藏感谢收起查看更多回答Java中是否可以继承String类,为什么 - jim_shen - 博客园
随笔 - 283, 文章 - 0, 评论 - 6, 引用 - 0
中,是否可以继承String类?为什么?
答案: 不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
public final class String implements java.io.Serializable, Comparable&String&, CharSequence
详细解析:
1. 关于final修饰符
参考文章:
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
  final类不能被继承,没有子类,final类中的方法默认是final的。
  final方法不能被子类的方法覆盖,但可以被继承。
  final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
  final不能用于修饰构造方法。
  注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
  使用final方法的原因有二:
  第一、把方法锁定,防止任何继承类修改它的意义和实现。
  第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。(这点有待商榷,《Java编程思想》中对于这点存疑)
下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
2. 关于String类
关于String类,要了解常量池的概念
String s = new String(“xyz”);
答案: 1个或2个, 如果”xyz”已经存在于常量池中,则只在堆中创建”xyz”对象的一个拷贝,否则还要在常量池中在创建一份
String s = "a"+"b"+"c"+"d";
答案: 这个和JVM实现有关, 如果常量池为空,可能是1个也可能是7个等
3. 相关类: StringBuffer, StringBuilder
参考文章:
String为immutable, 不可更改的,每次String对象做累加时都会创建StringBuilder对象, 效率低下。
StringBuffer是线程安全的
StringBuilder(5.0版本后添加的类,是StringBuffer的一个简单替换)为非线程安全的,但是效率会好些,在单线程环境中要做大量字符串累加时推荐使用该类请输入正确的邮箱
已有帐号请点击
帐号创建成功!
我们刚刚给你发送了一封验证邮件
请在48小时内查收邮件,并按照提示验证邮箱
感谢你对微口网的信任与支持
如果你没有收到邮件,请留意垃圾箱 或 重新发送
你输入的邮箱还未注册
还没有帐号请点击
你输入的邮箱还未注册
又想起来了?
邮件发送成功!
我们刚刚给你发送了一封邮件
请在5分钟内查收邮件,并按照提示重置密码
感谢你对微口网的信任与支持
如果你没有收到邮件,请留意垃圾箱 或 重新发送
对不起,你的帐号尚未验证
如果你没有收到邮件,请留意垃圾箱 或
意见与建议
请留下您的联系方式
* 留下您正确的联系方式,以便工作人员尽快与你取得联系在java中String类为什么要设计成final? - 知乎465被浏览40895分享邀请回答public class FuckString extends String {
private char[]
public FuckString(String string) {
chars = string.toCharArray();
public void setChar(int index, char ch) {
chars[index] =
public String toString() {
return new String(chars);
// other fucking method
你瞬间就改变了String类的一些基本约定,如String一经创建就不会被改变等特性。153 条评论分享收藏感谢收起

我要回帖

更多关于 java final 方法 的文章

 

随机推荐