java8的函数式编程

Lambda表达式

1.初步印象

示例代码:

View view = findViewById(R.id.textView2);
view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lambda", String.format("点击[%d]", v.getId()));
                Toast.makeText(MainActivity.this, "显示文字", Toast.LENGTH_SHORT).show();
            }
        });

这是我们经常设置监听器的代码,不管你要做的事情是什么,必须有:

new View.OnClickListener(){
  @Override
  public void onClick(View view){
    ...
  }
}

这代码就很繁琐,很多“杂音”,而且当你需要使用外部的Context时候,你还不得不老是调用 XXXX.this,Lambda表达式不会从超类(supertype)中继承任何变量名,也不会引入一个新的作用域

如果用Lambda表达式简化,代码如下:

  view.setOnClickListener(v -> {
    Log.i("lambda", String.format("点击[%d]", v.getId()));
    Toast.makeText(this, "显示文字", Toast.LENGTH_SHORT).show();
  });

Lambda表达式可以认为是对行为的抽象,表达式比较清晰的表明了一个我们要做什么

2.Lambda表达式常用形式

示例代码:

    // 没有参数的方法,也没有返回
    Runnable run = () -> System.out.println("do something");
    // 1个参数的方法可以省略(),且有返回, 只有一句, 默认认为是要返回表达式的结果
    IntUnaryOperator func = x -> x + 1; // 输入x, 返回 (x + 1)的结果
    // 2个参数的方法
    DoubleBinaryOperator func2 = (x, y) -> x + y;
    // 可以在()里面定义好变量的类型,多数情况下,可以不需要,由编译器自己推断参数类型
    IntBinaryOperator func3 = (int x, int y) -> x + y;
    // 多行语句,需要用{表达},且需要返回值的函数不能省略return
    IntBinaryOperator func4 = (x, y) -> {
        int temp = x % 3;
        int temp2 = y / 2;
        return temp + temp2;
    };

Lambda表达式是由左右两部和"->"构成的:
1.左边部分代表参数,无参用()表达,一个参数可以省略(),多个参数必须用()包含。
2.右边部分代表方法体,可以是一个表达式,也可以是{}包含的代码块

Lambda表达式对外部局部变量的引用属于值引用,类似匿名类引用外部局部变量时候,必须声明为final,只不过Lambda表达式引用时候,不需要显示的声明为final,减少代码杂音。

3.Java8对函数的抽象---函数接口

我们经常使用只有一个方法的接口来表示某特定方法行为,比如我们的OnClickListener。这种函数接口反复出现,现在Java8提供了一些核心的函数接口,抽象化了这些行为。(在java.util.function包下面),列出比较重要的几个函数接口如下:

1.Predicate
代表:参数为T -> Boolean,用于判断
示例 :

Predicate func = x -> x > 1;
System.out.println("是否大于1? " + func.test(2));

2.Consumer
代表:参数 T ->{} ,用于处理某事物
示例:

Consumer func = x -> System.out.println(x);
func.accept(2);

3.Function
代表 : 参数 T -> R, 用于对数据进行处理变换,返回R类型
示例:

Function func = p -> p.distance(0, 0);
System.out.println("点[3,4]距离原点的距离是? " + func.apply(new Point(3, 4)));

4.Supplier
代表:参数 ( ) -> T,生成对象
示例:

Supplier func = () -> new Point(3, 4);

5.UnaryOperator
代表:参数 T -> T,对T进行处理,并且返回同类型
示例:

UnaryOperator addSelf = x -> x + 1;
System.out.println("1+1="+addSelf.apply(1));

6.BinaryOperator
代表:参数(T, T) -> T ,变换返回同类型T
示例:

BinaryOperator max = (x, y) -> {
    return x > y ? x : y;
};
System.out.println("比较5和4,大的是: " + max.apply(4, 5));

Java8 流

Java8 对集合类库进行了大量修改,并且引入了新概念:流

1.外部迭代到内部迭代
/**
 * 计算字符串里面所有数字的和, 只考虑单一字符
 * 
 * @param text
 * @return
 */
private static void countNum(String text) {
    char[] arr = text.toCharArray();
    int sum = 0;
    for (int i = 0; i < arr.length; i++) {
        if (Character.isDigit(arr[i])) {
            sum += Character.digit(arr[i], 10);
        }
    }
    System.out.println(String.format("它们的和是[%d]", sum));
}

需要进行for循环,每次迭代都必须写类似代码,如果多个for循环,还要考虑很多其他问题,如果要并行处理,就需要修改for循环逻辑
传统的for循环,或者Iterator叫做外部迭代。Java 8提供了流,进行内部迭代,代码如下:

/**
 * 计算字符串里面所有数字的和
 * 
 * @param text
 * @return
 */
private static void countNumSeq(String text) {
    IntStream stream = IntStream.range(0, text.length())
        .mapToObj(i -> text.charAt(i))//拿到每个字符
        .filter(c -> Character.isDigit(c))//过滤掉不是数字的字符
        .mapToInt(c -> Character.digit(c, 10));//将所有字符转为数字
    System.out.println(String.format("它们的和是[%d]", stream.sum()));
}

