Java函数式编程的第一个作用是可以将匿名类改写成函数式表达式,由系统自动判断类型
我们先定义一个接口
public interface Goods { boolean test(int i); }
传统的匿名类写法
public class Anonymous { public static void main(String[] args) { List<Goods> goodsList = new ArrayList<>(); goodsList.add(new Goods(){ @Override public boolean test(int i) { return 5 - i > 0; } }); System.out.println(goodsList.get(0).test(3)); } }
运行结果:
true
使用lamdba表示式的写法
public class AnonymousLambda { public static void main(String[] args) { List<Goods> goodsList = new ArrayList<>(); goodsList.add(i -> 5 - i > 0); System.out.println(goodsList.get(0).test(3)); } }
运行结果
true
像这种一个接口里面只有一个方法的接口,我们可以称之为函数接口,JDK中有很多这样的接口,当然我们可以在该接口上打上@FunctionalInterface以表明该接口是一个函数接口
@FunctionalInterface public interface Goods { boolean test(int i); }
我们先来看一下JDK中的Predicate接口,这是一个可以进行条件判断的接口
我们先用传统方式来看一下这么几个条件
- 判断传入的字符串的长度是否大于5
- 判断传入的参数是否是偶数
- 判断数字是否大于10
public class PredicateOne { /** * 判断长度大于5 * @param judgeString * @return */ public boolean judgeStringLength(String judgeString) { return judgeString.length() > 5; } /** * 判断是否是偶数 * @param judgeInteger * @return */ public boolean judgeIntegerOdds(int judgeInteger) { return (judgeInteger & 1) == 0; } /** * 判断数字是否大于10 * @param num * @return */ public boolean judgeSecialNumber(int num) { return num > 10; } public static void main(String[] args) { PredicateOne predicateOne = new PredicateOne(); System.out.println(predicateOne.judgeStringLength("12345")); System.out.println(predicateOne.judgeIntegerOdds(12345)); System.out.println(predicateOne.judgeSecialNumber(12345)); } }
运行结果
false
false
true
现在改成函数式编程如下
public class PredicateTwo<T> { public boolean judgeConditionByFunction(T t, Predicate<T> predicate) { return predicate.test(t); } public static void main(String[] args) { PredicateTwo<Integer> integerPredicateTwo = new PredicateTwo<>(); System.out.println(integerPredicateTwo.judgeConditionByFunction(12345,t -> String.valueOf(t).length() > 5)); System.out.println(integerPredicateTwo.judgeConditionByFunction(12345,t -> (t & 1) == 0)); System.out.println(integerPredicateTwo.judgeConditionByFunction(12345,t -> t > 10)); }
运行结果
false
false
true
我们知道逻辑判断中有与、或、非的比较,Predicate接口同样具有这样的功能,我们来看一下它的源码
@FunctionalInterface public interface Predicate<T> { /** * 需要实现的比较方法 * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); /** * 逻辑与,类似于&& * * @param other a predicate that will be logically-ANDed with this * predicate * @return a composed predicate that represents the short-circuiting logical * AND of this predicate and the {@code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** * 逻辑非,类似于! * * @return a predicate that represents the logical negation of this * predicate */ default Predicate<T> negate() { return (t) -> !test(t); } /** * 逻辑或,类似于|| * * @param other a predicate that will be logically-ORed with this * predicate * @return a composed predicate that represents the short-circuiting logical * OR of this predicate and the {@code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * 逻辑等,类似于equals()方法 * * @param <T> the type of arguments to the predicate * @param targetRef the object reference with which to compare for equality, * which may be {@code null} * @return a predicate that tests if two arguments are equal according * to {@link Objects#equals(Object, Object)} */ static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
现在我们加上条件的交集判断
public class PredicateThree<T> { /** * 两个条件的与操作 * @param t * @param predicate * @param predicate1 * @return */ public boolean judgeConditionByFunctionAnd(T t, Predicate<T> predicate,Predicate<T> predicate1) { return predicate.and(predicate1).test(t); } /** * 两个条件的或操作 * @param t * @param predicate * @param predicate1 * @return */ public boolean judgeConditionByFunctionOr(T t, Predicate<T> predicate,Predicate<T> predicate1) { return predicate.or(predicate1).test(t); } /** * 对一个条件进行取反 * @param t * @param predicate * @return */ public boolean judgeConditionByFunctionNegate(T t,Predicate<T> predicate) { return predicate.negate().test(t); } /** * 判断两个对象的值是否相等 * @param t1 * @param t2 * @return */ public boolean judgeConditionIsEquals(T t1,T t2) { return Predicate.isEqual(t1).test(t2); } public static void main(String[] args) { PredicateThree<Integer> integerPredicateThree = new PredicateThree<>(); System.out.println(integerPredicateThree.judgeConditionByFunctionAnd(12345,t -> t > 10,t ->(t & 1) == 0)); System.out.println(integerPredicateThree.judgeConditionByFunctionOr(12345,t -> t > 10,t ->(t & 1) == 0)); System.out.println(integerPredicateThree.judgeConditionByFunctionNegate(12345,t -> t > 10)); System.out.println(integerPredicateThree.judgeConditionIsEquals(123,123)); } }
运行结果
false
true
false
true
我们再来看一下JDK中的Consumer接口,Consumer的作用是 给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作.
我们来获取一群人中名字为lisa的所有人,传统方式如下
@Data @AllArgsConstructor public class Person { private String name; private int age; }
public class ConsumerOne { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); Consumer<Person> consumer = new Consumer<Person>() { @Override public void accept(Person person) { if (person.getName().equals("lisa")) { personList.add(person); } } }; List<Person> list = Arrays.asList( new Person("lisa", 23), new Person("Mike", 33), new Person("lisa", 27), new Person("Jack", 25) ); for (Person person : list) { consumer.accept(person); } System.out.println(JSON.toJSONString(personList)); } }
运行结果
[{"age":23,"name":"lisa"},{"age":27,"name":"lisa"}]
改写成函数式编程如下
public class ConsumerTwo { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); Consumer<Person> consumer = t -> { if (t.getName().equals("lisa")) { personList.add(t); } }; Arrays.asList( new Person("lisa", 23), new Person("Mike", 33), new Person("lisa", 27), new Person("Jack", 25) ).stream().forEach(consumer); System.out.println(JSON.toJSONString(personList)); } }
运行结果
[{"age":23,"name":"lisa"},{"age":27,"name":"lisa"}]
stream后面会全面讲解,我们来看一下Consumer接口的源码
@FunctionalInterface public interface Consumer<T> { /** * 给指定的参数t执行定义的操作 * * @param t the input argument */ void accept(T t); /** * 对给定的参数t执行定义的操作执行再继续执行after定义的操作 * * @param after the operation to perform after this operation * @return a composed {@code Consumer} that performs in sequence this * operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
现在我们只需要大于25岁的lisa
public class ConsumerThree { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); Consumer<Person> consumer = t -> { if (t.getName().equals("lisa")) { personList.add(t); } }; consumer = consumer.andThen(t -> personList.removeIf(x -> x.getAge() < 25)); Arrays.asList( new Person("lisa", 23), new Person("Mike", 33), new Person("lisa", 27), new Person("Jack", 25) ).stream().forEach(consumer); System.out.println(JSON.toJSONString(personList)); } }
运行结果
[{"age":27,"name":"lisa"}]
这里removeIf()的参数是一个Predicate接口,它的源码如下,它是在Collection接口源码中
default boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); boolean removed = false; final Iterator<E> each = iterator(); while (each.hasNext()) { if (filter.test(each.next())) { each.remove(); removed = true; } } return removed; }
我们再来看一下JDK中的Function接口,Function的作用是将一个给定的对象进行加工,然后返回加工后的对象,这个加工可以是任何操作.
传统方式如下
@Data @AllArgsConstructor @ToString public class Person { private String name; private int age; }
@AllArgsConstructor @Data @ToString public class Result { private String msg; private String code; private Person person; }
public class FunctionOne { public static void main(String[] args) { Function<Person,Result> function = new Function<Person, Result>() { @Override public Result apply(Person person) { return new Result("成功","200",person); } }; System.out.println(function.apply(new Person("lisa",24))); } }
运行结果
Result(msg=成功, code=200, person=Person(name=lisa, age=24))
改写成函数式编程如下
public class FunctionTwo { public static void main(String[] args) { Function<Person,Result> function = x -> new Result("成功","200",x); System.out.println(function.apply(new Person("lisa",24))); } }
运行结果
Result(msg=成功, code=200, person=Person(name=lisa, age=24))
Function接口的源码如下
@FunctionalInterface public interface Function<T, R> { /** * 将一个给定的对象进行加工,然后返回加工后的对象,可以将该方法理解为一个一维函数,参数R是自变量,参数T是因变量. * * @param t the function argument * @return the function result */ R apply(T t); /** * 组合函数,在调用当前function之前执行 * * @param <V> the type of input to the {@code before} function, and to the * composed function * @param before the function to apply before this function is applied * @return a composed function that first applies the {@code before} * function and then applies this function * @throws NullPointerException if before is null * * @see #andThen(Function) */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** * 组合函数,在调用当前function之后执行 * * @param <V> the type of output of the {@code after} function, and of the * composed function * @param after the function to apply after this function is applied * @return a composed function that first applies this function and then * applies the {@code after} function * @throws NullPointerException if after is null * * @see #compose(Function) */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** * 原函数,返回与参数一致的函数,即可以理解为 y = x * * @param <T> the type of the input and output objects to the function * @return a function that always returns its input argument */ static <T> Function<T, T> identity() { return t -> t; } }
实例代码如下
public class FunctionThree { public static void main(String[] args) { Function<Integer,Integer> x2 = x -> x * 2; Function<Integer,Integer> square = x -> x * x; //先计算3*3=9,再计算9*2=18 Integer result = x2.compose(square).apply(3); System.out.println(result); //先计算3*2=6,再计算6*6=36 Integer other = x2.andThen(square).apply(3); System.out.println(other); //返回3 Object apply = Function.identity().apply(3); System.out.println(apply); } }
运行结果:
18
36
3
我们再来看一下JDK中的BiFunction接口,BiFunction的作用跟Function类似,只不过是给出两种对象(可以相同也可以不同)进行加工,然后返回加工后的对象,这三种对象都是任意的,可以是不同的类的对象。加工可以使任意操作。
传统方式如下
public class BiFunctionOne { public static void main(String[] args) { BiFunction<Integer,Person,Map> personMap = new BiFunction<Integer, Person, Map>() { @Override public Map apply(Integer integer, Person person) { Map<Integer,Person> map = new HashMap<>(); map.put(integer,person); return map; } }; System.out.println(personMap.apply(1,new Person("李明",23))); } }
运行结果
{1=Person(name=李明, age=23)}
改写成函数式编程如下
public class BiFunctionTwo { public static void main(String[] args) { BiFunction<Integer,Person,Map> personMap = (x,y) -> { Map<Integer,Person> map = new HashMap<>(); map.put(x,y); return map; }; System.out.println(personMap.apply(1,new Person("李明",23))); } }
运行结果
{1=Person(name=李明, age=23)}
BiFunction函数式接口的源码如下
@FunctionalInterface public interface BiFunction<T, U, R> { /** * 将两个给定的对象进行加工,然后返回加工后的对象 * * @param t the first function argument * @param u the second function argument * @return the function result */ R apply(T t, U u); /** * 组合函数,再调用当前BiFunction之后执行Function接口 * * @param <V> the type of output of the {@code after} function, and of the * composed function * @param after the function to apply after this function is applied * @return a composed function that first applies this function and then * applies the {@code after} function * @throws NullPointerException if after is null */ default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); } }
实例代码如下
public class BiFunctionThree { public static void main(String[] args) { BiFunction<Integer,Integer,Integer> add = (x,y) -> x + y; Function<Integer,Integer> square = x -> x * x; //先执行3+4=7,再执行7*7=49 System.out.println(add.andThen(square).apply(3,4)); } }
运行结果
49
我们再来看一下JDK中的 Supplier 接口, Supplier 接口是相对于Conumer接口的, 这个接口是一个提供者的意思,只有一个get的方法,没有默认的方法以及静态的方法,传入一个泛型T的get方法,返回一个泛型T,一般可以理解为创建对象的工厂。
传统写法
public class SupplierOne { public static void main(String[] args) { Supplier<Person> supplier = new Supplier<Person>() { @Override public Person get() { return new Person("张三",23); } }; System.out.println(supplier.get()); } }
运行结果
Person(name=张三, age=23)
函数式编程写法
public class SupplierTwo { public static void main(String[] args) { Supplier<Person> supplier = () -> new Person("张三",23); System.out.println(supplier.get()); } }
运行结果
Person(name=张三, age=23)
Supplier接口的源码非常简单
@FunctionalInterface public interface Supplier<T> { /** * 获取一个结果 * * @return a result */ T get(); }
我们再来看一下JDK的 UnaryOperator 接口,该接口是继承于Function接口,其主要作用是返回一个跟参数对象加工后相同类的对象。
我们给Person类添加克隆方法,显然这是一个浅克隆。
@Data @AllArgsConstructor @ToString public class Person implements Cloneable { private String name; private int age; @Override protected Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } }
现在我们要通过一个Persion类的对象返回这个对象的克隆
传统方法
public class UnaryOperatorOne { public static void main(String[] args) { UnaryOperator<Person> operator = new UnaryOperator<Person>() { @Override public Person apply(Person person) { try { return person.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }; System.out.println(operator.apply(new Person("张三",23))); } }
运行结果
Person(name=张三, age=23)
函数式编程写法
public class UnaryOperatorTwo { public static void main(String[] args) { UnaryOperator<Person> operator = x -> { try { return x.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }; System.out.println(operator.apply(new Person("张三",23))); } }
运行结果
Person(name=张三, age=23)
UnaryOperator接口的源码如下,因为是继承了Function接口,所以它同样有compose和andThen默认方法
@FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { /** * 返回一个跟参数对象一模一样的对象 * * @param <T> the type of the input and output of the operator * @return a unary operator that always returns its input argument */ static <T> UnaryOperator<T> identity() { return t -> t; } }
实例代码如下
public class UnaryOperatorThree { public static void main(String[] args) { UnaryOperator<Integer> operator = x -> x * 2; UnaryOperator<Integer> square = x -> x * x; Integer apply = operator.compose(square).apply(3); System.out.println(apply); Integer result = operator.andThen(square).apply(3); System.out.println(result); System.out.println(UnaryOperator.identity().apply(new Person("张三",23))); } }
运行结果
18
36
Person(name=张三, age=23)
我们再来看一下JDK的 BinaryOperator 接口,它跟UnaryOperator类似,是两个相同类型的参数对象,返回一个该类型的结果对象
传统方式如下
public class BinaryOperatorOne { public static void main(String[] args) { BinaryOperator<String> operator = new BinaryOperator<String>() { @Override public String apply(String s, String s2) { return new StringBuilder(s).append(s2).toString(); } }; System.out.println(operator.apply("张三","李四")); } }
运行结果
张三李四
函数式编程如下
public class BinaryOperatorTwo { public static void main(String[] args) { BinaryOperator<String> operator = (x,y) -> new StringBuilder(x).append(y).toString(); System.out.println(operator.apply("张三","李四")); } }
BinaryOperator接口的源码如下,继承于BiFunction接口,有一个andThen方法,该方法的参数是一个Function接口。而它本身的静态方法minBy以及maxBy的参数则使用了Comparator接口
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { /** * 获取更小的值 * * @param <T> the type of the input arguments of the comparator * @param comparator a {@code Comparator} for comparing the two values * @return a {@code BinaryOperator} which returns the lesser of its operands, * according to the supplied {@code Comparator} * @throws NullPointerException if the argument is null */ public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } /** * 获取更大的值 * * @param <T> the type of the input arguments of the comparator * @param comparator a {@code Comparator} for comparing the two values * @return a {@code BinaryOperator} which returns the greater of its operands, * according to the supplied {@code Comparator} * @throws NullPointerException if the argument is null */ public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
代码示例
public class BinaryOperatorThree { public static void main(String[] args) { BinaryOperator<Integer> add = (x,y) -> x + y; Function<Integer,Integer> multiplication = x -> x * 30; //先计算3+6=9,再计算9*30=270 Integer result = add.andThen(multiplication).apply(3,6); System.out.println(result); BinaryOperator<Integer> minBy = BinaryOperator.minBy((x,y) -> x - y); System.out.println(minBy.apply(3,6)); BinaryOperator<Integer> maxBy = BinaryOperator.maxBy((x,y) -> x - y); System.out.println(maxBy.apply(3,6)); } }
运行结果
270
3
6
我们再来看一下JDK的Comparator接口,该接口是一个比较器,我们来对一组人的名称和年龄进行比较
传统方式如下
public class ComparatorOne { public static void main(String[] args) { List<Person> people = Arrays.asList(new Person("b",1), new Person("a",2), new Person("d",3), new Person("c",4)); Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getName().compareTo(o2.getName()); } }); System.out.println(people); Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); System.out.println(people); } }
运行结果
[Person(name=a, age=2), Person(name=b, age=1), Person(name=c, age=4), Person(name=d, age=3)]
[Person(name=b, age=1), Person(name=a, age=2), Person(name=d, age=3), Person(name=c, age=4)]
函数式编程如下
public class ComparatorTwo { public static void main(String[] args) { List<Person> people = Arrays.asList(new Person("b", 1), new Person("a", 2), new Person("d", 3), new Person("c", 4)); List<Person> collect = people.stream().sorted((x, y) -> x.getName().compareTo(y.getName())).collect(Collectors.toList()); System.out.println(collect); List<Person> collect1 = people.stream().sorted((x, y) -> x.getAge() - y.getAge()).collect(Collectors.toList()); System.out.println(collect1); } }
运行结果
[Person(name=a, age=2), Person(name=b, age=1), Person(name=c, age=4), Person(name=d, age=3)]
[Person(name=b, age=1), Person(name=a, age=2), Person(name=d, age=3), Person(name=c, age=4)]
这里需要注意的是,Collections.sort()是将原集合进行排序,而stream.sorted()只是将原集合排好顺序,但并没有保存,所以需要将流保存到新的集合中。
我们来看一下Comparator接口的源代码
@FunctionalInterface public interface Comparator<T> { /** * 函数式接口唯一方法,对两个对象进行比较,返回一个正数、负数或0,为正数时说明o1大于o2,为负数时说明o1小于o2,0时说明相等 */ int compare(T o1, T o2); /** * 该方法并非函数式接口方法,而是重写Object的方法被继承下来 */ boolean equals(Object obj); /** * 对比较的顺序进行反转 * * @return a comparator that imposes the reverse ordering of this * comparator. * @since 1.8 */ default Comparator<T> reversed() { return Collections.reverseOrder(this); } /** * 第一次排序后,如果有相同的元素,再按照另一个属性对该相同元素进行比较 */ default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; } /** * 转换成新对象,再用新对象来比较(第一次排序完之后,对相同元素进行新对象比较) */ default <U> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { return thenComparing(comparing(keyExtractor, keyComparator)); } /** * 转换成新对象,再用该新对象来比较(该新对象的类必须实现Comparable接口)(第一次排序完后,对相同对象比较) */ default <U extends Comparable<? super U>> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); } /** * 第一次排序完后,对相同对象的其他整形属性进行比较 */ default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) { return thenComparing(comparingInt(keyExtractor)); } /** * 第一次排序完后,对相同对象的其他长整形属性进行比较 */ default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) { return thenComparing(comparingLong(keyExtractor)); } /** * 第一次排序完后,对相同对象的其他浮点数属性进行比较 */ default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) { return thenComparing(comparingDouble(keyExtractor)); } /** * 静态方法的降序,一般适用于基础常用类型 */ public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); } /** * 静态方法的升序,一般适用于基础常用类型 */ @SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; } /** * 静态方法,如果比较中有null,则认为null为前置(无论升降),如果集合中有null,需适用该方法,否则会抛出异常 */ public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(true, comparator); } /** * 静态方法,如果比较中有null,则认为null为后置(无论升降),如果集合中有null,需适用该方法,否则会抛出异常 */ public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(false, comparator); } /** * 静态方法的生成新对象的比较 */ public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { Objects.requireNonNull(keyExtractor); Objects.requireNonNull(keyComparator); return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); } /** * 静态方法的生成新对象的比较,新对象必须实现Comparable接口 */ public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); } /** * 静态方法对整形的比较 */ public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)); } /** * 静态方法对长整形的比较 */ public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)); } /** * 静态方法对浮点数的比较 */ public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); } }
具体的样例代码如下,先将Result实现Comparable接口,因为代码中会用到
@AllArgsConstructor @Data @ToString public class Result implements Comparable<Result> { private String msg; private String code; private Person person; @Override public int compareTo(Result o) { return this.person.getAge() - o.getPerson().getAge(); } }
public class ComparatorThree { public static void main(String[] args) { List<Person> people = Arrays.asList(new Person("b", 1), new Person("a", 2), new Person("a",5), new Person("d", 3), new Person("c", 4)); //对名字的比较器 Comparator<Person> comparator1 = (x,y) -> x.getName().compareTo(y.getName()); //反转该名字比较器 Comparator<Person> comparatorReversed = comparator1.reversed(); //对people进行名字的降序排序 List<Person> collect = people.stream().sorted(comparatorReversed).collect(Collectors.toList()); System.out.println(collect); //对名字降序排序后,再相同名字的进行年龄降序序排序 List<Person> collect1 = people.stream() .sorted(comparatorReversed.thenComparing((x,y) -> y.getAge() - x.getAge())) .collect(Collectors.toList()); System.out.println(collect1); //对名字降序排序后,再相同名字的进行生成新对象Result的Person.age的比较进行排序 List<Person> collect2 = people.stream() .sorted(comparatorReversed.thenComparing(x -> new Result("成功","200",x), (x,y) -> x.getPerson().getAge() - y.getPerson().getAge())) .collect(Collectors.toList()); System.out.println(collect2); //对名字降序排序后,再相同名字的进行生成新对象Result的比较(Result类已实现Comparable接口) List<Person> collect3 = people.stream() .sorted(comparatorReversed.thenComparing(x -> new Result("成功","200",x))) .collect(Collectors.toList()); System.out.println(collect3); //对名字降序排序后,再相同名字的进行年龄升序排序 List<Person> collect4 = people.stream() .sorted(comparatorReversed.thenComparingInt(x -> x.getAge())) .collect(Collectors.toList()); System.out.println(collect4); List<Man> men = Arrays.asList( new Man("b",1,16.9), new Man("a",2,14.32), new Man("b",5,15.7), new Man("c",3,13.2), new Man("d",4,17.1)); //对Man的名字进行升序排序 Comparator<Man> comparator2 = (x,y) -> x.getName().compareTo(y.getName()); //对Main的名字进行升序排序后,再对相同的名字对长度进行升序排序 List<Man> collect5 = men.stream().sorted(comparator2.thenComparingDouble(x -> x.getLength())) .collect(Collectors.toList()); System.out.println(collect5); //对整数进行降序对比 Comparator<Integer> comparator3 = Comparator.reverseOrder(); List<Integer> numbers = Arrays.asList(3,1,7,4,5); //对numbers整数列表进行降序排序 List<Integer> collect6 = numbers.stream().sorted(comparator3).collect(Collectors.toList()); System.out.println(collect6); //对numbers整数列表进行自然升序排序 Comparator<Integer> comparator4 = Comparator.naturalOrder(); List<Integer> collect7 = numbers.stream().sorted(comparator4).collect(Collectors.toList()); System.out.println(collect7); List<Integer> nullNumbers = Arrays.asList(3,1,7,null,4,5); //对带null的整数列表进行降序排序,null为前序 List<Integer> collect8 = nullNumbers.stream().sorted(Comparator.nullsFirst(comparator3)).collect(Collectors.toList()); System.out.println(collect8); //对带null的整数列表进行降序排序,null为后序 List<Integer> collect9 = nullNumbers.stream().sorted(Comparator.nullsLast(comparator3)).collect(Collectors.toList()); System.out.println(collect9); //生成新对象result后进行result的person.age进行排序 List<Person> collect10 = people.stream().sorted(Comparator.comparing(x -> new Result("成功","200",x), (x,y) -> x.getPerson().getAge() - y.getPerson().getAge())).collect(Collectors.toList()); System.out.println(collect10); //生成新对象result后,对result进行排序(Result类已实现Comparable接口) List<Person> collect11 = people.stream().sorted(Comparator.comparing(x -> new Result("成功","200",x))) .collect(Collectors.toList()); System.out.println(collect11); //对年龄进行排序 List<Person> collect12 = people.stream().sorted(Comparator.comparingInt(x -> x.getAge())).collect(Collectors.toList()); System.out.println(collect12); //对Man的长度进行排序 List<Man> collect13 = men.stream().sorted(Comparator.comparingDouble(x -> x.getLength())).collect(Collectors.toList()); System.out.println(collect13); } }
运行结果
[Person(name=d, age=3), Person(name=c, age=4), Person(name=b, age=1), Person(name=a, age=2), Person(name=a, age=5)]
[Person(name=d, age=3), Person(name=c, age=4), Person(name=b, age=1), Person(name=a, age=5), Person(name=a, age=2)]
[Person(name=d, age=3), Person(name=c, age=4), Person(name=b, age=1), Person(name=a, age=2), Person(name=a, age=5)]
[Person(name=d, age=3), Person(name=c, age=4), Person(name=b, age=1), Person(name=a, age=2), Person(name=a, age=5)]
[Person(name=d, age=3), Person(name=c, age=4), Person(name=b, age=1), Person(name=a, age=2), Person(name=a, age=5)]
[Man(name=a, age=2, length=14.32), Man(name=b, age=5, length=15.7), Man(name=b, age=1, length=16.9), Man(name=c, age=3, length=13.2), Man(name=d, age=4, length=17.1)]
[7, 5, 4, 3, 1]
[1, 3, 4, 5, 7]
[null, 7, 5, 4, 3, 1]
[7, 5, 4, 3, 1, null]
[Person(name=b, age=1), Person(name=a, age=2), Person(name=d, age=3), Person(name=c, age=4), Person(name=a, age=5)]
[Person(name=b, age=1), Person(name=a, age=2), Person(name=d, age=3), Person(name=c, age=4), Person(name=a, age=5)]
[Person(name=b, age=1), Person(name=a, age=2), Person(name=d, age=3), Person(name=c, age=4), Person(name=a, age=5)]
[Man(name=c, age=3, length=13.2), Man(name=a, age=2, length=14.32), Man(name=b, age=5, length=15.7), Man(name=b, age=1, length=16.9), Man(name=d, age=4, length=17.1)]
----------------------------------------------------------------------------------
而另一个最主要的作用就是对集合的操作,从传统编程到函数式编程(lambda),我们先来看一个最简单的例子
@Data @AllArgsConstructor public class Artist { private String name; private String homeTown; public boolean isFrom(String homeTown) { if (this.homeTown.equals(homeTown)) { return true; } return false; } }
这是一个艺术家的简单类,包含名字和家乡。
现在我们来创建一个艺术家的集合,然后判定来自伦敦的艺术家有多少人
public class ComeFrom { public static void main(String[] args) { List<Artist> artists = new ArrayList<>(); artists.add(new Artist("Martin","London")); artists.add(new Artist("Shirly","Beijing")); artists.add(new Artist("Dilon","London")); artists.add(new Artist("Dave","NewYork")); int count = 0; for (Artist artist : artists) { if (artist.isFrom("London")) { count++; } } System.out.println(count); } }
运行结果:
2
现在我们来改造成函数式的编程
public class ComeFromStream { public static void main(String[] args) { List<Artist> artists = new ArrayList<>(); artists.add(new Artist("Martin","London")); artists.add(new Artist("Shirly","Beijing")); artists.add(new Artist("Dilon","London")); artists.add(new Artist("Dave","NewYork")); long count = artists.stream().filter(artist -> artist.isFrom("London")) .count(); System.out.println(count); } }
运行结果
2
这里第一种方式,我们称为外部迭代;而第二种方式,我们称为内部迭代,而stream是这种用函数式编程方式在集合类上进行复杂操作的工具。
如果我们对代码稍作修改
public class ComeFromStream { public static void main(String[] args) { List<Artist> artists = new ArrayList<>(); artists.add(new Artist("Martin","London")); artists.add(new Artist("Shirly","Beijing")); artists.add(new Artist("Dilon","London")); artists.add(new Artist("Dave","NewYork")); artists.stream().filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }); } }
执行该端代码,没有任何输出。像这种只过滤不计数的,filter只刻画出了Stream,但并没有产生新的集合的方法,我们称之为惰性求值方法;而像count这样最终会从Stream产生值的方法叫做及早求值方法。
我们再把上述代码改回求值。
public class ComeFromStream { public static void main(String[] args) { List<Artist> artists = new ArrayList<>(); artists.add(new Artist("Martin","London")); artists.add(new Artist("Shirly","Beijing")); artists.add(new Artist("Dilon","London")); artists.add(new Artist("Dave","NewYork")); long count = artists.stream().filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }).count(); System.out.println(count); } }
运行结果
Martin
Shirly
Dilon
Dave
2
判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。整个过程和建造者模式有共通之处。建造者模式使用一系列操作设置属性和配置,最后调用build方法,这时,对象才被真正创建。
Java的流式编程里面很多都是使用很多的惰性求值,最后来一个及早求值,得到我们所需要的结果,而流式方法的参数基本上都是函数式接口。
我们先来说明一些常用的流操作。
collect(toList())方法是由stream里的值生成一个列表,是一个及早求值操作,of()方法是使用一组初始值生成新的Stream.
public class CollectTest { public static void main(String[] args) { //通用方法,首先由列表生成一个Stream,然后再进行Stream惰性操作,最后求值操作collect() List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList()); System.out.println(assertEquals(Arrays.asList("a","b","c"),collected)); System.out.println(collected); } public static boolean assertEquals(Collection a,Collection b) { return a.equals(b); } }
运行结果
true
[a, b, c]
map操作可以将一种类型的值转换成另一种类型,将一个流中的值转换成一个新的流。
比方说我们要将一个字符列表里的字符串全部转换成大写,传统写法如下
public class MapOne { public static void main(String[] args) { List<String> collected = new ArrayList<>(); for (String string : Arrays.asList("a","b","hello")) { String upperCase = string.toUpperCase(); collected.add(upperCase); } System.out.println(collected); } }
运行结果
[A, B, HELLO]
函数式编程如下
public class MapTwo { public static void main(String[] args) { List<String> collect = Stream.of("a", "b", "hello").map(x -> x.toUpperCase()).collect(Collectors.toList()); System.out.println(collect); } }
这里需要注意的是map方法的参数是一个function的函数接口,之前我们说了function接口加工一个类型的对象转变成另一个类型的对象,当然这两种类型也可以相同,这里就都是String类型的对象。它将("a", "b", "hello")转换成新的流("A", "B", "HELLO"),当然它是一个惰性求值,我们必须使用及早求值的collect()方法来获取我们要的真正的列表。
运行结果
[A, B, HELLO]
filter操作可以在遍历数据并检查其中的元素时,找出符合规则的元素,它的参数也是一个函数式接口 Predicate接口 ,该接口为一个进行条件判断的接口。filter也是一个惰性求值,流式编程中惰性求值非常多。这里比如说我们要获取一个字符串列表中首字符为数字的所有字符串。
传统方式如下
public class FilterOne { public static void main(String[] args) { List<String> beginWithNumbers = new ArrayList<>(); for (String string : Arrays.asList("a","1abc","abc1","2bmw")) { if (Character.isDigit(string.charAt(0))) { beginWithNumbers.add(string); } } System.out.println(beginWithNumbers); } }
运行结果
[1abc, 2bmw]
函数式编程如下
public class FilterTwo { public static void main(String[] args) { List<String> collect = Stream.of("a", "1bac", "abc1", "2bmw").filter(x -> Character.isDigit(x.charAt(0))) .collect(Collectors.toList()); System.out.println(collect); } }
运行结果
[1bac, 2bmw]
flatMap操作,可以理解为打散,把流中本身的所有集合变成一个新的流,这个新的流是把旧的流中的集合中的元素拆分,在新流中生成一个集合,这个集合包含了之前旧流中的所有集合的所有元素。flatMap的参数跟map相同,也是一个function的函数式接口,但是它的返回值做了限定,只能为Stream类型。我们可以把流中的每一个集合看成一个单独的流,那么flatMap的作用就是多流变单流。
public class FlatMapOne { public static void main(String[] args) { List<String> collect = Stream.of(Arrays.asList("1", "2"), Arrays.asList("3", "4")) .flatMap(x -> x.stream()).collect(Collectors.toList()); System.out.println(collect); } }
运行结果
[1, 2, 3, 4]
由以上代码我们可以看到由两个List的集合[1,2][3,4],合并成了一个List集合[1,2,3,4].
min和max是在一个流中找最大值和最小值。它的返回值是一个Optional类,该类用于判断对象是否为null,用get()方法取出对象。min,max方法的参数是一个Comparator函数式接口。
我们先用最一般的方式来进行获取名字最短的一个Person对象。
public class MinOne { public static void main(String[] args) { List<Person> people = Arrays.asList(new Person("Dismica Dollan",23), new Person("Dilon Park",33), new Person("Nedy Pasa",25)); Person person = people.stream().min((x,y) -> x.getName().length() - y.getName().length()).get(); System.out.println(person); } }
运行结果
Person(name=Nedy Pasa, age=25)
然后我们使用Comparator的静态方法comparing来重写上面的实现
public class MinTwo { public static void main(String[] args) { List<Person> people = Arrays.asList(new Person("Dismica Dollan",23), new Person("Dilon Park",33), new Person("Nedy Pasa",25)); Person person = people.stream().min(Comparator.comparing(x -> x.getName().length())).get(); System.out.println(person); } }
Comparator.comparing返回的是一个Comparator的函数式接口,这没什么问题。comparing方法本身被重载为单参和双参的方法,双参为Function和Comparator两个函数式接口;单参只有一个Function函数式接口,但要求Function生成的新对象的类必须实现Comparable接口。那么用静态方法重写后的Comparator.comparing(x -> x.getName().length())明显是单参的comparing方法,x是一个person对象,返回一个Integer的整形对象,虽然length()是一个int类型的返回值,但是由于JDK 1.5以后支持自动封箱机制,两者已经可以通用。很明显Integer类早已经实现了Comparable接口。
public final class Integer extends Number implements Comparable<Integer>
reduce操作是从一组值中生成一个值,类似于count,min,max都属于reduce操作。reduce有3个重载的方法,其中一个是
T reduce(T identity, BinaryOperator<T> accumulator);
BinaryOperator函数式接口是由2个相同类型的对象参数,返回一个该类型的对象结果。T identity是一个初始值。
public class ReduceOne { public static void main(String[] args) { Integer count = Stream.of(1, 2, 3).reduce(1, (x, y) -> x + y); System.out.println(count); } }
运行结果
7
另外两个分别为
Optional<T> reduce(BinaryOperator<T> accumulator);
该方法返回一个Optional对象
示例代码
public class ReduceTwo { public static void main(String[] args) { Integer count = Stream.of(1, 2, 3).reduce((x, y) -> x + y).get(); System.out.println(count); } }
运行结果
6
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
该方法identity为初始值,用该初始值与BinaryOperator接口的结果再进行一次BiFunction的处理
示例代码
public class ReduceThree { public static void main(String[] args) { //先计算1+2+3=6,再计算2*6=12 Integer count = Stream.of(1, 2, 3).reduce(2, (x, y) -> x * y, (x, y) -> x + y); System.out.println(count); } }
运行结果
12
具体修改一段传统代码
需求为找出几张专辑中曲目大于1分钟的歌曲名称
定义一个音乐类
@Data @AllArgsConstructor @NoArgsConstructor public class Music { //歌曲名称 private String name; //歌曲播放长度(单位:秒) private Integer length; }
定义一个专辑类
@Data @AllArgsConstructor @NoArgsConstructor public class Album { //艺术家 private String artName; //曲目列表 private List<Music> musicList; }
现在用传统方法的外部循环来找出歌曲播放长度大于1分钟的歌曲
public class Tradition { public static Set<String> findLongMusic(List<Album> albums) { //目标歌曲集合 Set<String> musicNames = new HashSet<>(); //外部遍历所有的专辑 for (Album album : albums) { //外部遍历每张专辑所有的歌曲 for (Music music : album.getMusicList()) { //如果歌曲播放长度大于60秒 if (music.getLength() > 60) { //将该歌曲名加入到目标歌曲集合中 musicNames.add(music.getName()); } } } return musicNames; } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
运行结果
[北国之春, 东风破, 吻别, 霸王别姬]
做第一步改造,变成流
public class LambdaOne { public static Set<String> findLongMusic(List<Album> albums) { //目标歌曲集合 Set<String> musicNames = new HashSet<>(); //forEach方法的参数是一个Consumer接口,该接口是给定一个参数对象,然后可以做任何操作,无返回值 //此处是内部遍历每一个专辑 albums.stream().forEach(album -> { //获取该专辑的曲目列表,再内部遍历该曲目列表,获取每一个歌曲 album.getMusicList().forEach(music -> { //当该歌曲的播放长度大于60秒 if (music.getLength() > 60) { //将该歌曲名加入到目标歌曲集合中 musicNames.add(music.getName()); } }); }); return musicNames; } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
运行结果
[北国之春, 东风破, 吻别, 霸王别姬]
做第二部改造,使用过滤,变成新流
public class LambdaTwo { public static Set<String> findLongMusic(List<Album> albums) { //目标歌曲集合 Set<String> musicNames = new HashSet<>(); //对每一个专辑进行内部遍历 albums.stream().forEach(album -> { //将专辑的曲目列表转成流 album.getMusicList().stream() //filter方法的参数是一个Predicate函数式接口(给定一个参数对象,返回一个布尔值) //filter是一个惰性运算,所以返回值依然是一个流 //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //map方法的参数是一个Function函数式接口(给定一个参数对象,返回另一个结果对象) //将music对象流变成String对象流 .map(music -> music.getName()) //将String对象流的每一个对象添加到目标歌曲集合中 .forEach(name -> musicNames.add(name)); }); return musicNames; } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
做第三步改造,打散,合并
public class LambdaThree { public static Set<String> findLongMusic(List<Album> albums) { //目标歌曲集合 Set<String> musicNames = new HashSet<>(); //flatMap的参数是一个Function函数式接口,给定一个参数对象,返回一个流对象 //将所有专辑的歌曲列表组合成一个歌曲列表的流 albums.stream().flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 .map(music -> music.getName()) //将String对象流的每一个对象添加到目标歌曲集合中 .forEach(name -> musicNames.add(name)); return musicNames; } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
做第四步改造,全流操作,取消目标集合
public class LambdaFour { public static Set<String> findLongMusic(List<Album> albums) { //将所有专辑的歌曲列表组合成一个歌曲列表的流 return albums.stream().flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 .map(music -> music.getName()) //将String对象流求值操作,转化成Set集合 .collect(Collectors.toSet()); } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
做第五步操作,增加调试断点
@Slf4j public class LambdaFive { public static Set<String> findLongMusic(List<Album> albums) { //将所有专辑的歌曲列表组合成一个歌曲列表的流 return albums.stream().flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 .map(music -> music.getName()) //peek方法的参数是一个Consumer函数式接口,该接口是给定一个参数对象, //然后可以做任何操作,无返回值 //用peek方法可以获取流中的所有值,方便调试,并且流不变 //如果使用forEach虽然也可以获取流中的值,但是破坏了流,无法继续流 //的后续操作 .peek(name -> log.info(name)) //将String对象流求值操作,转化成Set集合 .collect(Collectors.toSet()); } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
运行结果
20:00:08.716 [main] INFO com.guanjian.until.LambdaFive - 北国之春
20:00:08.719 [main] INFO com.guanjian.until.LambdaFive - 霸王别姬
20:00:08.719 [main] INFO com.guanjian.until.LambdaFive - 东风破
20:00:08.719 [main] INFO com.guanjian.until.LambdaFive - 吻别
[北国之春, 东风破, 吻别, 霸王别姬]
函数式编程中有一个比较重要的概念就是关于null,因为有null值一旦调用将会引发空指针异常。针对这个问题,Java 8产生了一个新的类Optional,专门对null值进行处理
public class OptionalOne { public static void main(String[] args) { //创建一个Optional的值饮用 Optional<String> a = Optional.of("a"); //获取该值引用的值 assertEquals("a",a.get()); } public static void assertEquals(String a,String b) { if (a.equals(b)) { System.out.println(a + "等于" + b); }else { System.out.println(a + "不等于" + b); } } }
运行结果
a等于a
public class OptionalTwo { public static void main(String[] args) { //创建null值引用 Optional<String> emptyOptional = Optional.empty(); Optional<Object> alsoEmpty = Optional.ofNullable(null); //判断是否不为null assertFalse(emptyOptional.isPresent()); assertFalse(alsoEmpty.isPresent()); //如果为null,取默认值 assertEquals("a",emptyOptional.orElse("a")); //ofElseGet方法的参数为一个Supplier的函数式接口,它是一个不提供任何参数 //返回一个结果对象的任意操作 assertEquals("b",emptyOptional.orElseGet(() -> "b")); } public static void assertFalse(boolean a) { if (!a) { System.out.println("空"); }else { System.out.println("非空"); } } public static void assertEquals(String a,String b) { if (a.equals(b)) { System.out.println(a + "等于" + b); }else { System.out.println(a + "不等于" + b); } } }
运行结果
空
空
a等于a
b等于b
我们来看一下它的源码
public final class Optional<T> { /** * 直接创建一个空引用对象 */ private static final Optional<?> EMPTY = new Optional<>(); /** * 引用的值 */ private final T value; /** * 这是一个私有构造器,创建一个null值value的optional对象 */ private Optional() { this.value = null; } /** * 获取一个null值的Optional对象 */ public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } /** * 构造一个值不为null的Optional对象 */ private Optional(T value) { this.value = Objects.requireNonNull(value); } /** * 获取一个值不为null的Optional对象 */ public static <T> Optional<T> of(T value) { return new Optional<>(value); } /** * 对参数对象进行null值判断,为null则获取一个null值的Optional对象 * 否则,获取一个不为null值的Optional对象 */ public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } /** * 获取Optional对象的值,如果该值为null则直接抛出异常(也就是说我们是不可能在Optional对象里面拿到null值的) */ public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; } /** * 判断Optioanl对象的值是否不为null */ public boolean isPresent() { return value != null; } /** * 该方法的参数是一个Consumer的函数式接口,给定一个参数对象进行处理,没有返回值 * 必须在Optional对象的值不为null的情况下才能对该值进行Consumer操作 */ public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); } /** * 该方法的参数是一个Predicate函数式接口,给定一个参数对象,返回一个布尔值 * 如果Optional对象的值为null,返回该Optional对象 * 如果optioanl对象的值不为null,对值进行条件判断,判断结果为真,返回Optional对象本身,否则返回一个null值的optional对象 */ public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } /** * 该方法的参数是一个Function函数式接口,给定一个参数对象,返回另一个结果对象 * 如果该optional对象的值不为null,返回将该值对象转成另一个结果对象的Optional引用;如果optional对象的值为null,返回一个null值optional对象 */ public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); } } /** * 该方法参数为一个Function函数式接口,给定一个参数对象,返回一个Optional对象 * 如果该optional对象的值不为null,返回将该值对象转成Optional对象;如果optional对象的值为null,返回一个null值optional对象 */ public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Objects.requireNonNull(mapper.apply(value)); } } /** * 如果Optional的值不为null,返回该值,否则返回参数值 */ public T orElse(T other) { return value != null ? value : other; } /** * 该方法的参数为一个Supplier的函数式接口,无参数对象,返回一个结果对象 * 如果Optional的值不为null,返回该值,否则返回Supplier函数式接口结果对象 */ public T orElseGet(Supplier<? extends T> other) { return value != null ? value : other.get(); } /** * 该方法的参数是一个Supplier的函数式接口,无参数对象,返回一个可抛出异常对象 * 如果Optional的值不为null,返回该值,否则返回Supplier函数式接口结果异常对象 */ public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { if (value != null) { return value; } else { throw exceptionSupplier.get(); } } /** * 判断是否相等 */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Optional)) { return false; } Optional<?> other = (Optional<?>) obj; return Objects.equals(value, other.value); } /** * hashcode */ @Override public int hashCode() { return Objects.hashCode(value); } /** * 转化成字符串 */ @Override public String toString() { return value != null ? String.format("Optional[%s]", value) : "Optional.empty"; } }
我们再来看一下之前的例子,如果放入null值会如何
@Slf4j public class MethodRefence { public static Set<String> findLongMusic(List<Album> albums) { //将所有专辑的歌曲列表组合成一个歌曲列表的流 return albums.stream().flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 .map(music -> music.getName()) //peek方法的参数是一个Consumer函数式接口,该接口是给定一个参数对象, //然后可以做任何操作,无返回值 //用peek方法可以获取流中的所有值,方便调试,并且流不变 //如果使用forEach虽然也可以获取流中的值,但是破坏了流,无法继续流 //的后续操作 .peek(name -> log.info(name)) //将String对象流求值操作,转化成Set集合 .collect(Collectors.toSet()); } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(null); System.out.println(longMusic); } }
运行结果
Exception in thread "main" java.lang.NullPointerException
at com.guanjian.lanmda.lamda.MethodRefence.findLongMusic(MethodRefence.java:26)
at com.guanjian.lanmda.lamda.MethodRefence.main(MethodRefence.java:54)
直接抛出空指针异常java.lang.NullPointerException
我们可以用Optional来改写这段代码
@Slf4j public class MethodRefence { public static Set<String> findLongMusic(List<Album> albums) { //将所有专辑的歌曲列表组合成一个歌曲列表的流 //albums如果为null,map操作会直接返回一个null值引用的optional对象 //如果不为null才会继续后续操作 return Optional.ofNullable(albums).map(albums1 -> albums1.stream() .flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 //此处使用来方法饮用music -> music.genName()可以做如下改写 .map(Music::getName) //peek方法的参数是一个Consumer函数式接口,该接口是给定一个参数对象, //然后可以做任何操作,无返回值 //用peek方法可以获取流中的所有值,方便调试,并且流不变 //如果使用forEach虽然也可以获取流中的值,但是破坏了流,无法继续流 //的后续操作 .peek(name -> log.info(name)) //将String对象流求值操作,转化成Set集合 .collect(Collectors.toSet())) //此处判断optional的值是否为null,不为null,返回原值,为null返回一个默认的null值 //此处不会抛出异常 .orElse(null); } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(null); System.out.println(longMusic); } }
运行结果
null
再改回不使用null值看看结果如何
@Slf4j public class MethodRefence { public static Set<String> findLongMusic(List<Album> albums) { //将所有专辑的歌曲列表组合成一个歌曲列表的流 //albums如果为null,map操作会直接返回一个null值引用的optional对象 //如果不为null才会继续后续操作 return Optional.ofNullable(albums).map(albums1 -> albums1.stream() .flatMap(album -> album.getMusicList().stream()) //过滤曲目列表中播放长度大于1分钟的歌曲 .filter(music -> music.getLength() > 60) //将music对象流变成String对象流 //此处使用来方法饮用music -> music.genName()可以做如下改写 .map(Music::getName) //peek方法的参数是一个Consumer函数式接口,该接口是给定一个参数对象, //然后可以做任何操作,无返回值 //用peek方法可以获取流中的所有值,方便调试,并且流不变 //如果使用forEach虽然也可以获取流中的值,但是破坏了流,无法继续流 //的后续操作 .peek(name -> log.info(name)) //将String对象流求值操作,转化成Set集合 .collect(Collectors.toSet())) //此处判断optional的值是否为null,不为null,返回原值,为null返回一个默认的null值 //此处不会抛出异常 .orElse(null); } public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); Set<String> longMusic = findLongMusic(albums); System.out.println(longMusic); } }
运行结果
22:28:13.929 [main] INFO com.guanjian.lanmda.lamda.MethodRefence - 北国之春
22:28:13.931 [main] INFO com.guanjian.lanmda.lamda.MethodRefence - 霸王别姬
22:28:13.931 [main] INFO com.guanjian.lanmda.lamda.MethodRefence - 东风破
22:28:13.931 [main] INFO com.guanjian.lanmda.lamda.MethodRefence - 吻别
[北国之春, 东风破, 吻别, 霸王别姬]
说明无论这个值是否为null,都不会抛出异常。此处还有一个地方要注意的就是方法引用
music -> music.genName()可以做如下改写 Music::getName,这两种写法是等价的。
现在我们来看一下集合的有序性,我们都知道List是有序的,Set是无序的
public class OrderOne { public static void main(String[] args) { Comparator<Integer> comp1 = (x,y) -> y - x; List<Integer> numbers = Arrays.asList(4,3,2,1); Set<Integer> numbers2 = new HashSet<>(Arrays.asList(3,4,1,2)); List<Integer> sameOrder = numbers.stream().collect(Collectors.toList()); List<Integer> sameOrder2 = numbers2.stream() .sorted(comp1) //对无序集合进行排序,从大到小 .collect(Collectors.toList()); assertEquals(numbers,sameOrder); assertEquals(sameOrder,sameOrder2); assertEquals(Arrays.asList(1,2,3,4),sameOrder2); //串行流中,此处按照顺序执行 numbers.stream().forEach(i -> System.out.println(i)); //在并行流中,此处并不会按照顺序执行(本例只在于说明顺序问题,parallelStream请勿乱用) numbers.parallelStream().forEach(i -> System.out.println(i)); //在并行流中,按照顺序执行的方法 numbers.parallelStream().forEachOrdered(i -> System.out.println(i)); } public static void assertEquals(Collection collector, Collection another) { if (collector.equals(another)) { System.out.println("两个集合相同"); }else { System.out.println("两个集合不同"); } } }
执行结果:
两个集合相同
两个集合相同
两个集合不同
4
3
2
1
2
1
4
3
4
3
2
1
有的时候,一些操作在有序的流上开销更大,但大多数操作在有序流上效率更高,如filter,map,reduce等。
我们经常使用的collect(Collectors.toList())给我们返回了一个接口List,那它到底给我们返回的是哪个实现类呢?
public class CustomizedOne { public static void main(String[] args) { List<String> customer = Arrays.asList("a","b","c","d"); List<String> collect1 = customer.stream().collect(Collectors.toList()); System.out.println(collect1.getClass().getName()); //定制化实现类为LinkedList List<String> collect2 = customer.stream().collect(Collectors.toCollection(LinkedList::new)); System.out.println(collect2.getClass().getName()); //定制化实现类为LockFreeList List<String> collect3 = customer.stream().collect(Collectors.toCollection(LockFreeList::new)); System.out.println(collect3.getClass().getName()); } }
运行结果
java.util.ArrayList
java.util.LinkedList
org.amino.ds.lockfree.LockFreeList
由此可见Collectors.toList()默认的是ArrayList.
同理,Set的转化如下
public class CustomizedTwo { public static void main(String[] args) { List<String> customer = Arrays.asList("a","b","c","d"); Set<String> collect = customer.stream().collect(Collectors.toSet()); System.out.println(collect.getClass().getName()); //定制化实现类为TreeSet,Collectors.toCollection的参数是一个Supplier的函数式接口,不给定任何参数,返回一个结果对象 //() -> new TreeSet<>()等同于TreeSet::new // Set<String> collect1 = customer.stream().collect(Collectors.toCollection(() -> new TreeSet<>())); Set<String> collect1 = customer.stream().collect(Collectors.toCollection(TreeSet::new)); System.out.println(collect1.getClass().getName()); //定制化实现类为LockFreeSet Set<String> collect2 = customer.stream().collect(Collectors.toCollection(LockFreeSet::new)); System.out.println(collect2.getClass().getName()); } }
运行结果
java.util.HashSet
java.util.TreeSet
org.amino.ds.lockfree.LockFreeSet
由此可见Collectors.toSet()的默认实现类为HashSet.
注:像这种LinkedList::new的写法等同于() -> new LinkedList()
我们现在来看一下他的收集器Collector
我们在收集的时候并不仅仅只是可以收集例如toList,toSet这些,还可以收集我们需要的内容,例如我们有一个艺术群体对象类,我们要对这些艺术群体比较他们成员的数量。
@Data @AllArgsConstructor public class Artist implements Comparable<Artist> { private String name; private String homeTown; private List<String> members; public boolean isFrom(String homeTown) { if (this.homeTown.equals(homeTown)) { return true; } return false; } @Override public int compareTo(Artist o) { return this.members.size() - o.members.size(); } }
public class ChangeValueOne { public static void main(String[] args) { List<Artist> artists = Arrays.asList(new Artist("现世","London",Arrays.asList("黎明","丘伦","比加")), new Artist("千鸟","Beijing",Arrays.asList("武藏","谦逊","圣地家","帕克龙")), new Artist("飞花","Savni",Arrays.asList("华天","胜雄"))); //在流中直接找出最少成员的艺术群体 Artist artist = artists.stream().min(Comparator.comparing(art -> art)) .get(); System.out.println(artist); //通过收集器来找出最多成员的艺术群体 biggestGroup(artists.stream()).ifPresent(art -> System.out.println(art)); } public static Optional<Artist> biggestGroup(Stream<Artist> artists) { Function<Artist,Long> getCount = artist -> artist.getMembers().stream().count(); //此处将收集具有特殊需求的结果,而不再是toList,toSet return artists.collect(Collectors.maxBy(Comparator.comparing(getCount))); } }
运行结果
Artist(name=飞花, homeTown=Savni, members=[华天, 胜雄])
Artist(name=千鸟, homeTown=Beijing, members=[武藏, 谦逊, 圣地家, 帕克龙])
我们再来看一个计算所有专辑歌曲数目平均值的问题
public class ChangeValueTwo { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); Album album1 = new Album("华语歌手A",musicList1); Album album2 = new Album("华语歌手B",musicList2); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); System.out.println(averageNumberofMusic(albums)); } public static Double averageNumberofMusic(List<Album> albums) { return albums.stream().collect(Collectors.averagingInt(album -> album.getMusicList().size())); } }
运行结果
4.5
这里都是直接收集成单值的结果,而不是一个集合。
有一些艺术团体有独唱歌手和多人合唱的不同的团队,现在我们要把他们区分开,并统计出来。我们需要在艺术团体类中加一个是否独唱的标示
@Data @AllArgsConstructor public class Artist implements Comparable<Artist> { private String name; private String homeTown; private Boolean isSolo; //是否独唱歌手 private List<String> members; public Artist(String name,String homeTown,List<String> members) { this.name = name; this.homeTown = homeTown; this.members = members; } public boolean isFrom(String homeTown) { if (this.homeTown.equals(homeTown)) { return true; } return false; } @Override public int compareTo(Artist o) { return this.members.size() - o.members.size(); } }
public class ChangeValueThree { public static void main(String[] args) { List<Artist> artists = Arrays.asList(new Artist("现世","London",false,Arrays.asList("黎明","丘伦","比加")), new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")), new Artist("飞花","Savni",false,Arrays.asList("华天","胜雄")), new Artist("灭霸","Gana",true,Arrays.asList("常胜")), new Artist("修罗","Boel",true,Arrays.asList("降生"))); Map<Boolean, List<Artist>> listmap = bandsAndSolo(artists.stream()); listmap.keySet().stream().forEach(solo -> System.out.println(solo + "包含" + listmap.get(solo)) ); } public static Map<Boolean,List<Artist>> bandsAndSolo(Stream<Artist> artists) { //Collectors.partitioningBy参数为一个Predicate函数式接口,给定一个参数对象,返回一个布尔值 //它的返回对象为一个Map<Boolean, List<T>>的结果映射 return artists.collect(Collectors.partitioningBy(artist -> artist.getIsSolo())); } }
运行结果
false包含成员[Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加]), Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙]), Artist(name=飞花, homeTown=Savni, isSolo=false, members=[华天, 胜雄])]
true包含成员[Artist(name=灭霸, homeTown=Gana, isSolo=true, members=[常胜]), Artist(name=修罗, homeTown=Boel, isSolo=true, members=[降生])]
现在我们要按专辑里的艺术家团队来分开统计专辑,将专辑类修改为
@Data @AllArgsConstructor @NoArgsConstructor public class Album { //艺术家团队 private Artist artist; //曲目列表 private List<Music> musicList; }
public class ChangeValueFour { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Album album1 = new Album(new Artist("现世","London",false,Arrays.asList("黎明","丘伦","比加")),musicList1); Album album2 = new Album(new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album(new Artist("飞花","Savni",false,Arrays.asList("华天","胜雄")),musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); Map<Artist, List<Album>> listMap = albumsByArtist(albums.stream()); listMap.keySet().stream().forEach(artist -> System.out.println(artist + "包含" + listMap.get(artist))); } public static Map<Artist,List<Album>> albumsByArtist(Stream<Album> albums) { //Collectors.groupingBy参数为一个Function函数式接口,给定一个参数对象,返回另一个结果对象 //返回的是Map<K, List<T>>的映射对象,K为分组的类型 //Album::getArtist等同于album -> album.getArtist() return albums.collect(Collectors.groupingBy(Album::getArtist)); } }
运行结果
Artist(name=飞花, homeTown=Savni, isSolo=false, members=[华天, 胜雄])包含[Album(artist=Artist(name=飞花, homeTown=Savni, isSolo=false, members=[华天, 胜雄]), musicList=[Music(name=千年等一回, length=68), Music(name=暗香, length=73), Music(name=伏诛, length=55), Music(name=大地, length=56), Music(name=超人, length=66)])]
Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加])包含[Album(artist=Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加]), musicList=[Music(name=北国之春, length=75), Music(name=忘情水, length=58), Music(name=长恨歌, length=59), Music(name=霸王别姬, length=68)])]
Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙])包含[Album(artist=Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙]), musicList=[Music(name=东风破, length=68), Music(name=吻别, length=73), Music(name=朋友, length=55), Music(name=上海滩, length=56), Music(name=哥斯拉, length=66)])]
现在我们要将几个艺术团体的名称给组合成一个字符串
public class ChangeValueFive { public static void main(String[] args) { List<Artist> artists = Arrays.asList(new Artist("现世","London",false,Arrays.asList("黎明","丘伦","比加")), new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")), new Artist("飞花","Savni",false,Arrays.asList("华天","胜雄")), new Artist("灭霸","Gana",true,Arrays.asList("常胜")), new Artist("修罗","Boel",true,Arrays.asList("降生"))); //传统方法组合名称 StringBuilder builder = new StringBuilder("["); for (Artist artist : artists) { if (builder.length() > 1) { builder.append(","); } builder.append(artist.getName()); } builder.append("]"); System.out.println(builder.toString()); //流处理组合名称 String result = artists.stream().map(Artist::getName).collect(Collectors.joining(",", "[", "]")); System.out.println(result); } }
运行结果
[现世,千鸟,飞花,灭霸,修罗]
[现世,千鸟,飞花,灭霸,修罗]
这里要说明的是其实Collectors.joining内部也用到了StringBuilder来处理的,这里只是做了封装,方便我们使用,大大减少了代码量。
现在我们要来统计一个艺术团体有多少张专辑,将ChangeValueFour做如下修改
public class ChangeValueFour { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Artist artist1 = new Artist("现世", "London", false, Arrays.asList("黎明", "丘伦", "比加")); Album album1 = new Album(artist1,musicList1); Album album2 = new Album(new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album(artist1,musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); //将专辑按照艺术家团体进行分组 Map<Artist, List<Album>> listMap = albumsByArtist(albums.stream()); listMap.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + "包含" + entry.getValue())); System.out.println(""); Map<Artist,Integer> numberOfAlbums = new HashMap<>(); listMap.entrySet().stream().forEach(entry -> numberOfAlbums.put(entry.getKey(),entry.getValue().size())); numberOfAlbums.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + "有数量" + entry.getValue())); } public static Map<Artist,List<Album>> albumsByArtist(Stream<Album> albums) { //Collectors.groupingBy参数为一个Function函数式接口,给定一个参数对象,返回另一个结果对象 //返回的是Map<K, List<T>>的映射对象,K为分组的类型 //Album::getArtist等同于album -> album.getArtist() return albums.collect(Collectors.groupingBy(Album::getArtist)); } }
运行结果
Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加])包含[Album(artist=Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加]), musicList=[Music(name=北国之春, length=75), Music(name=忘情水, length=58), Music(name=长恨歌, length=59), Music(name=霸王别姬, length=68)]), Album(artist=Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加]), musicList=[Music(name=千年等一回, length=68), Music(name=暗香, length=73), Music(name=伏诛, length=55), Music(name=大地, length=56), Music(name=超人, length=66)])]
Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙])包含[Album(artist=Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙]), musicList=[Music(name=东风破, length=68), Music(name=吻别, length=73), Music(name=朋友, length=55), Music(name=上海滩, length=56), Music(name=哥斯拉, length=66)])]
Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加])有数量2
Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙])有数量1
很明显这么做,是做了两步操作,不利于以后做并行处理,现在我们要将收集器做一步完成该操作,直接获取一个艺术团体有多少专辑
public class ChangeValueSix { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Artist artist1 = new Artist("现世", "London", false, Arrays.asList("黎明", "丘伦", "比加")); Album album1 = new Album(artist1,musicList1); Album album2 = new Album(new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album(artist1,musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); Map<Artist, Long> artistMap = numberOfAlbums(albums.stream()); artistMap.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + "有数量" + entry.getValue())); } public static Map<Artist,Long> numberOfAlbums(Stream<Album> albums) { //这是一个组合收集器,先将元素分块,每块都与分类函数Album::getArtist相关联,然后使用下游的另一个收集器 //收集每快中的元素。最后将结果映射为一个Map return albums.collect(Collectors.groupingBy(Album::getArtist,Collectors.counting())); } }
运行结果
Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加])有数量2
Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙])有数量1
现在我们给专辑加上专辑名,并且我们要获得每一个艺术团体的所有专辑名。
@Data @AllArgsConstructor @NoArgsConstructor public class Album { //专辑名称 private String name; //艺术家团队 private Artist artist; //曲目列表 private List<Music> musicList; public Album(Artist artist,List<Music> musicList) { this.artist = artist; this.musicList = musicList; } }
public class ChangeValueSeven { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Artist artist1 = new Artist("现世", "London", false, Arrays.asList("黎明", "丘伦", "比加")); Album album1 = new Album("苍狼",artist1,musicList1); Album album2 = new Album("雪域",new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album("天鹰",artist1,musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); Map<Artist, List<String>> nameMap = nameOfAlbums(albums.stream()); nameMap.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + "有专辑" + entry.getValue())); } public static Map<Artist,List<String>> nameOfAlbums(Stream<Album> albums) { //这是一个可以无限组合下去的收集器,先按照艺术团体分组,再使用下游收集器收集专辑的名称列表 //Collectors.mapping类似于流中的map()转换 return albums.collect(Collectors.groupingBy(Album::getArtist, Collectors.mapping(Album::getName,Collectors.toList()))); } }
运行结果
Artist(name=现世, homeTown=London, isSolo=false, members=[黎明, 丘伦, 比加])有专辑[苍狼, 天鹰]
Artist(name=千鸟, homeTown=Beijing, isSolo=false, members=[武藏, 谦逊, 圣地家, 帕克龙])有专辑[雪域]
现在我们来自己定制一个收集器
还是以ChangeValueFive的例子来说明
public class ChangeValueEight { public static void main(String[] args) { List<Artist> artists = Arrays.asList(new Artist("现世","London",false,Arrays.asList("黎明","丘伦","比加")), new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")), new Artist("飞花","Savni",false,Arrays.asList("华天","胜雄")), new Artist("灭霸","Gana",true,Arrays.asList("常胜")), new Artist("修罗","Boel",true,Arrays.asList("降生"))); //传统方法组合名称 StringBuilder builder = new StringBuilder("["); for (Artist artist : artists) { if (builder.length() > 1) { builder.append(","); } builder.append(artist.getName()); } builder.append("]"); StringBuilder builder1 = new StringBuilder("["); System.out.println(builder.toString()); artists.stream().map(Artist::getName).forEach(name -> { if (builder1.length() > 1) { builder1.append(","); } builder1.append(name); }); builder1.append("]"); System.out.println(builder1.toString()); StringBuilder reduced = artists.stream().map(Artist::getName) //该reduce是一个三参数的reduce,它的返回值是由它的第一个参数决定的 //它的第二个参数是一个BiFunction函数式接口,给定任意两个参数对象,返回一个任意结果对象 //BiFunction函数式接口在reduce中做了限定,第一个参数必须跟reduce第一个参数的对象的类一致,返回的结果也一样,第 //二个参数跟流的类型保持一致 //它的第三个参数是一个BinaryOperator函数式接口,给定两个相同的参数对象,返回一个相同的结果对象 //BinaryOperator函数式接口在reduce中做了限定,这两个相同的参数以及返回参数必须跟reduce第一个参数的对象的类一致 .reduce(new StringBuilder("["), (builder2, name) -> { if (builder2.length() > 1) { builder2.append(","); } builder2.append(name); return builder2; }, (left, right) -> left.append(right)); reduced.append("]"); System.out.println(reduced.toString()); //StringCombiner::add等价于(stringCombiner, s) -> stringCombiner.add(s) //StringCombiner::merge等价于((stringCombiner, stringCombiner2) -> stringCombiner.merge(stringCombiner2)) StringCombiner comined = artists.stream().map(Artist::getName) .reduce(new StringCombiner(",","[","]"), StringCombiner::add, StringCombiner::merge); System.out.println(comined.toString()); String collect = artists.stream().map(Artist::getName).collect(new StringCollector(",", "[", "]")); System.out.println(collect); } }
运行结果
[现世,千鸟,飞花,灭霸,修罗]
[现世,千鸟,飞花,灭霸,修罗]
[现世,千鸟,飞花,灭霸,修罗]
[现世,千鸟,飞花,灭霸,修罗]
[现世,千鸟,飞花,灭霸,修罗]
抛开传统方式,我们来看一下第二种方式,用到了reduce方法,写的极其复杂。对比于第三种方法,我们需要自己写一个StringCombiner类,它对StringBuilder类进行了一次封装。
public class StringCombiner { private final String delim; private final String prefix; private final String suffix; private final StringBuilder builder; public StringCombiner(String delim, String prefix, String suffix) { this.delim = delim; this.prefix = prefix; this.suffix = suffix; builder = new StringBuilder(); } public StringCombiner add(String element) { if (areAtStart()) { builder.append(prefix); } else { builder.append(delim); } builder.append(element); return this; } private boolean areAtStart() { return builder.length() == 0; } public StringCombiner merge(StringCombiner other) { if (other.builder.length() > 0) { if (areAtStart()) { builder.append(prefix); } else { builder.append(delim); } builder.append(other.builder, prefix.length(), other.builder.length()); } return this; } @Override public String toString() { if (areAtStart()) { builder.append(prefix); } builder.append(suffix); return builder.toString(); } }
而最后我们需要做到通用,即我们所说的定制一个收集器,它实现了Collector接口
/** * 一个收集器由四部分组成,首先是一个Supplier,这是一个工厂方法,用来创建容器,在这个例子中,就是 * StringCombiner。和reduce操作中的第一个参数类似,它是后续操作的初值 */ public class StringCollector implements Collector<String, StringCombiner, String> { private static final Set<Characteristics> characteristics = Collections.emptySet(); private final String delim; private final String prefix; private final String suffix; public StringCollector(String delim, String prefix, String suffix) { this.delim = delim; this.prefix = prefix; this.suffix = suffix; } @Override public Supplier<StringCombiner> supplier() { return () -> new StringCombiner(delim, prefix, suffix); } @Override public BiConsumer<StringCombiner, String> accumulator() { return StringCombiner::add; } @Override public BinaryOperator<StringCombiner> combiner() { return StringCombiner::merge; } @Override public Function<StringCombiner, String> finisher() { return StringCombiner::toString; } @Override public Set<Characteristics> characteristics() { return characteristics; } }
而第四种方法即使用了该收集器
并行化之路
在CPU的多核时代,是我们发掘多核处理器能力的时候了。首先我们要搞清楚2个概念,并行和并发。
并发是很多任务共享同一个时间段称为并发,我们平时说的高并发就是说的这种情况,并发并不一定由多核处理器来执行,他们有可能是单核串行执行的,只不过他们的执行时间比较集中。
并行是很多任务在同一个时间点执行,由CPU的多核共同执行。而并行又分为数据并行和任务并行。数据并行化是指将数据分成块,为每块数据分配单独的处理单元。而任务并行化中,线程不同,任务各异,比如Java EE中的应用容器便是任务并行化的例子之一,每个线程不光可以为不同用户服务,还可以为同一个用户执行不同的任务。
我们想要知道并行能给性能提升的极限,就必须要知道阿尔达姆定律,因为我们无法越过自然法则边界来做事。
public class ParallelOne { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Artist artist1 = new Artist("现世", "London", false, Arrays.asList("黎明", "丘伦", "比加")); Album album1 = new Album("苍狼",artist1,musicList1); Album album2 = new Album("雪域",new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album("天鹰",artist1,musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); int sum = albums.stream().flatMap(album -> album.getMusicList().stream()) .mapToInt(Music::getLength) .sum(); System.out.println(sum); } }
运行结果
896
这是一段求取所有专辑歌曲总长度的代码,采用的是完全串行化的处理方式。现在来进行改造
public class ParallelTwo { public static void main(String[] args) { List<Music> musicList1 = Arrays.asList(new Music("北国之春",75), new Music("忘情水",58), new Music("长恨歌",59), new Music("霸王别姬",68)); List<Music> musicList2 = Arrays.asList(new Music("东风破",68), new Music("吻别",73), new Music("朋友",55), new Music("上海滩",56), new Music("哥斯拉",66)); List<Music> musicList3 = Arrays.asList(new Music("千年等一回",68), new Music("暗香",73), new Music("伏诛",55), new Music("大地",56), new Music("超人",66)); Artist artist1 = new Artist("现世", "London", false, Arrays.asList("黎明", "丘伦", "比加")); Album album1 = new Album("苍狼",artist1,musicList1); Album album2 = new Album("雪域",new Artist("千鸟","Beijing",false,Arrays.asList("武藏","谦逊","圣地家","帕克龙")),musicList2); Album album3 = new Album("天鹰",artist1,musicList3); List<Album> albums = new ArrayList<>(); albums.add(album1); albums.add(album2); albums.add(album3); int sum = albums.parallelStream().flatMap(album -> album.getMusicList().stream()) .mapToInt(Music::getLength) .sum(); System.out.println(sum); } }
运行结果
896
在这里我并没有加入诸如
long start = System.currentTimeMillis();
long time = System.currentTimeMillis() - start;
来比较他们的时间,因为这样比较是没有意义的。因为串行化和并行化谁更快并不是一个简单的问题。
一般来说,数据量很小,串行化要快过并行化几倍左右,比如我这个例子里面数据量就很小,不足以说明谁快谁慢。当数据量达到100倍左右,速度持平。当数据量达到10000倍的时候,并行化是串行化速度的2.5倍。当然具体要根据电脑的配置来定的,这些只是一个大概值。
数据量的大小并不决定并行化是否会带来速度提升的唯一因素,性能还会受到编写代码的方式和核的数量的影响。
现在我们来进行一个蒙特卡洛模拟法的实验。先看看什么是蒙特卡洛模拟法,而该模拟法来源于赌场。
基本原理思想
编辑解题步骤
编辑public class ManualDiceRollsOne { //投掷两次骰子的次数 private static final int N = 100000000; //一次的占比 private final double fraction; //每次投2次骰子的点数之和与概率的映射 private final Map<Integer,Double> results; //计算机线程数 private final int numbersOfThreads; //线程池 private final ExecutorService executor; //每个线程的工作次数 private final int workPerThread; public ManualDiceRollsOne() { fraction = 1.0 / N; results = new ConcurrentHashMap<>(); numbersOfThreads = Runtime.getRuntime().availableProcessors() * 2; executor = Executors.newFixedThreadPool(numbersOfThreads); workPerThread = N / numbersOfThreads; } public void simulateDiceRoles() { //计算所有投掷2次骰子的结果概率 List<Future<?>> futures = submitJobs(); //等待结果,拿取结果 awaitCompletion(futures); //打印结果 printResults(); } private void printResults() { //等同于results.entrySet().forEach(entry -> System.out.println(entry)); results.entrySet().forEach(System.out::println); } private List<Future<?>> submitJobs() { List<Future<?>> futures = new ArrayList<>(); for (int i = 0;i < numbersOfThreads;i++) { //我把我的所有计算任务全部交给Future集合,彼此间不影响 futures.add(executor.submit(makeJob())); } return futures; } private Runnable makeJob() { return () -> { //ThreadLocalRandom对应于不同线程都有一个线程的随机种子值 //在多线程下当使用ThreadLocalRandom来生成随机数 ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0;i < workPerThread;i++) { int entry = twoDiceThrows(random); //获取每次投掷2个骰子的点数之和,增加每次的概率(亿分之一),存入 //线程安全集合ConcurrentHashMap中 accumuLateResult(entry); } }; } private void accumuLateResult(int entry) { //Map的compute方法第二参数为BiFunction的函数式接口,给定两种不同的参数对象,返回另一个结果对象,这三种对象 //可以相同,可以不同 //如果results的entry键的值为null(该键不存在),则把该值设为fraction(单次概率亿分之一) //否则将该键的值设为原值加上fraction(单次概率亿分之一) results.compute(entry,(key,previous) -> previous == null ? fraction : previous + fraction); } private int twoDiceThrows(ThreadLocalRandom random) { int firstThrow = random.nextInt(1,7); int secondThrow = random.nextInt(1,7); return firstThrow + secondThrow; } private void awaitCompletion(List<Future<?>> futures) { //等待所有的计算任务完成后,拿取计算结果,关闭线程池 futures.forEach(future -> { try { future.get(); }catch (Exception e) { e.printStackTrace(); } }); executor.shutdown(); } public static void main(String[] args) { ManualDiceRollsOne rolls = new ManualDiceRollsOne(); long start = System.currentTimeMillis(); rolls.simulateDiceRoles(); System.out.println(System.currentTimeMillis() - start); } }
运行结果
2=0.027802380001955306
3=0.05559881000661214
4=0.08334154999679824
5=0.11106439998220616
6=0.13887008996757047
7=0.16663862995295434
8=0.1388884399675608
9=0.11110334998218566
10=0.08335782999678967
11=0.0555430600066028
12=0.027791460001953476
6488
我们可以看到概率分布,两次骰子点数之和为7的概率最大,为16.6%,全部计算完需要6秒多。
现在我们使用并行流来计算这个分布概率
public class ManualDiceRollsTwo { public static void main(String[] args) { int N = 100000000; double fraction = 1.0 / N; long start = System.currentTimeMillis(); Map<Integer, Double> collect = IntStream.range(0, N).parallel() //获取一个投掷两次骰子的点数和的流 .mapToObj(value -> twoDiceThrows()) //这是一个组合收集器,先将元素分块,然后使用下游的另一个收集器Collectors.summingDouble //收集每快中的元素。最后将结果映射为一个Map //Collectors.summingDouble的参数为一个ToDoubleFunction的函数式接口,给定一个参数对象,返回 //一个double的值,此处为每有一个相同的点数和,就以该点数和为分组,累加一个1/N。 .collect(Collectors.groupingBy(side -> side, Collectors.summingDouble(n -> fraction))); System.out.println(collect); System.out.println(System.currentTimeMillis() - start); } private static Integer twoDiceThrows() { ThreadLocalRandom random = ThreadLocalRandom.current(); int firstThrow = random.nextInt(1,7); int secondThrow = random.nextInt(1,7); return firstThrow + secondThrow; } }
运行结果
{2=0.027763270000000003, 3=0.055529230000000006, 4=0.08329742000000001, 5=0.11110633, 6=0.13887798, 7=0.16680228999999996, 8=0.13883699, 9=0.11110740999999999, 10=0.08336573000000003, 11=0.05553644, 12=0.02777691}
514
我们可以看到两次运算的结果非常接近,但是运行速度,明显并行流要比线程调度和等待线程池中的某项任务完成要快的多,只有半秒钟。