Lambda表达式、函数式接口、Stream

文章目录

  • Lambda
    • 简介
    • 类型推断
    • 函数式接口
    • 方法引用
      • 概述
      • 语法
    • 域以及访问限制
      • 局部变量
      • 静态变量和成员变量
    • 代码
  • 函数式接口
    • Function接口
    • Consumer接口
    • Supplier接口
    • Predicate接口
  • Stream
    • 简介
    • 流程
    • 创建
      • 有限Stream
      • 无限Stream
      • 空Stream
    • 操作
      • 中间操作(中间方法)
        • 过滤器(Filter)
        • 排序(Sorted)
        • 映射(Map)
        • 代码
      • 完结操作
        • 匹配(Match)
        • 收集(Collect)
        • 计数(Count)
        • 规约
    • 并行Stream
    • 懒操作

Lambda

简介

Lambda表达式本质上是匿名方法,其底层还是通过invokedynamic指令来生成匿名类来实现。它提供了更为简单的语法和写作方式,允许你通过表达式来代替函数式接口。在一些人看来,Lambda就是可以让你的代码变得更简洁,完全可以不使用——这种看法当然没问题,但重要的是lambda为Java带来了闭包。得益于Lamdba对集合的支持,通过Lambda在多核处理器条件下对集合遍历时的性能提高极大,另外我们可以以数据流的方式处理集合——这是非常有吸引力的。

类型推断

编译器负责推导lambda表达式的类型。它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。lambda表达式只能出现在目标类型为函数式接口的上下文中。
当然,lambda表达式对目标类型也是有要求的。编译器会检查lambda表达式的类型和目标类型的方法签名(method signature)是否一致。当且仅当下面所有条件均满足时,lambda表达式才可以被赋给目标类型T:

  • T是一个函数式接口 lambda表达式的参数和T的方法参数在数量和类型上一一对应
  • lambda表达式的返回值和T的方法返回值相兼容(Compatible) lambda表达式内所抛出的异常和T的方法throws类型相兼容

由于目标类型(函数式接口)已经“知道”lambda表达式的形式参数(Formal parameter)类型,所以我们没有必要把已知类型再重复一遍。也就是说,lambda表达式的参数类型可以从目标类型中得出:

可以看到,在lambda中的参数列表,有些时候可以不用写参数的类型,跟java7中 new ArrayList<>()不需要指定泛型类型,这样的<>棱形操作符一样,根据上下文做出类型推断。
即lambda表达式并不是第一个拥有上下文相关类型的Java表达式:泛型方法调用和“菱形”构造器调用也通过目标类型来进行类型推导

函数式接口

Lambda表达式所用的接口,必须是函数式接口;接口中只有一个抽象方法的接口,称为函数式接口;可以使用@FunctionalInterface注解修饰,对该接口做检查;如果接口里,有多个抽象方法,使用该注解,会有语法错误。
Lambda表达式的目标类型是函数性接口@FunctionalInterface,每一个Lambda都能通过一个特定的函数式接口与一个给定的类型进行匹配。因此一个Lambda表达式能被应用在与其目标类型匹配的任何地方,lambda表达式必须和函数式接口的抽象函数描述一样的参数类型,它的返回类型也必须和抽象函数的返回类型兼容,并且他能抛出的异常也仅限于在函数的描述范围中。

方法引用

概述

然而,有时候我们仅仅是调用了一个已存在的方法。如下:

Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));

在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。

Arrays.sort(stringsArray, String::compareToIgnoreCase);

这种特性就叫做方法引用(Method Reference)。
实际上就是

创建了一个类实现了函数式接口,并重写了接口中的抽象方法,在重写的方法中调用了被引用的方法。
创建该实现类对象。 

语法

1.指向静态方法的方法引用,例如Integer的parseInt方法 ,可以写成Integer::parseInt

类::静态方法名

2.引用某个类型的任意对象的实例方法,例如String的length方法,写成String::length;

类::实例方法名

3.指向现有对象的实例方法的方法引用

对象::实例方法名

4.构造器的引用:对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用

类::new

域以及访问限制

域即作用域,Lambda表达式中的参数列表中的参数在该Lambda表达式范围内(域)有效。在作用Lambda表达式内,可以访问外部的变量:局部变量、类变量和静态变量,但操作受限程度不一。
Lambda不能访问函数接口的默认方法

