Java中String类为什么被设计为final?
Java中String类为什么被设计为final
首先,String是引用类型,也就是每个字符串都是一个String实例。通过源码可以看到String底层维护了一个byte数组:private final byte[] value;(JDK9中为byte数组,并非网上所说的char数组)。虽然该数组被修饰为final,但这并不能保证数组的数据不会变化,因此还需要声明为private防止被其他类修改数据。
被final修饰的类不能被继承,也就是不能有子类。那么为什么要把String设计为不能被继承呢?简单来说有两点:安全和效率。
安全
要知道String是一个非常非常基础的类,用处超级广泛,各种各样的类基本都使用到了字符串。
假设String类可以被继承,现在有一个方法method,该方法的参数为String类型,并且该方法利用到了字符串的长度特性:
public int method(String s){
//do something
int a = s.length() + 1;
return a;
}
我们设计出一个String的子类MyString,并重写了其长度方法:
public class MyString{
@Override
public int length(){
return 0;
}
}
基于Java的多态特性,当我们把MyString的实例作为参数传入method()方法时,编译器是不会报错的。但是我们的运行结果则完全错误,这会造成非常严重的后果。
MyString myString = new MyString();
method(myString);//此时编译并不会报错,但是运行结果是完全错误的。
相对于每次使用字符串的时候使用final修饰,直接把String类定义为final更为安全,效率也更高。并且,整个类声明为final之后,如果有一个String的引用,则它引用的一定是String对象,而不会是其他类的对象(泛型允许引用子类)。防止世界被熊孩子破坏2333
除了由多态引起的安全问题,还有引用类型本身的问题。
比如现在有两个方法,appendStr负责在不可变的String参数后添加“bbb”并返回,appendSb负责在可变的StringBuilder后添加“bbb”并返回。
public static String appendStr(String s){
s = s + "bbb";
return s;
}
public static StringBuilder appendSb(StringBuilder sb){
sb.append("bbb");
return sb;
}
public static void main(String[] args) {
//String做参数
String str = new String("aaa");
String newStr = appendStr(str);
System.out.println("String aaa -> " + str.toString());
//StringBuilder做参数
StringBuilder sb = new StringBuilder("aaa");
StringBuilder newSb = appendSb(sb);
System.out.println("StringBuilder aaa -> " + newSb.toString());
}
但实际输出结果却是:
String aaa -> aaa
StringBuilder aaa -> aaabbb
如果程序员不小心像上面例子里,直接在传进来的参数上加"bbb",因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了"aaabbb"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。
再看下面这个HashSet用StringBuilder做元素的场景,问题就更严重了,而且更隐蔽。
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]
}
这就破坏了HashSet键的唯一性,因此千万不要使用可变类型做HashMap和HashSet的键值。(不可变的字符串则非常适合作为键)
除了上述两种问题,不可变的字符串还可以保证多线程时的线程安全问题。多线程时,只有读操作一般不会引发线程安全问题,当读写同时存在时便容易引发安全问题。当字符串不可变时也就不能写,当然不会引发线程问题。
效率
基于字符串的不可变,才能有字符串常量池这一特性。字符串常量池的诞生是为了提升效率和减少内存分配。可以说我们编程有百分之八十的时间在处理字符串,而处理的字符串中有很大概率会出现重复的情况。正因为String的不可变性,常量池很容易被管理和优化。
并且1.7之前,字符串常量池在方法区,1.7之后在堆内存中,并且不仅仅可以存储对象,还可以存储对象的引用:
String s = new String("A") + new String("B");//此时常量池存在"A"、"B",但是不存在"AB";堆中存在"A"、"B"、"AB",并且s指向"AB"
s.intern();//1.7之后这里加入的是对象s的引用,而非直接保存"AB"字符串
//intern用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后 返回引用。
对于什么时候会在常量池存储字符串对象:
- 显示调用String的intern方法的时候,例如上例。
- 直接声明字符串字面常量的时候,例如:
String a = "aaa"; - 直接
new String("A")方法的参数使用常量的时候 - 字符串直接常量相加的时候,例如:
String c = "aa" + "bb";其中的aa/bb只要有任何一个不是字符串字面常量形式,都不会在常量池生成"aabb". 且此时jvm做了优化,不会同时生成"aa"和"bb"在字符串常量池中
顺便说一句,Integer、Long、Double……这几个包装类也是final的~
Java中String类为什么被设计为final?的更多相关文章
- 在java中String类为什么要设计成final
在java中String类为什么要设计成final? - 胖胖的回答 - 知乎 https://www.zhihu.com/question/31345592/answer/114126087
- 在java中String类为什么要设计成final?
大神链接:在java中String类为什么要设计成final? - 程序员 - 知乎 我进行了重新排版,并且更换了其中的一个例子,让我们更好理解. String很多实用的特性,比如说“不可变性”,是工 ...
- 在Java中String类为什么要设计成final?String真的不可变吗?其他基本类型的包装类也是不可变的吗?
最近突然被问到String为什么被设计为不可变,当时有点懵,这个问题一直像bug一样存在,竟然没有发现,没有思考到,在此总结一下. 1.String的不可变String类被final修饰,是不可继承和 ...
- java中String类为什么要设计成final?
1 将方法或类声明为final主要目的是:确保它们不会在子类中改变语义.String类是final类,这意味着不允许任何人定义String的子类. String基本约定中最重要的一条是immutabl ...
- 【笔记】在java中String类为什么要设计成final?
部分内容转自知乎:https://www.zhihu.com/question/31345592 从自己的理解进行加工,压缩. String本质上是一个final类 public final clas ...
- java里String类为何被设计为final
前些天面试遇到一个非常难的关于String的问题,"String为何被设计为不可变的"?类似的问题也有"String为何被设计为final?"个人认为还是前面一 ...
- Java中String类的方法及说明
String : 字符串类型 一. String sc_sub = new String(c,3,2); // String sb_copy = new String(sb) ...
- java中String类学习
java中String类的相关操作如下: (1)初始化:例如,String s = “abc”; (2)length:返回字符串的长度. (3)charAT:字符操作,按照索引值获得字符串中的指定字符 ...
- 【转载】Java中String类的方法及说明
转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一. String sc_ ...
随机推荐
- DNS 访问 Service【转】
在 Cluster 中,除了可以通过 Cluster IP 访问 Service,Kubernetes 还提供了更为方便的 DNS 访问. kubeadm 部署时会默认安装 kube-dns 组件. ...
- 强制找回GitLab管理员账户密码的方法
为了开发运维工具,我们采用自行搭建的GitLab来管理所有代码.悲催的是最近忘记了管理员账户的密码,而且没有邮件服务器,因此无法接收密码找回的邮件,导致无法新建用户或者项目,这样一来,岂不就成为了一个 ...
- 解题报告:luogu P2678 跳石头
题目链接:P2678 跳石头 很简单的二分查找,可悲的是我并不会. 不过题解贴心的写得很清楚(学会了套路) 二分一次判断一次,复杂度是\(O(nlogl)\),可以通过此题. \(Code:\) #i ...
- jenkins#安装docker
环境:centos7 安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2 添加Docker软件包源 yum-config ...
- java 接口 2.19
接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的.
- JS取整方法
1.toFixed方法 定义:toFixed() 方法可把 Number 四舍五入为指定小数位数的数字. 例如:将数据Num保留2位小数,则表示为:toFixed(Num):但是其四舍五入的规则与数学 ...
- 0203 生成mysql的数据库的数据字典
原理 项目的数据库字典表是一个很重要的文档.通过此文档可以清晰的了解数据表结构及开发者的设计意图. 通常为了方便我都是直接在数据库中建表,然后通过工具导出数据字典. 在Mysql数据库中有一个info ...
- yolov3测试自己的数据
yolov3测试自己的数据 前言 上一篇我已经介绍了利用yolov3预训练权重文件(只包含卷积层)并训练 只需要进行如下编译: ./darknet detector train cfg/voc.dat ...
- JuJu团队12月3号工作汇报
JuJu团队12月3号工作汇报 JuJu Scrum 团队成员 今日工作 剩余任务 困难 于达 修改batch里给sentence加padding的方法 继续调试 无 婷婷 给crossentro ...
- Hbase排错
1.执行MR任务报Error: java.lang.RuntimeException: PipeMapRed.waitOutputThreads():?subprocess failed with c ...