可变对象(immutable)和不可变对象(mutable)
这个是之前一直忽略的一个知识点,比方说说起String为什么是一个不可变对象,只知道因为它是被final修饰的所以不可变,而没有抓住不可变三个字的重点:
1、不可变对象就是那些一旦被创建,它们的状态就不能被改变的对象,每次对它们的改变都是产生了新的对象
2、可变对象就是那些创建后,状态依然可以被改变的对象
举个例子:String和StringBuilder,String是不可变的,因为每次对String对象的修改都将产生一个新的String对象,而原来的对象保持不变;StringBuilder是可变的,因为每次对StringBuilder的修改都作用于该对象本身,并没有新的对象产生。如果这么说还不够清楚,截取两段源码,首先是String的concat方法,用户向已有的字符串后面拼接新的字符串:
1 public String concat(String str) { 2 int otherLen = str.length(); 3 if (otherLen == 0) { 4 return this; 5 } 6 char buf[] = new char[count + otherLen]; 7 getChars(0, count, buf, 0); 8 str.getChars(0, otherLen, buf, count); 9 return new String(0, count + otherLen, buf); 10 }
看到第9行,new了一个新的String出来。然后看一下StringBuilder,StringBuilder最常用的应该就是append方法了,append一个字符串的时候会调用StringBuilder的父类AbstractStringBuilder的append方法:
1 public AbstractStringBuilder append(String str) { 2 if (str == null) str = "null"; 3 int len = str.length(); 4 ensureCapacityInternal(count + len); 5 str.getChars(0, len, value, count); 6 count += len; 7 return this; 8 }
第5行的这个value就是一个char型数组"char[] value;",每次对StringBuilder的操作都是对value的改变。
不可变的对象对比可变对象有两点优势:
1、保证对象的状态不被改变
2、不使用锁机制就能被其他线程共享
实际上JDK本身就自带了一些不可变类,比如String、Integer、Float以及其他的包装类,判断的方式就是看它们真正的那个对象是不是final的就好了。
我们自己也可以创建不可变对象,创建不可变对象应该遵循几个原则:
1、不可变对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象
2、不可变对象的所有属性应该都是final的
3、对象必须被正确地创建,比如对象引用在创建过程中不能泄露
4、对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的不可变特性
使用不可变类的好处:
1、不可变类是线程安全的,可以不被synchronized修饰就在并发环境中共享
2、不可变对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程之间共享
3、不可变对象提高了程序的性能,因为它减少了synchronized的使用
4、不可变对象时可以被重复利用的,你可以将它们缓存起来,就像字符串字面量和整型数值一样,可以使用静态工厂方法来提供类似于valueOf这样的方法,它可以从缓存中返回一个已经存在的不可变对象,而不是重新创建一个
不可变对象虽然好,但是它有一个很大的缺点就是会制造出大量的垃圾,给垃圾收集带来很大的麻烦,由于它们不能被重用而且,所以不可变对象的使用依赖于开发人员合理的使用。另外,不可变对象也有一些安全问题,比如密码就建议不要用String,因为:
如果密码是以明文的形式保存成字符串 ,那么它将一直留在内存中,直到垃圾收集器把它清除。而由于字符创被放在字符串缓存池中以方便重用,所以它就可以在内存中被保留很长时间,而这将导致安全隐患,因为任何能够访问内存的人都可以清晰地看到文本中的密码,这也是为什么总是应该用加密的形式而不是明文来保存密码。由于字符串是不可变的,所以没有任何方式可以修改字符串的值,因为每次修改都将产生新的字符串,而如果使用char[]来保存密码,就可以将其中所有元素都设置为空或者是零。所以将密码保存到字符数组中很明显地降低了密码被窃的风险。