1.关于函数式接口
- 一个接口只有一个抽象方法,那么该接口就是一个函数式接口;
- 如果我们在某接口上声明了
@FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口。 - 如果某个接口只有一个抽象方法,但是我们并没有给该接口声明
@FunctionalInterface注解,但是编译器依旧会将该接口看做是函数式接口。
关于新特性中默认方法:
- 保证了jdk1.8开始引入函数式接口的新特性加入
- 兼顾了之前原来已经有的兼容
1.1 认识函数式接口
/**
* 一个信息性的注解类型,用于指示接口类型声明旨在成为《Java语言规范》中定义的函数式接口。
* 如果果接口声明了一个覆盖了
* {@code java.lang.Object}公共方法的抽象方法,这也不计入接口的抽象方法数量,
* 因为任何实现了该接口的对象都会从{@code java.lang.Object}或其他地方继承实现。
*
* 请注意,功能接口的实例可以使用 lambda 表达式、方法引用或构造方法引用来创建。
* 如果类型带有此注解,则编译器必须在以下情况下生成错误消息:
* - 类型是接口类型,而不是注解类型、枚举或类。
* - 被注解的类型满足功能接口的要求。
*
* 然而,编译器会将任何符合功能接口定义的接口视为功能接口,无论该接口声明上是否存在@FunctionalInterface注解。
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
1.2 如何定义一个函数式接口?
判断一个接口是否满足是函数式接口,就看是否只有一个方法,而默认方法和重写Obejcet父类方法不会占用,也可以直接加入@FuntionalInterface来判别。
@FunctionalInterface
public interface MyFunction {
int apply(String s);
// 可以重写Object类中的方法,因为所有接口默认实现Object类
String toString();
// 可以有默认方法
default String apply2(String s) {
return s;
}
}
1.3 创建接口实例的方式
我们从一个集合的遍历开始慢慢认识函数式接口
public class Test02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
// 遍历集合?
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
System.out.println("----------");
for(Integer i : list){
System.out.println(i);
}
System.out.println("------------");
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
System.out.println("lambada表达式");
list.forEach(integer -> System.out.println(integer));
System.out.println("方法引用:");
// method referece
list.forEach(System.out::println);
}
}
关于list.foreach()方法,是由于所有的list接口都实现了Iterable接口。
1.4 关于Iteralble接口源码解析
/**
* Implementing this interface allows an object to be the target of the enhanced
* {@code for} statement (sometimes called the "for-each loop" statement).
* <p>
* 实现这个接口允许一个对象成为增强型{@code for}语句(有时称为“for-each循环”语句)的目标。
* </p>
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
* @jls 14.14.2 The enhanced {@code for} statement
*/
public interface Iterable<T> {}
这是集合框架的顶层接口,实现该接口的类都可以使用增强for来快速遍历数组。
其中有一个默认方法
//对Iterable中的每个元素执行给定的操作,直到所有元素都被处理完毕或操作抛出异常。 如果迭代顺序是明确指定的,则操作将按照该顺序执行。 操作过程中抛出的异常会被传递给调用者。
default void forEach(Consumer<? super T> action) {
// 要求传入的action不能为null
Objects.requireNonNull(action);
for (T t : this) {
// 执行某个行为
action.accept(t);
}
}
其中Consumer就是一个很有代表性的函数式接口,表示接收一个参数,但是没有返回值,属于消费者。
/**
表示接受单个输入参数且不返回任何结果的操作。与大多数其他功能接口不同,`Consumer` 预计将通过副作用进行操作。
这是一个[功能接口]
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
```
/**
* Performs this operation on the given argument.
* 对于给定的参数给与操作
*
* @param t the input argument
*/
void accept(T t);
}
副作用?指的是它可能会修改传入的参数的值.
1.5 为什么需要Lambada表达式?
- 在java中,我们无法将函数作为参数传递给一个方法,也无法声明一个返回函数的方法
- 在JavaScript中,返回值是另一个函数的情况非常常见;是一个典型的函数式语言
- lambda表达式为Java添加了缺失的函数式编程的特性,使我们能将函数当做一等公民看待
- 在将函数作为一等公民的语言中,Lambda表达式的类型是函数。但在Java中,Lambda表达式是对象,他们必须依附于一类特殊的对象类型---函数式接口(FunctionalInterface)
2. 迭代器
2.1 外部迭代和内部迭代
外部迭代
在java7以前,集合框架都是依赖外部迭代,一个集合通过实现Iterable接口提供的一个便利自身所有元素的方法,即实现Iterator,使用者则通过它顺序地遍历集合中的元素.
List<String> list = Arrays.asList("a","b","c");
// 外部迭代,有迭代器 增强for循环
Iterator<String> it = list.iterator();
while (it.hasNext()){
String upperCase = it.next().toUpperCase();
System.out.println(upperCase);
}
for(String s: list){
String upperCase = s.toUpperCase();
System.out.println(upperCase);
}
使用外部迭代虽然直接,但是有下列问题:
- Java的for-each循环/迭代器天生就是顺序的,因此在处理集合元素时必须按照集合的顺序制定顺序. 2.它减弱了对可控制流的优化,这些优化本能够通过数据的乱序、并行、短路和懒惰等方式去实现。
内部迭代 有时我们需要for-each循环(顺序、依次)的特性,但是通常情况下这些特性有碍于性能的提升。通过将外部迭代替换为内部迭代,用户能够让库来控制遍历,从而只需要编写操作数据的代码。
// 内部迭代,使用函数式接口的迭代 这里可以使用lambda表达式或者stream流的操作
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s.toUpperCase());
}
});
外部迭代将“干什么”(将每个字符串变成大写)和“怎么做”(使用 for 循环/迭代器)混杂在一起,内部迭代则通过库来控制“怎么做”,而将“干什么”留给用户去实现,这样的方式能够提供很多潜在的好处:用户的代码能够变得更加简洁并聚焦于如何处理问题而不是通过什么方式去处理问题,同时也能够将复杂的性能优化代码移动到库的内部从而使所有用户受益。