java 函数式接口与Lambda表达式

目录

函数式接口

函数式接口简介

什么是 @FunctionalInterface

内置的函数式接口

Stream和Lambda常用的函数式接口

函数式接口的使用

Lambda表达式

Lambda来源

Lambda原理

Lambda语法

Lambda语法案例

Lambda简化Runnable例子

自定义接口实现lambda

方法引用

处理Unchecked Exception

处理checked Exception


函数式接口

函数式接口简介

函数式接口:接口中只能有一个抽象方法,其他的可以有default、static、Object里继承的方法等。

​ 作用:在Java中主要用在Lambda表达式和方法引用(想使用Lambda表达式, 接口必须为函数式接口)。只有确保接口中有且只有一个抽象方法,Java中的lambda才能顺利推导。

什么是 @FunctionalInterface

​ JDK8专门提供了@FunctionalInterface注解,用来进行编译检查。

就是说一个接口有这个注解了,一定是函数式接口,必须满足函数式接口的要求,不然就编译出错!

一旦使用该注解来定义接口,编译器就会强制检查该接口,是否确实有且只有一个抽象方法,否则就会编译报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义规范,这仍然是一个函数式接口,使用起来都一样。

要求:

只含有且只有单个抽象方法的接口

必须是接口不能是抽象类

接口中可以有default (默认) 方法,私有方法和其他的静态方法

主要用途 用作 Lambda 表达式的类型

@FunctionalInterface
public interface FuncInterface {
  //只有一个抽象方法
  public void  method1();
  //default方法不计
  default  void method2(){
  }
  //static方法不计
  static void method3(){
  }
  //从Object继承的方法不计
  public boolean equals(Object obj);
}

 

 

如图,不满足条件就会报错

内置的函数式接口

JDK 也提供了大量的内置函数式接口,使得 Lambda 表达式的运用更加方便、高效。这些内置的函数式接口已经可以解决我们开发过程中绝大部分的问题,只有一小部分比较特殊得情况需要我们自己去定义函数式接口。在这里特别介绍四个函数式接口。

Consumer:消费型接口(void accept(T t))。有参数,无返回值 (上文forEach的参数类型就是Consumer)

Supplier:供给型接口(T get())。只有返回值,没有入参

Function<T, R>:函数型接口(R apply(T t))。一个输入参数,一个输出参数,两种类型不可不同、可以一致

Predicate:断言型接口(boolean test(T t))。输入一个参数,输出一个boolean类型得返回值

函数式接口方法名输入参数输出参数参数/吃草 返回/挤奶
消费型接口Consumervoid accept(T t)Tvoid只吃草不挤奶
供给型接口SupplierT get()voidT只挤奶不吃草
函数型接口FunctionR apply(T t)TR又吃草又挤奶
断言型接口Predicateboolean test(T t)TbooleanBoolean类型

Stream和Lambda常用的函数式接口

函数式接口参数类型返回类型描述
Supplier<T>T提供一个T类型的值
Consumer<T>Tvoid处理一个T类型的值
BiConsumer<T,U>T, Uvoid处理T类型和U类型的值
Predicate<T>Tboolean一个计算Boolean值的函数
ToIntFunction<T>Tint计算int值的函数
ToLongFunction<T>Tlong计算long值的函数
ToDoubleFunction<T>Tdouble计算double的函数
IntFunction<R>intR参数为int类型的函数(特别注意)
LongFunction<R>longR参数为long类型的函数
DoubleFunction<R>doubleR参数类型为double的函数
Function<T,R>TR一个参数类型为T的函数
BiFunction<T,U,R>T,UR一个参数为T和U的函数
UnaryOperator<T>TT对T进行一元操作
BinaryOperator<T>T,TT对T进行二元操作

 lambda常用的函数式接口

函数式接口参数类型返回类型抽象方法名描述其他方法
Runnablevoidrun执行一个没有参数和返回值的操作
Supplier<T>Tget提供一个T类型的值
Consumer<T>Tvoidaccept处理一个T类型的值chain
BiConsumer<T,U>T,Uvoidaccept处理T类型和U类型的值chain
Function<T,R>TRapply一个参数类型为T的函数compose,andThen,identity
BiFunction<T,U,R>T,URapply一个参数类型为T和U的函数值andThen
UnaryOperator<T>TTapply对类型T进行的一元操作compose,andThen,identity
BinaryOperator<T>T,TTapply对类型T进行的二元操作andThen
Predicate<T>Tbooleantest一个计算boolean值的函数And,or,negate,isEqual
BiPredicate<T,U>T,Ubooleantest一个含有两个参数,计算boolean的函数and,or,negate

函数式接口的使用

例如容器类的api里面,sort,replaceAll,foreach方法,会接受一个接口类型的对象,这里的接口一般就是一个函数式接口。

使用这些api时,一般会传入一个匿名内部类或者一个lambda表达式进去,作为这个接口类型的实例。

    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

 消费型接口

