深入理解java.lang.String类的不可变性

1. 字符串 String 的不可变性

什么是不可变类?

这样理解:
        一个对象在创建完成后,不能去改变它的状态,不能改变它的成员变量(如果成员变量包含基本数据类型,那么这个基本数据类型的值不能改变;如果包含引用类型,那么这个引用类型的变量不能指向别的对象)

不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变。为了使类不可变,要遵循下面五条规则:

  • 不要提供任何会修改对象状态的方法
  • 保证类不会被扩展。 一般的做法是让这个类称为 final 的,防止子类化,破坏该类的不可变行为
  • 使所有的域都是 final 的
  • 使所有的域都成为私有的。 防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象
  • 确保对于任何可变性组件的互斥访问。 如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用

翻阅 API 文档:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // value 数组被 final 修饰
    private final char value[];
    ...
}

String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例来实现。 这些字面值都是直接存储在“方法区”的 字符串常量池

字符串是常量;它们的值在创建之后 不能改变,所以可以共享它们。例如:

String str = "abc";

这时就有人疑惑了:为什么 String 不可变?但我的代码中经常改变 String 啊,如下:

String str = "HELLO";
str = "WORLD";
System.out.println(str);    // WORLD

这样操作,不就是将 “HELLO” 对象改变成了 “WORLD” 对象了吗?

虽然字符串的内容看上去从“HELLO” 变成了“WORLD”,但实际上,这已经是生成了一个新的字符串了:

String str = "HELLO";
System.out.println(str.hashCode());  // 68624562
str = "WORLD";
System.out.println(str.hashCode());  // 82781042

变量 str 前后的 hashCode 值不一样,说明了 str 在改变前后,指向了不同的对象。所以,变量 str 只是指向了不同对象,字符串 “HELLO”对象本身没有被改变。

变量 str 的指向如下图所示(jdk1.8:字符串常量位于堆中):

我们也可以使用 javap 命令来查看 class 的常量池:

javap -c -v StringTest.class

执行后,常量池信息如下:

从常量池中可以看出,确实有两个字符串对象:HELLO、WORLD

【总结】:一旦一个 String 对象堆中被创建出来,它就无法被修改。而且,String 类的所有 API 方法都没有改变字符串本身的值,都是返回了一个新的字符串对象。

2. String 设计成不可变类的好处

在了解了“String 是不可变”的之后,大家是不是很疑惑:为什么要把 String 设计成不可变的呢?这样做又有什么好处呢?

主要从以下几个角度考虑:

  • 安全可靠性:字符串在 Java 应用程序中应用广泛(存储敏感信息,如:用户名、密码、连接 url、网络连接等);JVM类加载器在加载类的时也广泛地使用它。因此,保护 String 类对于提升整个应用程序的安全性至关重要。
  • 缓存:字符串是使用最广泛的数据结构,大量的字符串的创建是非常耗费资源的。JVM 中专门开辟了一部分空间来存储 Java 字符串,那就是字符串常量池。通过字符串常量池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源
  • 线程安全:不可变会自动使字符串成为线程安全的,因为当从多个线程访问它们时,它们不会被更改
  • hashcode 缓存:字符串也被广泛地用于哈希实现,如 HashMap、HashTable、HashSet 等。在对这些散列实现进行操作时,经常调用键的hashCode() 方法。不可变性保证了字符串的值不会改变,因此,hashCode() 方法在 String 类中被重写,以方便缓存。这样,在第一次hashCode() 调用期间计算和缓存散列,并从那时起返回相同的值。

3. 面试题

// 生成两个对象:一个在常量池中;一个中堆中,且都是 hello 对象
String s = new String("hello");

那么,下面会生成几个对象呢?

// 只会在字符串常量池中生成一个对象:helloworld。
String s3 = "hello" + "world";

这种字面量用“+”拼接,编译器在编译期间会直接进行优化。

// 这个会生成4个对象。2个在常量池中:hello、world
// 2个在堆中:StringBuilder、helloworld对象
String s = "hello";
String s2 = s + "world";

编译后,使用反编译软件 ------ jad 进行查看:

String s1 = "hell0";
String s2 = (new StringBuilder()).append(s1).append("world").toString();

发现:使用“+”将变量和字面量进行拼接的结果是:将 String 转成了StringBuilder 后,使用其 append() 方法进行处理的

查看 StringBuilder.toString() 方法源码:

@Override
public String toString() {
	// char[] value; value 是 StringBuilder 类的成员变量
    return new String(value, 0, count);
}

最后调用 toString() 方法时,会创建一个 String 对象。这个字符串对象只会在堆中创建,并不会在字符串常量池中创建。所以,会创建4个对象(hello 和 world 会直接在字符串常量池中创建)。

