面向对象(四)— Lambda 表达式

面向对象(四)

《疯狂Java讲义》学习笔记

Java8新增的 Lambda 表达式

Lambda 表达式允许使用代码块作为方法的参数,Lambda 表达式使用更简洁的代码来创建只有一个抽象方法的接口(这个接口被称为函数式接口

Lambda表达式入门

public class TestLambda {
    public static void main(String[] args) {
        CommandTest commandTest = new CommandTest();
        commandTest.test();
        //输出:使用匿名内部类:数组元素的和是:9
		//输出:使用Lambda:数组元素的和是:9
    }
}

//使用匿名内部类的情况:
//Command接口
interface Command {
    void process(int[] target);
}

class ProcessArray {
    public void process(int[] target, Command cmd) {
        cmd.process(target);
    }
}

class CommandTest {
    public void test() {
        ProcessArray pa = new ProcessArray();
        int[] target = {3, -4, 6, 4};
        // 使用匿名内部类的情况:
        pa.process(target, new Command() {
            @Override
            public void process(int[] target) {
                int sum = 0;
                for (int temp : target) {
                    sum += temp;
                }
                System.out.println("使用匿名内部类:数组元素的和是:" + sum);
            }
        });
        // 使用Lambda的情况:
        pa.process(target, (int[] array) -> {
            int sum = 0;
            for (int temp : target) {
                sum += temp;
            }
            System.out.println("使用Lambda:数组元素的和是:" + sum);
        });
    }
}
  1. 从上面的代码中可以看出使用 Lambda 与创建匿名内部类时需要实现的process(int[] target) 方法完全相同,只是不需要 new Xxx(){} 这种繁琐的代码,并不需要指定重写的方法的名字,也不需要给出重写的方法的返回值类型——只要给出重写方法的方法括号,以及括号中的形参列表即可
  2. 当使用 Lambda 表达式代替匿名内部类创建对象时,Lambda 表达式的代码块将会代替实现抽象方法的方法体,Lambda 相当于一个匿名方法
  3. Lambda 表达式由3部分组成:
    ①、形参列表:形参列表允许省略形参类型,如果参数列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
    ②、箭头(->):必须通过英文中划线号和大于符号组成。
    ③、代码块:如果代码块中只包含一条语句,Lambda 允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda 代码块中只有一条 return 语句,甚至可以省略 return 关键字。 Lambda 表达式需要返回值,而它的代码块中仅有一条省略了 return 的语句,Lambda 表达式会自动的返回这条语句
public class Test {
    public static void main(String[] args) {
        new TestLambda().test();
    }
}

//Lambda表达式
interface Eatable {
    void taste();
}

interface Flyable {
    void fly(String weather);
}

interface Addable {
    int add(int a, int b);
}

class TestLambda {
    // 该方法的需要 Eatable 对象
    public void eat(Eatable e) {
        e.taste();
    }

    // 该方法的需要 Flyable 对象
    public void drive(Flyable f) {
        System.out.println("我正在驾驶" + f);
        f.fly("天气很好");
    }

    // 该方法需要 Addable 对象
    public int testAdd(Addable add) {
        return add.add(1, 2);
    }

    public void test() {
        TestLambda lambda = new TestLambda();
        // Lambda 表达式只有一条语句时,可以省略花括号
        lambda.eat(() -> System.out.println("苹果的味道真不错!!"));
        lambda.drive(weather -> System.out.println(weather));
        int result = lambda.testAdd((a, b) -> a + b);
        System.out.println(result);
    }
}

调用上面三个方法依次需要传入的参数为 Eatable 对象 、Flyable 对象 、Addable 对象 ,但却都传入了 Lambda 表达式,这说明 Lambda 表达式会被当做 “任意类型” 的对象,到底需要何种类型的对象,这取决于运行环境的需要。

实现接口、使用匿名内部类、使用 Lambda 表达式的对比

public class Main {

    public static void main(String[] args) {
        LambdaTest test = new LambdaTest();
        // 使用实现类调用eat方法
        EatImpl eatImpl = new EatImpl();
        test.eat(eatImpl);
        // 使用匿名内部类,重写eat方法
        test.eat(new Eatable() {

            @Override
            public void eat() {
                System.out.println("吃饭");
            }
        });
        // 使用Lambda
        test.eat(() -> {
            System.out.println("吃饭");
        });

    }

}

interface Eatable {
    void eat();
}

class LambdaTest {
    //调用接口中的eat方法
    public void eat(Eatable e) {
        e.eat();
    }

}

//实现Eatable接口
class EatImpl implements Eatable {

    //重写eat方法
    @Override
    public void eat() {
        System.out.println("吃饭");
    }
}

Lambda 表达式与函数式接口

Lambda 表达式被称作 目标类型Lambda 表达式的目标类型必须为 函数式接口。“函数式接口”代表只含有一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法;
如果采用匿名内部类语法创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用 Lambda 表达式来创建对象;该表达式创建的对象目标类型就是这个函数式接口。

//检查一个接口是否是函数式接口
@FunctionalInterface
interface Flyable{
	void fly();
}
//由于 Lambda 表达式的结果就是被当做对象,因此程序中完全可以使用 Lambda 表达式进行赋值
public class Test {
    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println();
            }
        };
    }
}
  • Lambda实现的是匿名方法,他只能实现特定函数式接口中的唯一方法。
    因此Lambda有两个限制:
    ①、Lambda 表达式的目标类型必须是明确的函数式接口;
    ②、Lambda 表达式只能为函数式接口创建对象。Lambda 表达式只能实现一个抽象方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象
