什么是函数式编程?Lambda表达式?
- 函数式编程
面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。
核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
- Lambda表达式
lambda表达式仅能放入如下代码: 预定义使用了 @Functional
注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式
# Stream中常用方法?
stream()
,parallelStream()
filter()
findAny()
findFirst()
sort
forEach
voidmap(), reduce()
flatMap()
- 将多个Stream连接成一个Streamcollect(Collectors.toList())
distinct
,limit
count
min
,max
,summaryStatistics
# 什么是FunctionalInterface?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface{}
- interface做注解的注解类型,被定义成java语言规
- 一个被它注解的接口只能有一个抽象方法,有两种例外
- 第一是接口允许有实现的方法,这种实现的方法是用default关键字来标记的(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法)
- 第二如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则: 一个被它注解的接口只能有一个抽象方法, 比如:
java public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
- 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件:
- 这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class
- 这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。
- 编译器会自动把满足function interface要求的接口自动识别为function interface。
# 如何自定义函数接口?
@FunctionalInterface
public interface IMyInterface {
void study();
}
public class TestIMyInterface {
public static void main(String[] args) {
IMyInterface iMyInterface = () -> System.out.println("I like study");
iMyInterface.study();
}
}
# 内置的四大函数接口及使用?
- 消费型接口: Consumer< T> void accept(T t)有参数,无返回值的抽象方法;
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
- 供给型接口: Supplier < T> T get() 无参有返回值的抽象方法;
以stream().collect(Collector<? super T, A, R> collector)为例:
比如:
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
- 断定型接口: Predicate<T> boolean test(T t):有参,但是返回值类型是固定的boolean
比如: steam().filter()中参数就是Predicate
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
- 函数型接口: Function<T,R> R apply(T t)有参有返回值的抽象方法;
比如: steam().map() 中参数就是Function<? super T, ? extends R>;reduce()中参数BinaryOperator<T> (ps: BinaryOperator<T> extends BiFunction<T,T,T>)
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
# Optional要解决什么问题?
在调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法,我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。Java 8引入了一个新的Optional类:这是一个可以为null的容器对象,如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
# 如何使用Optional来解决嵌套对象的判空问题?
假设我们有一个像这样的类层次结构:
class Outer {
Nested nested;
Nested getNested() {
return nested;
}
}
class Nested {
Inner inner;
Inner getInner() {
return inner;
}
}
class Inner {
String foo;
String getFoo() {
return foo;
}
}
解决这种结构的深层嵌套路径是有点麻烦的。我们必须编写一堆 null 检查来确保不会导致一个 NullPointerException:
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
System.out.println(outer.nested.inner.foo);
}
我们可以通过利用 Java 8 的 Optional 类型来摆脱所有这些 null 检查。map 方法接收一个 Function 类型的 lambda 表达式,并自动将每个 function 的结果包装成一个 Optional 对象。这使我们能够在一行中进行多个 map 操作。Null 检查是在底层自动处理的。
Optional.of(new Outer())
.map(Outer::getNested)
.map(Nested::getInner)
.map(Inner::getFoo)
.ifPresent(System.out::println);
还有一种实现相同作用的方式就是通过利用一个 supplier 函数来解决嵌套路径的问题:
Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo())
.ifPresent(System.out::println);
# 什么是默认方法,为什么要有默认方法?
就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。
public interface A {
default void foo(){
System.out.println("Calling A.foo()");
}
}
public class Clazz implements A {
public static void main(String[] args){
Clazz clazz = new Clazz();
clazz.foo();//调用A.foo()
}
}
- 为什么出现默认方法?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
# 什么是类型注解?
类型注解被用来支持在Java的程序中做强类型检查。配合插件式的check framework,可以在编译的时候检测出runtime error,以提高代码质量。这就是类型注解的作用了。
-
在java 8之前,注解只能是在声明的地方所使用,比如类,方法,属性;
-
java 8里面,注解可以应用在任何地方,比如:
创建类实例
new @Interned MyObject();
类型映射
myString = (@NonNull String) str;
implements 语句中
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { … }
throw exception声明
void monitorTemperature() throws @Critical TemperatureException { … }
需要注意的是,类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。
# 什么是重复注解?
允许在同一申明类型(类,属性,或方法)的多次使用同一个注解
- JDK8之前
java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseOldVersion {
@Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
public void doSomeThing(){
}
}
由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解。
- Jdk8重复注解
我们再来看看java 8里面的做法:
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点