上帝视角学JAVA- 基础20-jdk8新特性-(基础完结)【2021-09-10】

1、Lambda 表达式

我们可以把Lambda表达式理解为一段可以传递的代码。使用它可以写出更简介、更灵活的代码。

实质是对编码风格的改变。并不会影响代码执行的结果。简单来说,你还是你,只是换了一件新衣服、更帅了。

前面讲的 ProxyFactory 类

public class ProxyFactory {
​
    /** 调用此方法,返回一个代理类对象 **/
    public static Object getProxyInstance(Object obj) {
        // 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), (proxy, method, args) -> method.invoke(obj, args));
    }
}

里面就使用了 Lambda 表达式。

再来看一个例子:一个Runnable 的匿名实现类给r1,通过r1调用方法。

Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱北京");
            }
        };
        r1.run();

使用lambda表达式改造

Runnable r1 = () -> System.out.println("我爱北京");
r1.run();

再来看看一个例子:

Comparator<Integer> com = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
};
com.compare(12, 21);

使用lambda表达式改造

Comparator<Integer> com = (o1, o2)-> Integer.compare(o1, o2);
com.compare(12, 21);

这就完了?再次升级 称为方法引用的写法。

// 方法引用的写法
Comparator<Integer> com = Integer::compare;
com.compare(12, 21);

1.1 Lambda 表达式的格式

Lambda 表达式实质是方法的简写。但是又不仅仅是这样。它还是一个对象。

比如:

Runnable r1 = () -> System.out.println("我爱北京");

可以被 Runnable 类型接收,说明这个表达式是一个 Runnable 实现类对象!!!

上面展示的几个例子基本对Lambda表达式有了初步了解。

它的基本格式是括号加箭头加大括号。()->{}

实际上这个形式是方法的简写。()里面是方法的参数,{}里面直接是方法体。

上面都是以接口为例的,这就要求这个接口里面只有一个抽象方法。因为没有方法名,多个的话就不知道是指哪一个了。

因为只有一个,所有Lambda表达式自然就可以省略方法名。

根据方法的多种情况,lambda表达式也就有多种形式。

  • 方法没有 参数,没有返回值

()-> {方法体}

就像上面的 Runable 的例子。又因为 方法体只有 1条语句,因此 {} 也可以省略。

  • 有参数,无返回值

(类型 p)->{方法体}

变量名p可以随便取。有几个参数就写几个

实例:

Consumer<String> con1 = (p)->System.out.println(p);
con1.accept("哈哈");

还是因为函数体只有一条语句,省略了{},而且 还省略了参数p的类型。因为这个类型可以通过 左边 Consumer的泛型推断得出。

我们发现,参数只有一个,还可以省略()

Consumer<String> con1 = o ->System.out.println(o);
con1.accept("哈哈");
  • 有参数,有返回值

Comparator<Integer> com = (a, b)->{ return a.compareTo(b);};
int compare = com.compare(90, 9);
System.out.println(compare);

由于泛型的存在,可以省略参数的类型,而且如果只有一条语句,那么 return 和 {} 都是可以省略的。

Comparator<Integer> com = (a, b)-> a.compareTo(b);
int compare = com.compare(90, 9);
System.out.println(compare);

下面这种写法是 多个参数,有返回值的情况。也是一条语句可以省略 return 和 {}

(proxy, method, args) -> method.invoke(obj, args)

如果是多个参数,多条语句,看下面的写法。

(proxy, method, args) -> {
                    GeneralMethod humanUtil = new GeneralMethod();
                    humanUtil.method1();
                    Object invoke = method.invoke(obj, args);
                    humanUtil.method2();
                    return invoke;
                }

就是 ruturn 和 {} 都不能省略了而已。

  • 无参数,有返回值

还是一样的。只是参数使用 ()

总结:

  • 标准格式 (参数类型 变量名)-> {方法体}

  • 如果参数类型可以推断,那么类型可以省略

  • 如果只有一个参数,那么()可以省略

  • 如果方法体只有一条语句,这条语句不是return语句,{}可以省略

  • 如果方法体 只有一条语句,这条语句是 return 语句,return 与{} 都可以省略。而且要省略必须都省略。

  • 其他情况就是 按照标准格式写了。

