Java SE8新特性-Lambda表达式与Stream

一、Lambda表达式

1.1 Lambda表达式

        只包含一个未实现方法的接口,称作“函数式接口”,最好加上注解 @FunctionalInterface

         这一类接口的匿名内部类的对象,可以用lambda表达式代替。

        ->符号,左边是要实现的方法的参数列表,右边是代码块。如果只有一句return xxx,那么可以同时省掉代码块的大括号边界和return关键字。

        强调:

  • C++可以将一个函数定义作为参数类型和返回值类型,并将函数指针作为实际参数和返回值

  • java8将“函数式接口”作为参数类型和返回值类型,并将一个实现了这个接口的类作为实际参数和返回值

@FunctionalInterface
interface FunInterface {
    String getString(String input);
}

public class A_01 {

    public static void main(String[] args) {
        FunInterface fa = (String a) -> "Yes, it is " + a;                    //Lambda表达式
        // FunInterface fa = a -> (String a) -> { return "Yes, it is " + a;}; 代码块如果比较复杂,用大括号括起来 
        // FunInterface fa = a -> "Yes, it is " + a;                          甚至可以更简单地写成这样
        FunInterface fb = new FunInterface() {                                //匿名内部类
            @Override
            public String getString(String input) {
                return "No, it is not " + input;
            }
        };

        System.out.println(fa.getString("java8!"));
        System.out.println(fb.getString("java8!"));
    }
}

1.2 ::操作符

        应当传入一个“函数式接口”的对象作为参数的地方,可以选择传入:

  • 一个实现了此接口的匿名内部类对象

  • 一个lambda表达式

  • 一个::表达式,这个表达式代表了一个方法,可以是

  •     对象名::非static方法

  •     类名::非static方法

  •     类名::static方法

  •     this::本对象中的方法

  •     super::父类对象中的方法

  •     类名::new (这是调用某个类的构造器,构造器其实就是一个生成对象的static方法)

@FunctionalInterface
interface ExampleInterface {
    int getInt(int input);
}

class Abc {
    static int fun(int a) {
        return a + 200;
    }

    int fun2(int a) {
        return a + 300;
    }
}

public class A_02 {
    static ExampleInterface face;
    static void setFace(ExampleInterface inputFace) {
        face = inputFace;
    }

    public static void main(String[] args) {
        ExampleInterface f = (int a) -> a + 100;
        setFace((int b) -> b - 100);
        System.out.println(face.getInt(0));

        setFace(f);
        System.out.println(face.getInt(0));

        setFace(Abc::fun);
        System.out.println(face.getInt(0));

        Abc bbb = new Abc();
        setFace(bbb::fun2);
        System.out.println(face.getInt(0));
    }
}

二、接口的新用法

        java8新增的接口特性,比较颠覆对“面向对象”的认知。

2.1 不必实现接口中每个方法

        接口中可以有default方法,default方法已经被实现了。

        在接口的实现类中:

  • 如果不重写default方法,则使用interface定义中的方法实现代码

  • 如果重写default方法,则使用实现类中的方法实现代码

        这么做,可以给以往的接口中增加方法,不然接口增加了某方法,所有已有的实现类都无法编译通过了……

interface ExampleInterface {
    int aaa(int input);
    default int bbb(int input) {
        return input + 500;
    }
}

public class B_02 implements ExampleInterface {
    @Override
    public int aaa(int input) {
        return input + 1000;
    }

//    @Override
//    public int bbb(int input) {
//        return input - 1000;
//    }

    public static void main(String[] args) {
        B_02 obj = new B_02();
        System.out.println(obj.aaa(0));
        System.out.println(obj.bbb(0));
    }
}

2.2 接口中可以有static方法

        接口中可以有static方法,这一类方法必须在接口定义中实现。

        可以直接通过接口来调用这个方法。

interface SimpleInterface {
    int aaa(int input);
    static int bbb(int input) {
        return input + 1000;
    }
}

public class B_01 {
    public static void main(String[] args) {
        System.out.println(SimpleInterface.bbb(0));
    }
}

三、使用stream操作集合

        先给一个直观的例子,看看stream又起来有多么方便吧。

        要统计一个String列表中符合某个条件的元素的数量,本来我们要写这么一段:

