Java String:重要到别人只能当老二的字符串类

字符串,是Java中最重要的类。这句肯定的推断不是Java之父詹姆斯·高斯林说的,而是沉默王二说的,因此你不必怀疑它的准确性。

关于字符串,有很多的面试题,但我总觉得理论知识绕来绕去没多大意思。你比如说:String cmower = new String("沉默王二");定义了几个对象?

我总觉得问我这样的问题,就好像是在拷问我:“既然你家买了冰箱,你难道不应该知道冰箱制冷的原理?”

再说,为什么要用String cmower = new String("沉默王二");而不是String cmower = "沉默王二";

我劝各位面试官不要再缠住这样的问题不放了,切记“学以致用”。理论知识如果一直是在绕弯弯,那真的毫无价值。如果要我来做面试官,我想要问的问题是:“你平常是怎么判断两个字符串相等的?是用equals()还是==?”

前言就说这么多。接下来,我们来探讨几个实用的知识点。

01 字符串是不可变的

我们来看一下String类的定义:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
}

可以发现,String类是final类型的,因此不能被继承。

如果类可以被继承,那么就会破坏类的不可变性机制。因为子类可以覆盖父类的方法,并且可以改变父类的成员变量值,一旦子类以父类的形式出现时,就不能保证类是不可变的。

String类的不可变性有什么好处呢?

1)作为HashMap的键。

因为字符串是不可变的,因此它在创建的时候哈希码(hash code)就计算好了。这也就意味着每次在使用一个字符串的哈希码的时候不用重新计算一次,这样更加高效,很适合作为HashMap中的键。

2)线程安全。

同一个字符串对象可以被多个线程共享,如果访问频繁的话,可以省略同步和锁等待的时间,从而提升性能。

3)字符串常量池的需要。

稍后介绍。

特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象

02 字符串常量池

在Java中,常用的创建字符串的方式有两种:

String cmower = "沉默王二";

String cmowsan = new String("沉默王三");

cmower使用双引号,cmowsan使用new关键字,它们有什么区别呢?

答案如下:

String cmower = "沉默王二";
String cmower1 = "沉默王二";
System.out.println(cmower == cmower1); // 输出true

String cmowsan = new String("沉默王三");
String cmowsan1 = new String("沉默王三");
System.out.println(cmowsan == cmowsan1); // 输出false

双引号创建的相同字符串使用==判断时结果为true,而new关键字创建的相同字符串使用==判断时结果为false。

这是为什么呢?

String在Java中使用过于频繁,为了避免在系统中产生大量的String对象,Java的设计者引入了“字符串常量池”的概念

当使用双引号创建一个字符串时,首先会检查字符串常量池中是否有相同的字符串对象,如果有,则直接从常量池中取出对象引用;如果没有,则新建字符串对象,并将其放入字符串常量池中,并返回对象引用。

这也就是说,"沉默王二"是放在字符串常量池中的,cmower和cmower1两个字符串对象引用是相同的。

而new关键字创建的字符串对象是不涉及字符串常量池的,直接放在堆中,也就是说,虽然cmowsan和cmowsan1都叫沉默王三,但不一个人。

强烈建议:不要使用new关键字的形式创建字符串对象。

03 +号和StringBuilder

由于字符串是不可变的,因此字符串在进行拼接的时候会创建新的字符串对象。大家都知道,内存是一定的,因此对象创建多了就会影响系统性能。

StringBuilder正是为了解决字符串拼接产生太多中间对象的问题而提供的一个类,可以通过append()方法把字符串添加到已有序列的末尾,非常高效。

那么有人在进行字符串拼接的时候,就会产生疑惑:“我到底是用+号还是StringBuilder?”

我们先来看这样一段代码:

String chenmo = "沉默";
String wanger = "王二";
System.out.println(chenmo + wanger);

这段代码是怎么编译的呢?可以使用JAD(Java反编译工具)来看一看。

String s = "\u5A0C\u5910\u7CAF";
String s1 = "\u941C\u5B29\u7C29";
System.out.println((new StringBuilder()).append(s).append(s1).toString());

你是不是看到了StringBuilder的影子?

没错,使用+号进行字符串拼接的时候,Java编译器实际是通过StringBuilder类来完成的。

难道可以使用+号来随意拼接字符串?反正Java编译器已经自动地为我们优化了。

但事实并非如此,来看这样一段代码:

String cmowers = "";
for (int i = 0; i < 9; i++) {
    cmowers += "沉默王二";
}
System.out.println(cmowers);

