java8学习:lambda表达式(1)

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:[java8实战]

  • 上一篇内容解释了行为参数化的概念并用实例演示了如何是行为参数化,文末提到了lambda表达式,那么本文将讲解lambda表达式
  • 如平常的使用方法如下

    Comparator<Apple> comparator = new Comparator<Apple>() {
        @Override
        public int compare(Apple o1, Apple o2) {
            return Integer.compare(o1.getWeight(),o2.getWeight());
        }
    };
    lambda:Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight());
    • 对照上面的普通使用方法和lambda,lambda没有名称,但是一样有参数列表,函数主体以及返回值,还可以拥有抛出的异常类型,但是上面的lambda还是可以进一步简化写法的,下面讲提到
  • lambda各部分名称

    Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight());
                                        参数列表     ->只是用来分割参数列表和函数主体的    函数主体    
    • 如上清楚的标明了lambda各部分的名称,那么就会有疑问,他的返回值是什么?上面lambda的函数主体的结果就是其返回值,只是没有写return而已,如下
    Comparator<Apple> comparator = (Apple a1,Apple a2) -> {
        return Integer.compare(a1.getWeight(),a2.getWeight());
    };       //注意大括号
    • 下面是一些lambda的例子
    (String s) -> s.length();    //参数为String,函数主体已给出,函数返回值是int
    (int x , int y ) ->{         //参数为(int,int),函数主体给出,函数无返回值void
      System.out.println(x);
      System.out.println(y);
    }
    () -> 42;                     //没有参数,但是有返回值是int,就好比是   public int get(){return 42;}
    • 下面这个两个lambda是不正确的
    (Integer i) -> return i + 1;  //如果是有return明确返回要使用花括号了,如上的Comparator代码中的return
    (String s) -> {"mes";}        //“mes”是一个表达式不是一个语句,如果想修正此问题,那么可以去掉花括号,或者显示声明return
  • lambda可以在那里使用呢?

    • 我们现在只是在跟着文章懵逼的一个字符字符的敲上去,然后运行看结果,只是知道这样一种lambda形式,但是根本就不知道这个鬼东西可以出现在哪里啊?在这就要涉及到一个函数式接口的概念了
    • 函数式接口就是接口中只有一个抽象方法,此方法不允许重载,而且你只要保证此接口中只有一个抽象方法,其他的default之类的其他方法任你编写,如下
    @FunctionalInterface
    public interface PP {
        boolean isX ();
        default void cc(){
            System.out.println("ccc");
        }
    }
    • 如上注解就类似与@Override注解一样,检测此接口是否是函数式接口,如果不是就会有报错信息了
    • 了解了函数式接口是什么东西,那么我们回忆一下上一篇中,我们其实已经编写了函数式接口,但是我们只是不知道函数式接口这个概念,比如
interface Filter<T>{
    boolean filter(T t);
}
public class Java8 {
        @Test
        public void test() throws Exception {
            List<Apple> apples = Arrays.asList();
            getAppleByColor(apples,apple -> apple.getWeight() > 1000);
        }
        public <T> void getAppleByColor(List<T> ts,Filter<T> filter){
            for (T t : ts) {
                if (filter.filter(t)){
                    System.out.println("...");
                }
            }
        }
}
  • 如上的函数式接口Filter就是我们上次编写的,他只有一个抽象方法,然后我们就可以利用lambda对含有此接口为参数的方法传入lambda了,然后实现了行为参数化
  • 我们也可以看上一篇提到的实例:Comparator和Runnable接口的定义,其中有很多方法,但是只有一个抽象方法,这就不贴出来了,自己查看源码就可以、
  • 用函数式接口可以干啥?lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,我们也可以用匿名内部类完后才能同样的事情,(这段话是书中的原话:自己的理解就是lambda实例化了接口并为唯一的抽象方法提供了实现),比如
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("xx");
    }
};
Runnable runnable1 = () -> System.out.println("xx");
  • 如上两段代码,一个是匿名内部类实现的,一个是lambda提供实现,那么都是为Runnable接口中的抽象方法run提供了逻辑实现即System.out.println("xx")

