当前位置:   article > 正文

Java8特性梳理_java8 新特性 (a,b)->{}

java8 新特性 (a,b)->{}

概述

java8包含了很多新特性,这里我们简述几个常用的。

  • Lambda表达式
  • 方法引用
  • Stream API
  • 日期时间类
  • Optional类
  • 接口默认方法
  • JavaScript引擎

1.Lambda表达式

官方解释:Lambda 表达式,是一个匿名函数,即没有函数名的函数。

1.1Lambda的用处

其实我更愿意把它理解为函数式接口的实现。

函数式接口(@FuctionalInterface):只有一个方法的接口

1.1.1 简单示例

举个例子,假如我们现在有一个接口:

  1. @FunctionalInterface
  2. public interface Arithmetic {
  3. int add(int a, int b);
  4. }

如果我们想使用这个接口,那么需要先定义一个实现类,再创建实现类的实例。代码如下:

实现类:

  1. public class ArithmeticImpl implements Arithmetic{
  2. @Override
  3. public int add(int a, int b) {
  4. return a + b;
  5. }
  6. }

具体调用:

  1. public class MainDemo {
  2. @Test
  3. public void test(){
  4. Arithmetic arithmetic = new ArithmeticImpl();
  5. int result = arithmetic.add(1, 2);
  6. System.out.println(result);
  7. }
  8. }

但如果使用lambda表达式的话,则可以直接免去定义实现类这步,直接使用lambda表达式作为接口实现。代码如下:

  1. public class MainDemo {
  2. @Test
  3. public void test2(){
  4. Arithmetic arithmetic = (a, b) -> a + b;
  5. int result = arithmetic.add(1, 2);
  6. System.out.println(result);
  7. }
  8. }

1.1.2 再来个Lambda范例

如果上面没看出来差别,我们再举个更常见的例子:创建线程。

方式一:定义Runnable实现类

1.定义Runnable实现类

  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("MyRunnable运行啦");
  5. }
  6. }

2.创建Thread并运行

  1. public class ThreadTest {
  2. @Test
  3. public void test1(){
  4. Runnable myRunnable = new MyRunnable();
  5. Thread thread = new Thread(myRunnable);
  6. thread.start();
  7. }
  8. }

方式二:匿名实现类

直接创建匿名实现类。比起上面省去了定义实现类这步。

  1. public class ThreadTest {
  2. @Test
  3. public void test2(){
  4. Runnable runnableImpl = new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("匿名实现类 运行啦");
  8. }
  9. };
  10. Thread thread = new Thread(runnableImpl);
  11. thread.start();
  12. }
  13. }

方式三:lambda表达式

那针对方式二,是不是可以更简单呢。

答案当然是可以啦,因为Runnable是一个函数式接口,只有一个方法嘛,所以我们可以连这个方法名都省去,只要有方法体就行。

所以我们可以直接用lambda表达式,作为它的实现类。

  1. public class ThreadTest {
  2. @Test
  3. public void test3(){
  4. Runnable lambda = () -> System.out.println("lambda实现类 运行啦");
  5. Thread thread = new Thread(lambda);
  6. thread.start();
  7. }
  8. }

通过上面两个例子,我们可以了解到lambda表达式可使用的地方。

即:凡是需要函数式接口实现类的地方,都可以用lamba表达式作为实现类。

(除此之外,可能还有其他地方可用lamba表达式,但恕我知识有限,暂时还不太清楚)

1.2 lambda表达式结构体

知道了它的用处,也更要知道它的写法。lambda既然是匿名类函数,那肯定也是有方法声明和方法体,结构如下:

(...) -> {...}

主要分为三部分:

1.         ()     参数部分

声明参数列表。接口中已声明参数类型个数,此处无需声明参数类型,仅需标明参数名。

2.        ->    箭头符号

区分参数部分和方法体部分。

3.        {}     方法体部分

就是正常方法体,和普通方法体一样写就行。

需要特殊说明的地方简单介绍下,举几个例子大家就明白了

参数部分说明:

1.如果参数列表为空的话,参数部分为空即可

() -> {System.out.println("hello")}

2.参数列表只有一个的时候,括号()可省略

(a) -> {System.out.println("hello " + a)}
a -> {System.out.println("hello " + a)}