闭上眼睛先想一想,Java编译器会怎么做?我们期望的结果是在循环外部就创建StringBuilder,Java编译器能如我们所愿吗?

JAD反编译后的结果如下:

String s = "";
for(int i = 0; i < 10; i++)
    s = (new StringBuilder()).append(s).append("\u5A0C\u5910\u7CAF\u941C\u5B29\u7C29").toString();

System.out.println(s);

这么看来,StringBuilder是在for循环内部创建的,也就是说会创建10次。天呐,这可不是我们期望的结果!我们只希望StringBuilder创建一次。

没办法,Java编译器是做不到的,只能靠我们自己:

StringBuilder cmowers = new StringBuilder();
for (int i = 0; i < 9; i++) {
    cmowers.append("沉默王二");
}
System.out.println(cmowers);

强烈建议:如果只是三四个字符串的拼接,尽管使用+号操作符,别想什么性能优化(举个例子,你离目的地只有100米,你是打算打个出租车,还是自己步行走过去?);如果遇到多于四个字符串的拼接,或者需要用到循环来拼接,那就选择StringBuilder。

在我年轻的时候,我还会犯这样一个错误:

StringBuilder cmowers = new StringBuilder();
for (int i = 0; i < 9; i++) {
    cmowers.append("沉默王二" + "和他的读者朋友们");
}
System.out.println(cmowers);

我去,竟然在append()方法的内部使用+号!因为这个错误,我差点没被领导打死。你可要小心点。

04 关于concat()

除了使用+号和StringBuilder对字符串进行拼接,还可以使用String类的concat()方法。

concat()方法只不过是String类的一个方法而已,为什么我要单独拎出来说呢?

因为之前我要在JSP页面的EL表达式中拼接字符串,刚开始想到的是用+号操作符,但EL表达式不是Java,+号操作符是不能拼接字符串的。我当时竟然没想起来用concat()

重新铭记一下:

${item.username.concat('-').concat(item.realname)}

05 关于intern()

关于字符串的性能问题,我常在一些技术文章中看到这样的建议:“如果一个字符串使用的频率非常高,建议使用String.intern()将其缓存。”

但我并不建议你这么做,因为这个方法要显式的调用,这样很麻烦;况且,在代码编写阶段,怎么可能知道哪个字符串使用频率很高呢?

06 关于StringUtils

据我的编程经验来看,字符串的操作往往需要用到一个工具类,那就是org.apache.commons.lang3.StringUtils(null安全的,也就是说,StringUtils类的方法可以接受为null的字符串,但不会抛出NullPointerException)。

不过,我最常用的方法就那么几个:

方法等价
IsEmpty(String str)str == null || str.length == 0
isBlank(String str)str == null || str.length == 0 || str.trim().length == 0
join(Object[] arrey)把数组中的元素连接成一个字符串返回

 

 

推荐阅读:

Java异常处理:给程序罩一层保险
Java集合类:我其实没那么简单

转载于:https://www.cnblogs.com/qing-gee/p/10277688.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue 中slot 的具体用法