List words = Arrays.asList("abc", "defh", "ghiko");
int count = 0;
for (String word : words) {
    if (word.length() >= 4)
        ++count;
}

但是使用stream之后,我们使用这么一句就行了:

List words = Arrays.asList("abc", "defh", "ghiko");
long count2 = words.stream().filter(w -> w.length() >= 4).count();

      可以把stream看多“集合”或者“数组”的一个抽象,我们可以对stream做过滤、聚合、统计,并把stream还原称为一个集合或者数组。

        stream并不是“集合”本身:

  1. stream不会存储元素

  2. stream不会改变集合对象本身

  3. stream可能被延迟执行

        注意,一个stream不能多次生成最终对象;复用stream对象时,应当十分谨慎。

3.1 创建stream对象

        创建stream对象有若干种方法:

  1. 集合类,调用 stream() 方法

  2. 数组,调用 Stream.of() 静态方法

List list = Arrays.asList(1, 2, 3);
Stream a = list.stream();

Integer[] array = {1, 2, 3};
Stream b = Stream.of(array);

Stream c = Stream.of(1, 2, 3);

       也可以创建含有无限多元素的stream

  1. Stream.generate() 静态方法,接收一个无参的方法(其实是Supplier<T>对象)作为参数

  2. Stream.iterate() 静态方法,第一个参数是“种子”,第二个参数是一个x->y类型的方法,用于根据种子产生下一个值

Stream d = Stream.generate(() -> 1234);
Stream e = Stream.generate(Math::random);

Stream f = Stream.iterate(Integer.MIN_VALUE, n -> (n + 1) / 10);

3.2 stream的转化

        使用map方法时,对stream中的所有元素逐一调用某个方法,转换成另一个对象,并将返回值收集到一个新的stream中。

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Stream sstream = list.stream().map(Integer::toBinaryString);

3.3 stream的过滤

        filter方法的参数,是一个返回值为boolean类型的方法。作为此方法的参数返回true的对象,是符合要求的对象,被放入到新的流之中。

        limit返回一个只包含n个元素的新stream。

        skip返回一个丢弃了前n个元素的新stream。

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);\

Stream moreThan5 = list.stream().filter(n -> n > 5);
Stream limit = list.stream().limit(4);
Stream skip = list.stream().skip(5);

3.4 Optional泛型

        Optional是一个T类型对象的封装,但是比T类型的对象使用起来更安全,功能也更多。

        以下的一些函数会以Optional作为返回值的类型,因此有必要了解这种泛型的用法。

        Optional中有一个final T value字段,其值不可改变,因此Optional对象初始化之后,其中包含的值就不能改变。

创建一个Optional对象

Optional<Integer> op = Optional.empty();
op = Optional.of(100);

得到对象中的值

如果没有,会抛出NoSuchElementException

int i = op.get();

检查其中是否有值

boolean b = op.isPresent();

如果其中有值,就对其中的值执行某个方法

op.ifPresent(v -> System.out.println(v * 10));

如果其中没有值,想返回些别的……

op = Optional.empty();
int
ii = op.orElse(3); //返回某个值
int iii = op.orElseGet(() -> 10); //返回某个方法的执行结果
int iiii = op.orElseThrow(RuntimeException::new); //抛出一个异常

3.5 stream的聚合

        一个SQL查询,可以用MAX,MIN,COUNT,SUM等函数得到单个的值。stream也有类似的聚合操作方法。

        感觉可以大致分为几类:最大最小值,加和,数量,是否有满足条件的元素,返回一个满足某条件的元素……

List list = Arrays.asList("ab", "cde", "fegr", "opfsg", "dgasdfa");
Optional op = list.stream().filter(v -> v.length() > 4).findFirst();
boolean b = list.stream().anyMatch(v -> v.endsWith("sg"));

List la = Arrays.asList(1, 3, 4, 5, 3, 2);
Optional i = la.stream().max((aa, ba) -> aa - ba);

3.6 stream生成数组、集合

        一个stream做完各种转换之后,可以将其恢复成为数组或者集合。

        生成数组:使用toArray方法,传入一个数组的构造方法作为参数,否则会返回Object[]类型。