函数描述符

  • 抽象方法的方法签名基本就是lambda的表达式签名,这种就叫做函数描述符
  • 就如上面代码Runnable,run方法的方法签名是void无返回并且无参数,那么对应的lambda就是无参数和无返回的void
  • 实例
public static Callable<String> fe(){
    return () -> "sd";
}
  • 上面的lambda是否是正确的可以通过编译呢?我们来调用一下

  1. static void main(String[] args) throws Exception {

    Callable<String> fe = fe();
    System.out.println(fe.call());   //sd

    }

  • 证明了上面的是正确的,我们来一步步的分析为什么它编译是正确的,首先观察Callable的定义

  1. interface Callable {

    V call() throws Exception;

    }

  • 首先它是一个函数是借口,那么返回一个lambda作为接口的实现肯定是没有问题的
  • 其次我们Callable的泛型是String,并且此泛型作为了抽象方法的返回值,方法的返回值也就是String,到这就是看出Callable的lambda的描述信息是() -> String
  • 我们再看return () -> "sd"; 这个lambda的返回值也是String,与上面我们分析的一致,所以编译无误
  • 我们来一个错误的

    Predicate<Apple> predicate = (Apple apple) -> apple.getWeight();
  • 为什么她是错误的,我们还的看Predicate的抽象方法的定义

  1. test(T t)

  • 她是返回boolean,但是我们的 apple.getWeight();返回int,这并匹配不上,所以是编译有错的

小实战

  • 内容要求,读取文本,让你返回几行你就返回几行
  • 观察要求,返回行的话便捷操作就使用到了BufferReader读取类的readLine,然后让返回随机行,那么这个肯定是根据意思随机返回了,很明显这里需要一个自定义函数式接口,以实现行为参数化
@FunctionalInterface
interface MyLineReader {
    String process(BufferedReader reader) throws IOException;
}
public class A{
    private String readLine(BufferedReader reader,MyLineReader lineReaderInterface) throws IOException {
        return lineReaderInterface.process(reader);
    }
    @Test
    public void test() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader("zookeeper.log"));
        String s = readLine(reader, readerIn -> readerIn.readLine() + "\n" + readerIn.readLine());
        System.out.println(s);
        reader.close();
    }
}
  • 如上定义函数式接口,然后创建方法,该方法参数一个是流,一个是函数式接口
  • 然后传入流和利用lambda实现抽象方法的实现逻辑,这里我们想读几行出来就可以了

这里我们还是有啰嗦的地方,那么就是定义接口!每次我们实现一个lambda的不同的逻辑,那么就必须定义一个新接口,xxxoooo,其实java8已经为我们准备好了一些已经写好的接口,我们拿来用就好了,如果以后变成找不到合适的已经默认实现的接口,那么我们还是的自己定义接口。emmmm

  • 接口全部都是java.util.funcation包下,这些接口我会在文末列出来的,如果写到这,那么就又懵逼了
  • 下面我们先来看内置函数接口Predicate
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    ...
    ...
}
  • 上面这个货不就是咱们编写的Filter嘛,拿过来比较一下
interface Filter<T>{
    boolean filter(T t);
}
  • 除了缺注解缺public,类名方法名不一样,其他的都一样好吧!!虽然这么多不一样,但是使用方法是一样的啊!我们把方法参数Filter改为Predicate就好了,这种接口的定义为传入T返回boolean:(T t) -> boolean;这样的接口有人叫断言型接口,不过我感觉称为判断接口更容易理解,传入一个T参数,根据自己逻辑判断出true和false
  • 试用一下
  Predicate<Integer> predicate = (Integer i) -> i > 3;
  System.out.println(predicate.test(4)); //true
  System.out.println(predicate.test(1)); //false
  • 下面是Consumer内置函数接口