子组件 <template><div class"slotcontent"><ul><!--<slot></slot>--><li v-for"item in items">{{item.text}}</li></ul></div> </template><script>export default{data(){re…

Java基础教程:多线程基础(3)——阻塞队列

Java基础教程&#xff1a;多线程基础&#xff08;3&#xff09;——阻塞队列 快速开始 引入问题 生产者消费者问题是线程模型中的经典问题&#xff1a;生产者和消费者在同一时间段内共用同一存储空间&#xff0c;生产者向空间里生产数据&#xff0c;而消费者取走数据。 模拟情景…

001.Linux开机启动过程

相关Linux启动过程解析&#xff0c;此作为通用启动参考&#xff1a; 转载于:https://www.cnblogs.com/itzgr/p/10285833.html

学习vim 从常用按键开始

撤销 u 前进 ctrl r移动 下一个单词 w 当前单词首或上个单词首 b 当前单词尾或上个单词尾 e ---- 大写就是忽略标点符号 行首行尾 $^ 查询 /word 下一个 n 上一个 Nv 可视化操作命令 删除操作 x 删除光标处的字符&#xff0c;向后删除 nx …

element ui 中 el-menu 如何添加链接router-link标签

在vue项目中&#xff0c;使用elementui 框架&#xff0c;做一个后台管理系统 在写左边菜单&#xff0c;菜用了&#xff0c;elementui 提供的组件 &#xff0c; el-menu 组件。但是组件没有链接&#xff0c;而我们知道添加链接使用router-link标签代码如下&#xff1a; <el-…

使用fastjson的parseObject方法将json字符串转换成Map 或者List

fastjson 转换成map HashMap<String,String> map JSON.parseObject(jsonStr,new TypeReference<HashMap<String,String>>() {}); fastjson 转换成list List<Person> list new ArrayList<Person>(); list JSON.parseArray(jasonArray.toStri…

【01】《正则表达式必知必会》(已看)(仅存放)

【01】《正则表达式必知必会》 共149页。扫描版&#xff0c;中文版。Sams Teach Yourselef Regular Expressions in 10 minutesBen Forta著。杨涛 翻译【】魔芋&#xff1a;这本书已经没有用了。内容已吸收。内容较为基础&#xff0c;也很全面。** 附件列表 链接&#xff1a;ht…

vue-cli脚手架的.babelrc文件

{// 此项指明&#xff0c;转码的规则"presets": [// env项是借助插件babel-preset-env&#xff0c;下面这个配置说的是babel对es6,es7,es8进行转码&#xff0c;并且设置amd,commonjs这样的模块化文件&#xff0c;不进行转码["env", {"modules": …

Java秒杀业务架构设计之路

Java秒杀业务架构设计之路

疑难杂症,逐个下药

用户登陆&#xff08;三次输错机会&#xff09;且每次输错误时显示剩余错误次数&#xff08;提示&#xff1a;使用字符串格式化&#xff09; 三次登录: 1.让用户输入三次的机会,错一次的时候就要询问用户是否要继续 2.分别判断用户名和密码,如果用户名错误就提示用户错误,如果是…

JS性能分析(测试代码运行时间)

console.time("timer"); for(var i0;i<10000;i){} console.timeEnd("timer"); timer: 0.274169921875ms转载于:https://www.cnblogs.com/smzd/p/10647455.html

jsonp原生js跨域拿新浪数据插件封装【可扩展】

//修改了一个bug,增加了手动释放垃圾 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-…

Ansible基本命令

Ansible安装完成之后就自带很多命令&#xff0c;其中较常用的有7个&#xff1a; ansibleansible-docansible-galaxyansible-initansible-playbookansible-pullansible-vaultansible ansible -h Usage: ansible <host-pattern> [options] 对本机执行一个命令&#xff1a; …

Java高并发高性能分布式框架从无到有微服务架构设计

Java高并发高性能分布式框架从无到有微服务架构设计

Makefile中几种赋值

延时变量&#xff0c;只有被使用时才展开定义 : 立即变量&#xff0c;定义时的赋值立即有效 ? 条件变量&#xff0c;当变量为空时才赋值 追加赋值转载于:https://www.cnblogs.com/smzd/p/10695962.html

线程的基本协作和生产者消费者

协作基础&#xff08;wait/notify&#xff09; Java的根父类是Object&#xff0c;Java在Object类而非Thread类中&#xff0c;定义了一些线程协作的基本方法&#xff0c;使得每个对象都可以调用这些方法&#xff0c;这些方法有两类&#xff0c;一类是wait&#xff0c;另一类是no…

L1-016 查验身份证

L1-016 查验身份证 &#xff08;15 分&#xff09;一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下&#xff1a; 首先对前17位数字加权求和&#xff0c;权重分配为&#xff1a;{7&#xff0c;9&#xff0c;10&#xff0c;5&#xff…

什么是高并发,如何避免高并发

之前我将高并发的解决方法误认为是线程或者是队列可以解决&#xff0c;因为高并发的时候是有很多用户在访问&#xff0c;导致出现系统数据不正确、丢失数据现象&#xff0c;所以想到 的是用队列解决&#xff0c;其实队列解决的方式也可以处理&#xff0c;比如我们在竞拍商品、转…

.sync 修饰符的理解

正常 子组件&#xff1a; this.$emit(update:title, newTitle)父组件&#xff1a; <text-documentv-bind:title"doc.title"v-on:update:title"doc.title $event" ></text-document>简洁&#xff1a; <text-document v-bind:title.sync&quo…

L1-025 正整数A+B

题的目标很简单&#xff0c;就是求两个正整数A和B的和&#xff0c;其中A和B都在区间[1,1000]。稍微有点麻烦的是&#xff0c;输入并不保证是两个正整数。 输入格式&#xff1a; 输入在一行给出A和B&#xff0c;其间以空格分开。问题是A和B不一定是满足要求的正整数&#xff0c;…