Java常用类——String
1、什么是String
在Java中String类是一个使用非常频繁的类,它不是java八个基本数据类型的其中之一,而是Java提供的在java.lang包下的用于创建和操作字符串的类。本文章通过搜集的几个常见面试题来学习String类的简单使用。
2、String的特点
- 使用final修饰:不可被继承,并且内部一些方法也被final修饰。
public final class String
- **不可变性:**String的不可变性主要有两方面决定,首先是String类使用fianl来修饰,决定了String不能被继承,另外一个原因是因为String用于存储字符串值得char行数组value[]也是使用final修饰的。
private final char value[];
- **常量池优化:**常量池的出现是为了解决对象快速创建问题。当一个String对象创建之后,会在字符串常量池中进行缓存,等到下次创建相同对象的时候将直接返回已缓存对象的引用,从而优化对象的创建过程。
3、String对象的创建方式
-
使用常量赋值的形式实例化对象
String str = "java";
-
使用构造方法的形式实例化对象
String str = new String("java");
这两种实例化方式到底有什么区别呢?我们先从存放的位置来分析。
两种实例化方式都会在栈中创建变量str。
第一种方式实例化对象,虚拟机首先会在字符串常量池中通过String的equals方法来查找是否存在“java”常量,如果存在,虚拟机会将该常量的引用地址直接复制给str变量,如果不存在,会将字符串常量“java”存放在字符串常量池中,并将其引用地址复制给str变量。
第二种方式创建的对象会在堆中开辟内存空间,这里需要注意的是,jvm并不是将字符串变量存放到了堆内存中,它只是在堆中开辟了一个内存块,用于存放指向内存本身的引用地址。它的字符串常量“java”也会存放到字符串常量池中,所以使用第二种方式创建对象的时候具体流程如下:
虚拟机首先会在字符串常量池中通过String的equals方法来查找是否存在“java”常量,无论常量池中是否存在“java”字符串虚拟机都会在堆空间开辟内存空间存放新对象,如果存在就直接使用,如果不存在,会将字符串常量“java”存放在字符串常量池中,并将其引用地址复制给堆中新开辟的地址。
总结:
- 常量赋值的形式实例化,字符常量内容存于常量池,变量存于栈中,直接指向常量池。
- 构造方法实例化,会先在堆中创建实例对象,引用对象存于栈中,然后再去常量 区寻找需要的字符常量,如果找到了,直接使用,没找到则开辟新的空间并存储内容。
4、“==”与equals的区别
在解释上述结果之前需要先明确一个概念,那就是“==”符号到底比较的是什么?这个分为两种情况
- 基本数据类型
对于基本数据类型来讲,"=="是判断左右两边的值是否相等
- 引用类型
对于引用类型的变量,"=="是判断左右两边所指向的地址是否一致
//Object的equals方法
public boolean equals(Object obj) {
return (this == obj);
}
从Object的equals方法源码可知,Object的equals方法其实也是直接比较对象的地址,和“==”没有任何区别。那我们怎么去比较两个对象的内容而不是对象地址是否一致呢?现在就需要我们去重写equals方法,下面以String的equals方法为例。
//String的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
首先我们先来看下String中equals方法源码,从中我们可以看出调用String的equals方法对两个变量进行比较时,首先是判断两个对象的地址是否相等,如果不相等还会对两个string变量进行逐字符比较,如果相同那么就返回true,否则返回false。当然对于我们自定义的类我们也可以根据需求自定义重写他们的equals方法。
5、String常用方法
方法名称 | 描述 |
---|---|
public String (char[] value,int offset,int count) | 将组部分字符数组变为String类 |
public String (char[] value) | 将组部分字符数组变为String类 |
public char cahrAt(int index) | 返回指定所索引对应的字符信息 |
public char[] toCharArrary() | 将字符串以字符数组的形式返回 |
public boolean equals(String anObject) | 进行相等判断,区分大小写 |
Public boolean equalsIgnoreCase(String anotherString) | 进行相等判断,不区分大小写 |
public int compareTo(String anoterString) | 判断两个字符串的大小(按照字符编码比较) =0:表示要比较的两个字符内容相等 >0:表示大于的结果 <0:表示小于的结果 |
public boolean contains(String s) | 判断指定的内容是否内存 |
public int indexOf(String str) | 由前向后查找指定字符串的位置, 如果查找到了则返回(第一个字母)位置的索引, 如果找不到返回-1 |
public int indexOf(String str,int fromIndex) | 从指定位置由前向后查找指定字符串的位置,找不到返回-1 |
public boolean starsWith(String perfix) | 判断是否以指定的字符串开头,如果是返回true,否则返回false |
public boolean endsWith(String suffix) | 判断是否以指定的字符串结尾 |
public String repaceAll(String regex(正则),String replacement) | 用新的内容替换掉全的旧的 |
public String substring(int beginIndex) | 从指定索引截取到结尾 |
public String substring(int beginIndex,int endIndex) | 截取部分字符串的数据 |
public String split(String regex) | 按照指定的字符串进行全部拆分 |
public String concat(String str) | 追加字符 |
public String toLowerCase() | 转小写 |
public String toUpperCase() | 转大写 |
public String trim() | 去掉字符串左右两边的空格,中间空格保留 |
public intern() | 数据入池 |
public boolean isEmpty() | 判断是否是空字符串(不是null,而是””长度是0) |
6、面试题目解析
-
考察String的实例化方式
String str1 = "java"; String str2 = "java"; String str3 = new String("java"); String str4 = new String("java"); System.out.println(str1==str2); System.out.println(str3==str4); System.out.println(str3.equals(str3)); 输出结果:true false true
str1与str2是通过赋值的方式进行的实例化,引用地址相同,所以结果为true
str3与str4是通过构造方法来进行的实例化,引用地址不同,所以结果为false
第三个结果为true是因为String的equals方法并不是直接比较两者指向的地址,还会判断内容是否相同。
-
String不可变性
String str3 = new String("java"); String str4 = str3; System.out.println(str3==str4); str3 = "dart"; System.out.println(str3==str4); 输出结果:true false
因为String对象创建后,它会将字符串值保存在final修饰的一个字符数组里面,所以String是不可变的
/** The value is used for character storage. */ private final char value[];
一旦字符串的值发生改变,那么该对象就变成了一个新的对象,其地址也会发生改变,所以最终结果应该为true和false。
-
String字符串拼接
String str1 = "java dart"; String str2 = "java "; String str3 = "dart"; String str4 = str2+str3; String str5 = "java "+"dart"; System.out.println(str1==str4); System.out.println(str1==str5); 输出结果:false true
拼接后得到的str4与str5字符串内容是一样的,但是输出结果为什么不同呢?
str2与str3均为变量,变量与变量进行拼接那么str4就会在堆中开辟内存地址,而str1则是保存在常量池中,所以str4与str5内存地址一致。
“java ”与“dart”均为常量,拼接后的结果将保存在常量池中,所以str5与str1内存地址一致
-
final修饰符的使用
String str1 = "java dart"; final String str2 = "java "; final String str3 = new String("java "); String str4 = str2+"dart"; String str5 = str3+"dart"; System.out.println(str1==str4); System.out.println(str1==str5); 输出结果:true false
这个结果似乎有点意外,为什么3中str1==str4为false,这里就变成了true?因为这里的str2使用final修饰,如果在编译期都可以知道确切值(定义变量的时候就初始化),那么在编译器会将其当做常量使用,所有用到该变量的地方就相当于直接使用该常量,所以常量与常量拼接还将保存在常量池中,而str3在编译期并不知道确切值,所以输出结果为false