到此这篇关于深入理解java.lang.String类的不可变性的文章就介绍到这了,更多相关java.lang.String不可变性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解决出现 java.lang.ExceptionInInitializerError错误问题

    解决出现 java.lang.ExceptionInInitializerError错误问题 今天启动一个项目... 在启动的时候报错... 查了半天错误信息 一直是说hibernate缓存管理出错.. 最后查看启动时候的log 发现在程序中报Java.lang.ExceptionInInitializerError 查看原因 最后是因为自己修改了静态常量是系统启动的时候自动获取properties值 结果获取错误 导致其在代码编译的时候造成出错 错误代码: public static fina

  • java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list错误解决办法

    java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list错误解决办法 玩web的SSH总会有些令你意想不到的exception,这里其中有很多事自己不小心,或者马虎所造成.因此,解决的方案会各有不同,别人出现的异常解决方案对你的可能无效,就像上面的我报的异常一样,百度了很多很多次,给我的答案无非就是在aop上加上一句,但是非常抱歉,我加上去无效!所以还是那句话,对于自己的异常,还是要自己解决. 首先说明一下,我这次的练习的ssh结构

  • java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.setXmlVersion问题解决方法

    读取本地的xml文件,通过DOM进行解析,DOM解析的特点就是把整个xml文件装载入内存中,形成一颗DOM树形结构,树结构是方便遍历和和操纵. DOM解析的特性就是读取xml文件转换为 dom树形结构,通过节点进行遍历. 这是W3c关于节点的概念 如果xml中包含有大量的数据,由于dom一次性把xml装入内存中的特性,所以dom不适合于包含大量数据的xml解析.当包含有大量xml的时候,用SAX进行解析比较节省内存. 下面是一个运用DOM进行解析xml文件的例子: xml文件结构如下: <?xm

  • java.lang.ExceptionInInitializerError异常的解决方法

    今天在开发的过程中,遇到java.lang.ExceptionInInitializerError异常,百度查了一下,顺便学习学习,做个笔记 静态初始化程序中发生意外异常的信号,抛出ExceptionInInitializerError表明在计算静态初始值或静态变量的初始值期间发生异常. 要理解这个异常从Java类中的静态变量初始化过程说起,在Java类中静态变量的初始化顺序和静态变量的声明顺序是一致的.示例程序为: package com.lang.ininitialException; im

  • java.lang.NoClassDefFoundError错误解决办法

    java.lang.NoClassDefFoundError错误解决办法 前言 在日常Java开发中,我们经常碰到java.lang.NoClassDefFoundError这样的错误,需要花费很多时间去找错误的原因,具体是哪个类不见了?类明明还在,为什么找不到?而且我们很容易把java.lang.NoClassDefFoundError和java.lang.ClassNotfoundException这两个错误搞混,事实上这两个错误是完全不同的.我们往往花费时间去不断尝试一些其他的方法去解决这

  • Java 异常java.lang.NoSuchFieldException解决方案

    这篇文章主要介绍了Java 异常java.lang.NoSuchFieldException解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 异常描述:java.lang.NoSuchFieldException: 处理方式:将属性修改为public 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • 详解java.lang.NumberFormatException错误及解决办法

    前言: 在做后台时用的jsp开发,在页面向controller传参时用String接收的参数,但是数据库实体中jies接收该参数时是int类型,做了一下强制转换,但是没有判断去空格,结果页面加载时就报500错误了. 错误截图: 在报错后注意分析报错信息,如上图提示在ShopController.java 的92行出错了,那么错误就很容易定位了,去看那里的代码. 错误关键字 java.lang.NumberFormatException 这句话明确告诉了我们是数字格式异常,接着后面有 null 提

  • Exception in thread main java.lang.NoClassDefFoundError错误解决方法

    错误描述 javac helloworld.java能够通过.但是java helloworld出现错误: hadoop@xuwei-erplab:~/jarfile$ java HelloWorld Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld (wrong name: org/xuwei/HelloWorld) at java.lang.ClassLoader.defineClass1(N

  • 深入理解java.lang.String类的不可变性

    1. 字符串 String 的不可变性 什么是不可变类? 这样理解:         一个对象在创建完成后,不能去改变它的状态,不能改变它的成员变量(如果成员变量包含基本数据类型,那么这个基本数据类型的值不能改变:如果包含引用类型,那么这个引用类型的变量不能指向别的对象) 不可变类只是其实例不能被修改的类.每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变.为了使类不可变,要遵循下面五条规则: 不要提供任何会修改对象状态的方法 保证类不会被扩展. 一般的做

  • java.lang.String类的使用

    目录 一.String的用法 2.求字符串长度和某一位置字符 3.提取子串 4.字符串比较 5.字符串链接 6.字符串中单个字符查找 7.大小写转换 8.字符串中字符的替换 9.其他方法 10.类型转换 二.String特性 三.StringBuffer和StringBuiler 一.String的用法 String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象.java把String类声明的final类,不能有子类.String类对象创建后不能修改

  • Java中String类的常用方法总结

    目录 概述 特点 使用步骤 常用方法 判断功能的方法 获取功能的方法 转换功能的方法 分割功能的方法 概述 java.lang.String 类代表字符串.Java程序中所有的字符串文字(例如"abc" )都可以被看作是实现此类的实例. 类 String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻 译为大写或小写的所有字符的字符串的副本. 特点 字符串不变:字符串的值在创建后不能被更改. String s1 = "abc&quo

  • java 枚举类定义静态valueOf(java.lang.String)方法的问题及解决

    目录 问题的起因 猜测.分析 最终解决方案 枚举类Enum方法简介(valueof,value,ordinal) 我们应该注意到enum类型有如下的一些特征 了解了这些基本特性,我们来看看如何使用它们 问题的起因 起因来自于我对于java枚举类的无知. 我本来想定义这样一个枚举类: public enum MenuOptions { CHAT_ROOM("#1"), MENU("#0"), ERROR("#9999"); private Stri

  • 深入理解Java 对象和类

    Java作为一种面向对象语言.支持以下基本概念: •多态 •继承 •封装 •抽象 •类 •对象 •实例 •方法 •消息解析 本节我们重点研究对象和类的概念. •对象:对象是类的一个实例,有状态和行为.例如,一条狗是一个对象,它的状态有:颜色.名字.品种:行为有:摇尾巴.叫.吃等. •类:类是一个模板,它描述一类对象的行为和状态. Java中的对象 现在让我们深入了解什么是对象.看看周围真实的世界,会发现身边有很多对象,车,狗,人等等.所有这些对象都有自己的状态和行为. 拿一条狗来举例,它的状态有

  • Java的string类为什么是不可变的

    答案一: 最流行的Java面试题之一就是:什么是不可变对象(immutable object),不可变对象有什么好处,在什么情况下应该用,或者更具体一些,Java的String类为什么要设成immutable类型?不可变对象,顾名思义就是创建后不可以改变的对象,典型的例子就是Java中的String类. 复制代码 代码如下: String s = "ABC";  s.toLowerCase(); 如上s.toLowerCase()并没有改变"ABC"的值,而是创建了

  • java.lang.Void类的解析与使用详解

    今天在查看源码的时候发现了 java.lang.Void 的类.这个有什么作用呢? 先通过源码查看下 package java.lang; /** * The {@code Void} class is an uninstantiable placeholder class to hold a * reference to the {@code Class} object representing the Java keyword * void. * * @author unascribed *

  • 浅谈为什么Java里面String类是不可变的

    在Java里面String类型是不可变对象,这一点毫无疑问,那么为什么Java语言的设计者要把String类型设计成不可变对象呢?这是一个值得思考的问题 Java语言的创建者James Gosling,曾经在一次采访中被人问到:什么时候应该使用不可变对象(immutable object),他回答:任何可以使用的时候都会使用. 在这之前,我们先来简单了解一下,什么是不可变对象? 不可变对象指的是在对象创建之后,对象的内部状态以及对象的内存指针地址都不不能被改变.在Java里面final关键字就是

  • Java中String类使用方法总结

    一.Java中关于String类的常用方法 本文只用来自己做笔记,随便写写,方便自己理解,谢谢各位的指正.下面是摘抄慕课的一部分 1.使用 substring(beginIndex , endIndex) 进行字符串截取时,包括 beginIndex 位置的字符,不包括 endIndex 位置的字符. 2.字符串 str 中字符的索引从0开始,范围为 0 到 str.length()-1 3.使用 indexOf 进行字符或字符串查找时,如果匹配返回位置索引:如果没有匹配结果,返回 -1 4.整

  • 关于Java中String类字符串的解析

    目录 一.前言 二.String类概述 三.字符串的特点 四.String 构造方法 五.String类对象的特点 六.比较字符串的方法 七.判断两个字符串地址是否相等 一.前言 在java中,和C语言一样,也有关于字符串的定义,并且有他自己特有的功能,下面我们一起来学习一下. 二.String类概述 string在软件包java.lang下,所以不需要导包. String字符串是java中的重点,String 类表示字符串类 ,所有的字符串(如"adf")都属于 此类,也就是说有&q

随机推荐