登录 |  注册
首页 >  编程语言 >  java8特性 >  Java8新特性介绍

Java8新特性介绍

Java8新特性中最为重要的便是Lambda表达式和Stream API了,先来了解一下Lambda表达式吧。

lambda表达式.jpg

Lambda表达式

Lambda表达式是一个匿名函数,我们可以将Lambda表达式理解为一段可以作为参数传递的代码,通过Lambda表达式,我们可以将Java程序变得更加简洁和灵活。

来看一段程序:

@Test
public void test() {
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    };
    Set<Integer> set = new TreeSet<>(comparator);
    set.add(3);
    set.add(2);
    set.add(1);
    System.out.println(set);
}

在JDK8以前,若是想实现对TreeSet的自定义排序,我们可以创建匿名的比较器类将其传入TreeSet的构造方法,而JDK8之后,使用Lambda表达式可以简化匿名内部类的代码:

@Test
public void test() {
    Comparator<Integer> comparator = (o1, o2) -> o2 - o1;
    Set<Integer> set = new TreeSet<>(comparator);
    set.add(3);
    set.add(2);
    set.add(1);
    System.out.println(set);
}

原本匿名内部类的创建现在直接被简化成了一行代码:

Comparator<Integer> comparator = (o1, o2) -> o2 - o1;

下面就来看看Lambda该如何使用,也就是学习一下它的语法。


Lambda表达式语法

在Java8中新引入了一个操作符:-> ,它被称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分成了左侧和右侧的两部分,其中左侧为Lambda表达式的 参数列表 ,右侧为Lambda表达式的功能代码,也叫 Lambda体 。对应一个具体的Lambda表达式:

Comparator<Integer> comparator = (o1, o2) -> o2 - o1;

此时箭头操作符的左侧就是Comparator接口中compare方法的参数列表,右侧即为该方法所需要实现的功能:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    ......
}

基于接口中方法声明的不同,Lambda表达式的编写方式也会随之发生相应的变化,大体分为以下几类:

  • 无参无返回值:Runnable runnable = () -> System.out.println("Hello Lambda!");

  • 有一个参数但无返回值:Consumer consumer = (s) -> System.out.println(s);

    • 若是方法只有一个参数,则可以省略小括号,所以可以简写为:Consumer consumer = s -> System.out.println(s);

  • 有多个参数有返回值,且Lambda体中有多条语句:

Comparator<Integer> comparator = (o1, o2) -> {
    System.out.println("从大到小排列");
    return o2 - o1;
};

若是Lambda体中有多条语句,则Lambda体必须用大括号包裹起来,若是只有一条语句,则可以省略大括号。

  • 有多个参数有返回值,但Lambda体中只有一条返回语句:Comparator comparator = (o1, o2) -> o2 - o1;

    • 对于这种情况,可以省略大括号和return关键字,Lambda体中只剩下需要return的内容

会发现在所有的Lambda表达式中我们并没有为参数定义任何类型,这是因为JVM编译器能够通过上下文自动推断出参数类型。

需要注意的是,并不是所有的接口实现都可以使用Lambda表达式,它需要 函数式接口 的支持,那么什么是函数式接口呢?

函数式接口指的是接口中只有一个抽象方法的接口

这非常好理解,若是接口中存在多个抽象方法,Lambda表达式是无法知晓我们到底需要实现哪个方法的,所以Lambda表达式的使用必须基于函数式接口。


JDK1.8为此专门提供了 @FunctionalInterface 注解来声明一个函数式接口,倘若在接口中声明了该注解,则该接口必须拥有且只能拥有一个方法:

@FunctionalInterface
interface calc{
    void  add();
}

若不满足要求则会编译报错。


有些同学可能会发现在使用Lambda表达式实现一些功能时,还需要自己去额外编写一个函数式接口,而事实上,JDK1.8已经为我们内置了四大核心函数式接口,分别是:

  1. Consumer:消费型接口,抽象方法为:void accept(T t);

  2. Supplier:供给型接口,抽象方法为:T get();

  3. Function<T, R>:函数型接口,抽象方法为:R apply(T t);

  4. Predicate:断言型接口,抽象方法为:boolean test(T t);

通过它们就已经能够解决大部分的问题了,具体使用哪个接口可以根据自己的实际需求决定,比如若是需要实现的功能带参数而无返回值,则使用消费型接口;若是需要实现的功能无参数但有返回值,则使用供给型接口。

这里以供给型接口为例,实现一个需求,产生指定个数的整数,并放入集合中,代码如下:

@Test
public void test() {
    List<Integer> list = getList(5, () -> new Random().nextInt(20));
    System.out.println(list);
}
public List<Integer> getList(int len, Supplier<Integer> supplier) {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < len; i++) {
        list.add(supplier.get());
    }
    return list;
}

方法引用

若Lambda体中的内容有方法已经实现了,那么就可以使用方法引用,可以理解为方法引用是Lambda表达式的另一种表现形式,方法引用主要有以下三种形式:

  1. 对象::实例方法名

  2. 类::静态方法名

  3. 类::实例方法名

来看一个例子:

@Test
public void test() {
    Consumer<String> consumer = (x)-> System.out.println(x);
    consumer.accept("Hello!");
}

这里使用Consumer消费型接口实现了一个输出字符串的功能,由于Lambda体中的内容已经被 System.out.println 实现了,所以可以简写为:

