不可变对象

概述

不可变对象一经安全发布,它就是不可变的。

需满足的条件

  • 对象创建后其状态不能被修改;
  • 对象所有的域都是final类型;
  • 对象是正确创建的(在对象创建期间,其this引用未逸出);

    其中可以采用的方式包括:

    1. 将类声明为final,不可被继承;
    2. 将所有的成员声明为私有的,不允许直接访问其私有成员;
    3. 对变量不向外提供set方法;
    4. 将所有成员也声明为final,只能赋值一次;
    5. 通过构造器初始化所有成员,进行深度拷贝;
    6. 在get方法中,不直接返回对象本身,而是克隆对象,并返回对象的拷贝。

final关键字

可以用来修饰类、方法、变量。

  1. 修饰类:该类不能被继承:如Java中的String、Integer、Long等基础类型的封装类均是。
  • final类中的成员变量可根据需要设定为final。
  • final类中的方法均会被隐式指为final方法。
  1. 修饰方法:场景如下
  • 锁定方法不会被继承类修改;
  • 效率:在早期的Java实现版本中,会将final方法转为内嵌调用;但若final方法过大,反而拉低效率。
  • 一个类的private方法会被隐式地指为final方法。

  1. 修饰变量:
  • 若修饰基础数据类型:一旦初始化就不再更改。
  • 若修饰引用类型:一旦初始化之后就不能再指向另一个对象。但可以修改其中的值。(好吧,这是废话…)

看个例子:

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
41
public 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();
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
throw new UnsupportedOperationException();
}

@Override
public V putIfAbsent(K key, V value) {
throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object key, Object value) {
throw new UnsupportedOperationException();
}

@Override
public boolean replace(K key, V oldValue, V newValue) {
throw new UnsupportedOperationException();
}

@Override
public V replace(K key, V value) {
throw new UnsupportedOperationException();
}

@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
throw new UnsupportedOperationException();
}

那么为什么抛出UnsupportedOperationException异常就一目了然了,该unmodifiableMap类自定义或重写的方法定义了直接不允许操作。

再看一下Collections类总共提供了多少unmodifiableXXX方法:

图示


Guava的ImmutableXXX类

相似的,其中XXX可以是CollectionListSetMap
这些类都提供了带初始化数据的声明方法,一旦初始化完成就成不可变对象了。

看一个例子:

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

例子分析:

  1. ImmutableList通过of(a,b,c,xxxx)方法来填充数据,其中的数据为初始化的数据。
  2. copyOf(xx)方法直接拷贝其他集合中数据。
  3. 通过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
27
public 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方法的实现基本是异曲同工的:

  1. 都是既不允许再指向其他对象,也不能修改其中的值;
  2. 否则都会抛出UnsupportedOperationException异常。
SupriseMF wechat
欢迎关注微信订阅号【星球码】,分享学习编程奇淫巧技~
喜欢就支持我呀(*^∇^*)~

热评文章