西安特产,深化了解Java中的不可变目标,自顾不暇

深化了解Java中的不行变方针

不行变方针想必大部分朋友都不生疏,咱们在平常写代码的进程中100%会运用到不行变方针,比方最常见的String方针、包装器方针等,那么究竟为何Jshoocava言语要这么规划,真实目的和考虑点是什么?或许一些朋友没有细想过这些问题,今日咱们就来聊聊跟不行变方针有关的论题。

以下是本文目录纲要:

一.什么是不行变方针

二.深化了解不行变性

三.怎样创立不行变方针

四.不行变方针真的"彻底不行改动"吗?

一.什么是不行变方针

下面是《Effective Java》这本书关于不行变方针的界说:

1

不行变方针(Immutable Object):方针一旦被创立后,方针一切的状况及特点在其生命周期内不会发作任何改动。

从不行变方针的界说来看,其实比较简略,便是一个方针在创立后,不能对该方针进行任何更改。比方下面这段代码:

public class ImmutableObject {
private int value;

public ImmutableObject(int value) {
this.value = value;
}

public int getValue() {
return this.value;
}
}

因为ImmutableObject不供给任何setter办法,而且成员变量value是根本数据类型,getter办法回来的是value的仿制,所以一旦ImmutableObject实例被创立后,该实例的状况无法再进行更改,因而该类具有不行变性。

再比方咱们平常用的最多的String:

public class Test {

public static void main(String[] args) {
String str = "I love java";
String str1 = str;

System.out.println("after replace str:" + str.replace("java", "Java"));
System.高h辣文out.println("after replace str1:" + str1);
}
}

输出成果:

从输出成果能够看出,在对str进行了字符串替换替换之后,str1指向的字符串方针依然没有发作改动。

二.深化了解不行变性

咱们是否考虑过一个问题:假设Java中的String、包装器类规划成可变的ok么?假设String方针可变了,会带来哪些问题?

咱们这一节主要来聊聊不行变方针存在的含义。

1)让并发编程变得更简略

提到并发编程,或许许多朋友都会觉得最苦恼的作业便是怎样处理共享资源的互斥拜访,或许稍不留神,就会导致代码上线后呈现不行思议的问题,而且大部分并发问题都不是太简略进行定位和复现。所以即便对错常有经历的程序员,在进行并发编程时,也会十分的当心,心里如履薄冰。

大多数状况下,关于资源互斥拜访的场景,都是选用加锁的办法来完成对资源的串行拜访,来确保并发安全,如sy蒂莉娅战记nchronize关键字,Lock锁等。可是这种计划最大的一个难点在于:在进行加锁和解锁时需求十分地稳重。假设加锁或许解锁机遇稍有一点误差,就或许会引发重大问题,可是这个问题Java编译器无法发现,在tab进行单元测验、集成测验时也发现不了,乃至程序上线后也能正常运转,可是或许忽然在某一天,它就不行思议地呈现了。

可是人类是机敏的,已然选用串行办法来拜访共享资源这么简略呈现问题,那么有没有其他办法来处理呢?答案是必定的。

事实上,引起线程安全问题的根本原因在于:多个线程需求一起拜访同一个共享资源。

假设没有共享资源,那么多线程安全问题就天然处理了,Java中供给ThreadLocal机制便是采纳的这种思维。

可是大多数时分,线程间是需求运用共享资源互通信息的,假设共享资源在创立之后就彻底不再改动,好像一个常量,而多个线程间并发读取该共享资源是不会存在线上安全问题的,因为一切线程不管何时读取该共享资源,总是能获取到共同的、完好的资源状况。

不行变方针便是这样一种在创立之后就不再改动的方针,这种特性使得它们天然生成支撑线程安全,让并发编程变得更简略。

咱们来看一个例6pm子:

