Java入门系列之字符串特性(二)
前言
上一节我们讲解到字符串本质上就是字符数组,同时详细讲解了字符串判断相等需要注意的地方,本节我们来深入探讨字符串特性,下面我们一起来看看。
不可变性
我们依然借助初始化字符串的方式来探讨字符串的不可变性,如下:
String str = "Jeffcky";
System.out.println(str);

上述我们通过字面量的方式来创建字符串,接下来我们对字符串str进行如下操作:
String str = "Jeffcky";
str.substring(0,3).concat("wang").toLowerCase().trim();
System.out.println(str);

我们看到针对str字符串进行截取、连接、小写等操作后,字符串的值依然未发生改变,这就是字符串的不可变性,我们通过查看任意一个对字符串操作的方法,比如concat方法源码:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
通过查看concat源码得出:原始的str值永远都没有发生改变,它的值只是被复制,然后将我们连接的文本添加到复制的副本里,最后返回一个新的String。所以到这里我们知道针对字符串的操作都是复制一份字符串,然后对复制后的字符串进行操作,最终返回一个新的String对象。
字符串池
我们再来看看上一节所给出的代码示例,如下:
public class Main {
public static void main(String[] args) {
String str1 = "Jeffcky";
String str2 = "Jeffcky";
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
}
}