@Test
public void test() {
    Consumer<String> consumer = System.out::println; // 简写为...
    consumer.accept("Hello!");
}

然而方法引用需要遵循一个原则,即:Lambda体中的方法参数和返回值需要与函数式接口中的抽象方法声明一致,比如这里的Consumer接口中的抽象方法为:

void accept(T t);

而输出语句的声明如下:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

它们都带有一个参数且无返回值,所以可以使用方法引用。

又比如:

@Test
public void test() {
    User user = new User();
    user.setName("aaa");
    Supplier<String> supplier = () -> user.getName();
    String str = supplier.get();
    System.out.println(str);
}

这里的 user.getName() 也可以使用方法引用,简写为:

Supplier<String> supplier = user::getName;

这也是因为User中get方法与函数式接口中方法参数和返回值的声明相同:

// User对象的getName方法
public String getName() {
    return name;
}
// Supplier接口的get方法
T get();

我们还可以方法引用类的静态方法,例如:

@Test
public void test() {
    Comparator<Integer> comparator = Integer::compare;
    int result = comparator.compare(1, 2);
    System.out.println(result);
}

因为Lambda体中的内容已经被compare方法实现且参数和返回值声明与Comparator接口中的抽象方法声明相同,它就能够使用方法引用:

Comparator<Integer> comparator = Integer::compare;

且compare是Integer类的静态方法,这种引用方式被称为类的静态方法引用。

最后一种形式是类的实例方法引用,比如:

@Test
public void test() {
    BiPredicate<String, String> biPredicate = (s1, s2) -> s1.equals(s2);
    boolean flag = biPredicate.test("abc", "abc");
    System.out.println(flag);
}

这里实现了函数式接口的方法使其能够判断两个字符串的内容是否相同,它能够被简写为:

BiPredicate<String, String> biPredicate = String::equals;

对于类的实例方法引用,也有它的要求,必须满足第一个参数是方法的调用者,第二个参数是调用方法的参数才能使用该引用方式。

构造器引用

构造器引用与方法引用类似,它通过 类名::new 实现,比如:

@Test
public void test() {
    Supplier<User> supplier = ()->new User();
    User user = supplier.get();
    System.out.println(user);
}

此时创建User对象的过程就可以使用构造器引用来简化:

Supplier<User> supplier = User::new;

我们都知道一个类可以有多个重载的构造器,那么构造器引用调用的是类中的哪个构造器呢?和方法引用类似,我们仍然通过构造器方法与接口中抽象方法参数和返回值的声明来判断调用哪个构造器,这里的Supplier接口中的抽象方法是一个不带参数的方法:

T get();

所以它将调用对象的无参构造方法,又比如:

@Test
public void test() {
    Function<String, User> function = User::new;
    User user = function.apply("zhangsan");
    System.out.println(user);
}

来看看Function接口中的抽象方法:

R apply(T t);

它带有一个参数,所以调用的是User对象带一个参数的构造方法:

public class User {
    private String name;
    
    public User(String name){
        this.name = name;
    }
 ......
}
下一课程>>
推荐文章
  • 之前看过一篇文章介绍H标签的使用方法:H1必须有只能是一个,H2标签可以是多个。不同标签的作用h1和h2标签主要是提高关键词的密度更容易让蜘蛛抓取我们首先要明白HEADER标签是什么。HEADER标签就是HTML语言中的h1到h6定义标题头的六个不同文字大小的TAGES。本质是为了呈现内容结构,共有
  • 高并发下如何设计秒杀系统?秒杀系统是网络商家为了促销等目的进行的网上限时抢购活动。比如某宝某东某夕夕上的秒杀。用户在规定的时间内,定时定量的秒杀,无论商品是否秒杀完毕,该场次的秒杀活动都会结束。秒杀系统具有瞬时流量、高并发读、高并发写以及高可用等特点。秒杀时会有大量用户在同一时间进行抢购,瞬时并发访
  • 经常做仓储管理系统,很少关注电商系统,之前兼职做过一个电商系统,其对应的商品档案就很容易和仓储管理系统混淆,今天抽个时间整理下他们在前端展示的区别和联系。一个电商系统,SPU、SKU、快照等设计的好坏,影响到后面的开发进度,以及架构的调整等。而SPU、SKU、快照又是一个电商系统的核心。SPU,是s
  • Java8新特性中最为重要的便是Lambda表达式和StreamAPI了,先来了解一下Lambda表达式吧。Lambda表达式Lambda表达式是一个匿名函数,我们可以将Lambda表达式理解为一段可以作为参数传递的代码,通过Lambda表达式,我们可以将Java程序变得更加简洁和灵活。来看一段程序
  • Java8中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregateoperation),或者大批量数据操作(bulkdataoperation)。StreamAPI借助于同样新出现的Lambda表达式,极大的提高编程效率和
  • 要养成一个好的编码习惯从自己编码开始,对自己代码的合理化命名,编码不仅对自己有好处,而且别人也容易读懂你的代码。所以下载阿里的代码规范插件来约束自己凌乱的代码。阿里规范插件GitHub地址:https://github.com/alibaba/p3cIDEA安装该插件步骤:1.打开IDEA,File
学习大纲
0.1 Java8新特性介绍
0.2 Java 8 Stream