//编译出错
// Target type of a lambda conversion must be an interface
Object obj = () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println();
    }
};
  • 为保证 Lambda 表达式的目标类型是一个明确的函数式接口,可以使用如下的三种方式:
    ①、将 Lambda 表达式赋值给函数式接口类型的变量;
    ②、将 Lambda 表达式做为函数式接口类型的参数传给某个方法;
    ③、使用函数是借口对 Lambda 表达式进行强制类型转换;
//把下面的 Lambda 表达式进行强制类型转换,这样就确定了目标类型为 Runnable 函数式接口;
Object obj = (Runnable) () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println();
    }
};

方法引用与构造器引用

如果 Lambda 表达式的代码块中只有一条代码,程序就可以省略 Lambda 表达式中代码块的花括号。
不仅如此,如果 Lambda 表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。
方法引用和构造器引用都需要使用两个英文冒号。

Lambda 表达式支持如下的几种引用方式

种类示例说明对应的 Lambda 表达式
引用类方法类名::类方法函数式接口中被实现方法的全部参数传给该类方法作为参数(a,b,…) -> 类名 . 类方法(a,b…)
引用特定对象的实例方法特定对象::实例方法函数式接口中被实现方法的全部参数传给该方法作为参数(a,b,…) -> 特定对象 . 实例方法(a,b…)
引用某类对象的实例方法类名::实例方法函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数(a,b,…) -> a . 实例方法(b…)
引用构造器类名::new函数式接口中被实现方法的全部参数传给该构造器作为参数(a,b,…) -> new 类名 (a,b…)
  • ①、引用类方法,格式:类名::类方法
public class MethodRefer {

    public static void main(String[] args) {
        // 使用 Lambda 表达式
        // 子类类型赋给父类变量
        Converter c1 = from -> Integer.valueOf(from);
        // 父类变量调用convert方法
        System.out.println(c1.convert("123") + 1);
        // 方法引用代替 Lambda 表达式:引用类方法
        Converter c2 = Integer::valueOf;
        System.out.println(c2.convert("123") + 1);
    }

}

//接口
@FunctionalInterface
interface Converter {
    // 把字符串转换成Integer类型
    Integer convert(String from);
}

方法引用代替 Lambda 表达式,引用类方法
函数式接口中被实现的方法的全部参数传给该方法作为参数
Converter c2 = Integer::valueOf;

  • ②、引用特定对象的实例方法
public class MethodRefer {

    public static void main(String[] args) {
        // 使用 Lambda 表达式
        // 子类类型赋给父类变量
        Converter c1 = from -> "fkit.java".indexOf("it");
        // 父类变量调用convert方法
        System.out.println(c1.convert("it"));
        // 方法引用代替 Lambda 表达式:引用特定对象实例的方法
        Converter c2 = "fkit.java"::indexOf;
        System.out.println(c2.convert("it"));
    }

}

//接口
@FunctionalInterface
interface Converter {
    // 把字符串转换成Integer类型
    Integer convert(String from);
}
  • ③、引用某类对象实例的方法
//MyTest接口
@FunctionalInterface
interface MyTest {
    String test(String a, int b, int c);
}
public class MethodRefer {

    public static void main(String[] args) {
        //使用lambda表达式
        MyTest myTest = (a, b, c) -> a.substring(b, c);
        System.out.println(myTest.test("mytest", 0, 2));
        //引用某类对象实例的代替 Lambda 表达式:引用某类对象实例的方法
        myTest = String::substring;
        System.out.println(myTest.test("mytest", 0, 2));

    }
}
  • ④、引用构造器
