JAVA8基础与实战(上)
学习网址:https://www.bilibili.com/video/BV1w4411e7T8?p=3
https://gitee.com/LiuDaiHua/jdk8
JAVAEE8在2017年9月正式发布,这是自2013年6月,Java企业版的首次更新。JAVAEE8提供了一些新的API,提供了对HTTP/2的新支持。
在JDK1.8以前的版本中,定义一个接口时,所有的方法必须是抽象方法,这是Java语法规定的。但是在JDK1.8中定义一个接口时,在满足特定的前提下,可以有方法的具体实现。这样一个接口中可以有抽象方法,也可以有具体方法,这跟JDK1.8以前的接口比,明显接口的功能变得强大了。
接口中定义具体的方法实现是有限制的,它不能像我们在一个普通类那样随便定义方法实现,它只能定义default和static类型的方法。在调用的时候,被default修饰的默认方法需要通过实现了该接口的类去调用,被static修饰的静态方法可以通过接口名直接调用。
1 package interfact; 2 3 /** 4 * 接口中方法默认是public 5 * 接口中的方法只能使用:default/static/abstract 三者之一修饰,其中使用abstract修饰时可以省略abstract关键字 6 */ 7 public interface MyInterface { 8 String HELLO = "hello"; 9 10 /** 11 * default是jdk8引入的关键字、只能用于接口中方法的修饰 12 * 被default修饰的方法必须有方法体 13 */ 14 default void canDoAny() { 15 System.out.println("I am default method! " + HELLO); 16 } 17 18 /** 19 * default是jdk8引入的关键字、只能用于接口中方法的修饰 20 * 在接口中被static修饰的方法必须有方法体 21 */ 22 static void canUseByInterface() { 23 System.out.println("I am static method! " + HELLO); 24 } 25 26 /** 27 * 抽象方法可以省略abstract关键字 28 */ 29 void abs(); 30 31 32 }
package interfact; public class MyInterfaceImpl implements MyInterface { @Override public void abs() { System.out.println("I implements MyInterface and overrid abs method"); } @Override public void canDoAny() { System.out.println("I implements MyInterface and overrid canDoAny method"); } }
1 package interfact; 2 3 public class Main { 4 public static void main(String[] args) { 5 6 // 接口中的default/abstract方法只能由接口的实现类调用 7 new MyInterfaceImpl().abs(); 8 new MyInterfaceImpl().canDoAny(); 9 10 // 接口可以直接调用其内静态方法 11 MyInterface.canUseByInterface(); 12 } 13 }
I implements MyInterface and overrid abs method I implements MyInterface and overrid canDoAny method I am static method! hello
default是非常巧妙的设计,默认方法的出现一方面保证的java8新特性(lambda表达式、函数式接口)的加入,同时它又确保了jdk8与老版本代码的完全兼容。如果你又想在接口的层面上增加一个实现,又想老版本接口兼容,jdk的设计者也是思考了很长时间,最后才提出了在接口里面采用default方式去满足这种需求。
举个例子:例如在jdk1.8中,对Iterable接口又添加了forEach方法,他就是一个default方法,只要实现了Iterable接口的类,可以直接调用接口里的forEach进行元素的遍历,而不需要自己在手写实现。
1 public interface Iterable<T> { 2 ... // 略 3 4 /** 5 * Performs the given action for each element of the {@code Iterable} 6 * until all elements have been processed or the action throws an 7 * exception. Unless otherwise specified by the implementing class, 8 * actions are performed in the order of iteration (if an iteration order 9 * is specified). Exceptions thrown by the action are relayed to the 10 * caller. 11 * 12 * @implSpec 13 * <p>The default implementation behaves as if: 14 * <pre>{@code 15 * for (T t : this) 16 * action.accept(t); 17 * }</pre> 18 * 19 * @param action The action to be performed for each element 20 * @throws NullPointerException if the specified action is null 21 * @since 1.8 22 */ 23 default void forEach(Consumer<? super T> action) { 24 Objects.requireNonNull(action); 25 for (T t : this) { 26 action.accept(t); 27 } 28 } 29 30 ... // 略 31 }
public static void main(String[] args) { // List实现了Iterable接口 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); }
Java8引入默认方法,避免了对原有的类或接口造成破坏,主要是为了保证向后兼容。
当某两个接口A和B具有相同的default方法时,一个实现类C同时实现了这两个接口时,需要指明到底实现的是这两个接口里的哪一个default方法:
package interfact; public interface MyInterface1 { default void myMethod() { System.out.println("MyInterface2"); } }
package interfact; public interface MyInterface2 { default void myMethod() { System.out.println("MyInterface2"); } }
1 package interfact; 2 3 public class InterfaceMain implements MyInterface1, MyInterface2 { 4 5 /** 6 * 声明实现了哪个接口的方法 7 */ 8 @Override 9 public void myMethod() { 10 // 我们直接可以把需要实现某个接口的具体default方法拷贝进来,也可以 11 MyInterface1.super.myMethod(); 12 } 13 14 public static void main(String[] args) { 15 InterfaceMain interfaceMain = new InterfaceMain(); 16 interfaceMain.myMethod(); 17 } 18 19 }
当某两个接口A和B具有相同的default方法时,一个实现类C实现了接口A的default,现在某个实现类D继承了实现类C实现了接口B:
package interfact; public class MyInterface1Impl implements MyInterface1 { @Override public void myMethod() { System.out.println("MyInterface1Impl"); } }
package interfact; public class InterfaceMain extends MyInterface1Impl implements MyInterface2 { public static void main(String[] args) { InterfaceMain interfaceMain = new InterfaceMain(); interfaceMain.myMethod(); } }
打印结果为MyInterface1Impl。因为接口只是一种抽象的表述,它表示一种规范协议,而实现类更加具体的描述了接口的行为,在java语言中,认为实现类比接口更加高级,所以最终打印的结果是以实现类优先级高打印的。
1 public interface Iterable<T> { 2 /** 3 * Returns an iterator over elements of type {@code T}. 4 * 5 * @return an Iterator. 6 */ 7 Iterator<T> iterator(); 8 9 /** 10 * 对迭代器的每一个原生执行给定的操作,直到所有元素都已处理完毕 11 * 或该操作引发异常。如果实现类没有重写,操作将按迭代顺序执行 12 * 。操作引发的异常将传达给调用者。 13 * @param action The action to be performed for each element 14 * @throws NullPointerException if the specified action is null 15 * @since 1.8 16 */ 17 default void forEach(Consumer<? super T> action) { 18 Objects.requireNonNull(action); 19 for (T t : this) { 20 action.accept(t); 21 } 22 }
public interface Collection<E> extends Iterable<E> { ...// 略 /** * Returns a sequential {@code Stream} with this collection as its source. * * <p>This method should be overridden when the {@link #spliterator()} * method cannot return a spliterator that is {@code IMMUTABLE}, * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()} * for details.) * * @implSpec * The default implementation creates a sequential {@code Stream} from the * collection's {@code Spliterator}. * * @return a sequential {@code Stream} over the elements in this collection * @since 1.8 */ default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } ...// 略 }
Stream提供支持一个元素序列的串行和并行聚合操作,以下示例说明使用Stream和IntStream:
int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum();
在此示例中,代码里widgets是一个Collection<Widget>。我们通过Collection#stream创建一个Widget对象流,它过滤以产生只有红色widgets的流,然后将其转变成int值流,这些值代表每个红色小部件的权重,然后该流求和以产生一个总权重。
此外,除了对象引用的流Stream,还存在一些原始类型的流,例如IntStream、LongStream、DoubleStream,所有的这些都称为流,它们都符合此处描述的特征和限制:
要执行计算,流业务被组合成一个流的管道,一个流的管道由一个源头(源头可以是一个数组,集合,generator方法,I/O通道等),零个或多个中间操作(将一个流转换成另外一个流,例如:filter(Predicate),一个终端操作(产生结果或副作用(例如count 或 forEach),【终端操作指的是截至操作,即流结束】)),流是惰性的,只有在终端操作启动时才会对源数据进行计算,源数据只在需要时才被使用。
集合和流虽然表面上有一些相似之处,但是有着不同的目标。集合主要涉及对其元素的有效管理和访问。相比之下,流不提供直接访问或操作元素的方法,而是关注于声明性地描述其源和将在该源上聚合执行的计算操作。但是,如果提供的流操作没有提供所需的功能(指仅创建stream,不进行其他filter等操作),则可以使用流的iterator()和spliterator()操作获取遍历器。
流管道pipeline,类似上面“widgets”示例,可以看作是对流数据源的查询。除非流的数据源对象明确指明可以并发修改(例如ConcurrentHashMap),否则在查询流源时修改流源可能会导致不可预测或错误的行为【遍历过程中,内部或外部随意修改流源可能会引发错误】。
大多数流操作都接受 描述用户指定行为 的参数,例如上例中传递给mapToInt的lambda表达式w->w.getWeight()。为了保持正确的行为,这些行为参数:
大多数情况下必须是无状态的(它们的结果不应该依赖于在执行流水线过程中可能改变的任何状态,即结果不可变)
这些参数总是函数式接口的实例,通常是lambda表达式或方法引用。除非另有规定,否则这些参数必须非空。
一个流只能被操作一次(调用中间流或终端流操作)。例如,这就排除了“分叉”流,即同一个源为多个管道提供数据,或者同一个流的多次遍历。如果流实现检测到正在被重复使用,它可能会抛出illegalStateException。然而,在某些情况下,可能不是所有的操作都返回一个新的流,有些操作会重新使用它们的流。
Stream有一个close()方法并实现类AutoCloseable,但几乎所有的流实例在使用后实际上并不需要关闭。通常只有源为IO通道的流(例如文件.lines(Path,Charset))将需要关闭。大多数流由集合、数组或generater函数支持,这些数据源不需要特殊的资源管理(即try catch close一套操作)。(如果流确实需要关闭,则可以在try with resources语句中将其声明为资源。)
流管道可以顺序执行,也可以并行执行。此执行模式是流的特性。流的初始创建可以选择是并行的还是串行的(例如,集合.stream()创建一个串行流,集合.parallelStream创建一个并行流)。此执行模式的选择可以通过sequential()或parallel()方法进行修改,也可以使用isParallel()方法进行查询。
Stream的出现有两大概念,第一流,第二管道。流是一个抽象的概念,可以表示成移动的数据单元,管道的作用就是对流里的每个单元起着什么样的操作。流又分为中间流和终端流,中间流也称为节点流,它指的是输入是一个流返回的还是一个流的一种流,而终端流指的是输入是一个流,输入完后流就被中断了,不会传递到下一个管道里了。
public static void main(String[] args) { List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; Stream<Person> originStream = persons.stream(); Stream<Person> filterStream = originStream.filter(person -> person.getAge() > 20); System.out.println(originStream); // java.util.stream.ReferencePipeline$Head@7ba4f24f System.out.println(filterStream); // java.util.stream.ReferencePipeline$2@3b9a45b3
从打印结果上看originStream经过filter之后不在是之前的流对象了,也就是说产生了一个新的中间流对象。既然中间流对象是一个新的流对象那mapToInt等这些操作把originStream转化成其他中间流肯定也不在是原始流了
Stream<Person> originStream = persons.stream(); IntStream intStream = originStream.mapToInt(person -> person.getAge()); System.out.println(originStream); // java.util.stream.ReferencePipeline$Head@7ba4f24f System.out.println(intStream); // java.util.stream.ReferencePipeline$4@3b9a45b3
Stream<Person> originStream = persons.stream(); Stream<Integer> integerStream = originStream.map(person -> person.getAge()); Stream<Person> sortStream = originStream.sorted(); // 再次使用originStream中间流 System.out.println(originStream); System.out.println(integerStream); System.out.println(sortStream);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203) at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94) at java.util.stream.ReferencePipeline$StatefulOp.<init>(ReferencePipeline.java:647) at java.util.stream.SortedOps$OfRef.<init>(SortedOps.java:111) at java.util.stream.SortedOps.makeRef(SortedOps.java:51) at java.util.stream.ReferencePipeline.sorted(ReferencePipeline.java:389) at newApi.StreamTest.main(StreamTest.java:48)
// 流操作过程中参数(即forEach里lambda表达式)的执行结果必须不可变 // 即下面的lambda表达式不可变 originStream.forEach(person -> { // lambda表达式内的参数person状态更改不影响节点流 person.setAge(person.getAge() + 1); System.out.println(person.getAge()); }); // 流操作过程中参数(即forEach里lambda表达式),必须时无干扰的(不可以改变数据源) originStream.forEach(person -> persons.remove(0)); // originStream.forEach(person -> persons.add(new Person(43, "aobama")));
执行结果:上面打印能正常,但是下面修改就报错了,不允许修改数据源
21 31 27 41 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at newApi.StreamTest.main(StreamTest.java:56)
Stream<Person> originStream = persons.stream(); originStream.forEach(person -> persons.remove(person));
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at newApi.StreamTest.main(StreamTest.java:48)
public static void main(String[] args) { /** * 流是存在短路运算的。 * 它里面相当于存在这样一个容器,容器里存的是对流里面每一个元素 * 的操作,当对流进行处理的时候,它会拿着流里面的 * 每操作逐个的应用到流里面的当前元素,而且流是存在短路运算的。 * 如下打印结果为: * hello * 5 */ List<String> strings = Arrays.asList("hello", "world", "hello world"); strings.stream().mapToInt(item -> { int length = item.length(); System.out.println(item); return length; }).filter(item -> item == 5).findFirst().ifPresent(item -> System.out.println(item)); }
// 流的常用创建方式 Stream stream1 = Stream.of("beijing", "tianjing", "shanghai"); stream1.forEach(System.out::println); String[] myArray = new String[]{"hello", "world"}; Stream stream2 = Stream.of(myArray); Stream stream3 = Arrays.stream(myArray); Stream stream4 = Arrays.asList(myArray).stream(); // 基本使用 IntStream.of(new int[]{5, 6, 7}).forEach(System.out::println); System.out.println("----[3,8)--------"); IntStream.range(3, 8).forEach(System.out::println); System.out.println("-----[3,8]-------"); IntStream.rangeClosed(3, 8).forEach(System.out::println); System.out.println("------统计源数据里单元个数------"); long count = IntStream.of(new int[]{1, 2, 3}).count(); System.out.println(count); System.out.println("------1、求和------"); long sum = IntStream.of(new int[]{1, 2, 3}).map(value -> 2 * value).sum(); System.out.println(sum); System.out.println("------2、求和------"); long sum2 = IntStream.of(new int[]{1, 2, 3}).map(value -> 2 * value).reduce(0, Integer::sum); System.out.println(sum2);
/** * stream.toArray将一个流转换为当前流内元素类型的数组 * 其中toArray参数函数式接口IntFunction的构造函数第一个参数为当前流元素的个数 */ List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; Stream<Person> stream = persons.stream(); // 将一个流转换为数组 Person[] people = stream.toArray(Person[]::new); String[] strings = Stream.of("welcome", "to", "beijing").toArray(length -> new String[length]);
注意collect是区分并行和串行流的,对于并行流和串行流执行的方式也不一样,Collectors.toList默认帮我们实现了collect方法的参数案例如下:
/** * stream.collect */ Stream<Person> streamCol = persons.stream(); System.out.println("------将一个流转换为集合-----"); List<Person> personListCol = streamCol.collect(Collectors.toList()); personListCol.forEach((person) -> System.out.println(person.getName()));
进入Collectors.toList可以查看到,它帮我们实现了collect入参接口:
public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); }
先来解释以下实现类CollectorImpl构造函数参数是什么意思:
CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Set<Characteristics> characteristics) { this(supplier, accumulator, combiner, castingIdentity(), characteristics); }
第一个参数表示供应商,第二个参数表示消费,第三个参数表示一个二元操作,第四个参数不必关心。供应商主要是提供后续操作对象的,也就是说它提供了一个对象给第二个参数BiConsumer构造方法的第一个参数,而BiConsumer构造方法的第二个参数则是当前stream内的某个数据单元。第三个操作表示一个二元操作。这么一解释很难懂,直接看Collectors.toList案例:
第一个参数是个供应商,提供ArrayList对象的创建,如果流是一个并行流,则会创建多个ArrayList对象,如果是一个串行流则只创建一个。第二个参数把供应商创建的某个ArrayList对象添加一个当前流内的元素。第三个参数,二元操作,在这里指的是合并操作,如果是并行流:把创建的第二个ArrayList对象(如果存在),合并到第一个ArrayList对象里,并返回一个ArrayList对象作为下一次合并操作的输入。当流内所有元素操作完毕,则返回ArrayList对象(已经合并了其他所有ArrayList)。如果是串行流:则忽略该参数的任何操作,当流内所有元素操作完成后返回创建的那个唯一的ArrayList对象。
/** * Simple implementation class for {@code Collector}. * * @param <T> the type of elements to be collected * @param <R> the type of the result */ CollectorImpl(Supplier<A> supplier, // 供应商,无入参,出参:供应一个A类型的对象(A类型是一个中间对象) BiConsumer<A, T> accumulator,// 消费者,入参:A类型的对象,T类型的集合元素,无出参 BinaryOperator<A> combiner,// 中间操作,入参:两个A类型的对象,出参:一个A类型的对象 Function<A,R> finisher,// 封装/转换结果集,将combiner最终返回的结果集(A类型)作为入参,返回一个R类型的对象 Set<Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.characteristics = characteristics; }
System.out.println("------使用serial stream测试collect方法的第三个参数-----"); Stream<Person> streamSerial = persons.stream(); List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> { System.out.println(theList1); // 忽略该参数任何操作,根本不进入断点 System.out.println(theList2); theList1.addAll(theList2); }); personList1.forEach(person -> System.out.println(person.getName())); System.out.println(personList1.size()); System.out.println("------使用parallel stream测试collect方法的第三个参数-----"); Stream<Person> streamParallel = persons.parallelStream(); List<Person> personList2 = streamParallel.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> { System.out.println(theList1); // 进入断点 System.out.println(theList2); theList1.addAll(theList2); // 如果不合并,则返回的第一个ArrayList只添加了流里的第一个元素,下面打印结果也就只有一个元素 }); personList2.forEach(person -> System.out.println(person.getName()));
当是串行流的时候第三个参数不可以为null,但可以是一个空的实现:
List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> {});
List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> theList1.addAll(theList2));
Collectors.toList的原理就是像上面手动封装了一个对ArrayList的操作,我们也可以自己封装一个对LinkedList的操作
streamParallel.collect(LinkedList::new, LinkedList::add, LinkedList::addAll); // 等效于: streamParallel.collect(Collectors.toCollection(LinkedList::new));
/** * flatMap返回一个流,它将提供的映射函数(即入参)应用于每个元素而生成的映射流的内容,替换this stream的每个元素的结果组成。 * <p> * 什么时候用flatMap什么时候用map? * 当你需要把多个流【首先你要把多种情况构建成流】,扁平到一个最终的流上时候需要使用flatMap * flatMap入参是一个Function,你可以使用lambda返回任何一个可以被扁平化的流,返回结果是一个合并其他流结果的的新流 */ public class StreamTest4 { public static void main(String[] args) { /** * List扁平化 */ Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); stream.flatMap(theList -> theList.stream()).forEach(System.out::println); System.out.println("----"); /** * 找出集合中所有单词并去重 * 最终期望输出如下三个单词(不要求顺序):hello welcome world */ List<String> list = Arrays.asList("hello welcome", "welcome hello", "hello world hello", "hello welcome"); // 每一个元素先转化为数组,然后使用数组初始化一个新的流,在使用flatMap扁平化多个流 list.stream().flatMap(str -> Stream.of(str.split(" "))).distinct().forEach(System.out::println); // map先把字符映射为字符数组并返回一个新的流,现在流里面的元素都是字符数组了,使用flatMap,flatMap把Arrays::Stream应用到每一个字符 // 数组上以返回一个映射流,然后把该映射流的内容组合到要返回的流上,最终flatMap返回的是一个个单词,然后去重。 list.stream().map(str -> str.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println); /** * 要求输出: * Hi 毛毛 * Hi 可可 * Hi 吉吉 * Hello 毛毛 * ... */ List<String> list1 = Arrays.asList("Hi", "Hello", "你好"); List<String> list2 = Arrays.asList("毛毛", "可可", "吉吉"); Stream<String> stringStream = list1.stream().flatMap(item1 -> list2.stream().map(item2 -> item1 + " " + item2)); stringStream.forEach(System.out::println); } }
package newApi; import java.util.*; import static java.util.stream.Collectors.*; /** * 分组与分区 */ public class StreamTest5 { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<Student>() {{ add(new Student("maomao", "english", 25, 80)); add(new Student("maomao", "chinese", 25, 90)); add(new Student("keke", "math", 25, 80)); add(new Student("keke", "english", 25, 80)); add(new Student("keke", "chinese", 25, 60)); add(new Student("jiji", "chinese", 25, 70)); add(new Student("jiji", "english", 25, 90)); }}; /** * 需求:实现select * from student group by name; * 原始写法:1、遍历、获取名称,查看是否在map中存在,不存在创 * 建List然后把当前元素放入,存在直接放入List,最后返回map */ Map<String, List<Student>> collect1 = students.stream().collect(groupingBy(Student::getName)); System.out.println(collect1.toString()); /** * 需求:实现select name,count(*) group by name; */ Map<String, Long> collect2 = students.stream().collect(groupingBy(Student::getName, counting())); System.out.println(collect2.toString()); /** * 需求:实现select name, avg(score) group by name; */ Map<String, Double> collect3 = students.stream().collect(groupingBy(Student::getName, averagingDouble(Student::getScore))); System.out.println(collect3.toString()); Map<String, Double> collect4 = students.stream().collect(groupingBy(Student::getCourse, averagingDouble(Student::getScore))); System.out.println(collect4.toString()); System.out.println("----分区-----"); /** * 需求:select name,sum(score) from student group by name * 班级总平均分,学生个人平均分,各个人平均分低于总平均分的为一个区,高于总平均分的分为一个区 */ students.addAll(Arrays.asList(new Student("jiji", "math", 25, 77), new Student("maomao", "math", 25, 87))); Double avg = students.stream().collect(averagingDouble(Student::getScore)); Map<String, Double> studentAvg = students.stream().collect(groupingBy(Student::getName, averagingDouble(Student::getScore))); Map<String, List<String>> result = new HashMap<String, List<String>>() {{ put("up", new ArrayList<>()); put("down", new ArrayList<>()); }}; studentAvg.forEach((name, score) -> { if (score > avg) { result.get("up").add(name); } else { result.get("down").add(name); } }); System.out.println("班级总平均分:" + avg); System.out.println("学生平均分:" + studentAvg); System.out.println(result); System.out.println("----分区----"); Map<Boolean, List<Student>> collect = students.stream().collect(partitioningBy(student -> student.getScore() > 80)); System.out.println(collect); System.out.println("--------"); /** * 找出学生中最低分数 */ Map<String, Student> collect5 = students.stream().collect( groupingBy(Student::getName, collectingAndThen(minBy(Comparator.comparingDouble(Student::getScore)), Optional::get))); System.out.println(collect5); /** * 找出学生中平均分最低的 */ Collection<Double> collect6 = students.stream().collect( collectingAndThen( groupingBy( Student::getName, collectingAndThen( averagingDouble(Student::getScore), item -> Math.floor(item) ) ), ittem1 -> ittem1.values() ) ); System.out.println(collect6); System.out.println(Collections.min(collect6)); } }
public static void main(String[] args) { /** * 找出集合中所有单词并去重 * 最终期望输出如下三个单词(不要求顺序):hello welcome world */ List<String> list = Arrays.asList("hello welcome", "welcome hello", "hello world hello", "hello welcome"); // list.stream().flatMap(str -> Stream.of(str.split(" "))).collect(Collectors.toCollection(HashSet::new)).forEach(System.out::println); // list.stream().flatMap(str -> Stream.of(str.split(" "))).distinct().forEach(System.out::println); list.stream().map(str->str.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println); /** * findFirst和findAny */ Stream<String> stringStream = Stream.generate(UUID.randomUUID()::toString); Stream<List<Integer>> generate = Stream.generate(() -> Arrays.asList(1, 2, 3)); Optional<String> first = stringStream.findFirst(); Optional<List<Integer>> any = generate.findAny(); // 对于optional操作一定要先判断,规避NPE first.ifPresent(System.out::println); any.ifPresent(System.out::println); System.out.println("----"); /** * 无限的串行流 * 传入一个种子数1,然后不断地对该种子数执行item -> item + 2操作 * 如果不使用limit限制,将不断的一边操作,一边输出,limit限制操作次数 * 注意limit一定要紧跟着iterate使用,否则始终会有一个流无法终止 */ Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println); System.out.println("----"); // 注意limit一定要紧跟着iterate使用,否则始终会有一个流无法终止 // Stream.iterate(1, item -> (item + 1) % 2).distinct().limit(6).forEach(System.out::println); /** * 找出该流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后在取出流中的前两个元素,最后求出流中元素的总和 */ OptionalInt reduce = Stream.iterate(1, item -> item + 2) .limit(6) .filter(item -> item > 2) .mapToInt(item -> item * 2) // 使用mapToInt避免自动装箱 .skip(2) .limit(2) .reduce((item1, item2) -> item1 + item2); // .sum不会发送NPE所以返回的不会被Optional包装、.max和.min会返回Optional reduce.ifPresent(System.out::println); System.out.println("----"); /** * 找出该流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后在取出流中的前3个元素,求总和、最大、最小 */ IntSummaryStatistics intSummaryStatistics = Stream.iterate(1, item -> item + 2) .limit(6) .filter(item -> item > 2) .mapToInt(item -> item * 2) // 使用mapToInt避免自动装箱 .skip(2) .limit(3) .summaryStatistics(); System.out.println("最大:" + intSummaryStatistics.getMax()); System.out.println("最小:" + intSummaryStatistics.getMin()); System.out.println("总和:" + intSummaryStatistics.getSum()); System.out.println("----"); }
/** * 注释略 * * @since 1.8 */ public final class Optional<T> {
Option是一种容器对象,可以包含也可以不包含非空值。如果存在值,isPresent()将返回true,get()将返回该值。
还提供了其他依赖于包含值的存在与否的方法,例如orElse()(如果值不存在,则返回默认值)和ifPresent()(如果值存在,则执行一段代码)。
这是一个基于值的类:在Optional的实例上使用对表示敏感的操作(包括引用相等(=)、标识哈希代码或同步)可能会产生不可预知的结果,因此应避免。
基于值的类是java8提出的新概念。说明如下:有些类,比如java.util.Optional和java.time.LocalDateTime,都是基于值的。基于值的类的实例由以下特征:
具有equals、hashCode和toString的实现,这些实现仅根据实例的状态而不是根据其标识或任何其他对象或变量的状态来计算
不使用身份敏感的操作,例如实例之前的引用相等(==),实例的身份哈希码或实例锁上的同步
没有可访问的构造参数,而是通过工厂方法实例化的,该方法对提交的实例的身份不做任何承诺
在相等时可以自由替换,这意味着在任何计算或方法调用中互换等于equals()的任意两个实例x和y都不会在行为上产生任何可见的变化。
如果程序尝试将两个引用区分为基于值的类的相等值,则可能会产生不可预测的结果,无论使通过直接引用相等,还是间接通过同步、身份哈希,序列化或任何其他身份敏感机制。在基于值的类的实例上使用这种对身份敏感的操作可能会产生不可预测的影响,应该避免。
Optional直译过来就是可选的,Optional的出现是为了解决Java当中经常出现的一个NPE问题即NullPointerException。空指针异常一般发生在使用空的对象去调用某个行为,所以为了防止发出异常,我们一般这么写代码:
if(null != person) { Address address = person.getAddress(); if(null != address) { .... } }
既然空指针异常出现的情况非常多,出现的频率非常高,因此很多语言都对空指针异常做了一定的规避,规避的手段其实都是采用的一种方式,即Optional。
Optional是一种基于值的类,那如何创建Option的对象实例?它创建实例提供了三种方式(这三种方式都是静态方法):
empty():返回一个空的Optional实例【即空的容器实例】
of(T value):使用指定的当前值value返回Option实例,当前值一定不能为null
ofNullable(T value):如果指定的当前值value不是null,则使用指定的当前值value返回Option实例,否则返回空的Option实例
ifPresent(Consumer<? super T> consumer):
如果存在值,则使用该值调用指定的使用者,否则不执行任何操作。
如果此Optional中存在值,则返回,否则抛出NoSuchElementException异常
filter(Predicate<? super T> predicate):
如果存在一个值,并且该值与给定的谓词匹配,返回描述该值的Optional,否则返回一个空的Optional
map(Function<? super T,? extends U> mapper):
如果存在一个值,将提供的映射函数应用与它,如果Functoin结果返回为非空,则返回一个Optional来描述结果。否则返回一个空的Optional
orElseGet<Supplier<? extends T> other>:
如果Optional里存在值,则返回值,否则调用other并返回该调用的结果
Optional不可以作为方法的参数,Optional也不可以作为类的成员变量,因为Optional是不可序列化的!
public static void main(String[] args) { // 原来写法 String hello = "hello"; if (null != hello) { System.out.println(hello); } // 现在使用Optional Optional<String> optional = Optional.of("hello"); if (optional.isPresent()) { System.out.println(optional.get()); } }
如上案例:如果使用Optional会发现本质上还是和原来的写法一样,使用Optional的方式写的代码还要更长,虽然说上面的代码不会出现任何的问题,但是是不推荐的,使用Optional应该转换固有的编程模式,Optional提供了一些参数是函数式接口的方法,我们可以使用这些方法简化上面的代码:
class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Company { private String name; private List<Employee> employees; public Company(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } } public class OptionalTest { public static void main(String[] args) { Optional<String> optional = Optional.of("hello"); if (optional.isPresent()) { System.out.println(optional.get()); } HashMap<String, Object> userInfo = new HashMap<String, Object>() {{ put("age", 23); put("name", "毛毛"); }}; // 简化上面的写法 // 1、ifPresent如果Optional为空什么都不做,否则把Optional里的值应用于Consumer optional.ifPresent(value -> System.out.println(value)); System.out.println(optional.orElse("hello")); // 如果存在则返回,不存在使用指定值other返回 System.out.println(optional.orElseGet(() -> new String("hello"))); // 如果存在,则返回,不存在,则调用供应商获取 /** * ofNullable:如果p是个null,则返回一个空的Optional,否则返回一个包装了p的Optional * orElseGet:如果当前Optional里有值,则返回该值,否则使用供应商提供的返回结果 */ Person p = null; int age = Optional.ofNullable(p).orElseGet(() -> new Person(23, "毛毛")).getAge(); System.out.println(age); /** * 一个关于Optional的真实案例: * 如果一个集合不为空返回这个集合,否则返回一个空的集合。 */ Employee employee1 = new Employee("mao"); Employee employee2 = new Employee("ke"); // Company company = null; Company company = new Company("wonders"); List<Employee> employees = Arrays.asList(employee1, employee2); company.setEmployees(employees); // company为空最终返回一个空的集合,company不为空,若employees为空,也返回一个空的集合,若employees也不为空,则返回employees Optional<Company> companyOption = Optional.ofNullable(company); System.out.println(companyOption.map((company1 -> company1.getEmployees())).orElse(Collections.emptyList())); // company.getEmployees()不为空则直接返回集合,为空返回空集合 // Optional.ofNullable(company.getEmployees()).orElse(Collections.emptyList()); } }
package date; import java.time.*; import java.time.temporal.ChronoUnit; import java.util.Set; import java.util.TreeSet; public class MyDate { public static void main(String[] args) { /** * LocalDate关注日期 */ LocalDate localDate = LocalDate.now(); System.out.println("当前日期: " + localDate); System.out.println("当前月份: " + localDate.getMonth()); System.out.println("当前日期: " + localDate.getYear() + "," + localDate.getMonthValue() + "," + localDate.getDayOfMonth()); LocalDate localDate1 = LocalDate.of(2020, 3, 24); System.out.println("构造任意日期: " + localDate1); /** * monthDay仅仅只关注月和日,不关注年 */ LocalDate localDate2 = LocalDate.of(2020, 8, 20); MonthDay monthDay = MonthDay.of(localDate2.getMonth(), localDate2.getDayOfMonth()); MonthDay from = MonthDay.from(LocalDate.of(2010, 8, 20)); if (monthDay.equals(from)) { System.out.println("今天是我生日"); } else { System.out.println("今天不是我生日"); } /** * 只关注年月 */ YearMonth yearMonth = YearMonth.of(2008, 2); System.out.println("2008年2月:" + yearMonth); System.out.println("2008年2月有多少天?" + yearMonth.lengthOfMonth()); System.out.println("2008年是闰年吗?" + yearMonth.isLeapYear()); /** * 日期加减 */ LocalDate after2Week = localDate.plus(2, ChronoUnit.WEEKS); System.out.println("当前日期增加两周:" + after2Week); LocalDate before2Week1 = localDate.plus(-2, ChronoUnit.WEEKS); LocalDate before2Week2 = localDate.minus(2, ChronoUnit.WEEKS); System.out.println("当前日期减去两周:" + before2Week1); System.out.println("当前日期减去两周:" + before2Week2); /** * 日期前后判断 */ System.out.println("日期前后判断:" + after2Week.isAfter(localDate)); System.out.println("日期前后判断:" + localDate1.isBefore(localDate)); /** * 周期 */ LocalDate localDate3 = LocalDate.now(); LocalDate localDate4 = LocalDate.of(2008, 12, 29); Period period = Period.between(localDate4, localDate3); System.out.println("这两个日期隔了多少年?多少月?多少天?" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"); /** * 时区 */ Set<String> availableZoneIds = ZoneId.getAvailableZoneIds(); TreeSet<String> treeSet = new TreeSet<String>() { // 对时区set排序 { addAll(availableZoneIds); } }; System.out.println(treeSet); ZoneId zoneId = ZoneId.of("Asia/Shanghai"); LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("带时区的日期:" + localDateTime); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId); System.out.println("带指定时区的日期:" + zonedDateTime); /** * Instant,不带UTC(时区)的日期 */ System.out.println(Instant.now()); /** * LocaleTime关注时分秒 */ LocalTime localTime = LocalTime.now(); System.out.println("当前时分秒:" + localTime); System.out.println("一个半小时后:" + localTime.plusHours(1).plusMinutes(30)); /** * Clock系统时间 */ Clock clock = Clock.systemDefaultZone(); System.out.println("当前时间毫秒数" + clock.millis()); } }
/** * 注释略 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}
FuncationInterface是一个表示信息提示的注解,用于指示interface类型声明是Java语言规范定义的函数接口。
概念上,函数式接口精确的只有一个abstract方法,因为jaav.lang.reflect.Method#isDefault()的默认方法有一个实现,它们不是抽象的(在jdk1.8中接口中不在是只能声明抽象方法,还可以声明默认方法和静态方法。)。如果一个接口的抽象方法重写了java.lang.Object的public方法,则它不计入抽象方法的计数中(看下面案例),因为任何一个接口的实现类一定是直接或间接的实现了java.lang.Object类,所以把它计入抽象方法中是没有任何意义的。
注意:函数式接口的实例可以被lambda表达式、方法引用、构造引用创建。
如果使用该注解对类型进行标注,则需要编译器校验,若不满足如下条件,则生成错误信息:
但是,编译器将把满足函数式接口定义的任何接口视为函数式接口,而不管接口声明中使用存在该注解。
案例:接口重写Object类中public方法,该方法不计入函数式接口所指的抽象方法中,
如下,注释term抽象函数,@FuncationInterface不会出现下划红线,因为我们满足只有一个非实现Object类public方法的抽象方法。但是如果我们取消term的注释(或注释abs),则@FuncationInterface会出现下划红线提示错误,原因是检测到有两个(或一个没有)抽象方法。
/** * 抽象方法可以省略 abstract关键字 * 如果一个接口中重新了Object对象中的public方法,则该接口不计入抽象接口中 */ @FunctionalInterface public interface MyInterface { void abs(); // void term(); @Override String toString(); }
/** * 表示一个函数,什么样的函数呢?接收一个参数并返回一个结果的函数 * * @param <T> 表示函数的输入类型 * @param <R> 表示函数的输出类型 * * @since 1.8 */ @FunctionalInterface public interface Function<T, R> { /** * 将此函数应用于给定参数 * * @param t the function argument * @return the function result */ R apply(T t); /** * 返回一个函数接口,在返回该接口之前,先把输入应用给before函数上,然后 * 把before函数的结果作为该接口的输入,返回当前函数的接口。 * 如果抛出异常将传递给调用者 * */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); // 表示返回一个Function接口的实现类,重写方法时传递的时before.apply执行的返回值 return (V v) -> apply(before.apply(v)); } /** * 返回一个函数接口,首先把输入应用与该函数上,然后把该接口的输出作为before * 函数的输入,返回after的函数接口 * 如果抛出异常将传递给调用者 * */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
Function<String, String> function = String::toUpperCase;
System.out.println(function.getClass().getInterfaces()[0]);
interface java.util.function.Function
上下文推断:函数式接口Funcation需要接受一个输入参数,并返回一个输出,而toUpperCase方法的输出正好对应的apply返回的R,但是apply方法的输入T是谁?toUpperCase没有入参,而且toUpperCase是一个对象方法,肯定不能直接使用String.toUpperCase去调用。那么一定是存在一个String字符串对象去调用这个toUpperCase方法,那这个字符串对象就作为输入。
有了Function接口有什么用?细细体会,它指代:接受一个参数并返回一个结果的方法。也就是说有了它,我们可以用它表示任意一个只有一个参数且有返回值的函数,它只是指代了这样一个函数,函数内的具体实现它并不关心。
package funcInterface.consumer; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; /** * 在java的世界中,方法被看作是对象的行为,而Function就是行为的一种抽象表述, * 它表示这样一种行为: 只有一个输入和一个输出的行为。 * 行为的具体调用方式是: 行为对象.apply(行为接收的入参) */ public class FunctionTest { public static void main(String[] args) { FunctionTest functionTest = new FunctionTest(); List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; System.out.println("-------------有一个需求:按照年龄给person排序--------------------"); functionTest.sortByAge(persons).forEach(person -> System.out.println(person.getName() + "----" + person.getAge())); System.out.println("-------------需求变动:按照名字长度给人排序--------------------"); functionTest.sortByNameLength(persons).forEach(person -> System.out.println(person.getName() + "----" + person.getAge())); System.out.println("--------------需求变动n多次,按照各种场景排序-------------------"); System.out.println("."); System.out.println("."); System.out.println("."); System.out.println("----------------使用函数式接口Funcation,把具体的行为交给调用者-----------------"); functionTest.sort(persons, personList -> personList .stream() .sorted( (personA, personB) -> (int) personA.getName().charAt(0) < (int) personB.getName().charAt(0) ? -1 : 1 ) .collect(Collectors.toList()) ).forEach(person -> System.out.println((int) person.getName().charAt(0) + "----" + person.getName() + "----" + person.getAge())); } /** * 按照年龄排序 * * @param personList * @return */ public List<Person> sortByAge(List<Person> personList) { return personList.stream().sorted((personA, personB) -> personA.getAge() < personB.getAge() ? -1 : 1).collect(Collectors.toList()); } /*** * 按照名字长度排序 * @param personList * @return */ public List<Person> sortByNameLength(List<Person> personList) { return personList.stream().sorted((personA, personB) -> personA.getName().length() < personB.getName().length() ? -1 : 1).collect(Collectors.toList()); } /** * 把具体排序规则交由调用者 * * @param personList * @param function * @return */ public List<Person> sort(List<Person> personList, Function<List<Person>, List<Person>> function) { return function.apply(personList); } }
创建一个抽象的行为A并返回,在这个抽象的行为A被执行前要先执行另一个给定的行为B。即当抽象行为A被执行时:
/** * 需求: * 执行行为A前先执行行为B(以下指示Before) * * compose语法解读: * compose返回一个行为X。 * * compose返回的行为X执行解读: * 在X行为调用apply时【x行为被执行】,入参是v,X行为的返回结果是行为A执行的返回值。 * 执行行为A,入参是before行为的返回值,所以先执行before行为以获取入参, * 执行行为before【before行为被执行】,before的返回结果作用入参传入行为A, * 行为A被执行【行为A被执行】,最后行为A的执行结果被X行为返回出去。 * * 总结: * 整个过程产生了三个行为,其中行为A和行为before是我们完成需求的必须行为, * 而行为X是触发我们需求的一个行为。 */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }
/** * 需求: * 执行完行为A后在执行after行为,其中A指代当前行为,即andThen的触发者。 * * andThen语法解读: * andThen返回一个行为X。 * * andThen返回的行为X执行解读: * 在X行为调用apply时【x行为被执行】,入参是t,X行为的返回结果是行为after执行的返回值。 * 执行行为after,入参是A行为的返回值,所以先执行A行为以获取入参, * 执行行为A【A行为被执行】,A的返回结果作用入参传入行为after, * 行为after被执行【行为after被执行】,最后行为after的执行结果被X行为返回出去。 * * 总结: * 整个过程产生了三个行为,其中行为A和行为after是我们完成需求的必须行为, * 而行为X是触发我们需求的一个行为。 */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
System.out.println("------compose和andThen “输入的行为” 是在 “输入的行为” 执行前执行还是之后?"); // 定义一个相加的行为,输入的行为是相乘 Function<Integer, Integer> A_Action = (i) -> i + i; Function<Integer, Integer> Input_Action = (Integer i) -> i * i; System.out.println("------compose---------"); // compose表示先执行输入的行为,然后执行自己 Function<Integer, Integer> X_Action1 = A_Action.compose(Input_Action); System.out.println(X_Action1.apply(2)); // 8 System.out.println("------andThen---------"); Function<Integer, Integer> X_Action2 = A_Action.andThen(Input_Action); // andThen表示先执行自己,然后执行输入的行为 System.out.println(X_Action2.apply(2)); // 16 // 完成需求的两个Action: System.out.println(A_Action); System.out.println(Input_Action); // 触发我们完成需求的Action: System.out.println(X_Action1); System.out.println(X_Action2);
BiFunction和Function十分类似,由于我们的Function指代的是只有一个参数和一个返回值的函数,如果是两个参数的话就不能在使用Function函数式接口。BiFunction是指代:只有两个参数和一个返回结果的函数。
package funcInterface.consumer; import java.util.function.BiFunction; /** * BiFunction和Function类似,只不过它接收的是两个参数,返回一个结果 */ public class BiFunctionTest { public static void main(String[] args) { BiFunctionTest biFunctionTest = new BiFunctionTest(); System.out.println(biFunctionTest.add(2, 3)); // 灵活性极大提升 System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x + y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x * y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x / y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x - y)); } public int add(int x, int y) { return x + y; // 具体行为是加操作 } public int cal(int x, int y, BiFunction<Integer, Integer, Integer> biFunction) { return biFunction.apply(x, y); // 把具体行为抽象出来,交给调用者实现 } }
/** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * 从给定参数计算判断结果 * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ 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> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * 静态方法 * 判断输入对象是否个目标对象equals */ static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); }
表示一个参数的谓词。Predicate的意思是谓词、断言、断定的意思,也就是说对输入的内容进行判断,然后返回布尔值。
Predicate更像是Function抽象行为的某种类型,这种类型的行为同样是一个输入,但是返回的是一个boolean值。但是有的时候,我们确实经常使用这样一种返回boolean类型的行为,而不是使用Function抽象的方式写:
Function<Object, Boolean> function = element -> (Integer) element > 5;
所以单独的把返回boolean类型的行为单独抽象出来,这样更方便我们统一使用。
Predicate<Object> predicate = element -> (Integer)element > 5;
default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); // 返回一个Predicate的实现,当这个Predicate实现类对象调用test时 // 将入参传入test(t) && other.test(t),把逻辑与的判断结果返回 return (t) -> test(t) && other.test(t); }
default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); // 返回一个Predicate的实现,当这个Predicate实现类对象调用test时 // 将入参传入test(t) || other.test(t),把逻辑与的判断结果返回 return (t) -> test(t) || other.test(t); }
public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 3, 4, 5, 8, 9, 10, 23); System.out.println("-------找到所有的奇数----------"); Predicate<Integer> predicate = element -> element % 2 == 0 ? false : true; integers.stream().filter(predicate).forEach(e -> System.out.println(e)); System.out.println("-------找到所有的偶数----------"); Predicate<Integer> predicate1 = element -> element % 2 != 0 ? false : true; integers.stream().filter(predicate1).forEach(e -> System.out.println(e)); System.out.println("-------找到所有大于5的数----------"); Predicate<Integer> predicate3 = element -> element > 5; integers.stream().filter(predicate3).forEach(e -> System.out.println(e)); System.out.println("-------找到大于10或小于4的数----------"); Predicate<Integer> gtPredicate = element -> element > 10; Predicate<Integer> ltPredicate = element -> element < 4; integers.stream().filter(gtPredicate.and(ltPredicate)).forEach(e -> System.out.println(e)); System.out.println("-------找到大于5或小于20的数----------"); integers.stream().filter(gtPredicate.or(ltPredicate)).forEach(e -> System.out.println(e)); System.out.println("---------isEqual--------"); Person person1 = new Person(23, "m"); Person person2 = new Person(23, "m"); System.out.println(Predicate.isEqual(person1).test(person2)); System.out.println(Predicate.isEqual(person2).test(person2)); }
/** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #accept(Object)}. * * @param <T> the type of the input to the operation * * @since 1.8 */ @FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * 对于给定参数执行此操作。 * * @param t the input argument */ void accept(T t); ...// 略 }
表示接受单个输入参数但不返回结果的操作。与大多数其他功能接口不同,Consumer需要通过副作用来操作。
accpet是一个函数式接口的抽象方法,T表示一个操作的输入类型。
Consumer意思是消费者,也就是它只是把输入的内容消费掉。这里的side-effects所说的副作用指的是:函数或表达式修改基本变量的行为。例如有两个int类型的变量x和y,x+y只是返回相加的结果,并没有改变x和y的值,那么这个操作就没有副作用;而赋值操作如x=1,由于将1赋值给x,改变了x的值,因此这个操作具有副作用。
简单来说就是Consumer接口并不返回任何值,而只是接收参数并将他们的状态通过副作用改变(即赋值操作)。
package funcInterface.consumer; import org.junit.Test; import java.util.function.Consumer; import java.util.function.Predicate; /** * 例如对学生而言,Student类包含姓名,分数以及待付费用,每个学生可 * 根据分数获得不同程度的费用折扣。 */ class Student { String firstName; String lastName; Double grade; Double feeDiscount = 0.0; Double baseFee = 20000.0; public Student(String firstName, String lastName, Double grade) { this.firstName = firstName; this.lastName = lastName; this.grade = grade; } public void printFee() { Double newFee = baseFee - ((baseFee * feeDiscount) / 100); System.out.println("The fee after discount: " + newFee); } } public class PredicateConsumerDemo { public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer) { // Use the predicate to decide when to update the discount. if (predicate.test(student)) { // Use the Consumer to update the discount value. consumer.accept(student); // 副作用 } return student; } @Test public void test() { Student student1 = new Student("Ashok", "Kumar", 9.5); student1 = updateStudentFee(student1, new Predicate<Student>() { @Override public boolean test(Student t) { return t.grade > 8.5; } }, new Consumer<Student>() { @Override public void accept(Student t) { t.feeDiscount = 30.0; } }); student1.printFee(); Student student2 = new Student("Rajat", "Verma", 8.0); student2 = updateStudentFee(student2, student -> student.grade >= 8, student -> student.feeDiscount = 20.0); student2.printFee(); } }
使用谓词判断学生的成绩是否大于8.5,如果大于8.5则可以享受折扣。
/** * 表示结果的提供者 * * 不要求每次调用提供商时都返回一个新的或不同的结果。 * * @since 1.8 */ @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
public static void main(String[] args) { // 不接受参数返回一个结果 Supplier<String> stringSupplier = () -> "Hello Supplier"; System.out.println(stringSupplier.get()); Supplier<String> stringSupplier1 = String::new; System.out.println(stringSupplier1); }
lambda是一种表示匿名函数或者闭包的运算符,它后面跟着的是lambda算子的使用。
Java Lambda表达式是一种匿名函数;它是没有声明的方法,即没有访问修饰符、返回值声明和名字。
Lambda表达式为Java添加了缺失的函数式编程特性,使我们能将函数当作一等公民看待。在没有Lambda时,函数是依附于类的,类才是一等公民。
在将函数作为一等公民的语言中,Lambda表达式的类型是函数。但是Java中,Lambda表达式是对象,他们必须依附于一类特别的对象类型---函数式接口。
在Java中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法
在JavaScript中,函数参数是一个函数,返回值是另一个函数的情况是非常常见的;JavaScript是一门非常典型的函数式语言。
public static void main(String[] args) { JFrame jFrame = new JFrame("my JFrame"); JButton jButton = new JButton("my Buutom"); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("click"); } }); jFrame.add(jButton); jFrame.pack(); jFrame.setVisible(true); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
在没有Lambda之前,我们要想写一些图形化编程时需要写很多和业务需求无关的代码,以以上案例来说,我只想给button添加一个click事件,当点击它是就执行打印。但是要想执行我的打印还要创建一个多余的匿名内部类ActionEvent,更奇葩的时我还要在一个莫名其妙的actionPerformed方法里写我的打印。对于我来说,我只想添加一个打印事件,这些ActionEvent和actionPerformed对于我来说就是多余的。
有了Java8的Lambda之后,我不需要在关心和我无关的事情。
public static void main(String[] args) { JFrame jFrame = new JFrame("my JFrame"); JButton jButton = new JButton("my Buutom"); // jButton.addActionListener(new ActionListener() { // @Override // public void actionPerformed(ActionEvent e) { // System.out.println("click"); // } // }); jButton.addActionListener(e -> System.out.println("click")); jFrame.add(jButton); jFrame.pack(); jFrame.setVisible(true); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
java是静态类型语言,但是使用Lambda之后,那个参数e我们并没有定义它的类型,在java8中java依旧是静态类型语言,上面的语法完整的写法应该如下:
jButton.addActionListener((ActionEvent e) -> System.out.println("click"));
之所以没有加ActionEvent类型说明,其实是借助于java编译系统里面的类型推断机制,推断出上下文里的这个e一定是ActionEvent类型,所以就不需要我们自己显示的声明了,当然如果推断机制推断不出来就需要我们像上面那样手动的声明类型。
(param1,param2) -> { // 业务 }
public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.5之前---------"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } System.out.println("---------jdk1.5---------"); for (int i : list) { System.out.println(i); } System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); System.out.println("---------jdk1.8 lambda+函数式接口---------"); list.forEach(integer -> System.out.println(integer)); }
在上面jdk1.8之后使用forEach并不能使我们的代码简化,反而使代码更加复杂了,为什么我们可以使用lambda替代Consumer的创建呢?原因是Consumer是一个函数式接口。
最原始的写法,使用多态,自己创建接口的实现,最终调用的是实现类的hello方法
package com; @FunctionalInterface interface IMyInterface { String hello(); String toString(); } public class MyInterfaceLambda { public static void printHello(IMyInterface iMyInterface) { System.out.println(1); System.out.println(iMyInterface.hello()); System.out.println(2); } public static void main(String[] args) { MyInterfaceLambda.printHello(new IMyInterface() { @Override public String hello() { return "HELLO"; } }); } }
1
HELLO
2
现在有了lambda表达式,而且知道这个接口是一个函数式接口,那我们如果实现这个接口的话肯定只是实现了它其中唯一的一个抽象方法,这样的话,我们就没必要在指定实现的是该接口二点那个方法,在函数的参数中,我们又明知道它的参数类型,那我们直接可以省略new IMyInterface,编译器自动推测出上下文环境是要实现IMyInterface:
public static void main(String[] args) { //函数式接口的实例可以由lambda表达式创建 // 函数体内只有一行代码可以省略大括号如果有return可以直接省略return MyInterfaceLambda.printHello(() -> "HELLO"); IMyInterface iMyInterface = () -> "HELLO"; // 相当于new IMyInterface的实现 System.out.println(iMyInterface); System.out.println(iMyInterface.getClass()); System.out.println(iMyInterface.getClass().getSuperclass()); System.out.println(iMyInterface.getClass().getInterfaces().length); System.out.println(iMyInterface.getClass().getInterfaces()[0]); }
1 HELLO 2 com.MyInterfaceLambda$$Lambda$2/1096979270@404b9385 class com.MyInterfaceLambda$$Lambda$2/1096979270 class java.lang.Object 1 interface com.IMyInterface
函数式接口的实例除了可以使用Lambda表达式创建,还可以由方法引用、构造方法引用创建。
public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); System.out.println("---------jdk1.8 lambda+函数式接口---------"); list.forEach(integer -> System.out.println(integer)); System.out.println("---------jdk1.8 方法引用+函数式接口---------"); list.forEach(System.out::println); }
lambda表达式和方法引用必须依附于一类特别的对象类型,它们不能单独存在:
一个lambda表达式到底是什么类型的,必须由上下文信息才能断定,
() -> {};
因为对于一个这样的表达式来说,你根本不知道方法名称是什么,它只是一个匿名函数的表示方式,但是一旦给定了上下文Consumer之后,我们就知道了这个lambda表达式的类型是Consumer类型,表达式的内容是对函数式接口唯一抽象方法进行重写。
当我们点击lambda表达式的"箭头"或者点击方法引用的"双冒号",将会自动进入函数式接口中,表示当前的lambda或方法引用是该函数式接口的实现。
在之前我们要想做某一个行为,比如说下面的加和乘,要先定义好,然后在调用。
package com; public class TransferAction { public int add(int i, int j) { return i + j; } public int mul(int i, int j) { return i * j; } public static void main(String[] args) { TransferAction transferAction = new TransferAction(); System.out.println(transferAction.add(1, 2)); System.out.println(transferAction.mul(2, 3)); } }
public class TransferAction { public int add(int i, int j) { return i + j; } public int mul(int i, int j) { return i * j; } public int cal(int i, Function<Integer, Integer> function) { return function.apply(i); } public static void main(String[] args) { TransferAction transferAction = new TransferAction(); // 之前要想做某个具体的行为要先定义才能调用 System.out.println(transferAction.add(1, 2)); System.out.println(transferAction.mul(2, 3)); // 现在lambda可以传递一个行为,到底是做什么操作由调用者决定, // 而不需要在提前定义具体的行为了,我们只需要把行为抽象出来,在根据 // 实际场景传入不同的行为 System.out.println(transferAction.cal(1, (value) -> 1 + value)); System.out.println(transferAction.cal(2, (value) -> 3 * value)); } }
方法引用是lambda表达式的语法糖,lambda是使用箭头的方式表示的,而方法引用则是使用双冒号的方式表示。
应用场景:当你的lambda表达式只有一行代码,并且这行代码恰好调用了一个已经存在的方法时就可以使用方法引用替换掉lambda表达式。
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); // lambda表达式与方法引用: studentList.forEach(student -> System.out.println(student)); studentList.forEach(System.out::println); // lambda即可以表述简单操作,也可以在方法体内进行复杂的操作, // 然后并不是所有的操作都是直接写在lambda表达式函数体内的, // 我们可以语法糖直接引用方法(复杂操作需要在具体引用的方法中) studentList.forEach(student -> { // 更复杂的操作 System.out.println(student.getName()); System.out.println(student.getScore()); }); // 本质都是lambda表达式,都必须依托与类,都是作为函数式接口的的实现。 Consumer<Student> studentConsumer1 = student -> System.out.println(student); Consumer<Student> studentConsumer2 = System.out::println; System.out.println(studentConsumer1); // methodReference.MainTest$$Lambda$4/1452126962@5fd0d5ae System.out.println(studentConsumer2); // methodReference.MainTest$$Lambda$5/931919113@2d98a335 }
当要传递给lambda表达式方法体的操作,已经有了实现,可以使用方法引用。要求是:实现函数式接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法参数列表和返回值类型保持一致。
注意:方法调用和方法引用是完全不同的概念,方法调用是直接执行这个方法【是直接执行某个行为,没有中间商】,而方法引用表述的是对一个方法的指向,如果真正要执行某个方法时,才去找到这个方法执行【个人理解更像是代理,方法引用和lambda都像是代理一样,本身只是指代某个抽象类的实现,真正要执行的时候才去在抽象类的具体实现里执行某个行为】。
类名::静态方法名 实例名::实例方法(非静态) 类名::实例方法(非静态) 构造方法引用: 类名::new
package methodReference; public class Student { private int score; private String name; public Student(int score, String name) { this.score = score; this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static int comepareStudentByScoue(Student student1, Student student2) { return student1.getScore() - student2.getScore(); } public static int comepareStudentByName(Student student1, Student student2) { return student1.getName().compareToIgnoreCase(student2.getName()); } }
compareStudentName和Comparator函数式接口的抽象方法参数类型、个数相同,返回值类型相同,符合方法引用的条件:
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByScoue(studentParam1, studentParam2)); // studentList.forEach(student -> System.out.println(student.getScore())); // 注释上面的排序,防止对下面排序影响 studentList.sort(Student::comepareStudentByScoue); studentList.forEach(student -> System.out.println(student.getScore())); // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByName(studentParam1, studentParam2)); // studentList.forEach(student -> System.out.println(student.getName())); // studentList.sort(Student::comepareStudentByName); // studentList.forEach(student -> System.out.println(student.getName())); Comparator<Student> comparator = Student::comepareStudentByName; System.out.println(comparator); }
package methodReference; public class CompareStudent { public int compareStudentByScore(Student s1,Student s2) { return s1.getScore() - s2.getScore(); } public int compareStudentByName(Student s1,Student s2) { return s1.getName().compareToIgnoreCase(s2.getName()); } }
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); /** * 方法引用: * 类实例::实例方法 */ CompareStudent compareStudent = new CompareStudent(); studentList.sort(compareStudent::compareStudentByScore); // studentList.sort(compareStudent::compareStudentByName); studentList.forEach(student -> System.out.println(student.getScore())); }
前面讲的两种方法引用,一:类::静态方法,因为是静态方法,静态方法属于类,所以可以通过类::静态方法的方式调用,二:对象::实例方法,因为实例方法属于对象,所以可以通过对象::实例方法调用。现在是类::实例方法,实例方法一定是由对象来调用的,然而现在却可以类::实例方法,这是为何?
现在我们来改造Student类里的两个比较方法,首先这两个方法本身没有用到Student类的任何属性或方法,它们这个类是没有任何关系的,它们可以写在任意一个其他类里,我们现在改造:在加入如下两个方法:
public int compareStudentScore(Student student) { return this.getScore() - student.getScore(); } public int compareStudentName(Student student) { return this.getName().compareToIgnoreCase(student.getName()); }
这两个方法依赖于这个类的调用者,即当前Student对象,然后和传入的对象进行比较。
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); /** * 方法引用 * 类::实例方法 */ // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByScoue(studentParam1, studentParam2)); studentList.sort(Student::compareStudentScore); studentList.forEach(student -> System.out.println(student.getScore())); }
sort方法的Comparator函数式接口是接受两个参数的,但是现在我们的compareStudentScore只是接受了一个参数。首先要明确一点实例方法一定是由实例对象来调用的,那这个调用对象是谁?就是这个sort方法lambda表达式的第一个参数来去调用compareStudentScore(注意:如果接受多个参数,那么除了第一个参数,其余参数都作为compareStudentScore方法的参数传递进去),所以真正调用排序方法compareStudentScore是那两个待排序对象的第一个,另一个作为参数传递到这个排序方法内。
List<String> cites = Arrays.asList("beijing", "tianjing", "shanghai", "qingdao"); // Collections.sort(cites, (cite1, cite2) -> cite1.compareToIgnoreCase(cite2)); // cites.forEach(cite -> System.out.println(cite)); Collections.sort(cites, String::compareToIgnoreCase); cites.forEach(cite -> System.out.println(cite));
public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); }
是实例方法,只接受一个参数,当我们使用lambda时传入了两个参数,所以在使用方法引用时,第一个参数cite1作为方法的调用者,第二个参数作为该方法的参数被传入。
public class MainTest { public static void main(String[] args) { MainTest mainTest = new MainTest(); System.out.println(mainTest.getStr(String::new));// 点击new自动定位到无参数的构造函数 System.out.println(mainTest.getString("hello", String::new)); // 点击new自动定位到有参数的构造函数 } public String getStr(Supplier<String> stringSupplier) { return "hey" + stringSupplier.get(); } public String getString(String hello, Function<String, String> function) { return function.apply(hello); } }