java8系列-01 Lambdas 表达式

Lambdas表达式是java 8的一个最重要的新特性。它允许把函数作为一个方法的参数(在一个方法中用函数来作为方法传递进去)。或者把一段代码看成是数据。

Lambdas表达式实例

基本语法如下:

1
2
3
(parameters) -> experssion

(parameters) -> {statements;}

举栗说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LombokTest {
public static void main(String[] args) {
// 简单情况下不用return
Arrays.asList("a","b","c").forEach(System.out::println);
// 复杂情况下可以带对花括号,像java代码块一样
Arrays.asList("d","e","f").sort((e1,e2) ->{
int res = e1.compareTo(e2);
return res;
});
// runoob例子==类型声明
MathOperation sub = (int a,int b) -> a - b;
// runoob例子==不用类型声明
MathOperation add = (a,b) -> a+b;
LombokTest lt = new LombokTest();
System.out.println("1+2="+lt.operate(1,2,add));
System.out.println("1-2="+lt.operate(1,2,sub));
}

interface MathOperation {
int operation(int a, int b);
}

private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}

控制台打印

1
2
3
4
5
a
b
c
1+2=3
1-2=-1

使用Lambdas表达式需要注意以下两点

  • Lambda表达式主要用来定义行内执行的方法类型接口。例如上面的MathOperation接口。
  • Lamda表达式免去了使用匿名方法的麻烦,并且给予java简单但是强大的函数化的编程能力。

Lambdas表达式中的变量作用域

Lambdas表达式可以引用类的成员变量和局部变量。如果这些类型不是final的话,它们也会隐含的转换成final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class LombokTest {
int a = 12;
public static void main(String[] args) {
// 引用成员变量
LombokTest lt = new LombokTest();
Converter<Integer,String> s = (param) -> System.out.println((param + lt.a));
s.convert(8);
// 引用局部变量
String separator = ",";
Arrays.asList("a","b","c").forEach((String e) -> System.out.print(e+separator));

// 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "1";
Comparator<String> comparator = (first,second)
-> Integer.compare(first.length(),second.length()); // idea编译报错Variable 'first' is already defined in the scope
// Lambda中this的应用
lt.sayOk();
}

public String toString(){
return "\n are u ok?";
}

private void sayOk(){
Runnable runnable = () ->{
System.out.println(this.toString());
};
new Thread(runnable).start();
}
private interface Converter<T1,T2>{
void convert(int i);
default void say() {
}
}
}

注释掉报错代码后,控制台打印

1
2
3
20
a,b,c,
are u ok?

==NOTE==

  • 和匿名对象有点不同的是,Lambda表达式当中引用的变量可以不用声明为final
  • Lambda表达式的字段类型为final,所以在后面的代码中不能被修改。如例子中String separator = ",";后续代码中如果修改为separator = "。";则会被编译器报错
  • Lambda表达式当中不允许声明一个与局部变量重名的参数或者局部变量。
  • 和局部变量不同的是,Lambda内部对于实例的字段(即:成员变量)以及静态变量是即可读又可写。
  • Lambda表达式中无法访问到接口的默认方法(java 8新特性,下面讲到)。如上Converter接口的 say()是无法被访问的。
  • Lambda中的this会去引用创建该Lambda表达式的方法的this,如上are u ok?

接口的默认方法与静态方法

接口的默认方法与静态方法是java 8的新概念,用来扩展接口的声明。与传统接口不同,它可以在接口中添加方法。另外它并不受函数式接口(带有注解@FunctionalInterface)的契约,可以任意使用。

默认方法

默认方法用default关键词修饰,可以在接口中存在多个。与接口抽象方法不同,它是一个非抽象的方法实现。它的使用方式有点类似于抽象类中的非抽象成员方法。这个特征也叫扩展方法

==Warning:==
在实现默认方法时,它是不能够重写Object中的方法,但是可以重载Object 中的方法。例如toStringhashCode等方法不能被覆盖,但是却可以被重载调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface DefaultM{
default String myDefaultMothod(){
return "https://code666.top";
}
// 因为重写了Object方法,以下就会报错
default String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}

static class DefaultMM implements DefaultM{
public void test(){
System.out.println(myDefaultMothod());
}
}

public static void main(String[] args) {
DefaultMM dmm = new DefaultMM();
dmm.test();
}

静态方法

静态方法用static修饰,可以通过接口名调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface DefaultM2{
static DefaultM create(Supplier<DefaultM> supplier){
return supplier.get();
}
}
static class DefaultMM implements DefaultM{
public void test(){
System.out.println(myDefaultMothod());
}
}

public static void main(String[] args) {
DefaultM defaultM = DefaultM2.create(DefaultMM::new);
System.out.println(defaultM.myDefaultMothod());
}

总结

为什么接口的默认方法不能重载toString(),hashCode()和equals()?

接口不能提供对Object类的任何方法的默认实现,因为若可以会很难确定什么时候该调用接口默认的方法。并且也是毫无意义的。因为每一个java类都是Object的子类,也都继承了它类中的equals等方法。

默认方法和静态方法的注意点

  1. 接口默认方法、静态方法可以有多个。
  2. 默认方法通过实例调用,静态方法通过接口名调用。
  3. default默认方法关键字只能用在接口中。
  4. 默认方法可以被继承,如果继承了多个接口,多个接口都定义了多个同样的默认方法,实现类需要重写默认方法不然会报错。
  5. 静态方法不能被继承及覆盖,所以只被具体所在的接口调用

函数式接口@FunctionalInterface

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。

定义如下

1
2
3
4
@FunctionalInterface
public interface Functional<T>{
void test(T t);
}

==Warnning:==

  1. 该注解只能标记在有且只有抽象方法的接口上。若接口有两个抽象方法则编译器会报错(上面提到的默认方法静态方法都不是抽象方法)。
  2. 因为接口也是默认继承java.lang.Object的,所以如果重写了Object内的方法则也不属于抽象方法。
  3. 并且该注解不是必须的。如果一个接口符合‘函数式接口’规范,那加不加该注解都不影响,但是加了该注解后编译器会校验。(==也就是说如果该接口是为了函数式接口而生的,就可以加上此注解==)

实例

接口还是上面那个。PS初级开发: static <T> void<T>不是返回值,表示传入参数有泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FunctionalTest {

private static <T> void Test(Functional<T> fun, T t){
System.out.print("https://");
fun.test(t);
System.out.print(".top");
}

public static void main(String[] args) {
String s = "code666";
Test(System.out::print,s);
}
}

输出 https://code666.top

通过输入和输出的参数来区分,函数式接口有如下四类

接口名 说明 接口方法
Function<T,R> 接收一个T类型的参数,返回一个R类型的结果 R apply(T t)
Consumer 接收一个T类型的参数,不返回结果 void accept(T t)
Predicate 接收一个T类型的参数,返回一个boolean类型的结果 boolean test(T t)
Supplier 不接收参数,返回一个T类型的结果 T get()

接收两个参数的相关函数式接口

接口名 说明
BiFunction<T,U,R> 接收T类型U类型两参数,返回一个R类型结果
BiConsumer<T,U> 接收T类型U类型两参数不返回值
BiPredicate<T,U> 接收T类型和U类型的两个参数,返回一个boolean类型的结果

总结: 函数式接口就是只包含一个抽象方法的接口


java8系列-01 Lambdas 表达式
https://code666.top/articles/2019/03/03/1551601060301.html
作者
Sean
发布于
2019年3月3日
许可协议