形式上,一条语句的情况如果都省略了,是一样的。实际上,省略的东西是不一样的。带return的语句不仅省略了 {},还要省略return

1.2 函数式接口

上面的例子应该可以发现,上面时候用Lambda表达式呢? 一般是一个接口写它的匿名实现类时。这个接口有一个特点,只有一个抽象方法。

这种只有1个抽象方法的接口叫做函数式接口。

lambda表示式其实就是函数式接口的匿名实现类的 写法优化,是一个匿名实现类对象。

// 这是一个lambda表达式,是Comparator接口的一个匿名实现类对象。直接作为参数就是 匿名实现类的匿名对象
(a, b)-> a.compareTo(b);

之所以要求只能有一个抽象方法(可以有默认方法、静态方法 等非抽象方法),是因为lambda没有名字,多个抽象方法就不知道重写的是哪个抽象方法了。只有一个的话,就很明确了。

JAVA 提供了一个注解 @FunctionalInterface 放在接口上面,用来检查这个接口是否是一个函数式接口。

根据 方法的参数与返回值的有无,可以有4中类型的方法。因此,也就有四种类型的函数式接口。

  • 无参数、无返回值 Runnable

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • 无参数、有返回值 Supplier 一般叫做供给型,不消费参数,还返回东西

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • 有参数、无返回值 Consumer 一般叫做消费型,消费参数,不返回东西

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
  • 有参数、有返回值

    Function 函数型、即消费参数、又提供返回值

    Predicate 断定型、根据参数返回真假。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }


    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Function 接口比较通用,有参数,有返回值;而Predicate 虽然也是有参数,有返回值,但是返回值类型必须是 Boolean型。

这是几个基本的 函数式接口。还有其他的一些编写。比如多个参数什么的。

如何需要定义函数式接口,符号上述类型的,基本可以不用定义了,直接使用就是了。

使用举例:

1、Consumer 接口

// 定义一个 使用 Consumer 接口作为参数的 函数
public void happyTime(double money, Consumer con){
    con.accept(money);
}

使用

@Test
public void test1() throws Exception {
    happyTime(400, a-> System.out.println("消费啊,快乐啊"+ a));
}
// 输出
消费啊,快乐啊400.0

2、Predicate 接口 例子

// 字符串过滤函数,使用 Predicate 作为一个参数
public List filterString(List<String> list, Predicate<String> pre){
    ArrayList<String> arrayList = new ArrayList<>();
    for (String s : list) {
        // 当判断为 true 时,添加到新的 List里面
        if (pre.test(s)){
            arrayList.add(s);
        }
    }
    return arrayList;
}

使用:

// 待过滤的 list
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "zzz", "eee");
// 调用 filterString 函数, 第二个参数是 Predicate 接口的实现类对象。使用lambda表达式写。
// Predicate 接口属于 只有1个参数,返回值是 Boolean类型。
// 所有 ()可以省略, 返回语句 只有一条 turn 和{} 也可以省略。
// 这里的规则是 如果 参数比“ccc“ 大就返回 true 否则返回false
List ggg = filterString(list, str -> str.compareTo("ccc") > 0);
System.out.println(ggg);
// 输出 
[ddd, zzz, eee]

 

上图是其他的一些函数式节点,主要就是 参数个数、返回值这里变来变去的。没什么意思。

2、方法引用与构造器引用

2.1 方法引用

当要传递给 Lambda体的操作已经有实现的方法了,就可以使用方法引用。

方法引用就是lambda表达式,只不过这个表达式不再使用 ()-{} 这种形式,而是直接利用现有的逻辑。通过方法的名称来指向这部分逻辑。

从上面lambda表达式的学习来看,就知道这个方法主要是根据 方法的 参数以及返回值来的。所以,被引用的方法能够用来替换表达式,就必须和原来的函数式接口的抽象方法具有 相同的参数以及相同的返回值。

格式: 使用操作费 "::" 双冒号将 类名(或者对象)与方法名隔开。

