Immutable类介绍和guava之ImmutableMap使用

1. 什么是Immutable(不可变对象)

按照Effective Java的说明,需要满足下面几条规则:

  • 保证类不能被继承 - 为了避免其继承的类进行mutable的操作
  • 移除所有setter/update等修改对象实例的操作
  • 保证所有的field是private和final的

immutable Objects就是那些一旦被创建,它们的状态就不能被改变的Objects,每次对他们的改变都是产生了新的immutable的对象
而mutable Objects就是那些创建后,状态可以被改变的Objects.

举个例子:

String和StringBuilder:
String是immutable的,每次对于String对象的修改都将产生一个新的String对象,而原来的对象保持不变;
StringBuilder是mutable,因为每次对于它的对象的修改都作用于该对象本身,并没有产生新的对象;

但有的时候String的immutable特性也会引起安全问题,这就是密码应该存放在字符数组中而不是String中的原因!

immutable objects 比传统的mutable对象在多线程应用中更具有优势,它不仅能够保证对象的状态不被改变,而且还可以不使用锁机制就能被其他线程共享。

注意:如果 immutable 集合中包含了 mutable 元素,比如 mutable 集合,那么该 immutable 集合在这种情况下是非线程安全的。

实际上JDK本身就自带了一些immutable类,比如String,Integer以及其他包装类。
为什么说String是immutable的呢?比如:java.lang.String 的trim,uppercase,substring等方法,它们返回的都是新的String对象,而并不是直接修改原来的对象。

1.1 如何在Java中写出Immutable的类?

要写出这样的类,需要遵循以下几个原则:
1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
2)Immutable类的所有的属性都应该是final的。
3)对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。
4)对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。
5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)

当然不完全遵守上面的原则也能够创建immutable的类,比如String的hashcode就不是final的,但它能保证每次调用它的值都是一致的,无论你多少次计算这个值,它都是一致的,因为这些值的是通过计算final的属性得来的!

有时候你要实现的immutable类中可能包含mutable的类,比如java.util.Date,尽管你将其设置成了final的,但是它的值还是可以被修改的,为了避免这个问题,我们建议返回给用户该对象的一个拷贝,这也是Java的最佳实践之一。

1.2 使用Immutable类的好处:

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

immutable也有一个缺点就是会制造大量垃圾,由于他们不能被重用而且对于它们的使用就是”用“然后”扔“,字符串就是一个典型的例子,它会创造很多的垃圾,给垃圾收集带来很大的麻烦。当然这只是个极端的例子,合理的使用immutable对象会创造很大的价值

2. jdk9以上自带的不可变集合方法

使用

  • 使用 List.of 工厂方法创建 immutable list,list 中的元素是有序的,允许重复,但不允许为 null。
  • 使用 Set.of 工厂方法创建 immutable set,set 中的元素是无序的,且不重复,不允许为 null。
  • 使用 Map.of 和 Map.ofEntries 方法创建 immutable map,map 中的 key 不允许重复,key 与 value 都不能为 null。
//----------------------- List ------------------------
// In JDK 8:
List<String> stringList = Arrays.asList("a", "b", "c");
stringList = Collections.unmodifiableList(stringList);

// In JDK 9:
List stringList = List.of("a", "b", "c");

//----------------------- Set ------------------------
// In JDK 8:
Set<String> stringSet = new HashSet<>(Arrays.asList("a", "b", "c"));
stringSet = Collections.unmodifiableSet(stringSet);

// In JDK 9:
Set<String> stringSet = Set.of("a", "b", "c");

//----------------------- Map ------------------------
// In JDK 8:
Map<String, Integer> stringMap = new HashMap<String, Integer>();
stringMap.put("a", 1);
stringMap.put("b", 2);
stringMap.put("c", 3);
stringMap = Collections.unmodifiableMap(stringMap);

// In JDK 9:
Map stringMap = Map.of("a", 1, "b", 2, "c", 3);
Map <Integer, String> friendMap = Map.ofEntries(
	entry(1, "Tom"),
	entry(2, "Dick"),
	entry(3, "Harry"),
	...
	entry(99, "Mathilde"));

拷贝集合:

  • JDK10 之后,可以使用 copyOf 方法来创建集合的拷贝。
  • 如果原 list 为 mutable,copyOf 方法会创建一个 immutable 集合,然后拷贝原 list 中的元素,之后,对原 list 的编辑和更新,不会影响 immutable list。如果原 list 为 immutable,copyOf 方法直接返回该 list 的引用。
