一、背景
1、Java Collections Framework(java集合框架)
JavaAPI提供了许多接口和类,允许我们创建集合,这些集合是一组需要储存的对象的容器。
对一组对象进行操作在编程中非常常见,因此JavaAPI专门为此目的提供了一组接口和类。这些接口和类在java.util包中,被称为Java集合框架(JCF)。我们可以使用这些接口和类来创建集合,这些集合可以充当其他对象的容器。可以使用一个集合来存储一组对象。存储在集合中的每个对象都被称为元素。集合允许我们添加、检索和删除对象。
2、集合的种类
有三种一般类型的集合:列表(lists)、集合(sets)和映射(maps)。
列表——列表是一个有序的集合,它按位置对其元素进行排序。元素x在列表中的位置也称为其索引,索引的值等于x元素之前的元素的数量。列表允许元素重复,即可以将两个相等的对象添加到列表中,列表根据其索引来区分相同的对象。
集合——在不允许重复的无序对象集合中的集合。与列表不同,一个集合没有其元素的位置概念。集的实现通常为搜索进行了优化,因此您可以快速找到特定的存储值。
映射——map根据x的属性存储每个对象x。这意味着映射中的每个元素都是一个键值对。键是对象的属性,值是对象本身。一个映射允许根据其键快速检索一个值。
3、函数式接口
java.util.function包定义了许多由JCF接口和类中定义的方法所引用的函数接口。在实际应用中,函数接口用于声明作为参数传递给集合类方法的lambda表达式接受的参数和返回的值的类型(一定注意!!lambda表达式实现的接口中的方法的参数类型和返回值类型规定了lambda表达式中接受的参数和返回的值的类型,而lambda表达式本身的类型由这个表达式接受的参数数量和是否需要返回值规定,与参数的数据类型和返回值的数据类型无关,只与参数数量和有无返回值有关!!后面会再说到)。了解命名这些函数式接口的命名方式将有助于更好地理解引用它们的JCF方法。命名系统由两部分组成,“根”部分使用supplier, consumer, predicate, operator, and function,前缀部分使用binary, bi, unary以及一些基础数据类型的名字。
事实上,supplier, consumer, predicate, operator, and function都是java中已经定义好的类,如果我们把一个操作赋给一个变量,我们如何确定这个变量的类型呢?我们并不以操作的返回值来确定变量的类型(如果我们将操作写进方法中,我们就是以操作最后的返回值来确定方法的类型的,但是这里是不一样的)而是根据操作需要接受的参数的数量 和 是否需要返回值来确定那个被赋值的变量的类型。以consumer为例,这个类代表接受一个参数,不返回值的操作的类型,源码如下:
import java.util.Objects;
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* 表示“接受一个参数输入且没有任何返回值的操作“。不同于其它的函数式接口,Consumer期望通过方法的实现来执行具体的操作。
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* 可实现方法,接受一个参数且没有返回值
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* 默认方法,提供链式调用方式执行。执行流程:先执行本身的accept在执行传入参数after.accept方法。
* 该方法会抛出NullPointerException异常。
* 如果在执行调用链时出现异常,会将异常传递给调用链功能的调用者,且发生异常后的after将不会在调用。
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
A function takes at least one parameter and returns a value. The generic interface Function<T, R> takes a parameter of type T and returns a value of type R . A bi-function takes two parameters and returns a value: the generic interface BiFunction<T, U, R> takes two parameters of type T and U , in that order, and returns a value of type R .It is useful to have a short hand notation for functional types. We will use the notation T → R for the type Function<T, R> and (T, U) → R for the type BiFunction<T, U, R> . Similar shorthand notation will be used for other functional types.There are also functional interfaces that involve the primitive types int , long , and double . For example,the interface IntFunction<R> is the type int → R, the interface LongFunction<R> is the type long →R, and IntToLongFunction is the type int → long .An operator is a function whose parameters and return value have the same type. A binary operator is an operator that takes two parameters, and a unary operator is an operator that takes a single parameter. Thus BinaryOperator<T> has type (T, T) → T, the interface UnaryOperator<T> has type T → T, and LongUnaryOperator has type long → long .A consumer takes at least one parameter and returns no result. The interface Consumer<T> has type T → void , while the interface BiConsumer<T, U> has type (T, U) → void .A supplier is the opposite of a consumer: it takes no parameters and returns a value. The interface Supplier<T> has type ( ) → T and IntSupplier has type ( ) → int .A predicate is a function that returns a boolean value. Thus Predicate<T> has type T → boolean , while BiPredicate<T, U> has type (T, U) → boolean .
4、JFC的接口
Java集合框架定义了接口和类,以方便集合的创建和使用。这些类和接口是泛型的,因此它们可以与不同的元素类型一起使用。
列表和集合是特殊类型的集合,并且每个集合都是可迭代的。迭代是指从集合中连续检索元素,直到所有元素都被检查过,提供迭代方法的集合就是iterable。JCFIterable<E>接口定义了迭代E类型元素的集合的方法。Collection接口扩展了可迭代的元素,并添加了许多用于处理集合的方法。List接口对其元素进行排序的集合实现,它定义了按索引添加、删除和检索元素的方法。最后,Set接口作为所有无序集合的接口了,Set接口不向它从集合继承的方法添加任何方法。
5、forEach method
forEach方法是java接口Iterable<E>中的默认方法,对于所有继承自Iterable<E>的接口都可以使用。
forEach方法的形式如下:
void forEach(Consumer<? super E> action)
其源码如下:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
这个接口的默认方法很有意思,它使用一个方法作为其参数(这个接口接受的参数类型是一个方法),一般来说这里的方法是用lambda表达式实现的。 这里forEach方法具体执行的操作被融合到它的参数里去了,这里的参数不像之前是方法中的操作的操作对象(比如一个方法求一个数字的平方,那么那个数字就是平方操作的操作对象),这里的参数已经包含了对对象的操作。(如果参数用lambda表达式实现,那么这个lambda表达式实现的是哪个接口呢...)
forEach方法的一个例子如下:
import java.util.ArrayList;
/**
This program demonstrates Iterable forEach().
*/
public class ForEachDemo
{
public static void main(String [] args)
{
// Array of names.
String [] names = {"Anna", "Bob", "Carlos", "Debby"};
// Create list and add names.
ArrayList<String> nameList = new ArrayList<>();
for (String name : names)
{
nameList.add(name);
}
// Use forEach with lambda expression to print.
nameList.forEach(
x −>
{
System.out.printf("%s %d\n", x, x.length());
});
}
}
输出结果:
Anna 4Bob 3Carlos 6Debby 5
(2条消息) Java8中的forEach方法详解_青山师-CSDN博客https://blog.csdn.net/zixiao217/article/details/70196751
6、Iterators
另外一个Iterable<E>中的方法是iterator()。
迭代方法会创建一个迭代器,对于一个集合C而言,迭代器是一个可以对集合C提供受控迭代的一个对象。相比于forEach方法,迭代方法更加精细,它不会对集合中的所有元素执行操作,而是对特定的元素执行操作。
使用iterator()方法得到一个访问集合中元素的迭代器。我们通常在循环中使用迭代器,调用hasNext()来查看是否还有更多的元素需要检查,并调用next()来获取下一个可用的元素。remove()方法是可选的,如果被调用了,它将从底层集合(不是迭代器)中删除next()上次调用返回的元素。forEachRemaining()方法可以对迭代器尚未返回的每个元素执行一个操作。
看一个例子:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Consumer;
/**
This program demonstrates Iterators.
*/
public class IteratorDemo
{
public static void main(String [] args)
{
// Array of names.
String [] names = {"Anna", "Bob", "Carlos", "Debby"};
// Create list and add names.
ArrayList<String> nameList = new ArrayList<>();
for (String name : names)
{
nameList.add(name);
}
// Define an action for the "remaining" elements.
Consumer<String> action = x −>
{
System.out.printf("%s\n", x);
};
// Get the iterator to the list.
Iterator<String> iter = nameList.iterator();
// Process list elements with the iterator.
while (iter.hasNext())
{
String name = iter.next();
System.out.printf("%s %d\n", name, name.length());
if (name.equals("Bob"))
{
// Act differently for names after "Bob".
iter.forEachRemaining(action);
}
}
}
}
几点值得注意的地方:
Consumer<String> action = x −>
{
System.out.printf("%s\n", x);
};
这里定义了一个变量action,这个变量实际上是一种操作,之前我们会把一个操作写在方法里,但是这里我们把一个操作赋给了一个变量。
if (name.equals("Bob"))
{
// Act differently for names after "Bob".
iter.forEachRemaining(action);
}
注意这里对forEachRemaining方法的使用以及传参。