左边是 调用者、右边是 方法名。类也是Class类的一个对象。

例如有如下三种主要情况

  • 对象::实例方法名

  • 类::静态方法名

  • 类::实例方法名

例子1:

// 普通的 lambda 表达式
Consumer con = str-> System.out.println(str);
con.accept("哈哈哈");
// 方法引用
Consumer con1 = System.out::println;
con.accept("哈哈哈");

可以看到 System.out::println 替换了 str-> System.out.println(str)

原因是 str-> System.out.println(str) 这一部分逻辑,已经在 System.out 这个类 即 PrintStream 里面定义了 println 方法,这个方法的参数与返回值也是 一个参数,没有返回值。与Consumer 接口的 accept 方法一致。

    public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

为什么可以使用方法引用呢?

因为lambda表达式本质上是要实现 抽象方法,写方法体。而现在已经有函数实现了,就用现成的不久行了。

再看一个例子2:

// 普通 lambda表达式
Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
int compare1 = com1.compare(10, 20);
System.out.println(compare1);
// 方法引用 Integer类里面的compare 方法的参数与返回值类型 都与 Comparator接口的抽象方法 compare 一致。可以使用
// Integer类的compare方法 里面的逻辑。
Comparator<Integer> com2 = Integer::compare;
int compare2 = com1.compare(10, 20);
System.out.println(compare2);

再来例子3:

// Comparator 中的 int compare(T t1, T t2)
// String 中的 int t1.compareTo(t2)
Comparator<String > com1 = (s1, s2) -> s1.compareTo(s2);
com1.compare("abc", "abd");

Comparator<String > com2 = String::compareTo;
com2.compare("abc", "abd");

不是说 String 类的 compareTo 方法的参数返回值要与 compare一样吗?为什么这样写也可以呢?

如果 第一个参数 是 方法的调用者,那么是可以使用 方法引用的。这就出现了 类名::实例方法的写法。

例子4:

// BiPredicate 中的 boolean test(T t, U u)
// String 中的 boolean equals(Object anObject)
BiPredicate<String,String> com1 = (s1, s2) ->s1.equals(s2);
com1.test("abc", "abd");
// 方法引用
BiPredicate<String,String> com2 = String::equals;
com2.test("abc", "abd");

这个例子也是,第一个参数是方法的调用者。使用方法引,在 :: 之前要写第一个参数的类型。看起来就像是类名调用实例方法。

例子5:

// BiPredicate 中的 R apply(T t);
// Person 中的 String getName()
Function<Person, String> f1 = e -> e.getName();
System.out.println(f1.apply(new Person("aa", 11)));
// 方法引用
Function<Person, String> f2 = Person::getName;
System.out.println(f1.apply(new Person("aa", 11)));

这里也是 被引用的方法getName的调用者是 第一个参数Person。

2.2 构造器引用

例子1:

// 普通 Lambda 表达式
Supplier<FullPerson> per = ()-> new FullPerson();
FullPerson person = per.get();
// 构造器引用
Supplier<FullPerson> per1 = FullPerson::new;
FullPerson fullPerson = per1.get();

例子2:

// 普通lambda表达式
Function<Integer, FullPerson> func = (a)-> new FullPerson(a);
FullPerson apply = func.apply(12);
System.out.println(apply);
// 构造器引用
Function<Integer, FullPerson> func1 = FullPerson::new;
FullPerson apply1 = func1.apply(12);
System.out.println(apply1);

这里面构造器引用是 因为构造器的参数和返回值与 函数式接口的抽象方法一致。

依然符合上面方法引用的原则。

2.3 数组引用

例子1:

// 普通lambda表达式
Function<Integer, String[]> funcArr = (num)-> new String[num];
String[] apply = funcArr.apply(20);
System.out.println(apply.length);
// 数组引用
Function<Integer, String[]> funcArr1 = String[]::new;
String[] apply1 = funcArr1.apply(20);
System.out.println(apply1.length);

把数组看成是一个特殊的类,那么数组引用和构造器引用就一样了。

3、Stream