看起来貌似代码复杂多了,但是我们清晰的看出了所有要执行的“意图”:
1.查找所有字符
2.过滤掉不是数字的
3.将字符转为数字
4.求和
而for循环代码无法如此清晰的表达我们需要执行的动作,所以代码得简洁性得到提高。

惰性求值方法:在stream方法里面,像mapToObj这样的方法,只刻画了什么样的stream而不会去产生新集合,结果也只是返回一个新的stream
及早求值方法:像count这样会得到一个具体值,会对集合真正进行操作

所以上面代码,只有真正执行到count(及早求值方法)时候,才会去对集合操作,不会进行多次循环。

2.常用流操作

可以发现,流基本全部都是传入前面提到的函数接口

1.Stream filter(Predicate predicate);
代表:对流进行过滤
2. Stream map(Function mapper);
代表:对流进行转换从T->R
类似:
IntStream mapToInt(ToIntFunction mapper);
LongStream mapToLong(ToLongFunction mapper);
DoubleStream mapToDouble(ToDoubleFunction mapper);
3. Stream flatMap(Function> mapper);
代表:将流转换为新类型的流,而map是得到新类型
示例如下:

/**
 * 合并流
 */
List together = Stream.of(asList(1, 2), asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(toList());
assertEquals(asList(1, 2, 3, 4), together);

4.Stream distinct();
代表:去重,依赖T.equals()方法判断
5.Stream sorted();
代表:排序,依赖T是可比较的,如果没有实现Comparable会报错
6.Stream sorted(Comparator comparator);
代表:排序
7.Stream peek(Consumer action);
代表:对流进行处理的时候,可以对这些被处理的元素进行消费,而不影响新集合内容

List list = Stream.of("one", "two", "three", "four","five", "six", "seven")
        .filter(e -> e.length() > 3)
        .peek(e -> System.out.println("Filtered value: " + e))
        .collect(Collectors.toList());
System.out.println("List size " + list.size());

输出结果:
Filtered value: three
Filtered value: four
Filtered value: five
Filtered value: seven
List size 4

8.Stream limit(long maxSize);
代表:最多拿取多少个源数据
上面代码加入:

stream.limit(2)

Filtered value: three
Filtered value: four
List size 2

9.void forEach(Consumer action);
代表:循环遍历
10.Stream skip(long n);
代表:对开始的n个数据忽略
上面代码加入:

stream.skip(1);

Filtered value: four
Filtered value: five
Filtered value: seven
List size 3

11.long count();
代表:获取新集合个数

12.boolean anyMatch(Predicate predicate);
13.boolean allMatch(Predicate predicate);
14.T reduce(T identity, BinaryOperator accumulator);
代表:reduce 操作可以实现从一组值中生成一个值,如 count方法,max方法,min方法,都可以用reduce来实现。

书本练习题: 只用 reduce 和 Lambda 表达式实现 Stream 上的map()

试着写了下,不知道是不是这个意思,如下:

/**
 * reduce模拟map操作
 */
private static  List map(Stream stream, Function todo) {
    List list = new ArrayList<>();
    stream.reduce(null, (init, e) -> {
        list.add(todo.apply(e));
        return init;
    });
    return list;
}

List list = map(Stream.of("1", "2", "3", "4"),
                (s) -> Double.valueOf(s));
list.stream()
    .forEach((d) -> System.out.println(d));

Java8 新增特性

1.接口静态方法

上面代码,我们在使用stream时候,可以如下调用:

Stream s = Stream.of("1", "2", "3", "4");

查看源码,发现Stream是接口,如下:

    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static Stream of(T... values) {
        return Arrays.stream(values);
    }

也就是说可以直接在接口里面定义我们自己的静态方法实现。

如果一个方法有充分的语义原因和某个概念相关,那么就应该将该方法和相关的类或接口放在一起,而不是放到另一个工具类中。这有助于更好地组织代码

2.接口default方法

解决场景(二进制接口的兼容性):

1.Java8中,对Collection 接口中增加了新的 stream()。
2.如果你在Java8 以前实现了自己的MyList继承了Collection接口,那么你在Java8运行时候会报错,因为以前的实现里面根本没有stream()
3.为解决如上问题,Java8新增default字段,标记默认实现方法Collection 接口告诉它所有的子类:“如果你没有实现 stream 方法,就使用我的吧。”接口中这样的方法叫作默认方法

public interface IStuff {
    String getName();
    double getPrice();

    default void showSelf() {
        System.out.println(String.format("[%s]%.02f", getName(), getPrice()));
    }
}

//子类的实现不一定要有showSelf,如果没有回默认使用default方法
IStuff stuff = new IStuff() {

    @Override
    public String getName() {
        // TODO Auto-generated method stub
        return "iPhone7";
    }

    @Override
    public double getPrice() {
        // TODO Auto-generated method stub
        return 4897.4647;
    }
};
stuff.showSelf();
3.方法引用

Lambda 表达式有一个常见的用法:Lambda 表达式经常调用参数,比如:

Function func = (i) -> new String[i];
Function func2 = p -> p.getX();

这种用法如此普遍,Java 8 提供了一个简写语法,叫作方法引用

func = String[] :: new;
func2 = Point :: getX;

形式就是** Classname :: Methodname** ,如果想调用构造函数则用new代替

参考:

《Java 8 函数式编程》

你可能感兴趣的:(java8的函数式编程)