点击上方“中兴开发者社区”,关注我们
每天读一篇一线开发者原创好文
作者简介:
作者史培培,主要关注Java以及云计算方向。插科打诨于前端,后端,产品工程师之间;在现实中分层抽象,在Bug的坟头上蹦迪;常于Github海岸边拾捡贝壳,沾沾自喜;用Java可倚天屠龙,用Shell则庖丁解牛;常修程序员之道,常读Man文档。
作者的公众号“码上论剑”分享Java相关技术干货及实践,关注“码上论剑”,可获取更多精彩内容~
Jdk目前已经发展到Java 9
了,历史上有两个版本变化比较大,一个是Java 5
,另一个就是Java 8
。
本Java8新特性系列将着重分析理解Java8的新特性,以及其是怎么为我们开发提升效率的。
Java8变化最大的几个特性分别是:
Interface
Lambda
引用
Stream
Optional
Interface
在Java8版本以前,Interface接口中所有的方法都是
抽象方法
和常量
,那么在Java8中,Interface有什么新特性呢?
静态成员
在Java8以前,我们要定义一些常量,一般会写一个类,类中都是
final static
的一些变量,如下:
public class Constants {
public static final int MAX_SERVICE_TIME = 100;
}
public class Hello {
public static void main(String[] args) {
System.out.println(Constants.MAX_SERVICE_TIME);
}
}
在Java8中Interface支持
静态成员
,成员默认是public final static
的,可以在类外直接调用。
public interface MyInterface {
int MAX_SERVICE_TIME = 100;
}
public class Hello {
public static void main(String[] args) {
System.out.println(MyInterface.MAX_SERVICE_TIME);
}
}
default函数
在Java8以前,Interface中的函数是不能实现的,如下:
public interface MyInterface {
int MAX_SERVICE_TIME = 100;
void test();
}
在Java8中,Interface中支持函数有实现,只要在函数前加上
default
关键字即可,如下:
public interface MyInterface {
int MAX_SERVICE_TIME = 100;
void test();
default void doSomething() {
System.out.println("do something");
}
}
default
函数,实现类可以不实现这个方法,如果不想子类去实现的一些方法,可以写成default
函数。在Java8之前,如果我们想实现这样的功能,也是有办法的,那就是先定义
Interface
,然后定义Abstract Class
实现Interface
,然后再定义Class
继承Abstract Class
,这样Class
就不用实现Interface
中的全部方法。
static函数
在Java8中允许Interface定义
static
方法,这允许API设计者在接口中定义像getInstance一样的静态工具方法,这样就能够使得API简洁而精练,如下:
public interface MyInterface {
default void doSomething() {
System.out.println("do something");
}
}
public class MyClass implements MyInterface {
}
public interface MyClassFactory {
public static MyClass getInstance() {
return new MyClass();
}
}
@FunctionalInterface注解
什么是
函数式接口
?
函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个
Lambda
表达式作为参数。
@FunctionalInterface
public interface MyInterface {
void test();
}
@FunctionalInterface
注解能帮我们检测Interface是否是函数式接口,但是这个注解是非必须的,不加也不会报错。
@FunctionalInterface
public interface MyInterface {//报错
void test();
void doSomething();
}
Interface中的
非default/static方法都是abstract的
,所以上面的接口不是函数式接口,加上@FunctionalInterface的话,就会提示我们这不是一个函数式接口。
函数式接口的作用?
函数式接口,可以在调用时,使用一个lambda表达式作为参数。
Lamdba表达式
定义
Lambda表达式基于函数式接口实现,故可以认为Lambda表达式是对函数式接口的匿名内部类的一种简写形式。
格式
Lambda表达式的具体形式为:()->{}
箭头表达式->
将Lambda表达式分为了左右两部分,左侧为参数列表,右侧为具体实现,即Lambda体。
具体有以下以及情形:
1. 无参数无返回值
Runnable runnable = () -> {
System.out.println("run");
};
2. 有一个参数无返回值
public interface MyInterface {
void test(int x);
}
MyInterface i = (x) -> System.out.println(x);
3. 只要一个参数,小括号可以不写
MyInterface i = x -> System.out.println(x);
4. 有多个参数有返回值,并且Lambda体有多条语句
Comparator<Integer> comparator = (x, y) -> {
System.out.println("Comparator");
return Integer.compare(x, y);
};
5. Lambda体中只有一条语句,return和{}可以省略
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
6. Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
Comparator<Integer> comparator = (Integer x, Intergery) -> Integer.compare(x, y);
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
总结:
参数类型可忽略,若写所有参数都要写,若不写,可以类型推断
参数有且仅有一个时,
()
可以省略Lambda体只有一条语句,
return
和{}
都可忽略
Java8内置四大核心函数式接口(java.util.function.*包)
Consumer
消费型接口
public static void main(String[] args) {
String str = "str";
consumer(str, s -> System.out.println(s));
}
public static void consumer(String str, Consumer<String> function) {
function.accept(str);
}
Supplier
供给型接口
public static void main(String[] args) {
supplier(() -> "str");
}
public static String supplier(Supplier<String> function) {
return function.get();
}
Function
函数型接口
public static void main(String[] args) {
String str = "str";
function(str, s -> s);
}
public static String function(String str, Function<String, String> function) {
return function.apply(str);
}
Predicate
断定型接口
public static void main(String[] args) {
String str = "str";
predicate(str, s -> s.isEmpty());
}
public static boolean predicate(String str, Predicate<String> function) {
return function.test(str);
}
Lambda表达式就到这里了,一开始用起来会不习惯,用多了上手起来就熟练了,而且越用越信手拈来。
引用
一、方法引用
定义
若Lambda体中的功能,已经有方法提供实现,可以使用方法引用,可以将方法引用理解为Lambda 表达式的另外一种表现形式。
格式
方法引用的具体形式为:
1. 对象的引用 :: 实例方法名
public class People implements Serializable {
private static final long serialVersionUID = -2052988928272007869L;
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public static void main(String[] args) {
People p = new People();
// Supplier<String> supplier = () -> p.getId();
Supplier<String> supplier = p::getId;
// Consumer<String> consumer = id -> p.setId(id);
Consumer<String> consumer = p::setId;
...
}
}
2. 类名 :: 静态方法名
public static void main(String[] args) {
// Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
Comparator<Integer> comparator = Integer::compare;
...
}
当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数 (或无参数) 时:ClassName::methodName
3. 类名 :: 实例方法名
public static void main(String[] args) {
// Predicate<String> predicate = x -> x.isEmpty();
Predicate<String> predicate = String::isEmpty;
...
}
总结:
方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致(就是函数签名和返回值一致)
若Lambda的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式:ClassName::MethodName
二、构造器引用
定义
构造器的参数列表,需要与函数式接口中参数列表保持一致 (就是函数签名一致)
格式
类名 :: new
public class People implements Serializable {
private static final long serialVersionUID = -2052988928272007869L;
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public static void main(String[] args) {
// Supplier<People> supplier = () -> new People();
Supplier<People> supplier = People::new;
...
}
}
三、数组引用
格式
类型[] :: new
public static void main(String[] args) {
Function<Integer, People[]> function = People[]::new;
People[] peoples = function.apply(10);
}
Stream
Stream是什么?
在Java8源代码中,是这么定义Stream的:
A sequence of elements supporting sequential and parallel aggregate operations.
简单翻译就是流是支持顺序和并行的汇聚操作的一组元素。
从这个定义上来说,Stream
可以说是一个高级版本的Iterator
,Iterator只能一个一个遍历元素从而对元素进行操作,但是Stream可以执行非常复杂的查找、过滤和映射数据等操作,并且中间操作可以一直迭代。
Collections是存储元素,Stream是计算。
Stream可以理解为一个管道(Pipeline
),数据从管道的一边进入,经过中间各种处理,然后从管道的另一边出来新的数据。
几个注意点:
Stream自己不会存储元素。
Stream不会改变原对象。相反,他们会返回一个持有结果的新Stream。
Stream操作是延迟执行。这意味着他们会等到需要结果的时候才执行。
Stream的pipeline
创建Stream
中间操作:一个中间操作链,对数据源数据进行处理,但是是延迟执行的
终止操作:执行中间操作链,并产生结果,正如上面注意点3
创建Stream
1、java.util.Collection内置了获取流的方法,分别为串行流与并行流
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
2、java.util.Arrays内置了获取流的方法
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
3、java.util.stream.Stream内置了创建流的方法,分别为通过对象创建流和通过函数创建流
public static<T> Stream<T> of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
final Iterator<T> iterator = new Iterator<T>() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
中间操作(java.util.stream.Stream)
1、截断与切片
filter:过滤
Stream<T> filter(Predicate<? super T> predicate);
distinct:去除重复元素(通过equals和hashCode)
Stream<T> distinct();
limit:限制数量
Stream<T> limit(long maxSize);
skip:跳过
Stream<T> skip(long n);
是不是有点类似SQL语句呢?
2、映射
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
mapToInt
mapToLong
mapToDouble
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMapToInt
flatMapToLong
flatMapToDouble
3、排序
sorted
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
4、包装
peek
Stream<T> peek(Consumer<? super T> action);
终止操作
查找与匹配
allMatch:检查是否匹配所有元素
boolean allMatch(Predicate<? super T> predicate);
anyMatch:检查是否至少匹配一个元素
boolean anyMatch(Predicate<? super T> predicate);
noneMatch:检查是否没有匹配所有元素
boolean noneMatch(Predicate<? super T> predicate);
findFirst:返回第一个元素
Optional<T> findFirst();
findAny:返回当前流中的任意元素
Optional<T> findAny();
count:返回流中元素总数
long count();
max:返回流中最大值
Optional<T> max(Comparator<? super T> comparator);
min:返回流中最小值
Optional<T> min(Comparator<? super T> comparator);
forEach:内部迭代
void forEach(Consumer<? super T> action);
规约
reduce
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
收集
collect
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
Collectors静态方法
List<T> toList()
Set<T> toSet()
Collection<T> toCollection
Long counting
Integer summingInt
Double averagingInt
IntSummaryStatistics summarizingInt
String joining
Optional<T> maxBy
Optional<T> minBy
...
Stream是不是很方便呢?
Optional
背景
只要是Java程序员,都应该遇到过空指针异常:NullPointerException
,简称NPE
。
在Java8之前,我们都要判断下对象是否为null,或者用Google
提供的Guava
的Optional
在Java8中,提供了Optional
使用
init
empty 构造一个空对象
of 不能传null
ofNullable 可以为null
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
get
如果为null,会抛异常,用isPresent来判断
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
isPresent
判断元素是否为null
public boolean isPresent() {
return value != null;
}
ifPresent
判断不为null时执行操作,如optional.ifPresent(System.out::println)
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
filter
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
map
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
flatMap
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
orElse
如果为null返回某个默认值,否则返回具体值
public T orElse(T other) {
return value != null ? value : other;
}
orElseGet
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
总结
Optional有什么用?
简化代码
类似于Lambda
能一定程度避免空指针
比如:optional.ifPresent(…)等
增加可读性
比如:optional.ifPresent(…).orElse(…);是不是比if/else分支更可读呢?
Optional正确使用姿势:使用Optional时尽量不直接调用Optional.get()方法,Optional.isPresent()更应该被视为一个私有方法,应依赖于其他像Optional.orElse()、Optional.orElseGet()、Optional.map()等这样的方法。