Stream 流 用于处理集合的关键抽象概念。它可以指定你希望对集合进行的操作。可以执行非常复杂的查找、过滤和映射数据等操作。

工作中用的很多,也非常简单。

Stream API是对集合进行处理的。

// 这是Collection接口中的默认方法 stream; 返回值是 Stream 接口的实现类对象。
default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

Stream 是一个接口,继承了 BaseStream 接口和 AutoCloseable 接口。接口是可以多继承的。

学习Strem、就是学习Stream 接口中的方法是怎么用的。

  • Stream 自己不会存储元素

  • 不会改变源对象,会返回一个持有结果的 新Stream对象

  • 操作是延迟执行的,这意味着会等待需要结果的时候才执行。

Stream的操作有三个步骤。

1、创建

从集合中获取一个Stream对象。

2、中间操作

过滤、映射、排序、去重 等待

3、终止操作

产生结果,不能再继续使用。

3.1 Stream的创建

1、从集合创建,我们知道,Collection中 有stream 默认方法,因此,集合都可以调用这个方法获取Stream对象。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 顺序流[安装元素顺序来取数据]
Stream<Integer> stream = list.stream();
// 并行流[不按顺序,多个一起]
Stream<Integer> integerStream = list.parallelStream();

2、Arrays 类的静态方法 stream,获取Stream对象

IntStream stream1 = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0});

3、通过Stream接口自己的 静态方法of 创建

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Stream.of(1,2,3,4,5,6);
Stream.of(list);

of 有2个重载方法,一个接收 数组。另一个接收 一个泛型,意味着很多类型都可以接收。

4、创建无限流, 一般用来造数据

 Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

4.1 、iterate方法

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
    Objects.requireNonNull(f);
    final Iterator<T> iterator = new Iterator<T>() {
        @SuppressWarnings("unchecked")
        T t = (T) Streams.NONE;

        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public T next() {
            return t = (t == Streams.NONE) ? seed : f.apply(t);
        }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
        iterator,
        Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}

UnaryOperator 接口,继承了Function,里面有一个静态方法identity,由于继承了Function,还有一个抽象方法 apply

但是继承的时候,泛型是 Function<T, T>,即 apply方法的参数 与返回值类型是一样的。

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}
  1. 2、generate 方法

// 产生10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);

generate 方法,接收一个 Supplier 函数式接口对象,返回的是一个流

    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

3.2 中间操作

3.2.1 筛选与切片 filter、limit、skip、distinct

  • Stream<T> filter(Predicate<? super T> predicate) 过滤,接收一个 Predicate 接口实现类对象。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 获取元素值大于5的元素
list.stream().filter(item-> item>5).forEach(System.out::println);
// 输出
6
7
8
9
  • Stream<T> limit(long maxSize) 截断流,使元素不超过指定数量

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 限制3个
list.stream().limit(3).forEach(System.out::println);
// 输出
1
2
3
  • Stream<T> skip(long n) 跳过元素,返回一个扔掉前 n个元素的流,如果元素不足n个,返回一个空流

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
// 跳过前6个
list.stream().skip(6).forEach(System.out::println);
// 输出
7
8
9
0
  • Stream<T> distinct() 去除重复元素

List<Integer> list = Arrays.asList(1, 3, 3, 6, 7, 8, 8);
list.stream().distinct().forEach(System.out::println);
// 输出
1
3
6
7
8

3.2 .2 映射 map、flatMap

  • <R> Stream<R> map(Function<? super T, ? extends R> mapper) 映射 接收一个 Function 实现类对象。

    每一个元素都会应用这个对象里的逻辑。

List<Integer> list = Arrays.asList(1, 3, 3, 6, 7, 8, 8);
// 每一个元素是 item, 应用逻辑 item+item,最后每个元素变成 item+item
list.stream().map(item->item+item).limit(3).forEach(System.out::println);
// 输出
2
6
6
  • <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

将流中的每一个值都换成另一个流,然后将所有流连接成一个流。如果一个集合的元素还是一个集合,就可以使用这个方法比较方法。

