使集合框架更便捷的工厂方法
JEP269中提议,为集合框架增添一些工厂方法,来使创建不可变集合类与含有少量元素的Map变得更加便捷。下文就为什么它们应运而生来展开详细的阐述。
集合框架增加工厂方法是必然的结果
Java饱受其语法臃肿的批评,比如,创建一个小而确定的集合类时(比如一个List),需要使用它的构造方法,然后将它的引用存放在局部变量中,通过引用来多次调用add()方法之后, 最后才来封装这个集合以获得不可变的视图。
早先的使用过程如下:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list = Collections.unmodifiableList(list);
- 1
- 2
- 3
- 4
- 5
上面这个语法如此臃肿的例子在先前的版本中并不能够简化,不可变的静态集合必须在静态初始块中来填充,而不是使用更加方便的字段表达式。但是,也不得不提一下下面这些单语句表达式:
List<String> list1 =
Collections.unmodifiableList(new ArrayList<>(Arrays.asList("a", "b", "c")));
List<String> list2 =
Collections.unmodifiableList(new ArrayList<String>() {{ add("a"); add("b"); add("c"); }});
List<String> list3 =
Collections.unmodifiableList(Stream.of("a", "b", "c").collect(toList()));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第一种方式比较扯淡,你走遍千山,你跨过弱水,只为取一瓜瓢饮,是的,你没有看错,你费尽千辛万苦只为了生成一个包含a,b,c三个元素的List,并且你要构建一个ArrayList还要仰仗Arrays.asList(“a”, “b”, “c”)这个乌七八黑的方式,它不好用不说,关键是它在短短的生命周期之后还要被GC,过程还是不可见的。。。
第二种好像看上去没那么扯淡,使用一个匿名内部类的实例初始化构造器来减少代码臃肿度,看上去很完美,但是可能会发生内存泄漏或者序列化的问题,因为它每次使用都会耗费额外的资源,还包含对封闭实例和任何捕获对象的隐藏引用。
第三种方式是使用Java8的Streams API来完成的,虽然代码没那么臃肿,但是过程中也涉及到了不必要的对象创建。此外,Streams API不能用来构建Map, 除非值是经键计算而来或者stream的元素包含键值对。
为解决这些问题,JEP186提议了集合字面量的概念,集合字面量是一种句法表达式,采用一种类数组的方式,来创建List、Map或者其它的集合类,下面是其原始类型的简明表达方式:
List<String> list = #[ "a", "b", "c" ];
- 1
没有任何新的语言特性,一切就像我们所思所想那样简明,但是这种集合字面量为什么没有被整合到Java9中去呢?取而代之的是,Java9采用了工厂方法来替代它,这其实是为了使语言改动尽量最小化,采用现有的方式,生产语法糖来达到这个目的的。
如此,集合工厂方法应运而生了。
一起来看看集合工厂方法
JEP 269的工厂方法受到类java.util.Collection和java.util.EnumSet类中的类似工厂方法的启发。 Collection提供用于创建空List,java.util.Set和Map的工厂方法,以及创建具有一个元素或键值对的单例List,Set和Map。 EnumSet提供了几个重载的of(…)工厂方法,它们采用固定或可变数量的参数,是为了更方便地创建指定元素的EnumSet。Java 9中的EnumSet模型的of()方法提供一致和通用的方式来创建包含任意类型对象的List,Set和Map。
以下工厂方法已添加到List接口中:
static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
static <E> List<E> of(E e1, E e2, E e3)
static <E> List<E> of(E e1, E e2, E e3, E e4)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
static <E> List<E> of(E... elements)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
以下工厂方法已添加到Set接口中:
static <E> Set<E> of()
static <E> Set<E> of(E e1)
static <E> Set<E> of(E e1, E e2)
static <E> Set<E> of(E e1, E e2, E e3)
static <E> Set<E> of(E e1, E e2, E e3, E e4)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
static <E> Set<E> of(E... elements)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在每个方法列表中, 第一个方法创建一个空的不可修改的集合。接下来的10个方法可创建1-10个元素的不可修改集合。尽管这些方法比较混乱,但它们避免了final类型的可变参方法产生的数组分配,初始化和垃圾回收开销,这种方法还支持任意大小的集合。
以下是List和Set的示例:
import java.util.List;
import java.util.Set;
public class ColDemo
{
public static void main(String[] args)
{
List<String> fruits = List.of("apple", "orange", "banana");
for (String fruit: fruits)
System.out.println(fruit);
try
{
fruits.add("pear");
}
catch (UnsupportedOperationException uoe)
{
System.err.println("unable to modify fruits list");
}
Set<String> marbles = Set.of("aggie", "alley", "steely");
for (String marble: marbles)
System.out.println(marble);
try
{
marbles.add("swirly");
}
catch (UnsupportedOperationException uoe)
{
System.err.println("unable to modify marbles set");
}
}
}
- 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
运行后输出:
apple
orange
banana
unable to modify fruits list
steely
alley
aggie
unable to modify marbles set
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
以下工厂方法则添加到Map接口中:
static <K,V> Map<K,V>
of()
static <K,V> Map<K,V>
of(K k1, V v1)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,
K k8, V v8)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,
K k8, V v8, K k9, V v9)
static <K,V> Map<K,V>
of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,
K k8, V v8, K k9, V v9, K k10, V v10)
static <K,V> Map<K,V>
ofEntries(Map.Entry<? extends K,? extends V>... entries)
- 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
第一个方法创建了一个空的不可变的Map,接下来10个方法创建包含1-10和键值对的Map,尽管这些方法比较混乱,但它们避免了final类型的可变参方法产生的数组分配,初始化和垃圾回收开销,且支持任意大小的Map。
虽然Map的可变参数方法近似List和Set的,但是它的每个键值对必须被包装起来,下面这个方法可以方便地将包装键值对转换为Map标准键值对:
Map.Entry<K,V> entry(K k, V v)
- 1
下面是Map的ofEntries() 和entry()方法的示例:
import java.util.Map;
import static java.util.Map.entry;
public class MapDemo
{
public static void main(String[] args)
{
Map<String, String> capCities =
Map.ofEntries(entry("Manitoba", "Winnipeg"),
entry("Alberta", "Edmonton"));
capCities.forEach((k, v) ->
System.out.printf("Key = %s, Value = %s%n", k, v));
try
{
capCities.put("British Columbia", "Victoria");
}
catch (UnsupportedOperationException uoe)
{
System.err.println("unable to modify capCities map");
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
运行后输出:
Key = Alberta, Value = Edmonton
Key = Manitoba, Value = Winnipeg
unable to modify capCities map
- 1
- 2
- 3
注意,未来的JDK版本可能会让开发者指定值类型来减少包装键值对所带来的性能开销,从entry()方法可以看出,通过它返回一个新的实现自Map.Entry的具体引用类型,我想这是为了后面把潜在特性迁移到值类型中去设下的铺垫吧。
原文:https://www.javaworld.com/article/3164262/java-language/java-9s-other-new-enhancements-part-1.html
CompletableFuture类的增强,等等。。
java.util.concurrent.CompletableFuture
在本文中,展开对响应式流的介绍,然后介绍这个发布订阅框架。
响应式流(Reactive Streams)
批处理系统在收集了足够多的数据,达到某一个阈值亟待进行下一步操作的时候,就衍生出了一个新的名词—数据处理(Data processing)。这时候,面向流(stream-oriented)的架构思想可以帮助我们尽快达成这个目标。它可以捕获和处理实时数据,并且可以快速地(秒级甚至更短)基于处理的结果来对系统进行相应的操作。和它相比,一个批处理系统可能会花费数秒、数天、甚至更久来做出响应。
处理数据流(特别是大小不定的实时数据)需要在异步系统中特别小心。主要问题是要控制资源消耗,避免数据源和处理系统出现供大于求(积压)的情况。这时候,需要异步地来对数据进行并行处理,利用分布式系统或者发挥多核CPU的效能,能有效地使数据处理过程变得快速高效。
响应式流(Reactive Streams)为这种非阻塞背压的异步流处理提供了一个标准。在处理系统出现过载的时候,采用异步发送信号的方式通知数据源做相应的处理。这个通知的信号就像是水管的阀门一样,关闭这个阀门会增加背压(数据源对处理系统的压力),同时也会增加处理系统的压力。
这个标准的目的是治理跨异步边界的流数据交换(比如向其他线程传输数据) ,同时确保处理系统不被缓冲数据而压垮。换一种说法,背压是这个标准模型的一个组成部分,以便允许在线程之间调停的队列被界定。特别注意,背压通信是异步的。
响应式流(Reactive Streams)的提出就致力于提供一组最小规模的接口、方法、或者协议来描述这个操作或实体:具有非阻塞背压的异步数据流。
发布-订阅(publisher-subscriber)框架
java.util.concurrent.Flow
和java.util.concurrent.SubmissionPublisher
Flow
- Publisher:数据项发布者、生产者
- Subscriber:数据项订阅者、消费者
- Subscription:发布者与订阅者之间的关系纽带,订阅令牌
- Processor:数据处理器
Flow.Publisher
void subscribe(Flow.Subscriber<? super T> subscriber)
- 1
onError()
方法来抛出IllegalStateException
异常,除此之外,订阅者的onSubscribe()
方法会调用一个新的Flow.Subscription
,当空对象传给订阅者时,subscribe()
Flow.Subscriber<T>
void onSubscribe(Flow.Subscription subscription)
void onComplete()
void onError(Throwable throwable)
void onNext(T item)
- 1
- 2
- 3
- 4
onSubscribe()
方法用来确认订阅者注册到发布者是否注册成功,它以参数列表的方式接收一个Flow.Subscription
类型的参数,而这个参数类型里面声明的方法允许向发布者请求发布新的数据项,或请求发布者不再发布更多的数据项。onComplete()
方法用在当订阅者没有调用其他方法,而Subscription
onError(Throwable throwable)
onNext()
Flow.Subscription
void request(long n)
void cancel()
- 1
- 2
request()
方法添加n个数据项到当前未满的订阅请求中。如果n小于或等于0,订阅者的onError()
方法会被调用,并且抛出IllegalArgumentException 异常,此外,如果n大于0,订阅者就会在onNext()
cancel()
Processor
接口来自行构建。SubmissionPublisher
实现自Flow.Publisher
接口,向当前订阅者异步提交非空的数据项,直到它被关闭。每个当前订阅者以一个相同的顺序接收新提交的数据项,除非数据项丢失或者遇到异常。SubmissionPublisher
SubmissionPublisher
提供了三个构造方法来获取实例。无参的构造器依赖于 ForkJoinPool.commonPool()
SubmissionPublisher
import java.util.Arrays;
import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;
public class FlowDemo
{
public static void main(String[] args)
{
// Create a publisher.
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
// Create a subscriber and register it with the publisher.
MySubscriber<String> subscriber = new MySubscriber<>();
publisher.subscribe(subscriber);
// Publish several data items and then close the publisher.
System.out.println("Publishing data items...");
String[] items = { "jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec" };
Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
publisher.close();
try
{
synchronized("A")
{
"A".wait();
}
}
catch (InterruptedException ie)
{
}
}
}
class MySubscriber<T> implements Subscriber<T>
{
private Subscription subscription;
@Override
public void onSubscribe(Subscription subscription)
{
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(T item)
{
System.out.println("Received: " + item);
subscription.request(1);
}
@Override
public void onError(Throwable t)
{
t.printStackTrace();
synchronized("A")
{
"A".notifyAll();
}
}
@Override
public void onComplete()
{
System.out.println("Done");
synchronized("A")
{
"A".notifyAll();
}
}
}
- 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
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
wait()
和notifyAll()
方法来使主线程等到onComplete()
下面是输出结果:
Publishing data items...
Received: jan
Received: feb
Received: mar
Received: apr
Received: may
Received: jun
Received: jul
Received: aug
Received: sep
Received: oct
Received: nov
Received: dec
Done
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
最后说一句,熟悉RxJava的同学可以会心一笑了。