public class TestFunctional1 {
  public static void main(String[] args) {
    List<Integer > list = new ArrayList<>();
    Collections.addAll(list,34,56,89,65,87);
    
    //使用匿名内部类实现
    Consumer consumer = new Consumer<Integer>() {
      @Override
      public void accept(Integer elem) {
        System.out.println(elem);
      }
    };
    list.forEach(consumer);
    
    //使用Lambda表达式
    //list.forEach((elem)->{System.out.println(elem);});
    list.forEach((elem)->System.out.println(elem));
  }
}

断言型接口

public class TestFunctional2 {
  public static void main(String[] args) {
    List<Integer > list = new ArrayList<>();
    Collections.addAll(list,34,56,89,65,87);

    //使用匿名内部类实现
    System.out.println(list);
    Predicate predicate = new Predicate<Integer>(){
      @Override
      public boolean test(Integer i) {
        if(i<60){
          return true;
        }
        return false;
      }
    };
    list.removeIf(predicate);
    System.out.println(list);

    //使用Lambda表达式实现
    list.removeIf((i)->{
      if(i > 80) {
      	return true;
    	}
      return false;
    });
      //list.removeIf(i->i>80); 
    System.out.println(list);
  }
}

Lambda表达式

Lambda来源

2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。

Lambda表达式基于函数式编程思想,在数学中函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事”。和我们之前学的面向对象的编程思想“通过对象来做事情”有很大不同。函数式编程思想尽可能简化和忽略了面向对象的复杂语法----强调做什么,而不是像面向对象那样强调以什么形式做什么。

Java Lambda 表达式,也可以称为闭包,是Java 8引入的重要新特性, Lambda允许把函数作为一个方法的参数,使用它可以使代码变得简洁紧凑。

Lambda原理

“语法糖”是指使用更加方便,但原理不变的代码语法。例如在遍历集合是使用的for-each语法,其实它的底层的原理实现仍然是迭代器,这便是语法糖,从应用层面来说,Java中的Lambda可以被当作匿名内部类的“语法糖”,但两者在原理上是不一样的。 Lambda大致原理可能是,在编译的过程中动态生成一个内部类和一个静态私用方法,然后在使用lambda的地方调用这个生成的静态方法,这个静态方法的内容就和函数式接口中函数要实现的功能一致。

Lambda语法

( ) -> { }

一个放参数的圆括号 ():里面放参数,也可以没有,多个就以 ' , ' 分割

一个箭头 -> :用来传递参数到方法体中

一些要执行的代码 { } :重写的接口中的抽象方法的方法体

(parameters)->expression

//或

(parameter)->{statements;}

下面是lambda表达式的一些重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要。

可选的大括号:如果主题包含了一个语句,就不需要使用大括号。

可选的返回关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,大括号需要指定明表达式返回了一个值。

Lambda语法案例

//01 不需要参数,返回值是5。
()->5

//02 接收一个参数(数字类型),返回其两倍的值。
(x)->2*x  或  x->2*x

//03 接收两个参数(数字),并返回他们的差值。
(int x , int y)-> x - y

//接收一个String对象,并在控制台打印,不返回任何值,有点像void
(String s)->System.out.print(s)

Lambda简化Runnable例子

原本的实现方式:可以使用匿名内部类的方式创建一个线程,这已经是面向对象的编程方法能做到的最简化的书写了

 new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程启动成功");
            }
        }).start();


Thread类需要Runnable接口作为参数,其中的抽象run方法才是用来指定线程任务的核心。

为了指定run方法体,不得不创建Runnable接口的实现类。

为了省去定义实现类的麻烦,不得不使用匿名内部类。

为了覆盖重写原先在Runnable中的抽象方法run(),不得不再写一遍方法名称,方法参数,方法返回值,而且不能写错。

但经过分析好像只有run()中的方法体才是关键。

Lambda表达式的实现:

new Thread(()-> { System.out.println(Thread.currentThread().getName()+"线程启动成功"); }).start();

我们删去了前面提到的匿名内部类里面冗余的所有代码,只保留核心的任务——传递一个语句到Thread中执行。这样的写法在JDK1.8及以后都能编译通过,功能和前面写的匿名内部类的实现是一样的。

自定义接口实现lambda

先定义一个只有一个抽象方法的接口:

再在测试类中按格式定义一个静态方法并在主函数中书写Lambda表达式

public interface Cook {
    void makeFood(); //抽象方法无参无返回值
}

public class cookDemo {
    public static void main(String[] args) {
        invokeCook(()->System.out.println("做食物ing")); //调用定义的静态方法
    }
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }//按照这样的格式书写静态方法
}

方法引用

有时候,Lambda体可能仅调用一个已存在方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰。方法引用是一个更加紧凑,易读的 Lambda 表达式,注意方法引用是一个 Lambda 表达式,方法引用操作符是双冒号 “::”。

​ 方法引用有下面几种方式:

1 对象引用::实例方法名

2 类名::静态方法名

3 类名::实例方法名