List<String> list = Arrays.asList("aa","bb","cc","dd");

有这么一个方法:

public class TestMain {
	// 接收一个字符串,将每个字符变成一个集合的元素,最后返回这个集合的流
    // 比如字符串 "abc", 变成List,['a','b','b'], 再返回这个list的流
    public static Stream<Character> getStream(String str){
        ArrayList<Character> list = new ArrayList<>();
        for(Character c : str.toCharArray()){
            list.add(c);
        }
        return list.stream();
    }
}

当我们使用map时:

List<String> list = Arrays.asList("aa","bb","cc","dd");
Stream<Stream<Character>> stream = list.stream().map(TestMain::getStream);
stream.forEach(s->{ s.forEach(System.out::println);});
// 输出
a
a
b
b
c
c
d
d

map操作对每个元素都执行一下上面的getStream方法,得到一个 嵌套流Stream<Stream<Character>> stream;使用forEach遍历时,就需要内部在使用一下forEach进行遍历。

如果使用flatMap 就没有这么麻烦了。

List<String> list = Arrays.asList("aa","bb","cc","dd");
Stream<Character> stream = list.stream().flatMap(TestMain::getStream);
stream.forEach(System.out::println);
// 输出
a
a
b
b
c
c
d
d

可以看到 flatMap 得到的Stream<Character> stream不是一个嵌套流,它把内部的流打开了。

3.2.3 排序 sort

java中排序就是前面讲到的 2个接口 Comparable 和 Comparator

因此排序也有2个重载的方法

  • Stream<T> sorted() 自然排序 ,要求元素必须实现 Comparable 接口

List<String> list = Arrays.asList("aa","dd","cc","bb");
list.stream().sorted().forEach(System.out::println);
// 输出
aa
bb
cc
dd
  • Stream<T> sorted(Comparator<? super T> comparator) 接收Comparator 实现类对象

List<String> list = Arrays.asList("aa","dd","cc","bb");
list.stream().sorted((s1,s2)-> -s1.compareTo(s2)).forEach(System.out::println);
// 输出
dd
cc
bb
aa

3.3 终止操作

3.3.1 匹配与查找

allMatch、anyMatch、noneMatch

  • boolean allMatch(Predicate<? super T> predicate) 检查是否匹配所有元素

List<String> list = Arrays.asList("aa","dd","cc","bb");
// 判断元素是否都等于 dd 
boolean dd = list.stream().allMatch(item -> item.equals("dd"));
System.out.println(dd); // false
  • boolean anyMatch(Predicate<? super T> predicate) 判断是否能够匹配到至少一个元素

List<String> list = Arrays.asList("aa","dd","cc","bb");
// 判断是否至少有1个元素等于 dd 
boolean dd = list.stream().anyMatch(item -> item.equals("dd"));
System.out.println(dd); // true
  • boolean noneMatch(Predicate<? super T> predicate) 判断是否全部不匹配

List<String> list = Arrays.asList("aa","dd","cc","bb");
// 判断是否 没有一个元素 等于 dd
boolean dd = list.stream().noneMatch(item -> item.equals("dd"));
System.out.println(dd); // false

findFirst、findAny

  • Optional<T> findFirst() 查找第一个元素 。一般配合排序后使用

List<String> list = Arrays.asList("aa","dd","cc","bb");
Optional<String> first = list.stream().findFirst();
System.out.println(first);
// 输出
Optional[aa]
  • Optional<T> findAny() 查找任意一个元素;串行流 老是查找到第一个。并行流就随机了。

List<String> list = Arrays.asList("aa","dd","cc","bb");
// 串行流
Optional<String> first = list.stream().findAny();
// 并行流
Optional<String> any = list.parallelStream().findAny();
System.out.println(first); //Optional[aa]
System.out.println(any); // Optional[cc]

min、max、count、

  • long count() 统计有多少元素,一般配合过滤操作使用

List<String> list = Arrays.asList("aa","dd","cc","bb");
long count = list.stream().filter(item->item.equals("cc")).count();
System.out.println(count); // 1
  • Optional<T> max(Comparator<? super T> comparator) 获取最大值,需要接收一个 Comparator 实现类

