Java8特性及常用API使用

这篇文章主要介绍Java 8的Lambda表达式和Steam流的使用,以及其他特性。

目录:

<a name="简介"></a>

简介

Java 8带来的最大的改变的是添加了Lambda(λ)表达式,提供了新的API(称为"流",Stream)其思路在和使用数据库查询语言中的思路类似-用更高级的方式表达想要的东西,其他的还有新的日期的API、GC的改进、并发改进等。
<a name="函数式编程"></a>

函数式编程

函数式编程是一种编程范式,它的主要思想是把函数过程尽量写成一系列嵌套的函数调用。具有以下几个特点

  • 函数是一等公民:函数和其他数据类型一样,可以赋值给其他变量,也可以作为参数,传递给另外一个函数,或者作为返回值返回。

  • 没有副作用,不修改状态。

  • 强调将计算过程分解成可复用的函数。
    <a name="Lambda表达式"></a>

Lambda表达式

Lambda表达式没有名字,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

使用案例 Lambda示例
布尔表达式 (List<String> list) -> list.isEmpty();
创建对象 () -> new User();
消费一个对象 (User user) -> {System.out.println(user.getName());}
从一个对象中选择/抽取 (String s) -> s.length();
组合两个值 (int a, int b) -> a * b;
比较两个对象 (User user1, User user2) -> user1.getAge().compareTo(user2.getAge());

<a name="什么是函数式接口"></a>

什么是函数式接口

Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,于是产生了函数接口这个概念。函数式接口仅仅声明了一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式,不过函数式接口可以包含默认方法和静态方法。如下:

@FunctionalInterface
public interface FunctionInterface {

    void test();

    static void say() {
        System.out.println("hello");
    }

    default void play() {
        System.out.println("play");
    }
}

使用:

FunctionInterface.say();
FunctionInterface functionInterface = () -> System.out.println("这是一个函数式接口");
functionInterface.test();
functionInterface.play();

输出:

hello
这是一个函数式接口
play

<a name="Java8中重要的函数式接口"></a>

Java8中重要的函数式接口

下面是Java 8中一些常用的函数式接口。

接口名 参数 返回类型 示例
Predicate<T> <T> boolean 判断是否
Consumer<T> <T> void 输出一个值
Function<T> <T> R 获得一个对象的名字
Supplier<T> None T 工厂方法
UnaryOperator<T> T T 逻辑非(!)
BinaryOperator<T> (T, T) T 求两个数的乘积(*)

简单的使用例子:

Predicate<Integer> predicate = x -> x == 1;
Consumer<Integer> consumer = x -> System.out.println(x);
Function<Integer, Integer> function = x -> x = x + 1;
Supplier<Integer> supplier = () -> 1;
UnaryOperator<Integer> unaryOperator = x -> x + 1;
BinaryOperator<Integer> binaryOperator = (x, y) -> x * y;

方法引用

方法引用是Lambda的一种快捷写法,在某种情况下比Lambda更易读,在使用方法引用时引用对象方法在分隔符::的前面,方法的名称在后面。如下

(User user) -> user.getAge() == User::getAge
(str, i) ->  str.substring(i) == String::substring
(String s) -> System.out.println(s) == System.out::println

构建方法引用的方式有三种:

  • 指向静态方法的方法引用, 例如:Objects::nonNull
  • 指向任意类型实例方法的方法引用, 例如:String::length
  • 指向现有对象的实例方法的方法引用 , 例如:user::getName

<a name="Stream流"></a>

Stream流

Stream是一个借口继承了BaseStream接口,BaseStream接口继承了AutoCloseable接口。它只能被消费一次,如果想要继续使用,需要重新创建一个流。如下会抛出异常。

List<String> strs = Arrays.asList("A", "B", "C");
Stream<String> s = strs.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);

内部迭代实现机制

外部迭代:首先调用Iterator方法,产生一个新的Iterator对象,进行控制迭代过程。