@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
  ...
  ...
}
  • 只要理解了上面的Predicate,那么这个很容易理解了,函数描述符为:(T t) -> void,不返回任何东西,那么这个接口就是肉包子打狗,T就是肉包子,传入后进行逻辑执行完毕后,是不反回东西的
  • 试用一下
  Consumer<Integer> consumer = (Integer i) -> System.out.println(i);
  consumer.accept(1);    //1
  consumer.accept(2);    //2
  • 下面是Supplier函数接口
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • 这个理解也不难,就是不给他东西,但是他会造东西给你,函数描述符为:() - T
  • 试用一下
  Supplier<Integer> supplier = () -> new Integer(new Random().nextInt());
  System.out.println(supplier.get()); //-1853289089
  System.out.println(supplier.get()); //829915024
  • 内置函数Function
@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);
  ...
  ...
}
  • 传入一个T,返回一个R,你就可以用它传入一个apple,返回apple的颜色或者是重量,当让你也可以返回apple,泛型的限定并没有限制你必须返回什么东西
  • 试用一下
  Function<String,Integer> function = (String str) ->  Integer.parseInt(str);
  System.out.println(function.apply("123"));   //123
  public class A{
      @Test
      public void test() throws Exception {
          Function<A,A> function = (A a) -> a;
          A a = new A();
          A apply = function.apply(a);
          System.out.println(a == apply); //true
      }
  }
  • 好了到这算是了解了基本的函数式接口,那我们平常如果需要传入两个参数,返回一个东西,但是上面的也没有类似的泛型规定的函数式接口啊,这时候我们应该是去java.util.function包下找对应泛型的接口使用,如果没有就需要自己定义,定义方法跟上面定义Filter一样,只是泛型多加一个就好了,对于我们先在的要求是有内置的函数式接口的,比如BiFunction
@FunctionalInterface
public interface BiFunction<T, U, R> {
  R apply(T t, U u);
  ....
}
  • 传入T和U两个参数,返回R
  • 到这我们基本是能应对各种的泛型要求的函数式接口了,但是需要注意一个问题,那就是拆箱和装箱的问题,我们知道基本类型是要比它对应的包装类型的占用空间要小的,如下

    • 会装箱
    Predicate<Integer> predicate = (Integer i) -> i == 3;
    
    • 不会装箱
    IntPredicate intPredicate = (int i) -> i == 3;
    • 所以一般来说针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如上面的IntPredicate,IntConsumer,IntFunction等

好了这一篇内容以及内容不少了,下面是总结的java8内置的函数式接口,等下一篇内容继续详细了解lambda

BiConsumer<T,U>
BiFunction<T,U,R>
BinaryOperator<T>
BiPredicate<T,U>
BooleanSupplier
Consumer<T>
DoubleBinaryOperator
DoubleConsumer
DoubleFunction<R>
DoublePredicate
DoubleSupplier
DoubleToIntFunction
DoubleToLongFunction
DoubleUnaryOperator
Function<T,R>
IntBinaryOperator
IntConsumer
IntFunction<R>
IntPredicate
IntSupplier
IntToDoubleFunction
IntToLongFunction
IntUnaryOperator
LongBinaryOperator
LongConsumer
LongFunction<R>
LongPredicate
LongSupplier
LongToDoubleFunction
LongToIntFunction
LongUnaryOperator
ObjDoubleConsumer<T>
ObjIntConsumer<T>
ObjLongConsumer<T>
Predicate<T>
Supplier<T>
ToDoubleBiFunction<T,U>
ToDoubleFunction<T>
ToIntBiFunction<T,U>
ToIntFunction<T>
ToLongBiFunction<T,U>
ToLongFunction<T>
UnaryOperator<T>

猜你喜欢

转载自yq.aliyun.com/articles/665658
今日推荐