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>
代表:参数为T -> Boolean,用于判断
示例 :

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

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

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

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

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

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

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

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

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

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

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

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

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

List<String> 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<T> limit(long maxSize);
代表:最多拿取多少个源数据
上面代码加入:

stream.limit(2)

Filtered value: three
Filtered value: four
List size 2

9.void forEach(Consumer<? super T> action);
代表:循环遍历
10.Stream<T> 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<? super T> predicate);
13.boolean allMatch(Predicate<? super T> predicate);
14.T reduce(T identity, BinaryOperator<T> accumulator);
代表:reduce 操作可以实现从一组值中生成一个值,如 count方法,max方法,min方法,都可以用reduce来实现。

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

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

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

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

Java8 新增特性

1.接口静态方法

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

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

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

    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> 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<Integer, String[]> func = (i) -> new String[i];
Function<Point, Double> func2 = p -> p.getX();

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

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

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

参考:

《Java 8 函数式编程》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,144评论 1 2
  • 原文http://www.codeceo.com/article/learn-java-lambda.html L...
    与我常在1053阅读 1,088评论 1 7
  • 简介 概念 Lambda 表达式可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主...
    刘涤生阅读 3,159评论 5 18
  • 注:之前关于Java8的认知一直停留在知道有哪些修改和新的API上,对Lambda的认识也是仅仅限于对匿名内部类的...
    mualex阅读 2,775评论 1 4
  • 原文链接: Lambdas 原文作者: shekhargulati 译者: leege100 lambda表达式是...
    忽来阅读 6,491评论 8 129