List<String> list = Arrays.asList("aa","dd","cc","bb");
Optional<String> max = list.stream().max(String::compareTo);
System.out.println(max);
// Optional[dd]
  • Optional<T> mix(Comparator<? super T> comparator) 获取最小值,需要接收一个 Comparator 实现类

List<String> list = Arrays.asList("aa","dd","cc","bb");
Optional<String> mix = list.stream().mix(String::compareTo);
System.out.println(mix);
// Optional[aa]

forEach 迭代遍历,内部迭代

  • void forEach(Consumer<? super T> action) 前面已经多次使用了forEach, 接收一个 Consumer 实现类对象。这是一个迭代方法

List<String> list = Arrays.asList("aa","dd","cc","bb");
list.stream().forEach(System.out::println);
// 输出
aa
dd
cc
bb

3.3.2 规约

  • T reduce(T identity, BinaryOperator<T> accumulator) 将流中的元素反复结合起来,得到一个值返回

  • Optional<T> reduce(BinaryOperator<T> accumulator)

  • <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

规约有上面3个重载的方法。

方法1:T reduce(T identity, BinaryOperator<T> accumulator) 接收的第一个参数是初始值,第二个参数类型是一个BinaryOperator 函数式接口,需要传递一个实现类对象。 有2个参数,一个返回值,类型都是一样的。

看源码:

public interface BinaryOperator<T> extends BiFunction<T,T,T> {省略其他}

可以看到,继承了BiFunction接口,而且三个泛型都是一样的。

看使用

List<String> list = Arrays.asList("aa","dd","cc","bb");
String reduce = list.stream().reduce("", (s1, s2) -> s1 + s2);
System.out.println(reduce);
// 输出
aaddccbb

再看看一个例子

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);

方法2、Optional<T> reduce(BinaryOperator<T> accumulator) 将流中元素反复结合起来,得到一个值。与上面的方法相比,这个方法没有了初始值,返回值类型也不同了。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Optional<Integer> reduce = list.stream().reduce(Integer::sum);
System.out.println(reduce);
// Optional[45]

方法3、<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

这个方法的解释参考:[五]java函数式编程归约reduce概念原理 stream reduce方法详解 reduce三个参数的reduce方法如何使用 - noteless - 博客园

第一个参数,依然是初始值

第二个参数是 计算逻辑

第三个参数是 多个结果的合并方式

大体就是 第三个参数是多个子任务的结果联合方式。在串行流中第三个参数没有什么用。但是在并行流中,第一个参数都会参与每一个线程的运算。这时,联合方式就有作用了。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(1, Integer::sum, Integer::sum);
Integer reduce2 = list.parallelStream().reduce(1, Integer::sum, Integer::sum);
System.out.println(reduce); // 46
System.out.println(reduce2); // 54

看了上面的文章你应该会理解为什么结果会不一样,因为初始值参与了每一个线程的计算。即多计算了几次。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer reduce = list.stream().reduce(8, Integer::sum, Integer::sum);
Integer reduce2 = list.parallelStream().reduce(8, Integer::sum, (n1, n2)-> n1+n2 - 8);
System.out.println(reduce); // 53
System.out.println(reduce2); // 53

解决方法是 要么初值为0,要么在联合的时候把多加的初值减掉。上面的代码就是采用减掉多加的初值。

3.3.3 收集

  • <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)

    这个collect方法 接收3个参数 第一个参数 是一个提供者函数是接口、第二个是 BiConsumer<R, T> 类型、第三个是BiConsumer<R, R>类型。

    这个方法用的较少,此处略过!

  • <R, A> R collect(Collector<? super T, A, R> collector) 方法接收一个 Collector 实现类对象

    Collectors 工具类提供了很多静态方法,可以产生 Collector 实现类对象,方便的收集 List、Set、Map等

    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
    // Collectors.toList() 获取Collector  实现类对象,收集为 List
    List<Integer> collect = list.stream().filter(item -> item > 5).collect(Collectors.toList());
    System.out.println(collect);
    // [6, 7, 8, 9]

    Collectors还有 toSet、toMap、toCollection 等方法

