java8系列-01 Lambdas 表达式

Published on in java with 262 views

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

Lambdas表达式实例

基本语法如下:

(parameters) -> experssion
或
(parameters) -> {statements;}

举栗说明

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);
    }
}

控制台打印

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

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

Lambdas表达式中的变量作用域

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

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() {
        }
    }
}

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

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等方法不能被覆盖,但是却可以被重载调用。

    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修饰,可以通过接口名调用。

    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 表达式。

定义如下

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

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

实例

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

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类型的结果

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

Responses