Java8新特性之lambda表达式(带实例)及新接口(Consumer、Supplier、Function、Predicate)

什么是Lambda表达式

可以把Lambda表达式理解为可传递的匿名函数的一种方式
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。这个定义够大的,让我们慢慢道来。

学习lambda表达式就要先知道函数式接口是什么?

函数式接口

函数式接口(Functional Interfaces):如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

/**
  * 函数式接口
  */
   
@FunctionalInterface
public interface Show {
    //只有有个没有返回值,没有参数的方法
    void print();
}

Lambda表达式:可以让你的代码更加的简洁。lambda无法单独出现,需要一个函数式接口来盛放,可以说lambda表达式方法体是函数式接口的实现,lambda实例化函数式接口,可以将函数作为方法参数,或者将代码作为数据对待。

主要优点:
1.代码变得更加简洁紧凑
2.可读性强,
3.并行操作大集合变得很方便,可以充分发挥多核cpu的优势,更加便于多核处理器编写代码等.

下是lambda表达式的重要特征:(对比案例,回过头,看下面的解释)

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
在这里插入图片描述
我们看来看看,如何实际实用lambda表达式.

1使用案例

//先定义一个函数式接口
	@FunctionalInterface
	public interface Show {
  	 void print();
	}


  //建立一个测试类. 定义方法,参数是接口
    public void test(Show show){
        show.print();
    }


//1111正常情况下,我们是这样使用的
  test(new Show() {
            @Override
            public void print() {
                System.out.println("哈哈哈哈");
            }
        });

 //2222换成Lambda表达式以后,变成下面这个样子.
   test(() ->{
            System.out.println("哈哈哈哈");
    });

以上两个输出语句是完全等价的.
但是第二个看起来就简单了很多.

2来一个带参数的用法

@FunctionalInterface
public interface Show2 {
   void print(String s);
}

仔细看 s 变量是没有括号的.
一个变量可以省略括号. 两个以上必须有.

test2(s -> {
    System.out.println(s);
 });
//和上面等价
test2((s) -> {
    System.out.println(s);
 });

3来一个带参数和返回值的

@FunctionalInterface
public interface Show3 {
    int add(int a ,int b);
}

//调用
 //定义方法,参数是接口
    public void test3(Show3 show3){
        int add = show3.add(1, 1);
        System.out.println(add);
    }

 //正常用法
 test3(new Show3() {
       @Override
            public int add(int a, int b) {
                return a+b;
            }
        });

//Lambda写法
test3((a,b) -> a+b);
//贼简单

方法引用

你为什么应该关心方法引用?方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。它是如何工作的呢?
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法。
下图给出了Java 8中方法引用的其他一些例子。
在这里插入图片描述
我们把test2的代码用方法引用写一下:

//原来的
test2((s) -> {
    System.out.println(s);
 });

//改变后
test2(System.out::println);

你可能觉得奇怪,这是什么玩意呢.两个冒号就能输出了.
仔细看System.out::println 两个冒号之前是一个类, 冒号之后是一个方法.

如何构建方法引用

方法引用主要有三类。