4、Optional 类

这个类主要是为了解决空指针异常,防止空指针异常污染代码。

Optional类是一个容器类。可以保存类型T的值,或者保存null,表示这个值不存在。

这是一个可以为null的容器对象,如果值存在,则isPresent方法会返回true、调用get方法会返回该对象。

否则返回false。

有一个Cat 类

public class Cat implements Comparable, Serializable {

    public static final long serialVersionUID = 1L;

    private Car car;

    public int age;
    public String type = "cat";

    @Override
    public int hashCode() {
        int result = age;
        result = 31 * result + (type != null ? type.hashCode() : 0);
        return result;
    }
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Cat)) {
            return false;
        }

        Cat cat = (Cat) o;

        if (age != cat.age) {
            return false;
        }
        return Objects.equals(type, cat.type);
    }


    @Override
    public int compareTo(Object o) {
        if ( !(o instanceof Cat)){
            throw new RuntimeException("必须传入相同类型的对象");
        }
        Cat cat = (Cat) o;
        if (this == cat){
            return 0;
        }
        return Integer.compare(this.age, cat.age);
    }

    public Cat(int age, String type) {
        this.age = age;
        this.type = type;
    }

    public Cat() {
    }

    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                ", type='" + type + '\'' +
                '}';
    }
}

再来一个类,Cat 作为 属性

public class BigCat {
    private Cat cat;

    public BigCat(Cat cat) {
        this.cat = cat;
    }

    public BigCat() {
    }

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "BigCat{" +
                "cat=" + cat +
                '}';
    }
}

使用:

Cat cat = new Cat();
// 使用of方法创建一个Optional实例, 参数不能为空
Optional<Cat> cat1 = Optional.of(cat);
// 使用empty方法创建一个空的实例
Optional<Object> empty = Optional.empty();
// 使用 ofNullable 创建实例, 可以为null
Optional<Cat> cat2 = Optional.ofNullable(cat);
// orElse: 如果当前对象不为空,就用这个对象,否则用传入的对象
Cat cat4 = cat2.orElse(new Cat());
System.out.println(cat4);

用法例子:

以前这样写: 会报空指针异常!

@Test
public void test1() throws Exception {
    BigCat bigCat = new BigCat();
    Integer catAge = getCatAge(bigCat);
    System.out.println(catAge);

}
public Integer getCatAge(BigCat bigCat){
    return bigCat.getCat().getAge();
}

一般是这样进行改进的:

@Test
public void test1() throws Exception {
    BigCat bigCat = new BigCat();
    Integer catAge = getCatAgeModify1(bigCat);
    System.out.println(catAge);

}
public Integer getCatAgeModify1(BigCat bigCat){
    if (Objects.nonNull(bigCat) && Objects.nonNull(bigCat.getCat())){
        return bigCat.getCat().getAge();
    }
    return -1;
}

但是有了Optional ,我们可以这样玩:

public Integer getCatAgeModify1(BigCat bigCat) {
    Optional<BigCat> bigCatOpt = Optional.ofNullable(bigCat);
    // orElse 返回的对象一定非空
    BigCat notNullBigCat = bigCatOpt.orElse(new BigCat());
    Cat cat = notNullBigCat.getCat();
    // 有2层,还要再包装一下
    Optional<Cat> optional = Optional.ofNullable(cat);
    Cat notNullCat = optional.orElse(new Cat());
    return notNullCat.getAge();
}

测试:

Integer catAge = getCatAgeModify1(null);
System.out.println(catAge);
// 0

就算放入null,也不会报错了。下面测试正常情况

BigCat bigCat = new BigCat();
bigCat.setCat(new Cat());
bigCat.getCat().setAge(15);
Integer catAge = getCatAgeModify1(bigCat);
System.out.println(catAge); // 15

如果得到一个Optional对象,如果获取里面的值呢?

可以使用get方法。但是需要保证一定要有值。可以先使用 isPresent 先判断一下再调用get。

【JAVA基础到此结束!!!】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值