List<Item> list= new ArrayList<>();
list.addAll(getItemsFromSomewhere());
list.addAll(getItemsFromElsewhere());
list.addAll(getItemsFromYetAnotherPlace());

List<Item> snapshot = List.copyOf(list);

使用 Streams 创建 immutable 集合

JDK 10 之后,java.util.stream.Collectors 类可根据 stream 元素创建 immutable 集合:

  • Collectors.toUnmodifiableList()
  • Collectors.toUnmodifiableSet()
  • Collectors.toUnmodifiableMap(keyMapper, valueMapper)
  • Collectors.toUnmodifiableMap(keyMapper, valueMapper, mergeFunction)
Set<Item> immutableSet = sourceCollection.stream()
				.map(...)
				.collect(Collectors.toUnmodifiableSet());

3. guava之ImmutableMap使用

  • 对不可靠的客户代码库来说,它使用安全,可以在未受信任的类库中安全的使用这些对象
  • 线程安全的:immutable对象在多线程下安全,没有竞态条件
  • 不需要支持可变性, 可以尽量节省空间和时间的开销. 所有的不可变集合实现都比可变集合更加有效的利用内存 (analysis)
  • 可以被使用为一个常量,并且期望在未来也是保持不变的,immutable对象可以很自然地用作常量,因为它们天生就是不可变的对于immutable对象的运用来说,它是一个很好的防御编程(defensive programming)的技术实践。

3.1 使用方法

初始化

// 初始化不可变map
Map<String,Object> immutableMap = new ImmutableMap.Builder<String,Object>().build();
// 或者
ImmutableMap<String, Object> immutableMap1 = new ImmutableMap.Builder<String, Object>()
        .put("name", "immutableMap")
        .put("describe", "immutable map")
        .build();
        
// 或者
ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
mapBuilder.put("one","1");
mapBuilder.put("two","2");
mapBuilder.put("three","3");
Map<String, Object> immutableMap2 = mapBuilder.build();

// 或者
ImmutableMap<String, Object> immutableMap0 =
        ImmutableMap.<String, Object>builder()
                .put("name", "immutableMap")
                .put("describe", "immutable map")
                .build();
        
// 不可变list用法
List<String> immutableList = new ImmutableList.Builder<String>().build();
// 或者
ImmutableList<Object> immutableList1 = new ImmutableList.Builder<>()
        .add("name")
        .add(20)
        .add("不可变集合")
        .build();
// 或者
ImmutableList.<Object>builder()
        .add("name")
        .add(20)
        .add("不可变集合")
        .build();
        
// 不可变set
Set immutableSet = new ImmutableSet.Builder<>().build();
// 或者
Set immutableSet1 = new ImmutableSet.Builder<>()
        .add("name")
        .add(20)
        .add("不可变集合")
        .build();
// 或者
ImmutableSet.<Object>builder()
        .add("name")
        .add(20)
        .add("不可变集合")
        .build();

3.2 ImmutableMap的使用场景:

适合
1、确定性的配置, 比如根据不同的key值得到不同的请求url
2、写单元测试

不适合
1、key, value为未知参数, 可能有null产生的情况

3.3 具体使用方法

// 1. 使用copyOf()方法: 它不能直接修改,但是可以改变其内部可变的Map:
Map<String, Object> buildMap = buildMap();
ImmutableMap<String, Object> immutableMapCopy = ImmutableMap.copyOf(buildMap);

// 2. 使用builder()方法
ImmutableMap immutableMapBuilder = ImmutableMap.<String, Object>builder()
        .putAll(buildMap)
        .put("Costa Rica", "North America")
        .build();

// 3. 使用of()
// 我们可以使用ImmutableMap.of() 方法创建一个不可变的Map,其中包含动态提供的一组条目。它最多支持五个键/值对:
Map map = ImmutableMap.<String, Object>of(
        "genName", "暂无",
        "falseNo", 123,
        "discernNo", "discernNo",
        "status", 1,
        "reviewStatus", 0);
private Map<String, Object> buildMap() {
    Map<String, Object> result = new HashMap<String, Object>();
    result.put("id", 1);
    result.put("type", "type");
    result.put("key", "key");
    result.put("value", "value");
    result.put("status", 0);
    result.put("createDate", new Date());
    result.put("updateDate", new Date());
    result.put("sort", "亚瑟");

    return result;
}
posted @ 2022-05-05 10:11  xiexie0812  阅读(1061)  评论(0编辑  收藏  举报