String,StringBuffer与StringBuilder的区别(equal和hashCode)

String,StringBuffer与StringBuilder的区别(equal和hashCode)

一月 09, 2020

前言

在Java中的字符串属于对象,那么Java中提供了String类来创建和操作字符串,即是使用对象;因为String类修饰的字符一旦被创建就不可改变,所以当对字符串进行修改的时候,需要使用到StringBufferStringBuilder类。

String类

用来修饰字符串的,字符串是一种特殊的对象,一旦初始化就不可被改变,用String修饰的字符串变量是不可以被改变的。
例子:

1
2
3
//定义一个字符串
String str = "hello world";
String str = new String("hello world");

在Java中,我们经常用到字符串,所以需要学习如何操作字符串。对于String类,存在java.lang.String中,String类代表字符串,如何实现字符串的字面值,就是用此类来实例的。
那么字符串是?
字符串是作为常量,被双引号包着的为常用,被初始化即不可被更改。那么接下来举个例子效果。

1
2
3
4
5
6
7
8
9
10
11
String i = "123";
System.out.println("i="+i);
//结果为
i=123

如果添加以下
String i = "123";
i = "12"
System.out.println("i="+i);
//结果为
i=12

看到这个效果,你会认为不是改了吗?其实不是的,而是新创建了一个对象而已。
String中,对象是不可变的,但可以共享的。那么怎么理解是共享的呢?这里引出常量池的概念,如下:

1
2
3
4
5
6
//多个引用指向同一个字符串
String str1 = "dashu"
String str2 = "dashu";
System.out.println(str1==str2);
//结果
true

true代表它们同时指向一个字符串,即为对象。创建了一个str1对象,字符串常理为"dashu",那么再次创建一个对象时,常理名相同,在常量池中发现有相同的"dashu",那么就同时指向一个值。
常量池是用来放置一堆常量的,如果其中有相同的值,那么就不用再次创建对象,这是为了节约内存空间,如果再次创建,就会浪费内存空间,第一个创建的字符串放在常量池中,如果要用的时候,就拿来用。

1
2
3
4
5
6
7
8
//内容相同,但是创建方式不同的情况
String str3 = "abc"
String str4 = new String ("abc");
System.out.println(str3==str4);//false
System.out.println(str3.equals(str4));//true
//结果
false
true

其实str3==str4,比较的是对象,而str3.equals(str4),比较的是内容。在代码中只要有new,表示在内存中开辟了一个新空间,所以地址不相同,即str3==str4false
那么这两种创建方式有何不同,对于str3来说,在内存中只创建了一个对象,而str4就不同了,而是在内存中有两个对象。那么怎么理解呢?
str3就不用说了,可以知道就创建一个对象,在str4中,因为有个new,在内存中有new即为创建了一个对象,new String()为构造函数,那么它接收的是一个字符串对象,那么接收的值是由常量池来的,常量池中的字符串本身就是一个对象。
一般不会像str4中那样创建,因为浪费内存了,但常用来存储数组,字符数组和字节数组。new Stirng(),所以字符和字节可以转换为字符串。

String方法

为什么我们要学习String呢?
是因为我们需要使用对象,使用String类中的一堆方法。如果要知道有哪些方法,可以查一下API,要使用时不知道用哪些方法,那么就可以去查,用到就查,也不用每个都记住。
记住字符串是一个对象,是不可被更改的,它的一切方法都是围绕着对象的数据而定的。String类为不可变对象,一旦被创建,就不能修改它的数值。
对于上方代码中的例子所示,已存在的String对象,对其修改都是重新创建一个新的对象,然后把新的值保存进去而已。
所以不要理解错了下方的代码:

1
2
3
4
5
String i = "123";
i = "12"
System.out.println("i="+i);
//结果为
i=12

对于方法可以自行查找API试试效果即可,查找百度JavaAPI文档即可下载使用。

StringBuffer

对于字符串是常量,它的值在创建后时不可以改变的,但字符串缓冲区支持可变的字符串。
StringBuffer类为java.lang中,StringBuffer为字符串缓冲,StringBuffer为线程安全的可变字符序列,类似String的字符串缓冲区,缓冲区不能改,但里面可以改,通过某方法可以改变序列的长度和内容。
StringBuffer提供了主要的两种方法,append(),inset()
StringBuffer为一个字符串缓冲区,相对于一个容器,长度是可变的,可以存储任意类型数据,是将任意数据转变为字符串进行存储,StringBuffer提供了对数据的很多的操作功能。
例子:

1
2
3
4
5
6
StringBuffer sb = new StringBuffer();
sb.append("da");
sb.append("shu");
System.out.println(sb);
//结果
dashu

如果要操作数据,要转换为字符串。StringBuffer所有存储的元素都被转成字符串才可使用。

1
String str = sb.append("da").append("shu").toString();

在指定位置插入元素

1
2
3
4
sb.insert(2,"hehe");//插入
System.out.println(sb);
//结果
daheheshu

StringBuffer和StringBuilder的区别

StringBuilderjava.lang类,是一个可变的字符序列,提供了与StringBuffer兼容的API,StringBufferStringBuilder方法是一模一样的。

StringBuilder不同步,不安全。如果同时append(),delete(),insert(),会导致出错,多线程访问不安全,添加修饰synchronized即可。在jdk1.5版本后,推出StringBuilder被用作一个StringBuffer的简易替换,用在字符串缓冲区被单个线程使用的时候。

使用StringBuilder的出现,是为了提高效率,是非同步的,是对单线程访问效率高,对于StringBuffer是同步的,对多线程的访问是安全的。这些是不同点,相同的是功能一模一样的哦。

重写equals为什么要重写hashCode方法?

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

class Student{
private String name;
private int age;

public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
/*
此处省略get set方法...
*/
@Override
public int hashCode() {
return this.getAge();
}
@Override
public boolean equals(Object obj) {
if(obj == this) {
return true;
}
if(obj == null) {
return false;
}
if(obj instanceof Student) {
Student s = (Student) obj;
if (this.name.equals(((Student) obj).getName())&& this.age==s.getAge()){
return true;
}
}
return false;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}


public class HashCodeTest {

public static void main(String[] args) {
Set set = new HashSet<Student>();
Student s1 = new Student("zhangsan",11);
Student s2 = new Student("zhangsan",11);
set.add(s1);
set.add(s2);
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
}
}

运行结果:
Student [name=zhangsan, age=11]

当将上面的程序修改:重写equals方法,但不重写hashCode(),并且将两个对象的属性值都改成一样,进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

class Student {
private String name;
private int age;

public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}

/*
此处省略get set方法...
*/

/*
* @Override
* public int hashCode()
* {
* return this.getAge();
* }
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof Student) {
Student s = (Student) obj;
if (this.name.equals(((Student) obj).getName())&& this.age==s.getAge()){
return true;
}
}
return false;
}

@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}

}

public class HashCodeTest {

public static void main(String[] args) {
Set set = new HashSet<Student>();
Student s1 = new Student("zhangsan", 11);
Student s2 = new Student("zhangsan", 11);
set.add(s1);
set.add(s2);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
}
}
运行结果:
Student [name=zhangsan, age=11]
Student [name=zhangsan, age=11]

两个完全一样的对象被加入到了set集合中,当重写了hashCode方法之后,结果表明,这两个对象是同一个对象,不会同时被加入到set集合中
总结规律:

1
2
3
4
equals相等,hashCode一定相等;
equals不相等,hashCode一定不等;
hashCode相等,equals不一定相等;
hashCode不相等,equals一定不相等。