List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);

Object[] arr = list.stream().map(n -> "int:" + n).toArray();
String[] arrr = list.stream().map(n -> "int:" + n).toArray(String[]::new);

        生成List:使用collect方法,注意参数

List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
List ll = list.stream().map(n -> "int:" + n).collect(Collectors.toList());

        生成Map:使用collect方法,使用不一样的参数。

        解释一下toMap的三个参数。第一个是map的键;第二个是map的值;第三个是有重复键时的解决方法。

List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
Stream ss = list.stream().map(n -> "int:" + n);
Map mm = ss.collect(Collectors.toMap(n -> n
        , n -> n.substring(2)
        , (existvalue, newvalue) -> newvalue));

        生成指定实现的集合,有两种方式。一种是使用Collectors.toCollection,参数是集合的构造方法;的第二种是三个参数,分别给出构造方法、添加方法、批量添加方法。

List list = Arrays.asList(1, 2, 4, 3, 8, 5, 6, 9, 0, 4);
LinkedList lll = list.stream().map(n -> "int:" + n).collect(Collectors.toCollection(LinkedList::new)); //指定使用LinkedList
HashSet hhh = list.stream().map(n -> "int:" + n).collect(HashSet::new, HashSet::add, HashSet::addAll); //指定使用HashSet

3.7 分组与分片

        其行为很像SQL中的GROUP BY操作。

        重点关注示例中的Collectors.groupingBy方法,带的参数决定了生成的Map的key/value的类型。不带参数时,默认为对象的List;带参数时,可以是set等别的集合,也可以是一个计算得到的值。

static class Demo {
    public Demo(int aa, int bb) {
        a = aa;
        b = bb;
    }
    private int a;
    private int b;

    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }

    @Override
    public String toString() {
        return String.format("{a:%d, b:%d}", a, b);
    }
}

public static void main(String[] args) {
    List demoList = new ArrayList<>();
    demoList.add(new Demo(1, 12));
    demoList.add(new Demo(1, 13));
    demoList.add(new Demo(2, 22));
    demoList.add(new Demo(2, 23));
    demoList.add(new Demo(3, 33));
    demoList.add(new Demo(3, 34));

    Map> map = demoList.stream()
            .collect(Collectors.groupingBy(Demo::getA));
    System.out.println(map);

    Map> map2 = demoList.stream()
            .collect(Collectors.groupingBy(Demo::getA, Collectors.toSet()));
    System.out.println(map2);

    Map map3 = demoList.stream()
            .collect(Collectors.groupingBy(Demo::getA, Collectors.averagingInt(Demo::getB)));
    System.out.println(map3);
}

四、函数式接口

        第一大节已经给出了“函数式接口”的定义,就是只有一个未实现的方法,且用@FunctionalInterface注解标注的接口。

        上面的例子中,各种接受一个lambda表达式、::表达式作为参数的地方,其实际的参数类型,实际就是函数式接口。

        以下是书中总结的函数式接口列表

接口定义(只写了未实现的方法,忽略了static和default方法)

描述

@FunctionalInterface
public interface Supplier<T> {
T get();
}

提供一个T类型的值

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

处理一个T类型的值

*/
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}

处理两个类型,分别是T和U的值

@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

输入一个类型T,得到boolean值

@FunctionalInterface
public interface ToIntFunction<T> {
int applyAsInt(T value);
}

@FunctionalInterface
public interface ToLongFunction<T> {
long applyAsLong(T value);
}

@FunctionalInterface
public interface ToDoubleFunction<T> {
double applyAsDouble(T value);
}

 

 输入一个类型T,得到int/long/double值

@FunctionalInterface
public interface IntFunction<R> {
R apply(int value);
}

@FunctionalInterface
public interface DoubleFunction<R> {
R apply(double value);
}

@FunctionalInterface
public interface LongFunction<R> {
R apply(long value);
}

输入一个int/double/long值,产生一个R类型的值

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

}

输入T类型值,返回R类型值

@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}

输入T、U类型值,得到R类型值

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

由T类型得到T类型

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

}

由两个T类型得到T类型

仅供内部使用,未经授权,切勿外传

你可能感兴趣的:(java8,stream)