3.多个参数的话,挨个声明变量名即可

  1. (a,b,c) -> {
  2. System.out.println("hello " + a);
  3. System.out.println("hello " + b);
  4. System.out.println("hello " + c);
  5. }

箭头部分:

箭头部分固定格式,不可缺少,不可改动。

方法体部分:

1.正常结构:

  1. (a) -> {
  2. System.out.println("hello " + c);
  3. }

2.如果方法体中只有一行代码,{}括号可以省略

a -> System.out.println("hello " + a);

3.如果方法有返参,正常使用return关键字即可

  1. (a,b,c) -> {
  2. System.out.println("hello " + a);
  3. System.out.println("hello " + b);
  4. System.out.println("hello " + c);
  5. return a + b + c;
  6. }

4.如果方法只有一行且有反参,{} 可省略,return 关键字可省略。

(a,b,c) -> a + b + c;

1.3 java.util.function包

JDK 1.8 API包含了很多内建的函数式接口,简单枚举如下:

nametypedescription
ConsumerConsumer< T >接收T对象,不返回值
PredicatePredicate< T >接收T对象并返回boolean
FunctionFunction< T, R >接收T对象,返回R对象
SupplierSupplier< T >提供T对象(例如工厂),不接收值
UnaryOperatorUnaryOperator接收T对象,返回T对象
BinaryOperatorBinaryOperator接收两个T对象,返回T对象

好多时候需要使用上述接口的一些实现类,那这些地方就可以直接使用lamba表达式啦。

比如我们后面要说的Stream类,就大量依赖这些接口,所有我们才经常说Stream要配合lambda表达式一起使用。

  1. public interface Stream<T> extends BaseStream<T, Stream<T>> {
  2. Stream<T> filter(Predicate<? super T> predicate);
  3. <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  4. IntStream mapToInt(ToIntFunction<? super T> mapper);
  5. LongStream mapToLong(ToLongFunction<? super T> mapper);
  6. ...
  7. }

2.方法引用

方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用使用一对冒号 ::  表示调用某个方法,通常格式为下面两种

ClassName::MethodName
ObjectName::MethodName

直接讲定义不太好理解,我们直接看个demo。还是上面那个Arithmetic接口。

  1. @FunctionalInterface
  2. public interface Arithmetic {
  3. int add(int a, int b);
  4. }

我们用lamba表达式来实现这接口

  1. public class ReferDemo {
  2. @Test
  3. public void test1(){
  4. Arithmetic arithmetic = (a, b) -> a + b;
  5. System.out.println(arithmetic.add(1,2));
  6. }
  7. }

上面代码已经很简便了,但还可以再简便一些吗?当然可以, a+b 这个方法体可以直接用Integer.sum()接口替换。替换后结果代码如下:

    Arithmetic arithmetic = (a, b) -> Integer.sum(a,b);

看到这种格式,就到了使用方法引用的时候了,直接使用方法引用再次替换上述代码:

  1. public class ReferDemo {
  2. @Test
  3. public void test2(){
  4. Arithmetic arithmetic = Integer::sum;
  5. System.out.println(arithmetic.add(1,2));
  6. }
  7. }

看到这里我们可以总结下方法引用是什么了:

如果lambda表达式中,仅是调用了某个类或对象的方法,且表达式的入参与方法的入参一致,那这个lambda表达式就可以直接简写为方法引用。

3.Stream API

终于到了Java8 的重头戏,Stream类是我们日常工作中处理数据集合类最常用的工具类。

Stream API的特点是:

  • Stream API提供了一套新的流式处理的抽象序列;
  • Stream API支持函数式编程和链式操作;
  • Stream可以表示无限序列,并且大多数情况下是惰性求值的。

(说实话上述概念我不懂,我只会用...)

3.1使用简述

使用Stream处理集合使用时具体分为三个步骤:

一个流式过程中,可以包含多个处理操作,以及一个终结操作。

1.生成流

toStream()        生成流

toParallel()       生成并行流 (数据量不是很大的话,转换流的成本会比并行节省的时间成本高)

2.处理

处理过滤stream中的数据,但不生成最终结果。

filter()                过滤某些数据,将符合条件的元素提取到新的流中的操作

