Java 不可变对象
不可变对象(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获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。
优点:
- Immutable对象是线程安全的,可以不用被synchronize就在并发环境中共享
- Immutable对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程间共享
- Immutable对象提高了程序的性能,因为它减少了synchroinzed的使用
- 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不可变类(immutable)机制与String的不可变性
Java 不可变对象的更多相关文章
- 深入理解java不可变对象(转)
深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...
- Java不可变对象
在创建状态后无法更改其状态的对象称为不可变对象.一个对象不可变的类称为不可变类.不变的对象可以由程序的不同区域共享而不用担心其状态改变. 不可变对象本质上是线程安全的. 示例 以下代码创建了不可变类的 ...
- java 不可变对象 final Collections guava 简单样例
本地环境 jdk1.8 连接 Google Guava官方教程(中文版) journaldev 说明 java的final关键字大家都了解,但是final修饰的如果是引用类型,那么不可修改的其实只是重 ...
- java String不可变对象,但StringBuffer是可变对象
什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...
- 为什么Java字符串是不可变对象?
转自 http://developer.51cto.com/art/201503/468905.htm 本文主要来介绍一下Java中的不可变对象,以及Java中String类的不可变性,那么为什么Ja ...
- 深入理解Java中的不可变对象
深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...
- JAVA核心技术I---JAVA基础知识(不可变对象和字符串)
一:不可变对象 不可变对象(Immutable Object) –一旦创建,这个对象(状态/值)不能被更改了–其内在的成员变量的值就不能修改了. –典型的不可变对象 • 八个基本型别的包装类的对象 • ...
- Java进阶知识点:不可变对象与并发
一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...
- Java进阶知识点4:不可变对象与并发 - 从String说起
一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...
随机推荐
- Vue.js 安装及其环境搭建
For me or other first studying vue.js. For Windows PC: 1.先安装node.js 安装官网最新的即可 版本应该要大于6.0版本 nodejs的官网 ...
- U14.04 teamviewer install
转载http://www.cnblogs.com/kunyuanjushi/p/5205639.html 安装i386库 sudo apt-get install libc6:i386 libgcc1 ...
- js 二维数组排序sort()函数
一.按数值排序 var arr = [[1, 2, 3], [7, 2, 3], [3, 2, 3]]; arr.sort(function(x, y){ return x[0] – y[0];}) ...
- 运行maven build报错No goals have been specified for this build.
运行maven报错: [ERROR] No goals have been specified for this build. You must specify a valid lifecycle p ...
- css3的那些高级选择器一
css大家都不陌生了,从1996年12月css1正式推出,经历了1998年5月css2,再到2004年2月css2.1,最后一直到2010年推出的css3.css的推出给web带来巨大 的改变,使我们 ...
- Python htmlTestRunner生成测试报告Demo
#该代码段是ReadTxt_demo.py 的代码,用户读取txt 文件中的用户信息. #ReadTxt_demo.py def readTxt(filePath): fo = open(filePa ...
- 【bzoj1066】: [SCOI2007]蜥蜴 图论-最大流
[bzoj1066]: [SCOI2007]蜥蜴 把石柱拆点,流量为高度 然后S与蜥蜴连流量1的边 互相能跳到的石柱连inf的边 石柱能到边界外的和T连inf的边 然后跑dinic就好了 /* htt ...
- kali linux之webshell
webacoo(web backdoor cookie) 类终端的shell 编码通信内容通过cookie头传输,隐蔽性较强 cm:base64编码的命令 cn:服务器用于返回数据的cookie头的名 ...
- ORA-14402:更新分区关键字列将导致分区更改
开启行迁移就好了:alter table TABLE_NAME enable row movement; 注意:表分区的时候要确定分区字段是否会UPDATE,如果会的话一定要开启行迁移,否则就会报这个 ...
- 洛谷P4525 【模板】自适应辛普森法1
题面 传送门 题解 我似乎连积分都不太熟练→_→ 总之就是对于一个原函数,我们找一个二次函数来近似它,那么有 \[ \begin{aligned} \int_a^bf(x)dx &\appro ...