Java登陆第十七天——Java8之Lambda表达式

发布时间 2023-12-05 16:06:32作者: ocraft

在实例化Thread类时,需要传入一个Runnable接口的实现类。

public Thread(Runnable target)

实际开发中,通常是使用匿名内部类实现Runnable接口。

栗子:

public class Test27 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {//匿名内部类实现Runnable接口
            @Override
            public void run() {//run()方法的传参
                System.out.println("hello,world!");//run()方法的方法体
            }
        });
        t.start();
    }
}

上述代码中对于开发人员,有书写价值的是

//run()方法的传参和方法体
() {
System.out.println("hello,world!");
}

于是乎,Java8提供了Lambda表达式,对于这种类似的匿名内部类的写法,进行缩减。

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {//匿名内部类实现Runnable接口
            @Override
            public void run() {
                System.out.println("hello,world!");
            }
        });
        t.start();
    }

//上下等价

    public static void main(String[] args) {
        Thread t = new Thread( () -> {  //lambda优化
            System.out.println("hello,world!"); } );
        t.start();
    }
}

image

Lambda表达式让书写更具有简洁性,但可读性降低

验证语法糖

首先使用匿名内部类手动地抛一个异常。

栗子:

public class Test27 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                throw new ArrayIndexOutOfBoundsException();
            }
        });
        t.start();
    }
}

程序运行结果:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException
	at module3.Test27$1.run(Test27.java:8)
	at java.lang.Thread.run(Thread.java:750)

注意看运行结果的第二行 at module3.Test27$1.run(Test27.java:8)

匿名内部类的形式会在编译后自动生成一个类。详情请看基于接口的匿名内部类内部结构解析

再次使用Lambda表达式手动地抛一个异常。

栗子:

public class Test27 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            throw new ArrayIndexOutOfBoundsException();
        });
        t.start();
    }
}

程序运行结果:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException
	at module3.Test27.lambda$main$0(Test27.java:6)
	at java.lang.Thread.run(Thread.java:750)

注意看运行结果的第二行 at module3.Test27.lambda$main$0(Test27.java:6)

可以看到,实际上是Test27类中的lambda$main$0()方法抛出的异常。

由此可确定Lambda表达式并不是匿名内部类的语法糖,而是编译后自动生成了一个方法供给接口使用。

Lambda规范

标准格式

([参数类型] 参数名,...) -> { 方法体(包括返回值) };

且Lambda仅支持接口,不支持抽象类。
接口内部必须且仅有一个抽象方法。(可以有多个默认方法,必须留出一个抽象方法)

可以在接口上添加注解@FunctionalInterface,检查接口合法性。

@FunctionalInterface//添加了此注解的接口,都支持lambda表达式,符合函数式接口定义
public interface Runnable {
    public abstract void run();
}

因此,接口Runnable可以由Lambda表达式缩写成:

public class Test27 {
    public static void main(String[] args) {
        Runnable r = () -> { /*方法体*/ };
    }
}

自定义一个函数式接口

注解@FunctionalInterface检验合法的接口,称之为函数式接口。

@FunctionalInterface
interface TestLambda{//检验合法,接口TestLambda可以称之为函数式接口
    String getString(String str);//接口中的方法默认public abstract

   default void sayString(){
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

使用Lambda表达式和匿名内部类,实现该接口

栗子:

public class Test27 {
    public static void main(String[] args) {
        TestLambda t1 = (str) -> {//lambda
            return "123";
        };
        TestLambda t2=new TestLambda() {//匿名内部类
            @Override
            public String getString(String str) {
                return "123";
            }
        };
    }
}

@FunctionalInterface
interface TestLambda {
    String getString(String str);//接口中的方法默认public abstract

    default void sayString() {
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

对于仅需一个参数的接口方法,可以省略小括号(多个参数不能省略小括号)

栗子:

public class Test27 {
    public static void main(String[] args) {
        TestLambda t = str -> {//一个参数可以省略小括号
            return "123";
        };
        //lambda
        TestLambda t2= (str) -> {
            return "123";
        };
    }
}

@FunctionalInterface
interface TestLambda {
    String getString(String str);//接口中的方法默认public abstract

    default void sayString() {
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

如果方法体中仅有返回语句一行,可以省略return和花括号

栗子:

public class Test27 {
    public static void main(String[] args) {
        TestLambda t = str -> "123";//可以省略return和花括号

        TestLambda t2= str -> {"123";};//报错!!!不可以仅省略return"123"
    }
}

@FunctionalInterface
interface TestLambda {
    String getString(String str);//接口中的方法默认public abstract

    default void sayString() {
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

Lambda引用方法

除了手动编写方法体之外,还可以直接引用方法。

如果一个方法的传参列表和返回值均与某函数式接口中抽象方法相同时,就可以直接引用该方法。

如果该方法是静态方法

栗子:

public class Test27 {
    public static void main(String[] args) {
        TestLambda t1= Test27::test27Impl;//使用 类名::方法名 的形式来直接引用一个静态方法作为实现

        TestLambda t2=str -> test27Impl(str);//省略小括号,return和花括号的lambda

        TestLambda t3=(str) ->{//最基础的lambda
          return test27Impl(str);
        };
    }

    public static String test27Impl(String str){//该传参列表,返回值。都与函数式接口中getString()抽象方法相同。
        return str+"lambda还可以直接引用已经存在的方法";
    }
}

@FunctionalInterface
interface TestLambda {
    String getString(String str);//接口中的方法默认public abstract

    default void sayString() {
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

上述栗子中,t1,t2,t3等价

如果该方法是普通方法

栗子:

public class Test27 {
    public static void main(String[] args) {
        Test27 test27 = new Test27();//使用 对象名::方法名 的形式来直接引用一个普通方法作为实现
        TestLambda t= test27::test27Impl;

        TestLambda t2=str -> test27.test27Impl(str);

        TestLambda t3=(str) ->{
          return test27.test27Impl(str);
        };
    }

    public  String test27Impl(String str){//该传参列表,返回值。都与函数式接口中getString()抽象方法相同。
        return str+"lambda还可以直接引用已经存在的方法";
    }
}

@FunctionalInterface
interface TestLambda {
    String getString(String str);//接口中的方法默认public abstract

    default void sayString() {
        System.out.println("可以有多个默认方法,但必须留有一个抽象方法");
    }
}

如果该方法是构造方法
对于函数式接口TestLambda,符合其抽象方法的传参列表和返回值的构造方法。有String的构造方法

    public String(String original) {//String的构造方法
        this.value = original.value;
        this.hash = original.hash;
    }

栗子:

public class Test27 {
    public static void main(String[] args) {
        TestLambda t= String::new;//使用 类名::new 的形式直接引用一个构造方法

        TestLambda t2=str -> new String(str);

        TestLambda t3=(str) ->{
          return new String(str);
        };
    }
}