interface YourTest {
    JFrame win(String title);
}
public class TestLambda {
    public static void main(String[] args) {
        //使用Lambda 表达式创建YourTest 对象
        YourTest yt = a -> new JFrame(a);
        JFrame jFrame = yt.win("哈哈哈");
        System.out.println(jFrame);
        //使用构造器引用代替 Lambda
        YourTest yt2 = JFrame::new;
        JFrame hhh = yt2.win("hhh");
        System.out.println(hhh);
    }
}

对于上面的构造器引用,也就是调用某个 Jframe 类的构造器来实现 YourTest 函数式接口中唯一的抽象方法,当调用 YourTest 函数式接口中唯一的抽象方法时,调用参数会传给 JFrame 构造器。

Lambda 表达式与匿名内部类的区别和联系

Lambda表达式是匿名内部类的一种简化,因此他部分取代匿名内部类的作用,Lambda 表达式与匿名内部类存在如下相同点;
①、Lambda 表达式和匿名内部类一样,都可以访问 Effectively final 的局部变量,以及外部类的成员变量(包括类变量和实例变量)
②、Lambda 表达式和匿名内部类生成的对象一样,都可以调用从接口中继承的默认方法

package testlambda.lambdaandinnerclass;

//接口
interface Displayable {
    // 定义一个抽象方法和默认方法
    void display();

    default int add(int a, int b) {
        return a + b;
    }
}

public class LambdaAndInner {
    private int age;
    private static String name = "疯狂软件教育中心";

    public void testInnerClass() {
        String book = "疯狂Java讲义";
        //使用匿名内部类
        Displayable displayable = new Displayable() {
            @Override
            public void display() {
                //访问 "Effectively final" 的局部变量
                System.out.println("book的局部变量:" + book);
                //访问外部类的实例变量和类变量
                System.out.println("外部类age实例变量为:" + age);
                System.out.println("外部类name 类变量为:" + name);
            }
        };
        displayable.display();
        System.out.println(displayable.add(2, 3));
    }

    public void testLambda() {
        String book = "疯狂Java讲义";
        //使用 Lambda 表达式
        Displayable displayable = () -> {
            //访问 "Effectively final" 的局部变量
            System.out.println("book的局部变量:" + book);
            //访问外部类的实例变量和类变量
            System.out.println("外部类age实例变量为:" + age);
            System.out.println("外部类name 类变量为:" + name);
        };
        displayable.display();
        System.out.println(displayable.add(2, 3));
    }

    public static void main(String[] args) {
        LambdaAndInner lambdaAndInner = new LambdaAndInner();
        lambdaAndInner.age = 10;
        lambdaAndInner.testInnerClass();
        lambdaAndInner.testLambda();
    }
}
  • 上面程序分别使用匿名内部类和 Lambda 表达式创建了 Displayable 对象,由此可以得到,Lambda 表达式不仅可以访问“Effectively final”局部变量,也可以访问类变量和实例变量。
  • 与匿名内部类相似的是,Lambda 表达式访问的局部变量 book 之后,就不允许再次修改 book 的值了;
  • Lambda 表达式与匿名内部类存在如下区别
    ①、匿名内部类可以为任意接口创建实例,不管接口中存在多少个抽象方法,只要匿名内部类全部实现接口中的抽象方法即可;但 Lambda 只可以为函数式接口创建实例
    ②、匿名内部类可以为抽象类或者普通类创建实例;Lambda只能为函数是接口创建实例
    ③、匿名内部类实现的抽象方法的方法体中调用接口中定义的默认方法;但 Lambda 表达式的代码块不允许调用接口中的默认方法
//在匿名内部类调用接口的默认方法
public void testInnerDefault() {
    Displayable displayable = new Displayable() {
        @Override
        public void display() {
        	//调用上面 Displayable 接口的默认方法:add(int a, int b)
            System.out.println(add(2, 3));
        }
    };
    displayable.display();
}
//使用 Lambda 表达式调用接口中的默认方法
public void testLambda() {
    String book = "疯狂Java讲义";
    //使用 Lambda 表达式
    Displayable displayable = () -> {
    	//编译出错
        //No candidates found for method call add(2,3).
        System.out.println(add(2,3));
    };
    displayable.display();
}
  • 使用 Lambda 表达式调用Arrays的类方法
public static void main(String[] args) {
    String[] arr1 = new String[]{"java", "fkava", "fkit", "ios", "android"};
    Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
    int[] arr2 = {3, -4, 25, 16, 30, 18};
    Arrays.parallelPrefix(arr2, (left, right) -> left * right);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值