map()                将每个元素转换成其他对象,生成新的流

sort()                将元素按规则排序,生成新的流

...

3.终结/汇总

针对前面过程中处理过的数据进行呢汇总,生成最终结果。

forEach()        遍历每一个元素进行操作

collect()           汇总数据,转成集合

max()               获取最大值

count()             获取数量

anyMatch()       是否存在元素满足条件

...

3.3 常见使用

我们下面的案例都会使用个Person类,这类事先定义好。

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class Person {
  5. private String name;
  6. private int age;
  7. }

1.遍历

void forEach(Consumer<? super T> action);

先生成流,再对每一个元素进行打印输出。

注意看哦,我们这里还有了上面说过的方法引用

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init(){
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang",22));
  7. personList.add(new Person("ming",15));
  8. }
  9. @Test
  10. public void test1(){
  11. personList.stream().forEach(System.out::println);
  12. }
  13. }

2.过滤

Stream filter(Predicate<? super T> predicate);

只有满足条件的数据才会传递下去生成新的流。即年龄大于18的人员才会被打印出来。

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. }
  9. @Test
  10. public void test2() {
  11. //过滤,获取年龄大于18的人员
  12. personList.stream()
  13. .filter(s -> s.getAge() > 18)
  14. .forEach(System.out::println);
  15. }
  16. }

3.转换

 Stream map(Function<? super T, ? extends R> mapper);

将流中的元素转换为其他类型的元素。

这里将Person类型的对象转换为String类型的名字了。

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. }
  9. @Test
  10. public void test3() {
  11. //转换,Person 转为 String
  12. personList.stream()
  13. .map(s->s.getName() + "," + s.getAge())
  14. .forEach(System.out::println);
  15. }
  16. }

4.排序 + 转换

既然是流式处理,那么在写法上,其实也是可以流式传递的。

下面就是一个先排序,再转换的一个案例。

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. }
  9. @Test
  10. public void test4() {
  11. //过滤,获取年龄大于18的人员
  12. personList.stream()
  13. .sorted(Comparator.comparing(Person::getAge))
  14. .map(s->s.getName() + "," + s.getAge())
  15. .forEach(System.out::println);
  16. }
  17. }

5.转为List

此处显示了一个将Person转换为String,最终收集为一个List的操作。

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. }
  9. @Test
  10. public void test5() {
  11. List<String> descList = personList.stream()
  12. .map(s -> s.getName() + "," + s.getAge())
  13. .collect(Collectors.toList());
  14. System.out.println(descList);
  15. }
  16. }

6.转为Map

这里将一个list转为了一个map:

name作为key,Person对象作为value。

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. personList.add(new Person("xiao", 15));
  9. }
  10. @Test
  11. public void test6() {
  12. Map<String, Object> objectMap = personList.stream().collect(Collectors.toMap(Person::getName, s -> s));
  13. System.out.println(objectMap);
  14. }
  15. }

7.按年龄分组

 这里将list的元素按年龄分组:

age作为key,List作为value

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. personList.add(new Person("xiao", 15));
  9. }
  10. @Test
  11. public void test7() {
  12. Map<Integer, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getAge));
  13. System.out.println(group);
  14. }
  15. }

8.获取年龄总和

  1. public class StreamDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("xiang", 22));
  7. personList.add(new Person("ming", 15));
  8. personList.add(new Person("xiao", 15));
  9. }
  10. @Test
  11. public void test8() {
  12. int ageSum = personList.stream().mapToInt(Person::getAge).sum();
  13. System.out.println(ageSum);
  14. }
  15. }

诸如此类的操作,后续大家挨个试下就会觉得Stream超级好用了。

4.日期时间类

因为之前的Date时间类,无论是计算还是格式化,都不是很好用。所以java8中推出了java.time包。其中的LocalDate、LocalDateTime等时间类无论是效率还是使用上都以前提升很多。