(1) 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
(2) 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。
(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。依照一些简单的方子,我们就可以将Lambda表达式重构为等价的方法引用,
在这里插入图片描述

请注意,还有针对构造函数、数组构造函数和父类调用(super-call)的一些特殊形式的方法
引用。

跳过理论看案例

让我们举一个方法引用的具体例子吧。

比方说你想要对一个字符串的List排序,忽略大小写。List的sort方法需要一个Comparator作为参数。你在前面看到了,Comparator描述了一个具有(T, T) -> int签名的函数描述符。你可以利用String类中的compareToIgnoreCase方法来定义一个Lambda表达式(注意compareToIgnoreCase是String类中预先定义的)。
使用正常的流程来实现这个功能

//使用正常的流程来实现这个功能
  List<String> list = Arrays.asList("a", "b", "C", "A", "B");

   list.sort(new Comparator<String>() {
       @Override
       public int compare(String o1, String o2) {
           return o1.compareToIgnoreCase(o2);
       }
   });

   for (String s : list) {
       System.out.println(s);
   }

使用Lambda表达式完成

    List<String> list = Arrays.asList("a", "b", "C", "A", "B");
    list.sort((s1,s2)-> s1.compareToIgnoreCase(s2));
    list.forEach(s -> {System.out.println(s);});

在加上lambda的方法引用

	List<String> list = Arrays.asList("a", "b", "C", "A", "B");
	list.sort(String::compareToIgnoreCase);
    list.forEach(System.out::println);

完蛋了. 太方便了. 一旦学会了. 就再也回不去了.

到目前为止,我们只展示了如何利用现有的方法实现和如何创建方法引用。但是你也可以对类的构造函数做类似的事情。

构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new。它的功能与指向静态方法的引用类似。例如,假设有一个构造函数没有参数。
它适合Supplier的签名() -> Apple。你可以这样做:
此次的Supplier接口也是java8的新特性. 暂时先忽略,一会说.

在这里插入图片描述

函数式接口

Java 8的库设计师帮你在java.util.function包中引入了几个新的函数式接口.

内置4大接口

Predicate:断言型接口

java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。这恰恰和你先前创建的一样,现在就可以直接使用了。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String对象的Lambda表达式,
如下所示。

案例:
比如要从个集合中.打印所有字符串的长度大于2的…
老方法这么写:

        List<String> list = Arrays.asList("a11", "b232", "C111", "A1", "B");

        for (String s : list) {
            if(s.length() > 2){
                System.out.println(s.length());
            }
        }

Lambda表达式这么写:

        List<String> list = Arrays.asList("a11", "b232", "C111", "A1", "B");
        list.stream().filter((s)->s.length()>2).forEach(System.out::println);

stream 这个流先不用管. 下一遍文章分析.
主要看这段 (s)->s.length()>2 .
这就是用Predicate接口来实现的.

Consumer 接口(消费者)

consumer接口:对输入的参数进行操作。有输入没输出.

  Consumer<String>  consumer3 =  s -> {
            String s1 = s.toUpperCase();
            System.out.println(s1);
        };

只有调用了,控制台才有输出

consumer.accept("ss");

其实,主要是理解Consumer,消费者,就可以了~主要是对入参做一些列的操作,在stream里,主要是用于forEach;内部迭代的时候,对传入的参数,做一系列的业务操作,没有返回值;

Supplier (提供者)

看语义,可以看到,这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T.

        Supplier<Food> supplier = Food::new;

        Food food = supplier.get();

        food.setRet(1);
        int ret = food.getRet();

可以看到,这个接口,只是为我们提供了一个创建好的对象,这也符号接口的语义的定义,提供者,提供一个对象,

直接理解成一个创建对象的工厂,就可以了.

Function接口以及同类型的特化的接口

先看看Function的定义


@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
 
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
 
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
 
    static <T> Function<T, T> identity() {
        return t -> t;
    }

看看Function的作用
Funtion接口,定义了一个apply的抽象方法,接收一个泛型T对象,并且返回泛型R对象.
看看实例.

 Function<Integer,String> function = (i) ->{

            if(i == 1){
                return "1月";
            }else if(i == 2){
                return "2月";
            }
            return "没有";
        };

        System.out.println(function.apply(1));

下面我们看下Funtion这个接口的“扩展”的原始类型特化的一些函数接口

IntFunction,IntToDoubleFunction,IntToLongFunction,LongFunction,LongToDoubleFunction,LongToIntFunction,DoubleFunction,ToIntFunction,ToDoubleFunction,ToLongFunction

我们知道,我们在做基础数据处理的时候(eg: Integer i=0; Integer dd= i+1;),会对基础类型的包装类,进行拆箱的操作,转成基本类型,再做运算处理,拆箱和装箱,其实是非常消耗性能的,尤其是在大量数据运算的时候;这些特殊的Function函数式接口,根据不同的类型,避免了拆箱和装箱的操作,从而提高程序的运行效率.

看到这里可能还是看的不明白,也不知道他们的具体用处.
下一篇Stream说明这个问题.

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

馮贰爺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值