public class SynchronizedRGB {
private int red;西安特产,深化了解Java中的不行变方针,自顾不暇 // 色彩对应的赤色值
private int green; // 色彩对应的绿色值
p盛世妆娘rivate int blue; // 色彩对应的蓝色值
private String name; // 色彩称号

private void check(int red, int green, int blue) {
if (red < 0 || red > 255 || green < 0 || green > 255
|| blue < 0 || blue > 255) {
throw new IllegalArgumentException();
}
}

public SynchronizedRGB(int red, int green, int blue, String name) {
check(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}

public void set(int red, int green, int blue, String name) {
check(red, green, blue);
synchronized (this) {
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
}

public synchronized int getRGB() {
return ((red << 16) | (green << 8) | blue);
}

public synchronized String getName() {
return name;
}
}

例如一个有个线程1履行了以下代码:

SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black");
int myColorInt = color.getRGB(); // Statement1
String myColorName =bingbar color.getName(); // Statement2

然后有别的一个线程2在Statement 1之后、Statement 2之前调用了color.set办法:

color.set(0, 255, 0, "Green");

那么在线程1中变量myColorInt的值和myColorName的值就会不匹配。为了防止呈现这样的成果,必需求像下面西安特产,深化了解Java中的不行变方针,自顾不暇这样把这两条句子绑定到一块履行:

synchronized (co新泰数字电影院lor) {
int myColorInt = color.getRGB();
String myColorName = color.getName();
}

假设SynchronizedRGB是不行变类,那么就不会呈现这个问西安特产,深化了解Java中的不行变方针,自顾不暇题,比方将SynchronizedRGB改成下面这种完成办法:

public class ImmutableRGB {
private int red;
private int green;
private int blue;
private String name;

private void check(int red, int green, int blue) {
if (red < 0 || red > 255 || green < 0 || green > 255
|| blue < 0 || blue > 255) {
throw new IllegalArgumentException();
}
}

public ImmutableRGB(int red, int green, int blue, String name) {
check(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}

public ImmutableRGB set(int red, int green, int blue, String name) {
return new ImmutableRGB(red, green, blue, name);
}

public int getRGB() {
return ((red << 16) | (green << 8) | blue);
}

public String getName() {
return name;
}
}

因为set办法并没有改动本来的方针,而是新创立了一个方针,所以不管线程1或许线程2怎样调用set办法,都不会呈现并发拜访导致的数据不共同的问题。

2)消除副作用

许多时分一些很严重的bug是因为一个很小的副作用引起的,而且因为副作用一般不简略被发觉,所以很难在编写代码以及代码review进程中发现,而且即便发现了也或许会花费很大的精力才干定位出来。

举个简略的比方:

class Person {
private int age; // 年纪
private String identityCardID; // 身份证号码

publi西安特产,深化了解Java中的不行变方针,自顾不暇c int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getIdentityCardID() {
return identityCardID;
}

public void setIdentityCardID(String identityCardID) {
this.identityCardID = identityCardID;
}
}


public class Test {

public static void main(String[] args) {
Person jack = new Person();
jack.setAge(101);
jack.setIdentityCardID("42118220090315234X");

System.out.println(validAge(jack));

// 后续运用或许没有发觉到jack的age被修正了
// 为后续埋下了不简略发觉的问题

}

public static boolean validAge(Person person) {
if (person.getAge() >= 100) {
person.setAge(100); // 此处产生了副作用
return false;
}
return true;
}

}

validAge函数自身仅仅对age巨细进行判别,可是在这个函数里边有一个副作用,便是对参数person指向的方针进行了修正,导致在外部的jack指向的方针也发作了改动。

假设Person方针是不行变的,在validAge函数中是无法对参数person进行修正的,然后防止了validAge呈现副作用,削减了犯错的概率。

3)削减容器运用进程犯错的概率

咱们在运用HashSet时,假设HashSet中元素方针的状况可变,就会呈现元素丢掉的状况,比方下面这个比方:

class Person {
private int age; // 年纪
private String identityCardID; // 身份证号码

public int getAge() {
return age;
}

public 西安特产,深化了解Java中的不行变方针,自顾不暇void setAge(int age) {
this.age = age;
}

public String getIdentityCardID() {
return identityCardID;
}

public void setIdentityCardID(String identityCardID) {
this.identityCardID = identityCardID;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}

if (!(obj inst华海峰anceof Person)) {
return false;
}
Person personObj = (Per男欢女爱小说son) obj;
return this.age == personObj.getAge() && this.identityCardID.equals(personObj.getIdentityCardID());
}

@Override
public int hashCod异乡人e() {
return age * 37 + 汉中天气预报identityCardID.hashCode();
}
}