4 类名::new (也称为构造方法引用)

5 类型[]::new (也称为数组引用)

public class LambdaTestC {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 1,2,4,5,12,56,121);
        System.out.println("===========内部匿名类===============");
        Consumer<Integer> co = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        };
        list.forEach(co);
        //简写
        System.out.println("=============lambda================");
        Consumer<Integer> co1 = v-> System.out.println(v);
        list.forEach(co1);
        System.out.println("=============lambda2================");
        //省略赋值操作最终简写
		list.forEach(v-> System.out.println(v));
        //方法引用
        list.forEach(System.out::println);

    }
}

public class FunctionalTestA {
    public static void main(String[] args) {
        Comparator<Integer> co = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
//                return o1-o2;
            }
        };
        int i = co.compare(1, 20);
        System.out.println(i);
        System.out.println("===========lambda=============");
        Comparator<Integer> co2 = (x,y)->Integer.compare(x,y);
        System.out.println(co2.compare(1, 20));
        System.out.println("=============方法引用=================");
        Comparator<Integer> co3 = Integer::compare;
        System.out.println(co3.compare(1, 20));
;
    }
}

处理Unchecked Exception

Unchecked exception也叫做RuntimeException,出现RuntimeException通常是因为我们的代码有问题。RuntimeException是不需要被捕获的。也就是说如果有RuntimeException,没有捕获也可以通过编译。

我们看一个例子:

List<Integer> integers = Arrays.asList(1,2,3,4,5);

integers.forEach(i -> System.out.println(1 / i));

这个例子是可以编译成功的,但是上面有一个问题,如果list中有一个0的话,就会抛出ArithmeticException。

虽然这个是一个Unchecked Exception,但是我们还是想处理一下:

  integers.forEach(i -> {
   try {
    System.out.println(1 / i);
   } catch (ArithmeticException e) {
    System.err.println(
      "Arithmetic Exception occured : " + e.getMessage());
   }
  });

上面的例子我们使用了try,catch来处理异常,简单但是破坏了lambda表达式的最佳实践。代码变得臃肿。

我们将try,catch移到一个wrapper方法中:

 static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {
  return i -> {
   try {
    consumer.accept(i);
   } catch (ArithmeticException e) {
    System.err.println(
      "Arithmetic Exception occured : " + e.getMessage());
   }
  };
 }

则原来的调用变成这样:

integers.forEach(lambdaWrapper(i -> System.out.println(1 / i)));

但是上面的wrapper固定了捕获ArithmeticException,我们再将其改编成一个更通用的类:

 static <T, E extends Exception> Consumer<T>
 consumerWrapperWithExceptionClass(Consumer<T> consumer, Class<E> clazz) {

  return i -> {
   try {
    consumer.accept(i);
   } catch (Exception ex) {
    try {
     E exCast = clazz.cast(ex);
     System.err.println(
       "Exception occured : " + exCast.getMessage());
    } catch (ClassCastException ccEx) {
     throw ex;
    }
   }
  };
 }

上面的类传入一个class,并将其cast到异常,如果能cast,则处理,否则抛出异常。

这样处理之后,我们这样调用:

integers.forEach(
    consumerWrapperWithExceptionClass(
      i -> System.out.println(1 / i),
      ArithmeticException.class));

处理checked Exception

checked Exception是必须要处理的异常,我们还是看个例子:

static void throwIOException(Integer integer) throws IOException {

}

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);

integers.forEach(i -> throwIOException(i));

上面我们定义了一个方法抛出IOException,这是一个checked Exception,需要被处理,所以在下面的forEach中,程序会编译失败,因为没有处理相应的异常。

最简单的办法就是try,catch住,如下所示:

  integers.forEach(i -> {
   try {
    throwIOException(i);
   } catch (IOException e) {
    throw new RuntimeException(e);
   }
  });

当然,这样的做法的坏处我们在上面已经讲过了,同样的,我们可以定义一个新的wrapper方法:

 static <T> Consumer<T> consumerWrapper(
   ThrowingConsumer<T, Exception> throwingConsumer) {

  return i -> {
   try {
    throwingConsumer.accept(i);
   } catch (Exception ex) {
    throw new RuntimeException(ex);
   }
  };
 }

我们这样调用:

integers.forEach(consumerWrapper(i -> throwIOException(i)));

我们也可以封装一下异常:

static <T, E extends Exception> Consumer<T> consumerWrapperWithExceptionClass(
   ThrowingConsumer<T, E> throwingConsumer, Class<E> exceptionClass) {

  return i -> {
   try {
    throwingConsumer.accept(i);
   } catch (Exception ex) {
    try {
     E exCast = exceptionClass.cast(ex);
     System.err.println(
       "Exception occured : " + exCast.getMessage());
    } catch (ClassCastException ccEx) {
     throw new RuntimeException(ex);
    }
   }
  };
 }

然后这样调用:

integers.forEach(consumerWrapperWithExceptionClass(i -> throwIOException(i), IOException.class));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值