[其他]不可变数据类型
一、什么是不可变类
简言之,immutable, 创建完实例对象后,实例对象不可被改变。最典型的例子是String类。
String str = new String("hello");
str = "world";
System.out.println(str);
Out:world
看似这个对象被修改了,但实际上,是在内存中开辟了一个新空间。
注:在哪部分内存开辟了新空间?
学习String的时候,我们肯定被介绍过 常量池,知道String是个在底层存储是个final修饰的字符数组。我们的String被放在常量池中,如果新的字符串与之相同,新实例对象指向常量池。
String str1 = new String("hello");
String str2 = "hello";
String str3 = "hello";
System.out.println(str1 == str2);
System.out.println(str2 == str3);
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(System.identityHashCode(str3));
Out:
false
true
28014437
19451386
19451386
通过这段代码可以发现,使用new关键字创建的String并没有进入常量池,应该还是储存在堆内存空间中。所以上面问题,可能是在常量池也可能是在堆内存中开辟新空间,但新String肯定不是原内存地址,是不可变类。
二、创建一个不可变类
其实,我们可以创建一个不可变类,创建原则:
1、类的成员变量, final private修饰
2、构造器带参数,初始化成员变量
3、只提供getter方法,不提供setter方法
4、关于hashCode() 和equals() : 有必要的换进行重写,要根据成员变量的hash值进行equals 重写。
但是,这其中存在一个问题,如果一个不可变类中的某个成员变量是可变类的实例,那么这个不可变类,就变成了可变类。
public class Test {
public static void main(String[] args) {
Job job_1 = new Job("Student");
Person person_1 = new Person("Alex",job_1);
System.out.println(person_1);
job_1.setJobName("Beggar");
System.out.println(person_1);
}
}
class Job{
//可变
private String jobName;
public Job(){}
public Job(String jobName){
this.jobName = jobName;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
@Override
public String toString() {
return jobName ;
}
}
class Person{
//不可变
private final String name;
private final Job job;
public Person(String name,Job job){
this.name = name;
this.job = job;
}
public String getName() {
return name;
}
public Job getJob() {
return job;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", job=" + job +
'}';
}
}
Out:
Person{name='Alex', job=Student}
Person{name='Alex', job=Beggar}
产生这个问题原因是,直接引用了一个可变对象,所以不可变了类中的可变成员变量都需要在类中new出来。
对构造器的修改如下:
public Person(String name,Job job){
this.name = name;
this.job = new Job(job.getJobName());
}
三、缓存实例的不可变类
由于不可变实例不可被更改,所以相同的不可变实例要频繁开辟内存空间,这对系统资源是极大的消耗,所以我们要考虑缓存不可变实例。
注:当然盲目缓存,或者被缓存的对象使用频率很低会使得系统性能下降。
对Person修改如下:
public class Test {
public static void main(String[] args) {
Job job_1 = new Job("Student");
//new 类似 new创建String
//valueOf()相当于String s = "Student";类似于进入常量池
Person person_1 = new Person("Alex",job_1);
Person person_2 = new Person("Alex",job_1);
Person person_3 = Person.valueOf("Alex",job_1);
Person person_4 = Person.valueOf("Alex",job_1);
System.out.println(person_1 == person_2);
System.out.println(person_1 == person_3);
System.out.println(person_3 == person_4);
}
}
class Job{
private String jobName;
public Job(){}
public Job(String jobName){
this.jobName = jobName;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
@Override
public String toString() {
return jobName ;
}
@Override
public boolean equals(Object o) {
return o.toString().equals(jobName);
}
}
class Person{
//建立缓存,要在类之前加载static
private static int MAX_SIZE = 10;
private static Person[] cacheSpace = new Person[MAX_SIZE];
private static int position = 0;
private final String name;
private final Job job;
//构造器也可以隐藏
public Person(String name, Job job){
this.name = name;
this.job = new Job(job.getJobName());
}
public static Person valueOf(String name , Job job){
for(int i = 0 ; i < cacheSpace.length ; i++){
if (cacheSpace[i] != null &&
(cacheSpace[i].getName().equals(name)&&cacheSpace[i].getJob().equals(job))){
return cacheSpace[i];
}
}
if (position == MAX_SIZE){
cacheSpace[0] = new Person(name,job);
position = 1;
}else{
cacheSpace[position++] = new Person(name ,job);
}
return cacheSpace[position - 1];
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(job, person.job);
}
@Override
public int hashCode() {
return Objects.hash(name, job);
}
public String getName() {
return name;
}
public Job getJob() {
return job;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", job=" + job +
'}';
}
}
Out:
false
false
true
(参考了疯狂java讲义代码)