局部变量

在Lambda表达式外部的局部变量会被JVM隐式的编译成final类型,因此只能访问外而不能修改。

静态变量和成员变量

在Lambda表达式内部,对静态变量和成员变量可读可写。

代码

@FunctionalInterface
public interface FunctionalInterfaceTest<T> {
    T run(T t);
    default void defaultFun(){
        System.out.println("default...........");
    }
}



public class LambdaTest {

    private static int staticInt = 1;

    public int memberInt = 1;

    public static void main(String[] args) {
          int localInt = 1;
          FunctionalInterfaceTest<String> f = s -> s;
          f = s -> {
              System.out.println(localInt);
              return s;
          };
          //报错Variable used in lambda expression should be final or effectively final
          //在lambda表达式中使用的局部变量应该是final或有效的final,试图++,所以不是有效final。
          //f = s -> {
          //    localInt++;
          //    return s;
          //};
          f = s -> {
              staticInt++;
              return s;
          };
          f = s -> {
              new LambdaTest().memberInt++;
              return s;
          };
          f = LambdaTest::fun;
          
          //Cannot resolve method 'defaultFun' in 'LambdaTest'
          //        f = s -> {
          //  defaultFun();
          //    return s;
          // }; 
          
    }

    private static String fun(String s){
        return s;
    }
}

函数式接口

java.util.function包中内置许多函数式接口,内置了4个核心接口。
Predicate用来逻辑判断,Function用在有输入有输出的地方,Supplier用在无输入,有输出的地方,而Consumer用在有输入,无输出的地方。你可以通过其名称的含义来获知其使用场景。

Function接口

函数型接口,接收一个参数,返回单一的结果,默认的方法(andThen)可将多个函数串在一起,形成复合Funtion(有输入,有输出)结果

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


public class FunctionInterfaceTest {
    public static void main(String[] args) {
        Function<Integer,Integer> f = integer -> {
            return integer * 2;
        };
        //8
        System.out.println(f.andThen(f).apply(2));
    }
}

Consumer接口

消费型接口,代表了在单一的输入参数上需要进行的操作。和Function不同的是,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); };
    }
}



public class FunctionInterfaceTest {
    public static void main(String[] args) {
        Consumer<String> c = str -> {
            System.out.println(str);
        };
        //str
        //str
        c.andThen(c).accept("str");
        
    }
}

Supplier接口

供给型接口,返回一个给定类型的结果,与Function不同的是,Supplier不需要接受参数(供应者,有输出无输入)

@FunctionalInterface
public interface Supplier<T> {
 
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}


public class FunctionInterfaceTest {
    public static void main(String[] args) {
        Supplier<Integer> s = () -> 99;
        //99
        System.out.println(s.get());
    }
}

Predicate接口

断言型接口,输入一个参数,并返回一个Boolean值,其中内置许多用于逻辑判断的默认方法

@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);
	}
}



public class FunctionInterfaceTest {
    public static void main(String[] args) {
       
        Predicate<Integer> p = integer -> integer > 99;
        //true
        System.out.println(p.and(integer -> integer < 101).test(100));

    }
}

Stream

简介

Stream表示数据流,它没有数据结构,本身也不存储元素,其操作也不会改变源Stream,而是生成新Stream.作为一种操作数据的接口,它提供了过滤、排序、映射、规约等多种操作方法,这些方法按照返回类型被分为两类:凡是返回Stream类型的方法,称之为中间方法(中间操作),其余的都是完结方法(完结操作)。完结方法返回一个某种类型的值,而中间方法则返回新的Stream。中间方法的调用通常是链式的,该过程会形成一个管道,当完结方法被调用时会导致立即从管道中消费值,这里我们要记住:Stream的操作尽可能以“延迟”的方式运行,也就是我们常说的“懒操作”,这样有助于减少资源占用,提高性能。对于所有的中间操作(除sorted外)都是运行在延迟模式下。
Stream不但提供了强大的数据操作能力,更重要的是Stream既支持串行也支持并行,并行使得Stream在多核处理器上有着更好的性能。

