Java面向对象系列[v1.0.1][集合]

Lambda表达式遍历集合

Interable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口,而Iterable接口是Collection接口的父接口,因此Collection可以直接调用该方法
程序调用Iterable的forEach(Consumer action)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法,因为Consumer是函数式接口,因此可以使用Lambda表达式来遍历集合元素

package demo;
import java.util.*;
import static java.lang.System.*;

public class CollectionEach
{
    public static void main(String[] args)
    {
        // 创建一个集合
        HashSet books;
        books = new HashSet();
        books.add("天苍苍野茫茫");
        books.add("风吹草地见牛羊");
        books.add("我欲乘风归去");
        books.add("又恐琼楼玉宇");
        // 调用forEach()方法遍历集合
        books.forEach(obj -> out.println("迭代集合元素:" + obj));
    }
}

Iterator遍历集合元素

Collection、Map系列用于装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,它也被称为迭代器
Iterator接口定义了如下4个方法:

  • boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回true
  • Object next():返回集合里下一个元素
  • void remove():删除集合里上一次next方法返回的元素
  • void forEachRemaining(Consumer action):使用Lambda表达式来遍历集合元素
package demo;
import java.util.*;
import static java.lang.System.out;

public class IteratorTest
{
    public static void main(String[] args)
    {
        // 创建集合、添加元素的代码与前一个程序相同
        var books = new HashSet();
        books.add("What are you doing?");
        books.add("How are you doing today!");
        books.add("I do not even know what are you doing!");
        // 获取books集合对应的迭代器
        var it = books.iterator();
        while (it.hasNext())
        {
            // it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
            var book = (String) it.next();
            out.println(book);
            if (book.equals("How are you doing today!"))
            {
                // 从集合中删除上一次next方法返回的元素
                it.remove();
            }
            // 对book变量赋值,不会改变集合元素本身
            book = "测试字符串";
        }
        out.println(books);
    }
}

Iterator必须依附于Collection对象,若有一个Iterator对象则必然有一个与之关联的Collection对象
book = “测试字符串”;虽然进行了赋值,但当在此输出集合的时候没有任何改变,也就是说当使用Iterator对集合元素进行迭代的时候,Iterator并不是把集合元素本身传递给了迭代变量,而是把集合元素的值传递给了迭代变量,所以修改迭代变量的值对集合元素没有任何影响。

package demo;
import java.util.*;
import static java.lang.System.out;

public class IteratorErrorTest
{
    public static void main(String[] args)
    {
        // 创建集合、添加元素的代码与前一个程序相同
        var books = new HashSet();
        books.add("what are you doing now?");
        books.add("How are you doing today!");
        books.add("I do not event know what are you doing!");
        // 获取books集合对应的迭代器
        var it = books.iterator();
        while (it.hasNext())
        {
            var book = (String) it.next();
            out.println(book);
            if (book.equals("How are you doing today!"))
            {
                // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                books.remove(book);
            }
        }
    }
}

当使用Iterator循环迭代访问集合元素时,集合不能被改变,因此books.remove(book);会抛异常,只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以。

Lambda表达式遍历Iterator

Java8为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Consumer参数也是函数式接口

package demo;
import java.util.*;
import static java.lang.System.out;
public class IteratorEach
{
    public static void main(String[] args)
    {
        // 创建集合、添加元素的代码与前一个程序相同
        var books = new HashSet();
        books.add("What are you donig?");
        books.add("How are you doing today!");
        books.add("How are you!");
        // 获取books集合对应的迭代器
        var it = books.iterator();
        // 使用Lambda表达式(目标类型是Comsumer)来遍历集合元素
        it.forEachRemaining(obj -> out.println("迭代集合元素:" + obj));
    }
}

foreach循环遍历集合元素

package demo;
import java.util.*;
import static java.lang.System.*;

public class ForeachTest
{
    public static void main(String[] args)
    {
        // 创建集合、添加元素的代码与前一个程序相同
        var books = new HashSet();
        books.add(new String("粒粒皆辛苦"));
        books.add(new String("汗滴禾下土"));
        books.add(new String("锄禾日当午"));
        for (var obj : books)
        {
            // 此处的book变量也不是集合元素本身
            var book = (String) obj;
            out.println(book);
            if (book.equals("我醉欲眠卿且去"))
            {
                // 下面代码会引发ConcurrentModificationException异常
                books.remove(book);
            }
        }
        out.println(books);
    }
}

当使用foreach循环迭代访问集合元素时,集合不能被改变,因此books.remove(book);会抛异常

Predicate操作集合

Java8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法将删除符合filter条件的所有元素,而Predicate也是函数式接口,因此可以使用lambda表达式坐位参数

package demo;
import java.util.*;
import java.util.function.*;
import static java.lang.System.out;

