String、Integer 和其他包装类是 HashMap 键的自然候选项,而 String
也是最常用的键,因为 String 是不可变的和最终的,并且覆盖了 equals 和 hashcode() 方法。其他包装类也具有类似的属性。
为了防止用于计算 hashCode() 的字段发生更改,需要不可变性, 因为如果键对象在插入和检索过程中返回不同的 hashCode,则无法从 HashMap 获取对象。
不可变性是最好的,因为它提供了其他优点,例如线程安全,如果您可以通过仅使某些字段成为最终字段来保持哈希码相同,那么您也可以这样做。
由于在从 HashMap 检索值对象期间使用了 equals() 和 hashCode() 方法,因此关键对象必须正确覆盖这些方法并跟踪接触。如果一个不相等的对象返回不同的哈希码,那么冲突的可能性就会减少,从而提高 HashMap 的性能。
为什么字符串在 Java 中是最终的或不可变的 5 个原因
虽然 Java 设计者最清楚 String 类是最终类的真正原因,除了 James Gosling 对安全性的暗示之外,我认为以下原因也表明了为什么 String 在 Java 中是最终的或不可变的。
1. 字符串池
Java 设计师知道 String 将成为所有 Java 应用程序中最常用的数据类型,这就是为什么他们要从一开始就进行优化。朝着这个方向迈出的关键一步是将字符串文字存储在字符串池中。
目标是通过共享它们来减少临时 String 对象,并且为了共享它们,它们必须来自 Immutable 类。不能与彼此未知的两方共享可变对象。让我们举一个假设的例子,其中两个引用变量指向同一个 String 对象:
String s1 = “Java”;
字符串 s2 = “Java”;
现在,如果 s1 将对象从“Java”更改为“C++”,引用变量也会得到值 s2=“C++”,它甚至不知道它。 通过使 String 不可变,使 String 文本的共享成为可能。简而言之,如果不在 Java 中使 String 成为 final 或 Immutable,就无法实现 String 池的关键思想。
2. 安全
Java 在提供每个服务级别的安全环境方面有一个明确的目标,而 String 在整个安全方面至关重要。字符串已被广泛用作许多 java 类的参数,例如用于打开网络连接时,可以将主机和端口作为 String 传递,在 Java 中读取文件时,可以将文件和目录的路径作为 String 传递,对于打开数据库连接,可以将数据库 URL 作为 String 传递。
如果 String 不是不可变的,则用户可能已授予访问系统中特定文件的权限,但在身份验证后,他可以将 PATH 更改为其他内容,这可能会导致严重的安全问题。
同样,在连接到数据库或网络中的任何其他计算机时,更改 String 值可能会带来安全威胁。可变字符串也可能导致 Reflection 中的安全问题,因为参数是字符串。
3. 字符串在类加载机制中的使用
使 String 成为 final 或 Immutable 的另一个原因是它在类加载机制中被大量使用。由于 String 不是 Immutable,攻击者可以利用这一事实,并且可以将加载标准 Java 类(如 java.io.Reader)的请求更改为恶意类 com.unknown.DataStolenReader。通过保持 String final 和不可变性,我们至少可以确保 JVM 加载了正确的类。
4. 多线程的好处
由于并发和多线程是 Java 的关键产品,因此考虑 String 对象的线程安全性非常有意义。由于预期 String 将被广泛使用,因此将其设置为不可变意味着没有外部同步,这意味着涉及在多个线程之间共享 String 的代码要干净得多。
这一单一功能使本已复杂、混乱和容易出错的并发编码变得更加容易。因为 String 是不可变的,我们只是在线程之间共享它,所以它会导致代码更具可读性。
5. 优化和性能
现在,当你使一个类不可变时,你事先知道,这个类一旦创建就不会改变。这保证了许多性能优化(例如缓存)的开放路径。String 本身知道我不会改变,所以 String 缓存了它的哈希码。它甚至可以延迟计算哈希码,一旦创建,只需缓存它。
在一个简单的世界中,当您第一次调用任何 String 对象的 hashCode() 方法时,它会计算哈希代码,并且所有后续对 hashCode() 的调用都会返回已计算的缓存值。
这带来了良好的性能提升,因为 String 在基于 hashtable 和 HashMap 等基于哈希的映射中被大量使用。如果不使哈希码不可变和最终,就不可能缓存哈希码,因为它取决于 String 本身的内容。
字符串在 Java 中是不可变的或最终的优缺点
除了上述好处之外,您还可以依靠另一个优势,因为 String 在 Java 中是最终的。它是在基于哈希的集合(如 HashMap 和 Hashtable)中用作键的最流行的对象之一。
虽然不可变性不是 HashMap 键的绝对要求,但使用不可变对象作为键比使用可变对象要安全得多,因为如果可变对象的状态在 HashMap 中停留期间发生更改,则不可能将其检索回来,因为它的 equals() 和 hashCode() 方法取决于更改的属性。
如果类是不可变的,则当它存储在基于哈希的集合中时,不会有更改其状态的风险。另一个重要的好处,我已经强调过了,是它的线程安全性。由于 String 是不可变的,因此您可以在线程之间安全地共享它,而无需担心外部同步。它使并发代码更具可读性,更不容易出错。
尽管有所有这些优点,但不可变性也有一些缺点,比如它不是没有成本的。由于 String 是不可变的,因此它会生成大量临时使用和抛出对象,这会给垃圾回收器带来压力。Java 设计师已经考虑过这一点,将 String 文字存储在 pool 中是他们减少 String 垃圾的解决方案。
它确实有帮助,但您必须小心在不使用构造函数的情况下创建 String,例如 new String() 不会从 String 池中选择对象。此外,平均而言,Java 应用程序会产生过多的垃圾。此外,将字符串存储在池中也存在与之相关的隐藏风险。字符串池位于 Java 堆的 PermGen 空间中,与 Java 堆相比,它非常有限。
过多的 String 文字会很快填满这个空间,导致 java.lang.OutOfMemoryError: PermGen Space。值得庆幸的是,Java 语言程序员已经意识到了这个问题,从 Java 7 开始,他们已经将字符串池移动到了正常的堆空间,这比 PermGen 空间大得多。
使 String 成为最终版本还有另一个缺点,因为它限制了其可扩展性。现在,您只是无法扩展 String 以提供更多功能,尽管更一般的情况几乎不需要它,但对于那些想要扩展 java.lang.String 类的人来说,它仍然受到限制。
这就是为什么 String 在 Java 中是 final 或 Immutable 的全部原因。虽然我们不知道确切的原因,因为 Oracle 从未发布过他们在 Java 中使 String 类成为最终类的决定,但这 5 个关于缓存、安全性、并发性和性能的实际原因无疑暗示了为什么 String 类在 Java 中变成了 Final 和不可变。
当然,这是 Java 设计人员的决定,但看起来以上几点有助于他们做出这个决定。由于类似的原因,像 Integer、Long、Double、Float 和 Final这样的包装类也是不可变的。