流程

  1. 创建Stream
  2. 通过中间操作,对原始Stream进行“变化”并生成新的Stream
  3. 使用完结操作,生成最终结果

创建

有限Stream

   private static Stream<String> init() {
        List<String> list = new ArrayList<>(10);
        String[] arr = new String[]{"a","b","c"};
        Stream<String> stream = list.stream();
        stream = Stream.of(arr);
        stream = Arrays.stream(arr);
        return stream;
    }

无限Stream

        Stream.generate(new Supplier<Integer>() {
            Integer value = 0;
            @Override
            public Integer get() {
                return value++;
            }
        });

空Stream

        Stream<Object> empty = Stream.empty();

操作

可以分成两种类型的操作(方法),一种返回类型为接口本身的Stream,我们称这些方法为中间操作,返回其他具体类型的,我们称为终端操作。

中间操作(中间方法)

过滤器(Filter)

结合Predicate接口,Filter对流对象中的所有元素进行过滤,该操作是一个中间操作,这意味着你可以在操作返回结果的基础上进行其他操作。

排序(Sorted)

结合Comparator接口,该操作返回一个排序过后的流的视图,原始流的顺序不会改变。通过Comparator来指定排序规则,默认是按照自然顺序排序。

映射(Map)

结合Function接口,该操作能将流对象中的每个元素映射为另一种元素(或者同种元素之间的映射),实现元素类型的转换。

代码
 Stream<String> init = init();
 init.filter(s -> s.startsWith("a")).sorted((str1,str2) -> str1.compareTo(str2)).map(s -> s+"_map").forEach(System.out::println);

完结操作

匹配(Match)

用来判断某个predicate是否和流对象相匹配,最终返回Boolean类型结果,例如:

        //流对象中只要有一个元素匹配就返回true
        boolean anyStartWithA = lists.stream().anyMatch((s -> s.startsWith("a")));
        //流对象中每个元素都匹配就返回true
        boolean allStartWithA = lists.stream().allMatch((s -> s.startsWith("a")));
收集(Collect)

在对经过变换之后,我们将变换的Stream的元素收集,比如将这些元素存至集合中,此时便可以使用Stream提供的collect方法,例如:

List<String> list = lists.stream().filter((p) -> p.startsWith("a")).sorted().collect(Collectors.toList());
计数(Count)

类似sql的count,用来统计流中元素的总数,例如:

long count = lists.stream().filter((s -> s.startsWith("a"))).count();
规约

reduce方法允许我们用自己的方式去计算元素或者将一个Stream中的元素以某种规律关联,例如:

        init = init();
        Optional<String> a = init.filter(s -> s.startsWith("a")).reduce((str1, str2) -> str1 + str2);
        System.out.println(a.get());

并行Stream

到目前我们已经将常用的中间操作和完结操作介绍完了。当然所有的的示例都是基于串行Stream。接下来介绍并行Stream(parallel Stream)。并行Stream基于Fork-join并行分解框架实现,将大数据集合切分为多个小数据结合交给不同的线程去处理,这样在多核处理情况下,性能会得到很大的提高。这和MapReduce的设计理念一致:大任务化小,小任务再分配到不同的机器执行。只不过这里的小任务是交给不同的处理器。
通过parallelStream()创建并行Stream。并行Stream在某些场景能够提高性能。
你也会发现在某些情况,串行流的性能反而更好,至于具体的使用,需要你根据实际场景先测试后再决定。

懒操作

通过创建一个无穷大的Stream来说明,无限Stream通常配合limit使用,限制这个流生成的元素的个数。:

    private static void lazyTest(){
        Stream<Integer> stream = Stream.generate(new Supplier<Integer>() {
            Integer value = 0;

            @Override
            public Integer get() {
                return value++;
            }
        });
        stream.map(p -> p+1000).limit(1000).forEach(System.out::println);
    }

我们发现开始时对这个无穷大的Stream做任何中间操作(如:filter,map等,但sorted不行)都是可以的,也就是对Stream进行中间操作并生存一个新的Stream的过程并非立刻生效的(不然此例中的map操作会永远的运行下去,被阻塞住),当遇到完结方法时stream才开始计算。通过limit()方法,把这个无穷的Stream转为有穷的Stream。

你可能感兴趣的:(java)