Java 基础之String篇
这篇推文中基于JDK8来进行讲解,后版本的JDK部分改动会提出来。
JDK中的String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
OK,我们下面看下String的Diagrams:
OK,我们先梳理一下String的继承关系。
String类实现了三个接口,分别是Serializable,Compareable,CharSequence。
对于Serializable接口中,并没有需要实现的方法,该接口只是用于表示可以序列化。
对于Compareable接口中,只有一个方法<b>public int compareTo(T o);</b>这个方法主要是用于比较的。
对于CharSequence,它是一个描述字符串结构的接口,里面定义了字符串的最基本的操作。比如获取长度,获取第几位的字符,截取字符。
不知道大家有没有注意到String类使用了final修饰,为什么String类要使用final修饰呢?请继续看下去哦。
String类重要成员变量
/** The value is used for character storage. */
// String 的底层便是 char数组。在这里提一下,在JDK9之后,我们String的底层是 byte数组,并且通过coder变量来代表编码格式。
private final char value[];
/** Cache the hash code for the string */
// hash值,这个很重要哦。这个后面会讲到的。
private int hash; // Default to 0
// 序列版本号,这个很重要,后面会有一篇推文专门讲序列化的。大家可以期待下。
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
// serialPersistentFields变量可以指定序列化的数据,在这里并没有指定。
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
不知道大家看到这里,是否注意到我们的char数组也使用了final修饰呢。在这里大家得回忆回忆我们final的作用哦(本推文后面也讲述到了)。注意不管是JDK8中的char数组,还是JDK9之后的byte数组,都是使用了final修饰哦,下面会揭晓为什么。
String类常用的成员方法
在这里只会列举方法名,方法的实现大家可以自行阅读源码。(这里只是列举一部分常用的方法,String因为使用太频繁了,常用方法太多了)
// 获取字符串的长度
public int length() {}
// 判断字符串是否为空
public boolean isEmpty() {}
// 获取索引为index下的字符
public char charAt(int index) {}
// 这个方法是继承自Object对象的,用来比较对象的,大家可以看看在String里面的重写,还是很有意思的。
public boolean equals(Object anObject) {}
// 判断字符串是否是以suffix结尾,比如 laochou.jpg 就可以采用了 endsWith("jpg");
public boolean endsWith(String suffix) {}
// 获取对应字符的索引,索引从0开始。很多人会好奇,为啥参数不是char,因为都一样 'a' = 97
public int indexOf(int ch) {}
// 跟上面一样,但是这个是获取最后一个字符的索引。
public int lastIndexOf(int ch) {}
// 将字符串中的旧字符,替换成我们新字符
public String replace(char oldChar, char newChar) {}
// 截取字符串
public String substring(int beginIndex) {}
public String substring(int beginIndex, int endIndex) {}
// 分割字符串
public String[] split(String regex) {}
// 去重字符串两端的空白字符
public String trim() {}
// 全部转小写
public String toLowerCase() {}
// 全部转大写
public String toUpperCase() {}
// 生成一个字符数组
public char[] toCharArray() {}
如何创建String对象呢
在这里,我只列举一些常见的用法。如果大家想看全部的,可以自行阅读源码。
// 通过字面量直接赋值的方式(字面量就是""里面的东西,比如String a = "laochou",那么laochou就是字面量。)
String a = "";
// 通过new的方式(但是注意参数不同)
// 什么都不传
String b = new String();
// 传空字符串
String c = new String("");
// 传字符数组
String d = new String(new char[]{});
// 传byte数组
String e = new String(new byte[]{});
String对象到底创建在哪里。
String对象创建在哪里,取决于你是通过何种方式来使用的。我前面也讲了一些创建String对象的方式。
<b>在这里我们就细讲一下。</b>
// 通过使用字面量来赋值的,会在String Pool创建String对象,并返回引用地址。
String a = "finger dance";
// 通过new关键字来创建的,分为两种情况
// 1. 如果对应字面量在String Pool中没有,那么会先在String Pool创建一个对象。然后会在堆中也同样创建一个对象。
// 2. 如果对应字面量在String Pool中已经存在,那么就只会在堆中创建一个对象。
// 重点:只要是new关键字,那么就一定会在堆中创建。
String b = new String("laochou");
<b>有一个很重要的方法是intern。</b>。我们通过这个方法可以从String Pool中获取我们需要的字面量对应的对象引用,若有就返回。若无便在String Pool创建对象,并返回引用。因此我们可以通过intern方法后期动态添加对象到String Pool。
String Pool是?
是的,我们在这里,说一下,为什么要考虑使用到String Pool。在这里在提一下,String Pool 和 运行时常量池是不同的哈,后面会有专门一篇推文来讲。
因为在程序编写的过程中,会大量地用到String对象,如果每次声明一个String都要新建一个String对象,那么会造成空间的极大浪费。于是Java的堆中开辟了一块存储空间String Pool,用于存储String对象。当有多个String引用指向同样的String字符串时(New出来的除外),实际上是指向的是同一个String Pool中的对象,而不需要额外的创建对象。
也正是因为如此,String Pool的特性也需要我们String不可变。如果可变的话,那么指向就全会乱。
String是不可变的。
首先,大家得知道被final的作用,下面给大家分享下
- final 修饰类,代表类不可被继承。那么其成员方法也无法被重写。
- final 修饰变量,代表变量不可变。对于基本类型,那么值是不可变的。对于引用类型,那么对象是不可变的,但是对象的内容是可以变的。
- final 修饰方法,代表方法不可重写。
OK,那我们现在就来理一理 String为什么是不可变的。
- 首先我们String类是被final修饰的,那么String类是不可被继承的,那么它的成员方法就没有机会被重写。
- 其次我们String类的底层char数组是被final修饰的,那么char数组的地址是无法改变的
- 最后我们String类中并没有提供修改char数组内容的方法,因此String是不可变的。
看下面的代码
String i = "laochou";
String j = "FingerDance";
// 字符串使用+来拼接的底层其实是使用StringBuilder来操作的。(后面也会有一篇推文来讲String的扩展)
i = j + "_" + i;
System.out.println(i);
这就有人问了,这个i不是变了吗。其实原来i的引用地址所指向的内容并没发生改变,只是i这个引用地址改变了,改成了指向 "FingerDance_laochou" 这个字符串对象的地址。这里我画一个图方便大家理解。
String设计不可变有哪些好处
知道String设计成不可变有哪些好处,在面试的过程中如何被问到String,如果提及的话,也是加分项。
String设计成不可变的好处:
- 安全问题。在这里解释下,为什么安全?比如在网络传输过程中,字符串的值是不能被改变的。如果可以改变,那么无法保证安全,因为我们可以讲值改为我们任意的。
- 线程安全问题。不可变就天生具备线程安全。我们一般提的线程安全都是相对线程安全。
- 可以缓存hash值。因为String的hash值经常被使用,比如String作为HashMap的key。不可变的特性可以使得hash值可以进行缓存,因为是不变的。这也是为什么String类中有hash这个成员变量。
String对象如何判断是否相等
有两种方式可以用来判断对象是否相等。:
- ==,使用等等操作符的时候,不仅会比较对象的内容,也会比较对象的引用地址。只有两者都满足,才相等。
- equals,使用equals方法,只会比较对象的内容。如果内容相等就是相等。
因此我们需要根据我们的场景来使用这两者来判断String对象是否相等。
Demo:
String f = "laochou";
String g = "laochou";
String h = new String("laochou");
System.out.println(f == g);
System.out.println(f == h);
System.out.println(f.equals(h));
大家可以先小试牛刀看看哦。如果做完了,就可以编写代码试下校对。
关于String的一些面试题。
<b>String 的底层数据结构(根据不同的JDK,说法不同)。</b>
<b>String 为什么设计成不可变,是如何保证不可变的</b>
<b>讲一讲final关键字(这里虽然不是String的面试题,但是由于String被final修饰了,很容易被引出来问)</b>
<b>String是如何比较对象的</b>
这些面试题的答案都可以从推文中找到,这里只是帮大家回忆下推文中的知识。FingerDance后期会整理面试题给大家(专题,大杂烩都会有的呢,大家敬请期待)
最后
以上就是本期的内容了,希望看到这里的小伙伴,能收获一些知识。未来可期!!!
<b><font color="red">创作不易,如果觉得写的不错,还请点赞,关注,转发!!!</font></b>
<b>我是Laochou,一位又老又丑的前行者!!!下期见。</b>
<b>往期推荐:</b>