当我们实例化一个String时(在本例中为Jeffcky)保存在Java堆内存(用于所有Java对象的动态内存分配)中。虽然在这个例子中我们有两个不同的引用变量,但它们都只是指Java Heap Memory中的同一内存位置,虽然看起来有两个不同的String对象,但实际上只有一个,而str2永远不会被实例化为对象,而是在内存中分配对应于str1的对象,这是因为Java针对字符串进行了优化处理,每次要实例化此类String对象时,都会将要添加到堆内存的值与先前添加的值进行比较,如果值已存在,则不初始化对象,并将值分配给引用变量,这些值保存在名叫“字符串池”中,该字符串池包含所有文字字符串值,当然我们可以通过new运算符绕过这种情况。
为什么字符串是不可变或final呢?
上述我们通过例子说明了字符串的不可变性特性,那么为什么字符串是不可变的呢?可以参考知乎回答:《https://www.zhihu.com/question/31345592》。我认为主要在于有效共享对象,节省内存空间。当程序运行时,创建的String实例的数量也会增长,如果不缓存String常量,堆空间中会有大量的String,占用内存空间,所以String对象被创建后缓存在字符串池中,若缓存的字符串被多个客户端共享,此时一个客户端的操作修改了字符串则影响到其他客户端,因此通过字符串的不可变性来规避这种风险,同时通过缓存和共享字符串常量,JVM为Java应用程序节省内存。
有了字符串不可变性,可以很安全的被多线程所共享,我们不用担心线程同步问题,确保线程安全。
有了字符串不可变性,可以很好的使用比如HashMap,我们能正确检索到存储到HashMap中的对象,若字符串可变且在插入到HashMap后并修改了字符串内容,此时将会出现丢失对应字符串所映射的对象。
有了字符串不可变性,此时会缓存字符串哈希码,所以每次调用字符串的hashcode方法时都不用计算,使得在HashMap中使用键非常快。
其他等等......
接下来我们一起来通过源码的方式来看看String的实现,如下:
public class Main {
public static void main(String[] args) {
char a[] = {'j', 'e', 'f', 'f', 'c', 'k', 'y'};
String str = new String(a);
System.out.println(str);
}
}
我们通过字符数组创建字符串的方式去查看源码,如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; /**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = new char[0];
} /**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
} ...
}
我们看到字符串对象定义为final(当前我们还未学到final,我们只需要知道通过final关键字修饰说明该类不可继承),网上有很多例子说字符串对象通过final关键字修饰,说明字符串不可变,其实这种说法是不严谨且错误的。String通过final关键字修饰的原因在于:确保不能通过扩展和覆盖行为来破坏String类的不可变性而非说明字符串不可变。比如,如下例子:
public class Main {
public static void main(String[] args) {
String str1 = "Jeffcky";
String str2 = "Jeffcky".toUpperCase();
System.out.println(str1);
System.out.println(str2);
}
}

现在字符串str2为"Jeffcky".toUpperCase(),我们将同一个对象修改为“JEFFCKY”,如果修改了字符串变量,其他字符串变量也将自动受到影响,比如str1也将是"JEFFCKY",很显然是不可取的。
总结
本文我们详细介绍了字符串的不可变、字符串池特性,同时解释了字符串为何不可变,以及说明字符串类定义为final,并不是说明其不可变,只是为了不允许通过扩展或覆盖来破坏字符串的不可变性。
Java入门系列之字符串特性(二)的更多相关文章
- Java入门系列之字符串创建方式、判断相等(一)
前言 陆续从0开始学习Java出于多掌握一门语言以后的路也会更宽,.NET和Java兼顾,虽然路还很艰难,但事在人为.由于Java和C#语法相似,所以关于一些很基础的内容不会再重头讲,Java系列中所 ...
- Java入门系列-26-JDBC
认识 JDBC JDBC (Java DataBase Connectivity) 是 Java 数据库连接技术的简称,用于连接常用数据库. Sun 公司提供了 JDBC API ,供程序员调用接口和 ...
- 数据挖掘入门系列教程(十二)之使用keras构建CNN网络识别CIFAR10
简介 在上一篇博客:数据挖掘入门系列教程(十一点五)之CNN网络介绍中,介绍了CNN的工作原理和工作流程,在这一篇博客,将具体的使用代码来说明如何使用keras构建一个CNN网络来对CIFAR-10数 ...
- Java入门系列-19-泛型集合
集合 如何存储每天的新闻信息?每天的新闻总数是不固定的,太少浪费空间,太多空间不足. 如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象,可以使用Java集合框架. Java 集合框架提 ...
- Java入门系列之hashCode和equals(十二)
前言 前面两节内容我们详细讲解了Hashtable算法和源码分析,针对散列函数始终逃脱不掉hashCode的计算,本节我们将详细分析hashCode和equals,同时您将会看到本节内容是从<E ...
- Java入门系列(二)Java常用关键字
53个关键字 在JAVA中目前一共有53个关键字:其中由51+2个保留字=53个关键字 访问控制 private protected public default 类.方法和 ...
- Java入门系列(十二)Java反射
Why--指的是为什么做这件事,也既事物的本质. 反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化 ...
- Java入门系列(三)面向对象三大特性之封装、继承、多态
面向对象综述 封装 封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫接口. 有了封装,就可以明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者:而外部调用者也可以知道 ...
- ES6入门系列三(特性总览下)
0.导言 最近从coffee切换到js,代码量一下子变大了不少,也多了些许陌生感.为了在JS代码中,更合理的使用ES6的新特性,特在此对ES6的特性做一个简单的总览. 1.模块(Module) --C ...
随机推荐
- DynamicList
DynamicList设计要点——类模板 申请连续空间作为顺序存储空间 动态设置顺序存储空间的大小 保证重置顺序存储空间时的异常安全性 DynamicList设计要点——函数异常安全的概念 不泄露任何 ...
- post请求四种传送正文的方式
一.简介 HTTP协议规定post提交的数据必须放在消息主体(entity-body)中,但协议没有规定数据必须使用什么编码方式.HTTP协议是以ASCII码传输,建立再TCP/IP协议之上的应用层规 ...
- docker jenkins安装
https://hub.docker.com/r/jenkins/jenkins jenkins的docker官方镜像地址 https://jenkins.io/ jenkins官方网站 环境: 阿里 ...
- 高阶函数&&高阶组件(二)
高阶组件总共分为两大类 代理方式 操纵prop 访问ref(不推荐) 抽取状态 包装组件 继承方式 操纵生命周期 操纵prop 代理方式之 操纵prop 删除prop import React fro ...
- Vue 03
目录 组件 组件的分类 组件的特点 组件的使用 组件传参-父传子 组件传参-子传父 组件 组件就是html, css和js文件的集合体, 实现对代码的复用, 组件就是vue对象 组件的分类 根组件 & ...
- 改变JAVA窗体属性的操作方法
在本篇内容里小编给大家详细分析了关于改变JAVA窗体属性的操作方法和步骤,需要的朋友们学习下. 若将JDK版本升级到最新版本,Java窗体就可以简单实现窗体的透明效果,用户可以通过拉动滑块(Slide ...
- Java入门系列之StringBuilder、StringBuffer(三)
前言 上一节我们讲解了字符串的特性,除了字符串类外,还有两个我们也会经常用到的类,那就是StringBuffer和StringBuilder.因为字符串不可变,所以我们每次对字符串的修改比如通过连接c ...
- Windowns系统下搭建python环境
本文介绍下在windows系统下安装python和python环境搭建. 安装PYTHON 首先,我们去python的官方网站下载python安装包.官网地址:https://www.python.o ...
- Python中编写类的各种技巧和方法
简介 有关 Python 内编写类的各种技巧和方法(构建和初始化.重载操作符.类描述.属性访问控制.自定义序列.反射机制.可调用对象.上下文管理.构建描述符对象.Pickling). 你可以把它当作一 ...
- 关于scrapy中如何区分是接着发起请求还是开始保存文件
一.区分 根据yield迭代器生成的对象是request对象还是item对象 二.item 1.配置tem对象 在items.py文件中设置类 class MyscrapyItem(scrapy.It ...