JDK1.8新特性(一):Lambda表达式

JDK1.8系列文章

Lambda 表达式

Java 8 是 Java 语言开发的一个主要版本,Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,现在已经发布了 Java 11 和 Java 13。但 Java 8 还是目前使用最多的版本,最主要的原因是能在我们编程过程中带来很多便利,特别是 Lambda 表达式和 Stream 的支持,使得程序设计更加简洁,代码量更少,能把二三十行的代码,简化到十行以内,你敢信?快用上这些新特性来试试吧。
看到 GitHub 上面有个关于 Java 8 的英文仓库, 新特性的内容比较全,地址:https://github.com/winterbe/java8-tutorial ,有能力的朋友可以阅读。我以这个仓库作为参考,写了下面这一系列文章。

一、新特性

在 Java 8 新特性系列文章的第一篇中,稍微先介绍一下新特性有哪些:

  • 接口的静态方法和默认方法
  • Lambda 表达式
  • 函数式接口
  • 方法和构造函数引用
  • Optional 类
  • Streams (流)
  • Maps
  • 新的时间日期 API
  • Annotations (注解)

在本文中,将详细介绍前4点新特性(接口的静态方法和默认方法、Lambda 表达式、函数式接口、方法和构造函数引用),其他的新特性在接下来的几篇文章会介绍到。

二、接口的默认方法和静态方法

1、默认方法

Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现。

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula 接口中除了定义抽象方法 calculate,还定义了默认方法 sqrt。抽象方法需要先实现,然后才能够进行使用,默认方法 sqrt 可以直接使用,我们通过代码来演示一下。

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

通过增加 default 关键字,使接口可以有实现方法,而且不需要实现类去实现这个方法。思考一下为什么 Java 8 中要新增这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 Java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在 JDK 里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法,目的是为了解决接口的修改与现有的实现不兼容的问题。

2、静态方法

Java 8 中接口除了可以使用默认方法,也可以使用静态方法。

public interface Formula {
    public static void print(){
        System.out.println("接口中的静态方法");
    }
}

三、Lambda 表达式

Lambda 表达式是推动 Java 8 发布的最重要特性,允许把函数作为一个方法的参数,传递到方法中,使用 Lambda 表达式可以使代码变得更加简洁紧凑,接下来我们就来了解一下什么是 Lambda 表达式。

1、使用 Lambda 表达式

Java 8 中引入了一个新的操作符 -> ,该操作符被称为 Lambda 操作符,Lambda 操作符把 Lambda 表达式拆分成两部分,左侧为 Lambda 表达式的参数列表,右侧为 Lambda 表达式中所需的函数方法。
让我们从一个简单的例子开始,看看在以前的版本的 Java 中如何排序的字符串列表:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

给静态方法 Collections.sort 传入一个 List 对象和一个比较器,来对给定列表中的元素进行排序。通常做法都是创建一个匿名的比较器对象然后将其传递给 sort 方法。Java 8 提供了更简洁的语法,Lambda 表达式,不需要再创建匿名对象:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

可以看出,代码变得更短更具简洁,但实际上还可以写得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

可以去掉大括号 {} 以及 return 关键字,可以写得更短点:

names.sort((a, b) -> b.compareTo(a));

2、Lambda 表达式中的作用域

Lambda 表达式只能引用标记为 final 的外层局部变量,不能在 Lambda 内部修改定义在作用域外的局部变量。

  • 访问局部变量

可以直接在 Lambda 表达式中访问外部的局部变量:

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

Lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义),该代码同样正确:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

在Lambda表达式中试图修改num同样是不允许的,例如下面的就无法编译:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 4;
  • 访问字段和静态变量

与局部变量相比,对Lambda表达式中的实例字段和静态变量都有读写访问权限。

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

四、函数式接口

Java 语言设计者增加了函数式接口的概念,来使现有的函数友好地支持 Lambda。

1、概念

函数式接口(Functional Interface)在 Java 中是指,有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,适用于函数式编程场景。需要确保接口中只有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。

