java.lang.String 为什么是final 的?

java.lang.String 为什么是final 的?

之前只是模模糊糊的知道原因,今天在看另外一篇文章的时候,作者简单的说了下String类为什么是final的,但是他的理由说服不了我,这里我简单总结下为什么java.lang.String类是final的。

  • 使用final生成不变类
    • StringJava中是和基本数据类型差不多的存在,类似所有的基本数据类型的包装类一样,他们都是final的,而基本数据类型作为不可变类是因为,int 3的包装类value就一定是3而不会是其他。
    • 不变类在多线程中可以变量共享,不会存在多线程的线程安全的问题
    • 很多情况下HashMapkey都是String,而key仅仅存的是引用,如果String是可变的,可能会导致key丢失。
  • Stringintern的特性,也是Stringfinal的重要一点,因为String需要缓存到常量池中,而如果String是可变的,这样会导致一个地方的引用调用改变了String对象,其他所有引用所指向的对象也跟着改变,这简直就是灾难。
  • String声明为final,还有一个说法就是语义的问题,String类是final类,这意味着不允许任何人定义String的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。

说简单一点,就是为了性能和安全。


下面来简单的解释下可变类为什么最好不要做HashMap,HashSetkey

使用HashMap的时候我们应该都听过一句话:重载hashCode()方法的时候也要重载equals()

这是因为HashMapkey可能存在Hash碰撞,也就不同的keyhashcode可能相同,因此HashMap在获取某个key对应的value的时候,会先索引hashcode,然后再使用equals()验证是否是相等的对象。

 if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;

JDK 1.8 hashMap#getNode()部分源码


假如我们定义了一个普通的pojo:Student

@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@ToString
public class Student {

    private String name;

    private String sex;
}

然后通过业务系统将其放入了一个全局的HashMap

Student stuDcc=new Student("dcc","男");
map.put(stuDcc);

而在后来,因为其他的一些业务逻辑,我们修改了stuDccname

stuDcc.setName("dcc2");

就这样,这stuDcc所对应的value永远都获取不到了,除非stuDcc对象再将name修改为dcc.

map.get(new Student("dcc","男"));    //null
map.get(stuDcc);                     //null

为什么呢?


因为现在的HashMapStuDcc对象的hashCode是通过name=dcc ,sex=男来计算获得的,而equals需要是name=dcc2,sex=男

因为HashMap判断一个对象需要同时HashCodeEquals,但是hashCode的获取是在存入对象的时候就固定了,而equals是判断的对象目前的值,因此stuDcc对象在修改了name以后,其他任何一个对象都无法同时拥有stuDcc存入map的时候的hashCode以及stuDcc修改值的相同的值。

也就是下面的代码:

map.get(new Student("dcc","男"));    //null     hashCode() 匹配成功但是equals失败
map.get(stuDcc);                     //null    equals()成功 但是hashCode匹配不到

因此,最好使用不可变类做Key


参考链接:

在java中String类为什么要设计成final? – 胖君的回答 – 知乎

在java中String类为什么要设计成final? – Reversal的回答 – 知乎

Why String is Immutable or Final in Java

Why is the String class declared final in Java? – stackoverflow