public class PredicateTest
{
    public static void main(String[] args) {
        // 创建一个集合
        var books = new HashSet();
        books.add("锄禾日当午锄禾日当午");
        books.add("汗滴禾下土汗滴禾下土");
        books.add("谁知盘中餐谁知盘中餐");
        books.add("粒粒皆辛苦粒粒皆辛苦");
        books.add("胡说带八道");
        // 使用Lambda表达式(目标类型是Predicate)过滤集合
        books.removeIf(ele -> ((String) ele).length() < 10);
        out.println(books);

        // 统计书名包含“疯狂”子串的图书数量
        out.println(calAll(books, ele -> ((String) ele).contains("锄禾")));
        // 统计书名包含“Java”子串的图书数量
        out.println(calAll(books, ele -> ((String) ele).contains("盘中餐")));
        // 统计书名字符串长度大于10的图书数量
        out.println(calAll(books, ele -> ((String) ele).length() > 10));
    }
    public static int calAll(Collection books, Predicate p)
    {
        int total = 0;
        for (var obj : books)
        {
            // 使用Predicate的test()方法判断该对象是否满足Predicate指定的条件
            if (p.test(obj))
            {
                total++;
            }
        }
        return total;
    }
}

长度小于10的字符串元素都会被删除。

Stream操作集合

  • Java8增加了Stream、IntStream、LongStream和DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素,其中Stream是个通用的流接口,而IntStream、LongStream和DoubleStream代表元素类型为int、long、double的流
  • Java8还为每个流式API提供了对应的Builder,例如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder,可以通过这些Builder来创建对应的流
  • 当使用Stream的时候,首先是使用对应的builder()类方法创建该Stream对应的Builder,然后重复调用Builder的add()方法向该流中添加多个元素,调用Builder的build()方法获取对应的Stream,调用Stream的聚集方法。
package demo;
import java.util.stream.*;
import static java.lang.System.out;

public class IntStreamTest
{
    public static void main(String[] args)
    {
        var is = IntStream.builder()
                .add(20)
                .add(13)
                .add(-2)
                .add(18)
                .build();
        // 下面调用聚集方法的代码每次只能执行一个
        out.println("is所有元素的最大值:" + is.max().getAsInt());
        out.println("is所有元素的最小值:" + is.min().getAsInt());
        out.println("is所有元素的总和:" + is.sum());
        out.println("is所有元素的总数:" + is.count());
        out.println("is所有元素的平均值:" + is.average());
        out.println("is所有元素的平方是否都大于20:" + is.allMatch(ele -> ele * ele > 20));
        out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele * ele > 20));
        // 将is映射成一个新Stream,新Stream的每个元素是原Stream元素的2倍+1
        var newIs = is.map(ele -> ele * 2 + 1);
        // 使用方法引用的方式来遍历集合元素
        // 输出41 27 -3 37
        newIs.forEach(out::println);
    }
}

Stream提供了大量方法进行聚集操作,有些方法是“中间方法”(intermediate),有些是“末端方法”(teminal)

  • 中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法,例如代码中的map()方法就是,中间方法的返回值是另外一个流
  • 末端方法:末端方法是对流的最终操作,当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用,例如代码中的sum()、count()、average()等方法都是末端方法

流的方法还有两个特征

  • 有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等,但性能开销ye’bi’jiao’da
  • 短路方法:该方法可以尽早结束对流的操作,不必检查所有的元素

Stream常用的中间方法:

  • filter(Predicate predicate):过滤Stream中所有不符合predicate的元素
  • mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素
  • peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素,该方法主要用于调试。
  • distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true),这是一个有状态的方法
  • sorted():该方法用于保证流中的元素在后续的访问中处于有序状态,这也是一个有状态的方法
  • limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数,这也是一个有状态的且短路方法

Stream常用的末端方法:

  • forEach(Consumer action): 遍历流中所有元素,对每个元素执行action
  • toArray():将流中所有元素转换为一个数组
  • reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素
  • min():返回流中所有元素的最小值
  • max():返回流中所有元素的最大值
  • count():返回流中所有元素的数量
  • anyMatch(Predicate predicate): 判断流中是否至少包含一个元素符合Predicate条件
  • allMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件
  • nonMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件
  • findFirst():返回流中的第一个元素
  • findAny():返回流中的任意一个元素

Java8之后允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,用于返回该集合对应的流

package demo;
import java.util.*;
import static java.lang.System.out;

public class CollectionStream
{
    public static void main(String[] args)
    {
        // 创建books集合、为books集合添加元素的代码与8.2.5小节的程序相同。
        var books = new HashSet();
        books.add("What are you doing now?");
        books.add("How are you doing today day day?");
        books.add("How do you do!");
        books.add("How do you do do do do!");
        books.add("How are you doing today?");
        // 统计书名包含“疯狂”子串的图书数量
        // 输出4
        out.println(books.stream().filter(ele->((String) ele).contains("疯狂")).count());
        // 统计书名包含“Java”子串的图书数量
        // 输出2
        out.println(books.stream().filter(ele->((String) ele).contains("Java") ).count());
        // 统计书名字符串长度大于10的图书数量
        // 输出2
        out.println(books.stream().filter(ele->((String) ele).length() > 10).count());
        // 先调用Collection对象的stream()方法将集合转换为Stream对象,
        // 再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream
        // 这个mapToInt()就是个中间方法,因此程序可以继续调用forEach()方法遍历IntStream中每个元素
        // 输出8 11 16 7 8
        books.stream().mapToInt(ele -> ((String) ele).length()).forEach(out::println);
    }
}
发布了207 篇原创文章 · 获赞 124 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105058082