`

Java集合框架(二)--Java8新增的函数式集合操作方式

阅读更多
这是Java集合框架第二篇,介绍关于Java8新增的函数式集合操作方式

1、简单说一说Java8的新特性

在我看来,Java8新增的所有特性都是为FP(函数式编程)服务的,这就要求我们要有FP思维。长久以来,我们一直在OOP(面向对象编程)的思想下编程,OOP确实很不错,提供了清晰的接口声明,但是OOP的实现代码比较啰嗦,冗余的代码也比较多。而FP提供了更加简洁明了的语法,但是纯用FP的代码又比较晦涩难懂。这时就有人提倡接口声明和框架分层之间使用OOP,而在具体的实现或者算法封装中使用FP,这样就把OOP和FP的优点都结合起来了。更有甚者,有篇文章提到,接口声明使用Java提供,具体实现使用Groovy(JVM上一门动态语言,也包含了函数式的特性,还包含了元编程)实现。

参考这篇文章:
Java与groovy混编 —— 一种兼顾接口清晰和实现敏捷的开发方式
https://segmentfault.com/a/1190000002601659

这涉及到JVM多语言混合编程,提高了编程的复杂性,其实我们使用Java8的新特性也可以做到函数式编程了。而在Java8中,新增了很多函数式的特性,比如lambda表达式、高阶函数、闭包等,尽管依旧没有提供函数柯里化与偏函数(函数部分调用)的特性。

解释一下Java8中新增的函数式特性的概念

1)高阶函数:一种可以将函数作为参数传入或者将函数作为返回值的函数。

2)Lambda表达式:一种将类似数学表达式赋值给变量的语法,这个数学表达式其实就是被语法糖简化了的函数。然后将这个lambda表达式作为参数或返回值的函数就是高阶函数。
lambda有点像Java8之前的匿名内部类,其实不是。它们在JVM中的底层实现方式是不一样的,所以造成的结果也不完全一样。lambda与匿名内部类一个明显的区别就是this指针的引用。匿名内部类的this是指向该匿名内部类本身,而lambda则是指向使用该lambda表达式的对象。Java8的lambda表达式是作为实现只有单个抽象方法的接口来使用的。这种接口也叫函数式接口。

3)闭包:一种变量作用域,其实就是在lambda表达式中引用了外部变量时,这个外部变量自动变成final修饰的不可变变量了。也就是说这个变量在被赋予初始值后,不管是在lambda表达式外部还是内部都不可改变了。

4)Java8的接口语法被重新设计了,原本接口是全部都是抽象函数的OOP契约,现在接口中可以有很多default默认方法(其实就是实例方法)和static静态方法。其实变得跟抽象类的作用差不多了。不过永远记住default方法只在重构代码时才考虑引入,否则会容易造成多重继承代码无法编译通过的情况。

5)Java8的流式编程,这个才是Java8函数编程的核心,通过在集合框架的顶层接口中新增default方法的方式,为原本无法扩展的集合API提供了函数式编程的入口方法,比如stream()方法和parallelStream()方法。此外在其它的API中也提供了流式编程的入口,这里只介绍Java集合方面的。

2、Java8新的集合操作方式
在做了这么多啰嗦的介绍之后,终于进入本部分的正题了。
1) Iterator接口中新增的foreach()默认方法
先看下foreach方法的源代码:
default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
}


这个默认方法接受一个函数式接口Consumer作为参数,所以我们可以使用lambda作为参数,这个方法其实就是将集合遍历了一下,然后对每个集合元素进行了参数中lambda表达式的执行。
其实就是将遍历的行为交给了forEach()方法,我们只需要提供遍历的行为即可。

在没有forEach()方法之前,我们是这样进行遍历的
for (Integer e : ad) {
			System.out.println(e);
		}


而使用forEach()方法则把三行代码变成了一行
ad.forEach(e -> System.out.println(e));


实际上我们是实现了源码中的action.accept(t);这一行代码。

好吧,我承认这个forEach()方法好像并没有什么用。

2) Collection接口新增的removeIf()默认方法

先看一下removeIf()方法的源代码
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;
}


其实removeIf()方法就是帮我们把集合遍历了一把,然后把满足条件的元素删掉了。

没有removeIf()方法之前,我们是这样做的

首先有这么一个集合
List<Integer> al = new ArrayList<Integer>();
		al.add(4);
		al.add(3);
		al.add(5);


然后我们要遍历和删除大于3的元素
Iterator<Integer> it = al.iterator();
		while(it.hasNext()){
			if(it.next()>3){
				it.remove();
			}
		}


而使用removeIf()方法代码则一行代码就解决了
al.removeIf(e -> e>3);


3) Collection接口新增的stream()和parallelStream()方法

stream()和parallelStream()方法都是获取了一个Stream对象,之后的编程方式是一样。不同的是stream()得到的是串行流,也就是流对象的操作是单线程执行的。parallelStream()得到的是并行流,也就是流对象的操作可能是多线程同时进行的,然后将结果合并到一起。

永远记住Java8的流式编程没有改变原来的集合类,而是在流式方法调用之后产生了新的集合或者结果。

这里以stream()为例介绍函数式编程的三板斧(filter、map、reduce)
其实就是筛选、映射、合并。

还是先以面向对象的例子说起

如果我们要在一个集合中遍历并筛选出部分元素进行做乘2操作后求和,之前我们是这样做的
先有一个集合
List<Integer> al = new ArrayList<Integer>();
		al.add(4);
		al.add(3);
		al.add(5);
		al.add(7);
		al.add(6);


筛选大于4的元素记性乘2操作后求和
int sum = 0;
		for (Integer e : al) {
			if(e >4){
				sum = sum + e*2;
			}
		}


如果使用stream()的方式来操作是这样的
int sum = al.stream()
	.filter(e -> e>4)
	.map(e -> e*2)
    .reduce((e1,e2) -> (e1+e2)).get();


有些人认为使用collect来替代reduce好一些,其实是一样的,只是少了个get()操作而已。
int sum = al.stream()
	.filter(e -> e>4)
	.map(e -> e*2)
    .collect(Collectors.summingInt(e -> e));


4) List接口新增的sort()默认方法

先看下源码
default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
}


可以看到sort方法就是接受了一个Comparator比较器对象,然后将List集合转换成数组,然后再调用数组工具类Arrays的sort()方法进行排序,最后把排好序的数组遍历并赋值到原来的List集合上。

原来我们是使用Collections.sort()方法来进行排序的,现在这个Collections.sort()方法直接将实现委托到List接口的默认方法sort()下了。其实是一样的。
在Java8里面唯一比以前方便的就是可以直接使用lambda表达式传入Comparator比较器的实现参数了。

假设我们在List存放的元素是下面这个类
class KeyTest{
	int value;

	public KeyTest(int value){
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
	
}


然后往List插入几个KeyTest元素
List<KeyTest> al = new ArrayList< KeyTest>();
		al.add(new KeyTest(4));
		al.add(new KeyTest(3));
		al.add(new KeyTest(5));


然后排序
al.sort((e1,e2) -> Integer.valueOf(e1.getValue()).compareTo(e2.getValue()));


当然,sort()方法也可以传入null值作为参数,这要求插入List中的元素必须实现Comparable接口,即
如果KeyTest实现了Comparable接口
class KeyTest implements Comparable<KeyTest>{
		int value;

		public KeyTest(int value){
			this.value = value;
		}
		
		public int getValue() {
			return value;
		}

		@Override
		public int compareTo(KeyTest o) {
			return Integer.valueOf(this.value).compareTo(o.value);
		}
	}


那么排序时就可以这样
al.sort(null);


至此,Iterator继承系列的接口新增的默认方法就介绍完了,接下来说一说Map接口新增的一些默认方法,不过Map接口没有提供转换成Stream接口的方法。

5) Map接口新增的forEach()默认方法

这个Map接口的forEach()默认方法与Collection接口的forEach()方法用法是一致的,不过这里遍历的是Map的entrySet,看下源码就知道了。
default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
}


我们要实现的方法是action.accept(k, v);
map.forEach((k, v) -> System.out.println(k + "->" + v));


6) Map接口新增的replaceAll()默认方法

这个方法的逻辑其实是把entrySet遍历了一把,然后把所有的value替换成lambda表达式计算的结果,key值保存不变。

先看下源码
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }

            // ise thrown from function is not a cme.
            v = function.apply(k, v);

            try {
                entry.setValue(v);
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
        }
}


关键的代码是这行
v = function.apply(k, v);


我们传入的lambda参数需要实现这个apply()方法
有这么一个Map集合
Map<String, String> map = new HashMap<String, String>();
		map.put("test", "value test");
		map.put("jdbc", "value jdbc");
		map.put("spring", "value spring");


我们需要对这个map的所有value的”value”字符串变成”element”,那么
map.replaceAll((k, v) -> v.replaceAll("value", "element"));


使用之前的forEach()方法遍历就可以看到结果




7) Map接口其余的默认方法
以下几个方法均是对value取值和设置的方法

V getOrDefault(Object key, V defaultValue)
若key值对应的value为null,则返回defaultValue

V putIfAbsent(K key, V value)
若key值对应为value为null,设值为value,否则不操作

V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction)
若key值对应的value为null,根据mappingFunction计算的值设值,否则不操作

V computeIfPresent(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction)
若key值对应的value不为空,根据remappingFunction计算的值设值,否则不操作

V compute(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction)
根据remappingFunction计算的值对key对应的value设值,若计算的值为null,则删除key-value键值对

V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction)
若key对应的value为null,则直接将第二个参数value对key对应的value设值。若key对应的value不为null,根据remappingFunction计算的值对key对应的value设值。若计算的值为null,则删除key-value键值对。

最后,Java8对集合框架新增的默认方法大部分都是传入lambda表达式作为参数的高阶函数,刚接触函数式编程的同学可能觉得不容易看懂,其实对函数式编程的概念理解清楚之后,直接看这些API的源代码是比较容易的。
  • 大小: 6.7 KB
1
0
分享到:
评论

相关推荐

    java8集合源码-java8-problems-:java8练习题及解答

    Java 8对集合框架的改进主要包括函数式编程的支持、流(Stream)API的引入以及Lambda表达式的应用。 1. **Lambda表达式**:Java 8引入了Lambda表达式,这是一种简洁的匿名函数表示方式。Lambda可以作为方法参数,也...

    02-Java集合容器面试题-重点.docx

    List接口是Java集合框架中的一种接口,提供了对元素的添加、删除、遍历等操作。List接口的实现类有ArrayList、LinkedList、Vector等。 ArrayList ArrayList是List接口的一种实现类,提供了高效的随机访问和插入...

    java经典面试题目-面经-java-Java语言的进阶概念-常用的库和框架-并发编程-网络编程-Web开发-面经

    Stream API是Java 8新增的特性,用于处理集合数据,提供一种声明式编程风格。它可以方便地进行过滤、映射、聚合等操作,适用于大量数据的计算。例如,从列表中找出所有偶数:`list.stream().filter(n -&gt; n % 2 == 0)...

    Core Java, Volume II--Advanced Features(10th)

    Java集合框架是处理对象集合的重要工具。本书详细阐述了`List`、`Set`、`Map`接口及其实现类的使用,包括`ArrayList`、`LinkedList`、`HashSet`、`HashMap`等,以及高级集合类如`TreeSet`、`TreeMap`、`...

    JDK1.8 Java 官方 jdk-8u181-windows-x64.rar

    JDK 1.8新增了多个函数式接口,如Supplier、Consumer、Function、Predicate等,这些接口极大地丰富了Java的函数式编程能力。 3. **Stream API** Stream API是JDK 1.8引入的新特性,提供了一种处理数据集合的新方式...

    java jdk.8.0-45.rar 安装包

    - **Lambda表达式**:这是Java 8的一个重要新增特性,允许以更简洁的方式处理函数式编程。Lambda表达式可以作为方法参数,也可以被赋值给变量,使得代码更加简洁、易读。 - **方法引用和构造器引用**:与Lambda...

    Java 8编程入门官方教程

    Java 8引入了函数式接口的概念,这是一种只有一个抽象方法的接口。通过这种方式,Java支持了Lambda表达式,并使得接口能够作为方法参数传递,实现了真正的函数式编程风格。例如,`java.util.function`包下提供了多个...

    Lambda表达式和Java集合框架

    Lambda表达式和Java集合框架是Java8中两个重要的概念,本文主要介绍了这两个概念的相关知识,并对Java集合框架中新增的方法进行了详细的讲解。下面是相关知识点的总结: 一、Lambda表达式 Lambda表达式是Java8中...

    java面试——厦门-中软国际-Java中级.zip

    - Lambda表达式、Stream API、Optional类、函数式编程的概念。 - 新增的时间日期API:LocalDate、LocalTime、LocalDateTime等。 - 方法引用和构造器引用。 11. **Spring框架**: - AOP(面向切面编程)和IOC...

    Java8 API.rar_JAVA8API_Java 8_java 8 api_java 8 api下载_java8

    在反射API中,Java 8新增了MethodHandle和MethodHandles类,这些类提供了更灵活、更高效的动态类型操作方式,特别是在需要高性能和低级别的元数据访问时。 总的来说,Java 8 API文档包含了关于以上所有特性的详细...

    openjdk-8u292-b10-windows-x64.zip

    - Java 8引入了Lambda表达式,这是一种简洁的函数式编程语法,可以用于处理集合数据,简化回调函数,提高代码可读性和维护性。 - Lambda表达式与函数接口结合,使得匿名内部类的使用更为简便,减少了冗余代码。 3...

    Java发展史_&_Java9、10新特性

    - 集合框架 - EJB - Swing - Servlet+JSP+JavaBean - **2000年:Java 1.3** - 代号:Kestrel - 类或接口数量:1840 - 主要新特性或功能: - 内置HotSpot VM - 改进RMI对CORBA的兼容性 - 加入动态代理相关...

    Core-Java-8th-Edition.rar_core java II

    `Runnable`和`Callable`是早期版本的函数式接口,而在Java 8中,新增了如`Supplier`、`Consumer`、`Function`等接口,进一步丰富了函数式编程的生态。 Java 8还带来了Stream API,它为集合操作提供了新的方式,使得...

    Java集合容器面试题

    集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现...

    jdk-8u91-windows-x64.zip

    8. Parallel Collectors:Java 8对集合框架进行了优化,引入了并行流和并行收集器,可以利用多核处理器的优势,提高集合操作的性能。 对于描述中提到的NASA的Panoply软件,它是一个开源的数据可视化工具,能够读取...

    jdk-8u211-windows-x64.zip

    - Lambda表达式:JDK 8引入了函数式编程的概念,允许开发者使用简洁的lambda语法来表示匿名函数。 - 方法引用和构造器引用:这是对lambda表达式的补充,可以直接引用方法或构造器,简化代码。 - Stream API:提供...

    Pro Java 8 Programming

    8. **Parallel Collections**:Java 8对集合框架进行了优化,支持并行操作。比如,List的parallelStream()方法可以创建并行流,利用多核处理器提高执行速度。 9. **Nashorn JavaScript引擎**:Java 8引入了Nashorn ...

    java8参考手册

    同时,集合框架也得到了增强,如`EnumSet`和`EnumMap`的性能提升。 这个Java 8参考手册中的API文档详细列出了所有这些新特性的使用方式和示例,是开发者查阅和学习的宝贵资料。通过深入理解并熟练运用这些特性,...

    Java程序设计(第二版)-电子教案.rar

    13. **Java 8及以后的更新**:教程可能还会涉及Java 8引入的Lambda表达式、Stream API以及函数式编程的概念,以及后续版本的新增特性。 通过这份《Java程序设计(第二版)-电子教案》,学习者不仅可以掌握Java语言...

    java函数速查中文版.rar

    3. **集合框架**:Java集合框架包括`List`、`Set`、`Map`等接口及其实现类,如`ArrayList`、`HashSet`、`HashMap`等。函数速查会列出如何添加、删除、查找元素,以及遍历集合的方法。 4. **输入/输出**:`File`类和...

Global site tag (gtag.js) - Google Analytics