4.1 日期、时间的加减

  1. public class TimeTest {
  2. @Test
  3. public void test1(){
  4. LocalDate today = LocalDate.now();
  5. LocalDate yesterday = today.minusDays(1);
  6. LocalDate lastMonthDay = today.minusMonths(1);
  7. System.out.println(today);
  8. System.out.println(yesterday);
  9. System.out.println(lastMonthDay);
  10. }
  11. @Test
  12. public void test2(){
  13. LocalDateTime now = LocalDateTime.now();
  14. LocalDateTime tomorrow = now.plusDays(1);
  15. LocalDateTime nextMonthDay = now.plusMonths(1);
  16. System.out.println(now);
  17. System.out.println(tomorrow);
  18. System.out.println(nextMonthDay);
  19. }
  20. }

输出:

4.2 日期时间的格式化

  1. public class TimeTest {
  2. @Test
  3. public void test3(){
  4. //定义格式
  5. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  6. //字符串转日期
  7. LocalDate localDate = LocalDate.parse("2021-01-01", formatter);
  8. System.out.println(localDate);
  9. //日期转字符串
  10. LocalDate today = LocalDate.now();
  11. String todayStr = today.format(formatter);
  12. System.out.println(todayStr);
  13. }
  14. }

5.Optional类

在java8之前,我们针对某个对象可能要做一些判空处理,而Optional类则是帮我减少这类判空处理的代码逻辑。

它提供了一些诸如isPresent() 、orElse()等好用的方法。

Optional在使用时,可依据具体场景使用。

  1. public class OptionalDemo {
  2. List<Person> personList;
  3. @Before
  4. public void init() {
  5. personList = new ArrayList<>();
  6. personList.add(new Person("ming", 15));
  7. personList.add(new Person("xiao", 15));
  8. }
  9. @Test
  10. public void test1(){
  11. Optional<Person> first = personList.stream().filter(s->"xiang".equals(s.getName())).findFirst();
  12. //如果first.get()为空的话,提供一个默认值对象
  13. Person xiang = first.orElse(new Person("xiang",25));
  14. System.out.println(xiang);
  15. }
  16. }

6.接口默认方法

6.1 接口默认实现

接口可以有默认实现了,实现类可以直接使用它的默认实现。

接口:

  1. public interface Arithmetic {
  2. default int add(int a, int b){
  3. return a +b;
  4. }
  5. }

实现类:

  1. public class ArithmeticImpl implements Arithmetic{
  2. }

Test:

  1. public class InterfaceTest {
  2. @Test
  3. public void test1(){
  4. Arithmetic arithmetic = new ArithmeticImpl();
  5. System.out.println(arithmetic.add(1,2));
  6. }
  7. }

输出:

6.2接口静态方法

接口还可以拥有静态方法了,外部调用时就像调用类的静态方法一样使用

接口:

  1. public interface Arithmetic {
  2. static String hello(String name){
  3. return "hello " + name;
  4. }
  5. }

Test:

  1. public class InterfaceTest {
  2. @Test
  3. public void test2(){
  4. System.out.println(Arithmetic.hello("xiang"));
  5. }
  6. }

7.JavaScript引擎

java8提供了一个能够解析js脚本的引擎,可以运行js代码

  1. public class JSDemo {
  2. /**
  3. * var fun1 = function(name) {
  4. * print('Hello ' + name);
  5. * return "Hi!";
  6. * }
  7. *
  8. * var fun2 = function (object) {
  9. * print(Object.prototype.toString.call(object));
  10. * };
  11. */
  12. @Test
  13. public void test1() throws ScriptException, NoSuchMethodException {
  14. //js脚本
  15. String script ="var fun1 = function(name) {\n" +
  16. " print('Hello ' + name);\n" +
  17. " return \"Hi!\";\n" +
  18. "}\n" +
  19. "\n" +
  20. "var fun2 = function (object) {\n" +
  21. " print(Object.prototype.toString.call(object));\n" +
  22. "};";
  23. //声明js引擎
  24. ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
  25. //处理js脚本
  26. engine.eval(script);
  27. //调用js方法
  28. Invocable invocable = (Invocable) engine;
  29. Object result = invocable.invokeFunction("fun1", "Nashorn");
  30. System.out.println(result);
  31. System.out.println(result.getClass());
  32. }
  33. }

输出结果:

8.写在最后

无论怎么简便,不过都是语法糖而已,编译后的字节码还是没什么变化。

但只要能简化美化我们代码的语法,都应该去尝试写写。

毕竟代码是要给人看的,代码优雅些,更利于团队和谐嘛!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号