不可变对象(immutable objects):一旦对象被创建,它们的状态就不能被改变(包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变),每次对他们的改变都是产生了新的对象。JDK本身就自带了immutable类,比如String,Integer以及其他包装类。

遵循原则:

1. 类添加final修饰符,保证类不被继承
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。

2. 保证所有成员变量必须私有,并且加上final修饰
通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。

3. 不提供改变成员变量的方法,包括setter
避免通过其他接口改变成员变量的值,破坏不可变特性。

4.通过构造器初始化所有成员,进行深拷贝(deep copy)

如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:

public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
// this.myArray = array; wrong
this.myArray = array.clone(); // 采用深度copy来创建一个新的对象保证不会通过传入的array来修改myArray的数组元素
}
}

5. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。

优点:

  1. Immutable对象是线程安全的,可以不用被synchronize就在并发环境中共享
  2. Immutable对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程间共享
  3. Immutable对象提高了程序的性能,因为它减少了synchroinzed的使用
  4. Immutable对象是可以被重复使用的,你可以将它们缓存起来重复使用,就像字符串字面量和整型数字一样。你可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。

缺点:

  由于不可变对象不能修改重用,会制造大量垃圾,字符串就是一个典型的例子,但合理的使用immutable对象会创造很大的价值。

String不可变类

  Java的String类是不可变对象(immutable object),即创建后不可以改变的对象。一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。如果你需要频繁地修改一个字符串对象,可以使用StringBuffer或者StringBuilder,否则将会浪费大量时间进行垃圾回收,因为每次都会创建一个新的字符串。

  String的不可变特性主要为了满足常量池、线程安全、类加载的需求。

String s = "abcd";  // 可以修改变量s的引用,因为s不是final类型的变量(初始化之后不能更改),但是s指向的堆内存中的对象是不能更改的,因为它的类型是不可变的String类
String s2 = s; // s2保存了和s相同的引用值,他们指向同一个对象。
s = s.concat("ef"); // 重新创建一个string对象的引用
s.toUpperCase(); // 此处并没有改变“abcd“的值,而是创建了一个新的String类“ABCD”,然后将新的实例的指向变量s

相对于可变对象,String作为不可变对象有很多优势:

1) 不可变对象可以提高String Pool的效率和安全性。如果一个对象是不可变的,那么拷贝该对象的内容时,只需复制地址而不用复制它本身,需要很小的内存效率也很高。

2) 不可变对象对于多线程是安全的,因为在多线程的情况下,一个可变对象的值(堆中的实例)很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。

3) String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,可能被黑客们改变字符串变量指向的对象的值,从而引起各种安全隐患。

4) 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,如果变量改变了它的对象的内容,那么其它指向这个对象的变量的值也会一起改变。

5) 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

class User {
String name;
public User(String name) { this.name = name; }
public void setName(String name) { this.name = name; }
public String toString() { return name; }
} class UserWithHashCode extends User{
public UserWithHashCode(String name) { super(name); }
public int hashCode() {
final int prime = 31; int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
User other = (User) obj;
if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false;
return true;
}
}
public class Test {
public static void main(String[] args) {
// HashMap put和get方法都用key的hashcode去判断是否为同一个对象,自定义类默认hashcode为对象的地址。
// String类的hashcode是根据字符串的值来计算的,所以值相同的字符串hashcode也一样。
Map<String, Integer> map1 = new HashMap<String, Integer>();
String str = "key1";
map1.put(str, 1); print(map1, str); // Map: {key1=1}, value of key:1
str = "key2"; print(map1, str); // Map: {key1=1}, value of key:null 变量s指向新的对象
str = new String("key1"); print(map1, str); // Map: {key1=1}, value of key:1 变量s指向原来的对象,map里的key不会改变。 Map<User, Integer> map2 = new HashMap<User, Integer>();
User user = new User("Mike");
map2.put(user, 1); print(map2, user); // Map: {Mike=1}, value of key:1
user.setName("Sara"); print(map2, user); // Map: {Sara=1}, value of key:1 user指向的对象保持不变
user.setName("Mike"); print(map2, new User("Mike")); // Map: {Mike=1}, value of key:null 此处使用新的对象,因此取不到值 Map<UserWithHashCode, Integer> map3 = new HashMap<UserWithHashCode, Integer>();
map3.put(new UserWithHashCode("lily"), 1);
print(map3, new UserWithHashCode("Mike")); // Map: {lily=1}, value of key:null
print(map3, new UserWithHashCode("lily")); // Map: {lily=1}, value of key:1 重写hashCode和equals方法, 使得name相同的对象相等
}
}