在 Java 8 之前,java.lang.Runnablejava.util.concurrent.Callable 是函数式接口最典型的两个例子。Java 8 增加了一种特殊的注解 @FunctionalInterface ** ,但是这个注解不是必须的(建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用 @FunctionalInterface** ** ** 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的

如何定义一个函数式接口:

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

然后使用 Lambda 表达式来表示该接口的一个实现:

GreetingService greetService1 = message -> System.out.println("Hello " + message);

2、函数接口

JDK 1.8 API包含许多内置的函数接口。其中一些在旧版本的Java中比较常用,比如 Comparator 或 Runnable。对这些现有接口进行扩展,以通过 @FunctionalInterface 注释对 Lambda 的进行支持。

JDK 1.8 新增加的函数接口:

  • java.util.function

JDK 1.8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

3、函数接口实例

看到这里相信大家已经基本了解什么是函数式接口,如果想对函数式接口有更深入的了解,请继续看下面的内容,将为大家详细介绍常用的函数式接口如何使用。
在新增加的函数接口在 java.util.function包下,它包含了很多类,用来支持 Java 的函数式编程,那么让我们来看一看哪些是比较常用的:

java.util.function.Predicate
java.util.function.Consumer
java.util.function.Function
java.util.function.Supplier

Predicate

java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型 T 对象,并返回一个 boolean 值。在对类型 T 进行断言判断时,可以使用这个接口。
使用 Predicate 实现字符串判空操作,测试代码

// 实现 Predicate test 方法,进行判断传入的字符串是否为空
Predicate<String> predicate = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty() || s.trim().isEmpty();
    }
};
// 测试传入的字符串是否为空
System.out.println(predicate.test(""));
System.out.println(predicate.test("  "));
System.out.println(predicate.test("admin"));

测试结果

true
true
false

Consumer

java.util.function.Consumer 接口定义了一个名叫 accept 的抽象方法,接受泛型 T,没有返回值。如果需要访问类型 T 的对象,并对其执行某些操作,可以使用这个接口,通常称为消费性接口。

使用 Consumer 实现集合遍历操作,测试代码

// 实现 Consumer accept 方法,进行集合的遍历并打印
Consumer<Collection> consumer = new Consumer<Collection>() {
    @Override
public void accept(Collection collection) {
        if (null != collection && collection.size() > 0) {
            for (Object c : collection) {
                System.out.println(c);
            }
        }
    }
};

// 在集合中添加元素
List<String> list = new ArrayList<>();
list.add("杭州");
list.add("北京");
list.add("上海");

// 遍历 list 输出元素内容到控制台
consumer.accept(list);

测试结果

杭州
北京
上海

Function

java.util.function.Function<T, R> 接口定义了一个叫做 apply 的方法,接受一个泛型 T 的对象,并返回一个泛型 R 的对象。如果需要定义一个 Lambda,将输入的信息映射到输出,可以使用这个接口,通常称为功能性接口。

使用 Function 实现用户密码 Base64 加密操作,测试代码

// 实现 Function apply 方法,进行用户密码 Base64 加密操作
Function<String, String> function = new Function<String, String>() {
    @Override
    public String apply(String s) {
        return Base64.getEncoder().encodeToString(s.getBytes());
    }
};
// 输出加密后的字符串
System.out.println(function.apply("123456"));

测试结果

MTIzNDU2

Supplier

java.util.function.Supplier 接口定义了一个 get 的抽象方法,它没有参数,返回一个泛型 T 的对象,这类似于一个工厂方法。

使用 Supplier 实现返回字符串,测试代码

// 实现 Supplier get 方法,进行字符串返回
Supplier<String> supplier = new Supplier<String>() {
    @Override
    public String get() {
        return "get!";
    }
};
// 输出返回的字符串
System.out.println(supplier.get());

测试结果

get!

五、方法和构造函数引用

Java 8 允许通过::关键字传递方法或构造函数的引用。
下面,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

public class Car {
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}

1、构造器引用

它的语法是Class::new,或者更一般的Class< T >::new实例如下:

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

2、静态方法引用

它的语法是 Class::static_method,实例如下:

cars.forEach( Car::collide );

3、特定类的任意对象的方法引用

它的语法是 Class::method 实例如下:

cars.forEach( Car::repair );

4、特定对象的方法引用

它的语法是 instance::method 实例如下:

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

参考网站:

六、公众号

如果大家想要第一时间看到我更新的 Java 方向学习文章,可以关注一下公众号【Lucas的咖啡店】。所有学习文章公众号首发,请各位大大扫码关注一下哦!

Java 8 新特性学习视频请关注公众号,私信【Java8】即可免费无套路获取学习视频。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351