概述
不可变对象一经安全发布,它就是不可变的。
需满足的条件
- 对象创建后其状态不能被修改;
- 对象所有的域都是final类型;
- 对象是正确创建的(在对象创建期间,其this引用未逸出);
其中可以采用的方式包括:
final关键字
可以用来修饰类、方法、变量。
- 修饰类:该类不能被继承:如Java中的String、Integer、Long等基础类型的封装类均是。
- final类中的成员变量可根据需要设定为final。
- final类中的方法均会被隐式指为final方法。
- 修饰方法:场景如下
- 锁定方法不会被继承类修改;
- 效率:在早期的Java实现版本中,会将final方法转为内嵌调用;但若final方法过大,反而拉低效率。
一个类的private方法会被隐式地指为final方法。
- 修饰变量:
- 若修饰基础数据类型:一旦初始化就不再更改。
- 若修饰引用类型:一旦初始化之后就不能再指向另一个对象。但可以修改其中的值。(好吧,这是废话…)
看个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@Slf4j
@NotThreadSafe //非线程安全
public class ImmutableExample1 {
private final static Integer a = 1;
private final static String b = "2";
private final static Map<Integer, Integer> map = Maps.newHashMap();
static {
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
}
public static void main(String[] args) {
map.put(1, 3);
log.info("{}", map.get(1));
}
private void test(final int a) {
}
}
运行结果:
结果分析:
final修饰引用类型的Map:一旦初始化之后就不能再指向另一个对象。
但可以修改其中的值 -> 向map中put(a,b)。
Collections.unmodifiableXXX方法
其中XXX可以是Collection、List、Set、Map等
相应的可以通过传入对应的数据类型作为参数传入方法,即可变为不可变对象。
看一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Slf4j
@ThreadSafe
public class ImmutableExample2 {
private static Map<Integer, Integer> map = Maps.newHashMap();
static {
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
//调用unmodifiableMap方法
map = Collections.unmodifiableMap(map);
}
public static void main(String[] args) {
map.put(1, 3);
log.info("{}", map.get(1));
}
}
运行结果:
结果分析:
Collections.unmodifiableMap(xxx)方法根据字面意思,不允许修改。即既不允许再指向其他对象,也不能修改其中的值。
再来看一下unmodifiableMap方法的源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* Returns an unmodifiable view of the specified map. This method
* allows modules to provide users with "read-only" access to internal
* maps. Query operations on the returned map "read through"
* to the specified map, and attempts to modify the returned
* map, whether direct or via its collection views, result in an
* <tt>UnsupportedOperationException</tt>.<p>
*
* The returned map will be serializable if the specified map
* is serializable.
*
* @param <K> the class of the map keys
* @param <V> the class of the map values
* @param m the map for which an unmodifiable view is to be returned.
* @return an unmodifiable view of the specified map.
*/
public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
return new UnmodifiableMap<>(m);
}
注释还是非常易懂的。该类重新自定义的UnmodifiableMap,那么再看一下UnmodifiableMap的源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public V put(K key, V value) {
throw new UnsupportedOperationException();
}
public V remove(Object key) {
throw new UnsupportedOperationException();
}
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
throw new UnsupportedOperationException();
}
public V putIfAbsent(K key, V value) {
throw new UnsupportedOperationException();
}
public boolean remove(Object key, Object value) {
throw new UnsupportedOperationException();
}
public boolean replace(K key, V oldValue, V newValue) {
throw new UnsupportedOperationException();
}
public V replace(K key, V value) {
throw new UnsupportedOperationException();
}
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
throw new UnsupportedOperationException();
}
那么为什么抛出UnsupportedOperationException
异常就一目了然了,该unmodifiableMap类自定义或重写的方法定义了直接不允许操作。
再看一下Collections
类总共提供了多少unmodifiableXXX
方法:
Guava的ImmutableXXX类
相似的,其中XXX可以是Collection
、List
、Set
、Map
等
这些类都提供了带初始化数据的声明方法,一旦初始化完成就成不可变对象了。
看一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@ThreadSafe
public class ImmutableExample3 {
private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
private final static ImmutableSet set = ImmutableSet.copyOf(list);
private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);
private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
.put(1, 2).put(3, 4).put(5, 6).build();
public static void main(String[] args) {
System.out.println(map2.get(3));
}
}
运行结果:4
例子分析:
ImmutableList
通过of(a,b,c,xxxx)
方法来填充数据,其中的数据为初始化的数据。copyOf(xx)
方法直接拷贝其他集合中数据。- 通过
builder().put(a, b)..put(x, x).(...).build()
不停put(x,x)
填充数据。
如果需要初始化的数据不是三个呢?
废话不多说,看一下of()
的源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public static <E> ImmutableList<E> of(E element) {
return new SingletonImmutableList(element);
}
.
.
.
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11);
}
@SafeVarargs
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others) {
Object[] array = new Object[12 + others.length];
array[0] = e1;
array[1] = e2;
array[2] = e3;
array[3] = e4;
array[4] = e5;
array[5] = e6;
array[6] = e7;
array[7] = e8;
array[8] = e9;
array[9] = e10;
array[10] = e11;
array[11] = e12;
System.arraycopy(others, 0, array, 12, others.length);
return construct(array);
}
源码分析:
可以看出它提供了参数从1个到11、12个的函数重载情况。再定睛一看:
- 一个注解:@SafeVarargs –>安全的可变参数函数。
- 而且参数列表里最后还有
E... others
–> 指除前12参数外的参数,用一个数组表示。那函数里是怎么处理这个数组呢?
通过系统函数,将该数组安全地拷贝进来。
所以无论需要初始化的参数有多少都是可以处理的~ 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
其实Guava的ImmutableXXX类和Collections.unmodifiableXXX方法的实现基本是异曲同工的:
- 都是既不允许再指向其他对象,也不能修改其中的值;
- 否则都会抛出
UnsupportedOperationException
异常。