User jack = User.of("Jack", 21, "杭州");
User rose = User.of("Rose", 18, "杭州");
List<User> users = Arrays.asList(jack, rose);
for(User user : users) {
    System.out.println(user.getName());
}

内部迭代:首先调用stream方法,它的作用和iterator()方法一样,不过它返回的是内部迭代中相应的接口Stream。这样做的好处就是在迭代时要进行多次操作时可以不用多次循环,只需要迭代一次就好了。
像filter这种不产生新集合的方法叫做惰性求值方法,像count这样最终会从Stream产生值的方法叫作及早求值方法。

例子:

User jack = User.of("Jack", 21, "杭州");
User rose = User.of("Rose", 18, "杭州");
List<User> users = Arrays.asList(jack, rose);
users.stream()
     .filter(user -> {
         System.out.println(user.getName());
         return user.getAge() > 20;
     });
System.out.println("------------");
users.stream()
     .filter(user -> {
         System.out.println(user.getName());
         return user.getAge() > 20;
     }).count();

输出:

------------
Jack 
Rose

结论:只有在需要的时候进行计算可以更好的提示效率
<a name="collect方法"></a>

常用的方法

collect方法

将Stream流转换为一个集合

#输入
Stream<String> stream = Stream.of("a", "b", "c");
List<String> strs = stream.collect(Collectors.toList());
strs.forEach(System.out::println);
#输出
a
b
c

<a name="filter方法"></a>

filter方法

过滤掉流中不符合条件的元素

#输入
Stream<Integer> nums = Stream.of(18, 19 ,20);
nums.filter(num -> num > 18).forEach(System.out::println);

#输出
c

<a name="map方法"></a>

map方法

将流中的元素转换为另外一个元素。

#输入
Stream<Integer> nums = Stream.of(18, 19 ,20);
map(num -> num + 100).forEach(System.out::println);

#输出
118
119
120

<a name="flatMap方法"></a>

flatMap方法

将底层的元素全部抽出来放到一起,如下将List中的元素全部抽取出来,流中只包含Integer元素。

List<Integer> nums1 = Arrays.asList(1, 2, 3);
List<Integer> nums2 = Arrays.asList(4, 6);
Stream.of(nums1, nums2).flatMap(num -> num.stream()).forEach(System.out::println);

<a name="reduce"></a>

reduce方法

将Stream流中的数据聚合成一个数据


图片来源
#输入
int ages = Stream.of(1, 2, 34).reduce(0, (a, b) -> a + b);
System.out.println(ages);

#输出
37

<a name="创建无限流"></a>

创建无限流

Stream的API提供了两个静态方法创建无限流:iterate和generate。由两个静态方法创建的流会根据给定的函数按需创建流一般会使用limit限制流的大小。iterate方法会对每个新生成的值都调用函数,generate方法不会对每个新生成的值应用函数.

// 从0开始,生成10个偶数。
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
// 生成10个随机数。
Stream.generate(Math::random). limit(10).forEach(System.out::println);

<a name="其他方法"></a>

其他方法

  • skip:跳过指定个数的流。
  • limit:返回不超过给定长度的流。
  • max:找到最大的元素
  • min:找到最小的元素
  • findFirst:找到第一个匹配元素
  • findAny:获取任意一个元素
  • anyMatch:是否存在一个匹配元素
  • noneMatch:是否全部不匹配
  • allMatch:是否全部匹配
  • sorted:排序
  • distinct:去重

<a name="高效Java8编程"></a>

其他

Optional

Optional是为了减少NullPointException,增加代码的可读性。Optional是一个final类型的类