Stringl类的源码:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[]; /** The offset is the first index of the storage that is used. */
private final int offset; /** The count is the number of characters in the String. */
private final int count; /** Cache the hash code for the string */
private int hash; // Default to 0

  String的成员变量是private final的,即初始化之后不可改变。在这几个成员中value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,通常我们无法访问到这个私有成员value的引用,更不能更改其数组元素。但是反射可以获取String对象中的value属性,进而改变数组结构。

//创建字符串"Hello World", 并赋给引用s
String s = "Hello World"; //获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value"); //改变value属性的访问权限
valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s); //改变value所引用的数组中的第5个字符: Hello_World
value[5] = '_';

  在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化。也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

参考:

三张图彻底了解Java中字符串的不变性

为什么Java字符串是不可变对?

JAVA不可变类(immutable)机制与String的不可变性

 
 

Java 不可变对象的更多相关文章

  1. 深入理解java不可变对象(转)

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  2. Java不可变对象

    在创建状态后无法更改其状态的对象称为不可变对象.一个对象不可变的类称为不可变类.不变的对象可以由程序的不同区域共享而不用担心其状态改变. 不可变对象本质上是线程安全的. 示例 以下代码创建了不可变类的 ...

  3. java 不可变对象 final Collections guava 简单样例

    本地环境 jdk1.8 连接 Google Guava官方教程(中文版) journaldev 说明 java的final关键字大家都了解,但是final修饰的如果是引用类型,那么不可修改的其实只是重 ...

  4. java String不可变对象,但StringBuffer是可变对象

    什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...

  5. 为什么Java字符串是不可变对象?

    转自 http://developer.51cto.com/art/201503/468905.htm 本文主要来介绍一下Java中的不可变对象,以及Java中String类的不可变性,那么为什么Ja ...

  6. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  7. JAVA核心技术I---JAVA基础知识(不可变对象和字符串)

    一:不可变对象 不可变对象(Immutable Object) –一旦创建,这个对象(状态/值)不能被更改了–其内在的成员变量的值就不能修改了. –典型的不可变对象 • 八个基本型别的包装类的对象 • ...

  8. Java进阶知识点:不可变对象与并发

    一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...

  9. Java进阶知识点4:不可变对象与并发 - 从String说起

    一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...

随机推荐

  1. Part8-不用内存怎么行_我从内部看内存lesson1

  2. spark源码阅读之network(2)

    在上节的解读中发现spark的源码中大量使用netty的buffer部分的api,该节将看到netty核心的一些api,比如channel: 在Netty里,Channel是通讯的载体(网络套接字或组 ...

  3. hdu 2211 杀人游戏

    设f(N,K)返回最后取出的编号 那么f(n,k)进行第一次选后,剩下n-n/k个人,这剩下的人里最后被取出的编号为f(n-n/k,k)记为x 那么它在前一次队列里的编号则是(x-1)/(k-1)+x ...

  4. c#字符相似度对比

    字符串相似度算法使用 Levenshtein Distance算法(中文翻译:编辑距离算法) 这算法是由俄国科学家Levenshtein提出的. 下面使用C#实现 public class Leven ...

  5. C语言中无符号与有符号问题

    unsigned char a[5] = { 12,36,96,128,182 }; a[]范围为0~256. 数组中数都有效. char a[5] = { 12,36,96,128,182 }; a ...

  6. EXCEL vlookup和small 综合运用

    表数据如下: 如何通过EXCEL函数把 “谁拥有错误的代码” 的名称列出来,数组公式如下: =IFERROR(INDIRECT("A"& IFERROR(SMALL(IF( ...

  7. CentOS7 搭建 python pypi 私有源

    (1)寻找可用的同步源,我选择的是中科大的源:http://rsync.mirrors.ustc.edu.cn (2)创建数据同步目录:/root/pypi(如果想存放到其他目录,可以通过软链接的方式 ...

  8. Logic Controller(逻辑控制器)

    逻辑控制器主要用来控制采样器的执行顺序,仅对其子节点的逻辑控制器和采样器其作用. 1.Simple Controller(简单控制器) 简单控制器主要用来组织其他逻辑控制器和采样器,提供了一个块的结构 ...

  9. 移动端页面怎么适配ios页面

    1.viewport 简单粗暴的方式:<meta name="viewport" content="width=320,maximum-scale=1.3,user ...

  10. kali linux之webshell

    webacoo(web backdoor cookie) 类终端的shell 编码通信内容通过cookie头传输,隐蔽性较强 cm:base64编码的命令 cn:服务器用于返回数据的cookie头的名 ...