public class Test {

public static void main(String[] args) {
Person jack = new Person();
jack.setAge(10);
jack.setIdentityCardID("42118220090315234X");

Set personSet = new HashSet();
personSet.add(jack);

jack.setAge(11);

System.out.println(personSet.contains(jack));

}
}

输出成果:

所以在Java中,关于String、包装器这些类,咱们常常会用他们来作为HashMap的key,试想一下假设这些类是可变的,将会发作什么?成果不行预知,这将会西安特产,深化了解Java中的不行变方针,自顾不暇大大添加Java代码编写的难度。

三.怎样创立不行变方针

一般来说,创立不行变类准则有以下几条:

1)一切成员变量有必要是private

2)最好一起用final润饰(非有必要)

3)不供给能够修正原有方针状况的办法

  • 最常见的办法是不供给setter办法
  • 假设供给修正办法,需求新创立一个方针,并在新创立的方针上进行修正

4)经过结构器初始化一切成员变量,引证类型的成员变量有必要进行深仿制(deep copy)

5)getter办法不能对外走漏this引证以及成员变量的引证

6)最好不允许类被承继(非有必要)

JDK中供给了一系列办法便利咱们创立不行变调集,如:

Collections.unmodifiableList(List list)

别的,在Google的Guava包中也供给了一系列办法来创立不行变调集,如:

ImmutableList.copyOf(list)

这2种办法尽管都贺美琦能创立不行变list,可是两者是有差异的,JDK自带供给的办法实践上创立出来的不是真实含义上的不行变调集,看unmodifiableList办法的完成就知道了:

能够看出,实践上UnmodifiableList是将入参list的引证仿制了一份,一起将52youwu一切的修正办法抛出UnsupportedOperationException。因而假设在外部修正了入参list,实践上会影响到UnmodifiableList,而Guava包供给的ImmutableList是真实含义上的不行变调集,它实践上是对入参list进行了深仿制。看下面这段测验代码的成果便一望而知:

public class Test {

public stat西安特产,深化了解Java中的不行变方针,自顾不暇ic void mai萧何n(String[] args) {
List list = new ArrayList();
list.add(1);
System.out.println(list);

List unmodifiableList = Collections.unmodifiableList(list);
ImmutableList immutableList = ImmutableList.copyOf(list);

list.add(2);
System.out.println(unmodifiableList);
System.out.println(immutableList);

}

}

输出成果:

四.不行变方针真的"彻底不行改动"吗?

不行变方针尽管具有不行变性family,可是不是"彻底不行变"的,这儿打上引号是因为经过反射的手法是能够改动不行变方针的状况的。

咱们看到这儿或许有疑问双沟紫陶坊了,为什么已然能改动,为何还叫不行变方针?这儿右上腹部隐痛的原因面咱们不要误解不行变的原意,从不行变方针的含义剖析能看出来方针的不行变性仅仅用来辅佐协助咱们更简略地去编写代码,削减程序编写进程中犯错的概率,这是不行变方针的初衷。假设真要靠经过反射来改动一个方针的状况,此刻编写代码的人也应该会意识到此类在规划的时分就不期望其状况被更改,然后引起编写代码的人的留意。下面是经过反射办法改动不行变方针的比方:


public class Test {
public static void main(String[] args) throws Exception {
String s = "Hello World";
System.out.println("s = " + s);

Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfStri肖艺能ng.setAccessible(true);

char[] value = (char[]) valueFieldOfString.get(s);
value[5] = '_';
System.out.println("s = " + s);
}

}

输出成果:

欢迎作业一到五年的Java工程师朋友们参加Java程序员开发: 721575865

群内供给免费的Java架构学习材料(里边有高可用、高并发、高功能及分布式、Jvm功能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构材料)合理使用自己每一分每一秒的时刻来学习提高自己,不要再用"没有时刻“来粉饰自己思维上的懒散!趁年青,用力拼,给未来的自己一个告知!