Optional<User> optUser = Optional.of(user);
  • empty:返回一个空的Optional实例。
  • filter:如果满足提交返回Optional对象,否则返回一个空的Optional对象。
  • flatMap:如果值存在,就使用mapping函数调用,返回一个Optional对象,否则返回一个空对象。
  • get:如果值存在返回用Optional封装返回,否则抛出NoSuchElementException异常
  • ifPresent:如果值存在,就返回该方法的调用,否则什么也不做。
  • isPresent:如果值存在,就返回true,否则返回false。
  • map:如果值存在,就执行提供的mapping函数调用。
  • of:如果值不存在就抛出异常,否则返回一个Optional封装的对象。
  • ofNullable:如果值为空就返回一个空的Optional对象,否则调用of方法。
  • orElse:如果值存在就返回该值,否则返回默认值。
  • orElseGet:如果值存在就返回该值,否则返回一个函数接口生成的值。
  • orElseThrow:如果值存在就返回该值,否则抛出一个接口生成的异常。

DateTime

在Java 8 以前需要使用Date和SimpdateFormatter操作时间,而且都不是线程安全的,Date不仅包含日期还包含时间和毫秒数,使用起来非常的困难,而Java 8把日期分成了LocalDate和LocalTime,还有LocalDateTime。

使用LocalDate操作日期

// 获取当前日期
LocalDate now = LocalDate.now();

// 用静态方法创建日期
LocalDate date = LocalDate.of(2017, 5, 1);

// 将String类型转换为日期类型.注:02不能写成2,否则会抛出DateTimeParseException
LocalDate endOfFeb = LocalDate.parse("2017-02-28");

// 获取这个月的第一天的日期
now.with(TemporalAdjusters.firstDayOfMonth())

// 获取这个月的最后一天
now.with(TemporalAdjusters.lastDayOfMonth())

使用LocalTime操作时间

// 获取当前时间
LocalTime now = LocalTime.now();

// 舍弃纳秒
now.withNano(0);

/*
 * 获取特定的时间
 * Localtime.MIN 00:00
 * Localtime.MIDNIGHT 00:00
 * Localtime.NOON 12:00
 * LocalTime.MAX 23:59:59.999999999
 */
now.with(LocalTime.MIN);
now.with(LocalTime.MIDNIGHT);
now.with(LocalTime.NOON);
now.with(LocalTime.MAX);

// of可以传入四个参数
LocalTime zero = LocalTime.of(23, 59, 59, 999_999_999);

// parse方法可以转换的格式有 HH:mm:ss.nnnnnnnnn HH:mm:ss HH:mm
LocalTime last = LocalTime.parse("23:59:59.999999999");

// 也可以自定义转换格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
LocalTime start = LocalTime.parse("00:00:00", formatter);

使用LocalDateTime操作时间

// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 获取当前日期所在的月份、星期等
now.getDayOfMonth();
now.getDayOfWeek()
// 自定义
LocalDateTime last = LocalDateTime.parse("2017-05-09T23:25:58.700");

JDBC映射和新类型关联

date -> LocalDate
time -> LocalTime
timestamp -> LocalDateTime

代码地址

https://github.com/huangbuhuan/java8

<a name="参考链接"></a>

参考链接

  1. 函数式编程入门教程
  2. Java 8开发的4大顶级技巧
  3. Java 8 Optional: How to Use it
  4. Java 8 时间日期库的20个使用示例
  5. 如何在Java 8中愉快地处理日期和时间
  6. Java 8学习资料汇总
  7. Java 8中Stream详解
  8. Java 8 API在线版地址
  9. Java 8新特性终极指南
  10. 《Java8实战》
  11. 《Java8函数式编程》
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,208评论 1 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 第一章 为什么要关心Java 8 使用Stream库来选择最佳低级执行机制可以避免使用Synchronized(同...
    谢随安阅读 1,481评论 0 4
  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    谁在烽烟彼岸阅读 886评论 0 4
  • 你要知道的Java8 匿名内部类、函数式接口、lambda表达式与Stream API都在这里 转载请注明出处 h...
    WWWWDotPNG阅读 4,295评论 1 14