Java8特性梳理

概述

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

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

1.Lambda表达式

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

1.1Lambda的用处

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

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

1.1.1 简单示例

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

@FunctionalInterface
public interface Arithmetic {
    int add(int a, int b);
}

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

实现类:

public class ArithmeticImpl implements Arithmetic{
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

具体调用:

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

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

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

}

1.1.2 再来个Lambda范例

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

方式一:定义Runnable实现类

1.定义Runnable实现类

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable运行啦");
    }
}

2.创建Thread并运行

public class ThreadTest {
    @Test
    public void test1(){
        Runnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

方式二:匿名实现类

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

public class ThreadTest {
    @Test
    public void test2(){
        Runnable runnableImpl = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名实现类 运行啦");
            }
        };
        Thread thread = new Thread(runnableImpl);
        thread.start();
    }
}

方式三:lambda表达式

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

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

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

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

通过上面两个例子,我们可以了解到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.多个参数的话,挨个声明变量名即可

(a,b,c) -> {
    System.out.println("hello " + a);
    System.out.println("hello " + b);
    System.out.println("hello " + c);
}

箭头部分:

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

方法体部分:

1.正常结构:

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

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

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

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

(a,b,c) -> {
    System.out.println("hello " + a);
    System.out.println("hello " + b);
    System.out.println("hello " + c);
    return a + b + c;
}

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表达式一起使用。

public interface Stream<T> extends BaseStream<T, Stream<T>> {

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

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

    IntStream mapToInt(ToIntFunction<? super T> mapper);
   
    LongStream mapToLong(ToLongFunction<? super T> mapper);

    ...
}

2.方法引用

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

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

ClassName::MethodName
ObjectName::MethodName

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

@FunctionalInterface
public interface Arithmetic {
    int add(int a, int b);
}

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

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

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

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

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

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

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

如果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类,这类事先定义好。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}

1.遍历

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

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

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

public class StreamDemo {
    List<Person> personList;
    @Before
    public void init(){
        personList = new ArrayList<>();
        personList.add(new Person("xiang",22));
        personList.add(new Person("ming",15));
    }
    @Test
    public void test1(){
        personList.stream().forEach(System.out::println);
    }
}

2.过滤

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

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

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
    }

    @Test
    public void test2() {
        //过滤,获取年龄大于18的人员
        personList.stream()
                .filter(s -> s.getAge() > 18)
                .forEach(System.out::println);
    }
}

3.转换

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

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

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

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
    }

    @Test
    public void test3() {
        //转换,Person 转为 String
        personList.stream()
                .map(s->s.getName() + "," + s.getAge())
                .forEach(System.out::println);
    }
}

4.排序 + 转换

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

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

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
    }

    @Test
    public void test4() {
        //过滤,获取年龄大于18的人员
        personList.stream()
                .sorted(Comparator.comparing(Person::getAge))
                .map(s->s.getName() + "," + s.getAge())
                .forEach(System.out::println);
    }
}

5.转为List

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

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
    }

    @Test
    public void test5() {
        List<String> descList = personList.stream()
                .map(s -> s.getName() + "," + s.getAge())
                .collect(Collectors.toList());
        System.out.println(descList);
    }
}

6.转为Map

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

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

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
        personList.add(new Person("xiao", 15));
    }

    @Test
    public void test6() {
        Map<String, Object> objectMap = personList.stream().collect(Collectors.toMap(Person::getName, s -> s));
        System.out.println(objectMap);
    }
}

7.按年龄分组

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

age作为key,List作为value

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
        personList.add(new Person("xiao", 15));
    }

    @Test
    public void test7() {
        Map<Integer, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getAge));
        System.out.println(group);
    }
}

8.获取年龄总和

public class StreamDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("xiang", 22));
        personList.add(new Person("ming", 15));
        personList.add(new Person("xiao", 15));
    }

    @Test
    public void test8() {
        int ageSum = personList.stream().mapToInt(Person::getAge).sum();
        System.out.println(ageSum);
    }
}

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

4.日期时间类

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

4.1 日期、时间的加减

public class TimeTest {
    @Test
    public void test1(){
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.minusDays(1);
        LocalDate lastMonthDay = today.minusMonths(1);

        System.out.println(today);
        System.out.println(yesterday);
        System.out.println(lastMonthDay);
    }

    @Test
    public void test2(){
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime tomorrow = now.plusDays(1);
        LocalDateTime nextMonthDay = now.plusMonths(1);

        System.out.println(now);
        System.out.println(tomorrow);
        System.out.println(nextMonthDay);
    }
}

输出:

4.2 日期时间的格式化

public class TimeTest {
    @Test
    public void test3(){
        //定义格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        
        //字符串转日期
        LocalDate localDate = LocalDate.parse("2021-01-01", formatter);
        System.out.println(localDate);

        //日期转字符串
        LocalDate today = LocalDate.now();
        String todayStr = today.format(formatter);
        System.out.println(todayStr);
    }
}

5.Optional类

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

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

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

public class OptionalDemo {
    List<Person> personList;

    @Before
    public void init() {
        personList = new ArrayList<>();
        personList.add(new Person("ming", 15));
        personList.add(new Person("xiao", 15));
    }

    @Test
    public void test1(){
        Optional<Person> first = personList.stream().filter(s->"xiang".equals(s.getName())).findFirst();
        //如果first.get()为空的话,提供一个默认值对象
        Person xiang = first.orElse(new Person("xiang",25));
        System.out.println(xiang);
    }
}

6.接口默认方法

6.1 接口默认实现

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

接口:

public interface Arithmetic {
    default int add(int a, int b){
        return a +b;
    }
}

实现类:

public class ArithmeticImpl implements Arithmetic{
}

Test:

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

输出:

6.2接口静态方法

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

接口:

public interface Arithmetic {
    static String hello(String name){
        return "hello " + name;
    }
}

Test:

public class InterfaceTest {
    @Test
    public void test2(){
        System.out.println(Arithmetic.hello("xiang"));
    }
}

7.JavaScript引擎

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

public class JSDemo {
    /**
     * var fun1 = function(name) {
     *     print('Hello ' + name);
     *     return "Hi!";
     * }
     *
     * var fun2 = function (object) {
     *     print(Object.prototype.toString.call(object));
     * };
     */
    @Test
    public void test1() throws ScriptException, NoSuchMethodException {
        //js脚本
        String script ="var fun1 = function(name) {\n" +
                "    print('Hello ' + name);\n" +
                "    return \"Hi!\";\n" +
                "}\n" +
                "\n" +
                "var fun2 = function (object) {\n" +
                "    print(Object.prototype.toString.call(object));\n" +
                "};";

        //声明js引擎
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        //处理js脚本
        engine.eval(script);

        //调用js方法
        Invocable invocable = (Invocable) engine;
        Object result = invocable.invokeFunction("fun1", "Nashorn");
        System.out.println(result);
        System.out.println(result.getClass());
    }
}

输出结果:

8.写在最后

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

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

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值