Java8学习【002】

上一部分我们用Lambda表达式表示了匿名函数,看上去十分的简洁。所以我们现在就来学习一下。


一、什么是Lambda
  • 它是简洁表示匿名函数的一种方式。
  • 基本介绍:
    1. 匿名【没有自己的名称】,本身就是一个匿名函数。
    2. 是函数,是匿名函数【但是不属于某个特定的类】。
    3. 麻雀虽小五脏俱全:和函数一样拥有自己的参数列表、函数主体、返回类型、也可有可抛出的异常列表。
    4. 行为参数传递【作为参数传递给方法、存储在变量中】.
    5. 重中之重-简洁【要不也没必要用它了?】。
  • 基本组成:
    1. 参数列表:(a,b)、(a)、a
    2. 箭头: ->【中间没有空格】
    3. 函数主体 : System.out.println();
  • 既然知道了基本组成那Lambda大概长啥样呢?
    1. ( String str) -> str.length();
    2. (int a, int b)-> a+b;
    3. () -> 1;
    4. (Object o1,Object o2) -> o1.hashCode()+o2.hashCode();

    注意如果只有一个返回语句,return 关键字可以省略。


二、在哪里使用Lambda表达式:
  • 我们可以在函数式接口上使用Lambda表达式。
  • 什么是函数式接口:

    函数式接口就是只定义一个抽象方法的接口【当然也可以有很多默认范发个】。

    • 我们可以使用Lambda表达式为函数式接口的抽象方法提供具体的实现,把整个Lambda表达式作为函数式接口其中一个具体实现的实例。【这些之前我们可以通过匿名内部类实现,但现在我们可以使用更简单的方式】。
    • 举个栗子: 线程实现
      Runnable rbl = () -> System.out.println(“Runnable Lambda”);
      rbl.start();
      还可以像下面这样:
      needThread(Runnable rbl;
      needThread(() -> System.out.println(“Runnable Lambda”));
      直接将Lambda作为行为参数传递给方法。
    • 我们可以使用 @FunctionalInterface 来定义一个函数式接口。

  • 使用函数式接口:

    由于我们在使用Lambda表达式时需要有个函数式接口,jdk之前有一些已经定义好的函数式接口,但是并不全面,而我们在使用的时候也不能一直创建函数式接口,所以我们需要一些泛化的接口,而Java8就为我们提供了一些他们在java.util.fnction包下。


  • Predicate
@FunctionalInterface
public interface Predicate<T> {
   	boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

我们主要看一下test()方法,在我们需要一个接受泛型T 并返回一个布尔表达式的方法时我们就可以使用这个接口


  • Consumer
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

我们注意到accept函数,接受泛型T、且没有返回,所以如果我们需要处理类型T的对象就可以使用这个接口。


  • 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;
    }
}

这里有个apply()函数他接受一个泛型T的对象 , 并返回一个泛型 R的对象。


  • 避免自动拆装箱

Java 中数据分为引用类型和原始类型,我们在使用int、boolean、等基本类型的时候会需要将他们转成引用类型来使用,如:Boolean、Integer。而自动拆装箱是损耗性能的,所以我们可以来避免自动拆装箱,而java8为我们提供了这些办法。
接口

  1. 如上图:我们可以看到相应的接口
  2. 具体的使用如下:
		IntPredicate intPredicate = (int i) -> i % 2 == 0;
        System.out.println(intPredicate.test(2));
        System.out.println(intPredicate.test(3));
        结果:
        true
		false

  • 异常处理:
  1. 异常处理方式一:

    @FunctionalInterface
    public interface IFunctionOne {
    String do(String str) throws Exception;
    }

  2. 异常处理方式二:

    Function<User, String> f = (User u) -> {
    try {
    return u.do();
    }
    catch(Exception e) {
    throw new RuntimeException(e);
    } };

  • 我们在使用Lambda的时候需要根据上下文去看一下 ,调用Lambda方法的声明以及 所使用的函数式接口中的方法声明去进行推断。
  • Lambda使用的时候会对局部变量做了一些限制,Lambda可以没有限制的使用实例变量【成员变量】以及静态变量,但是对于局部变量会有一些限制【局部变量必须是final的】,

    那么为什么有这个限制呢?
    1.首先我们要知道 实例变量、局部变量的区别:实例变量是类的一部分存储于堆中,而局部变量则保存在栈中。
    2.如果Lambda可以直接访问局部变量,当我们在一个线程中使用Lambda表达式的时候,可能会出现使用Lambda的线程在分配该变量的线程将该变量回收之后,去访问该变量。【这显然是线程不安全的】。
    3. 所以Java在访问局部变量的时候实际上是访问它的副本,而不是原始变量,所以就有了这个限制。

  • 方法引用

    我们可以像下面这样打印一个List:

    list.forEach(System.out::println);
    
    • 方法引用可以看做是调用特定方法的Lambda表达式的简洁写法。

    • 既如果一个Lambda表达式只意味着-直接调用这个方法,那就直接用名称调用它。

    • 下面看一下等效对比:

      1. (User u) -> u.getSalary() | User::getSalary
      2. (String s) -> System.out.println(s) | System.out::println
    • 如何构建方法引用:

      1.指向静态方法的方法引用:Integer::parseInt。
      2.指向任意类型实例方法的引用:String::length。
      3. 指向现有方法的实例方法的引用:UserCon::getName。


总结:

1.Lambda 可以理解为一种匿名函数,没有名称,但是有函数体、参数列表、返回类型、也可以有抛出的异常列表。【更简洁】
2. 函数式接口就是只声明了一个抽象方法的接口【可以很多默认方法】
3. 只有在接受函数式接口的地方才可以使用Lambda表达式。
4. Lambda 允许我们直接实现抽象方法,并将整个表达式作为函数式接口的一个实例。
5. Java8 自带了一些常用的函数式接口位于:java.util.funciton包下:

Predicate< T> Function<T,R>
Supplier< T> Consumer< T> BinaryOperator< T>

6 .为了避免自动装箱操作:对Predicate< T > Function<T, R> 等通用的函数式接口的原始类型特化:IntPredicate IntToLongFunction 等。
7 .Lambda所代表的类型称为目标类型。
8 .方法引用让你重复使用现有的方法实现并直接传递他们。
9 .Comparator Predicate和Function 等函数式接口都有几个可以用来结合Lambda的默认方法。


发布了122 篇原创文章 · 获赞 32 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/YangzaiLeHeHe/article/details/98962617