一、Java8基础概念
1、流处理
流是一系列数据项,一次只生成一项。程序可以从输入流中一个一个读取数据项,然后利用同样的方式将数据项写入输出流。
一个程序的输出流很可能是另一个程序的输入流。
举例:
比如在Unix或Linux中,很多程序都从标准输入(Java中的System.in)来读取数据,然后把结果写入标准输出流(System.out)。
2、方法引用
方法引用通过方法的名字来指向一个方法,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
语法:
方法引用使用一对冒号::
传统方式筛选一个目录中的所有隐藏文件
//用isHidden方法筛选文件时,需要把方法包裹在FileFilter对象里,然后才能传递给File.listFiles方法
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
//筛选隐藏文件
return file.isHidden();
}
});
使用方法引用方式
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
二、行为参数化
1、定义
行为参数化:就是一个方法接受多个不同的行为(或战略)作为参数,并在内部使用它们,完成不同行为的能力。行为参数化可以更好的适应不断变化的需求,减少实际开发工作量。
2、应对不断变化的需求
1、假设有这么一个场景,某农场主有一个果园,第一次他需要选出绿色的苹果,可以有如下解决方案:
@Data
@AllArgsConstructor
public class Apple {
/** 重量 */
private int weight;
/** 颜色 */
private String color;
}
public class Test01 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(10, "green"));
apples.add(new Apple(5, "red"));
apples.add(new Apple(5, "green"));
apples.add(new Apple(10, "red"));
List<Apple> greenApples = filterGreenApples(apples);
for (Apple greenApple : greenApples) {
System.out.println(greenApple);
}
}
/**
* 筛选出绿色的苹果
* @param apples
* @return
*/
private static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (apple.getColor().equals("green")) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
Apple(weight=10, color=green)
Apple(weight=5, color=green)
2、现在农场主有了新的想法,想要筛选出更多颜色的苹果(红色、绿色、暗红色、黄色等),上面这种方式就应附不了,我们可以把颜色作为参数,这样就能灵活的适应变化了。
public class Test02 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(10, "green"));
apples.add(new Apple(5, "red"));
apples.add(new Apple(5, "green"));
apples.add(new Apple(10, "red"));
System.out.println("=====================筛选绿色=====================");
List<Apple> greenApples1 = filterGreenApples(apples,"green");
for (Apple greenApple : greenApples1) {
System.out.println(greenApple);
}
System.out.println("=====================筛选红色=====================");
List<Apple> greenApples2 = filterGreenApples(apples,"red");
for (Apple greenApple : greenApples2) {
System.out.println(greenApple);
}
}
/**
* 根据不同颜色筛选
* @param apples
* @param color
* @return
*/
private static List<Apple> filterGreenApples(List<Apple> apples,String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
=====================筛选绿色=====================
Apple(weight=10, color=green)
Apple(weight=5, color=green)
=====================筛选红色=====================
Apple(weight=5, color=red)
Apple(weight=10, color=red)
3、这是农场主又有了新的想法,要是能筛选重的苹果就好了,这时你将上面的代码复制了一份,并将重量作为参数,但是这种做法有点DRY(Don’t Repeat Yourself,不要重复自己)的软件的工程规则。这时,想到一个标识,来区分不同的属性。
public class Test03 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(10, "green"));
apples.add(new Apple(5, "red"));
apples.add(new Apple(5, "green"));
apples.add(new Apple(10, "red"));
System.out.println("=====================筛选颜色=====================");
List<Apple> greenApples1 = filterGreenApples(apples,"green",0,true);
for (Apple greenApple : greenApples1) {
System.out.println(greenApple);
}
System.out.println("=====================筛选重量=====================");
List<Apple> greenApples2 = filterGreenApples(apples,"",5,false);
for (Apple greenApple : greenApples2) {
System.out.println(greenApple);
}
}
/**
* 根据标识筛选不同属性的
* @param apples
* @param color
* @param weight
* @param flag
* @return
*/
private static List<Apple> filterGreenApples(List<Apple> apples,String color,int weight,boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (flag && apple.getColor().equals(color) || !flag && apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
=====================筛选颜色=====================
Apple(weight=10, color=green)
Apple(weight=5, color=green)
=====================筛选重量=====================
Apple(weight=10, color=green)
Apple(weight=10, color=red)
缺点:这样解决很简单但是很糟糕,因为并不能很好的解决需求的变化,假如还要要求对形状、大小等进行区分等方法将会十分复杂,可读性也会很差。
4、前面的示例已经看到,我们需要一种比添加很多参数更好的方法来应对变化的需求。来看看更高层次的的抽象吧。因为要根据属性进行筛选,所以我们对选择进行标准化建模,根据Apple的属性来返回一个boolean值,我们把它称为谓词(即一个返回boolean值的函数)。
定义一个接口
public interface ApplePredicate {
boolean appleTest (Apple apple);
}
筛选出重的
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean appleTest(Apple apple) {
return apple.getWeight() > 5;
}
}
筛选出绿色的
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean appleTest(Apple apple) {
return "green".equals(apple.getColor());
}
}
这种设计方式类似设计模式中的策略模式,定义一族算法,把它们封装起来(称为“策略”),然后在运行的时候选择一个算法,这里的算法族就是ApplePredicate,不同的策略就是AppleGreenColorPredicate与AppleHeavyWeightPredicate
行为参数化方式实现
public class Test04 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(10, "green"));
apples.add(new Apple(5, "red"));
apples.add(new Apple(5, "green"));
apples.add(new Apple(10, "red"));
System.out.println("=====================筛选颜色=====================");
List<Apple> greenApples1 = filterApples(apples,new AppleGreenColorPredicate());
for (Apple greenApple : greenApples1) {
System.out.println(greenApple);
}
System.out.println("=====================筛选重量=====================");
List<Apple> greenApples2 = filterApples(apples,new AppleHeavyWeightPredicate());
for (Apple greenApple : greenApples2) {
System.out.println(greenApple);
}
}
/**
* 根据抽象条件筛选
* @param apples
* @param applePredicate
* @return
*/
private static List<Apple> filterApples(List<Apple> apples,ApplePredicate applePredicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (applePredicate.appleTest(apple)) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
=====================筛选颜色=====================
Apple(weight=10, color=green)
Apple(weight=5, color=green)
=====================筛选重量=====================
Apple(weight=10, color=green)
Apple(weight=10, color=red)
优点:代码更灵活,易扩展,可以创建不同的类实现ApplePredicate就行了,就能对不同属性进行筛选了。
思考:其实本质上,filterApples在内部进行条件判断的时候,我们使用的是不是仅仅是它内部的代码片段进行判断,即谓语的代码块,我们可不可以直接使用代码块呢?
5、Lambda表达式方式实现
public class Test04 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(10, "green"));
apples.add(new Apple(5, "red"));
apples.add(new Apple(5, "green"));
apples.add(new Apple(10, "red"));
System.out.println("=====================筛选颜色=====================");
List<Apple> greenApples1 = filterApples(apples,(Apple apple) -> "red".equals(apple.getColor()));
for (Apple greenApple : greenApples1) {
System.out.println(greenApple);
}
System.out.println("=====================筛选重量=====================");
List<Apple> greenApples2 = filterApples(apples,(Apple apple) -> apple.getWeight() > 5);
for (Apple greenApple : greenApples2) {
System.out.println(greenApple);
}
}
/**
* 根据抽象条件筛选
* @param apples
* @param applePredicate
* @return
*/
private static List<Apple> filterApples(List<Apple> apples,ApplePredicate applePredicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (applePredicate.appleTest(apple)) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
=====================筛选颜色=====================
Apple(weight=10, color=green)
Apple(weight=5, color=green)
=====================筛选重量=====================
Apple(weight=10, color=green)
Apple(weight=10, color=red)
6、假如农场主想要对其他的水果也进行筛选,此时我们可以将List类型抽象化
public interface ApplePredicate<T> {
boolean appleTest (T t);
}
public class Test05 {
public static void main(String[] args) {
List<Orange> oranges = new ArrayList<>();
oranges.add(new Orange(10, "green"));
oranges.add(new Orange(5, "yellow"));
oranges.add(new Orange(5, "green"));
oranges.add(new Orange(10, "yellow"));
System.out.println("=====================筛选颜色=====================");
List<Orange> oranges1 = filterApples(oranges, (Orange apple) -> "yellow".equals(apple.getColor()));
for (Orange orange : oranges1) {
System.out.println(orange);
}
System.out.println("=====================筛选重量=====================");
List<Orange> oranges2 = filterApples(oranges,(Orange apple) -> apple.getWeight() > 5);
for (Orange orange : oranges2) {
System.out.println(orange);
}
}
/**
* 根据抽象条件筛选
* @param apples
* @param applePredicate
* @return
*/
private static <T> List<T> filterApples(List<T> apples,ApplePredicate<T> applePredicate) {
List<T> result = new ArrayList<>();
for (T apple : apples) {
if (applePredicate.appleTest(apple)) {
result.add(apple);
}
}
return result;
}
}
/** 运行结果 */
=====================筛选颜色=====================
Orange(weight=5, color=yellow)
Orange(weight=10, color=yellow)
=====================筛选重量=====================
Orange(weight=10, color=green)
Orange(weight=10, color=yellow)
三、Lambda表达式
1、概述
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
特点:
2、语法
(参数类型 参数1,参数类型 参数2...) -> {代码;}
说明:
注意:如果只有一行代码,并且是使用
return
的情况下,要使此Lambda有效,必须使用花括号,因为return
是一个流程控制语句。
3、Lambda省略规则
4、函数式接口
函数式接口就是只定义一个抽象方法的接口,有且仅有一个抽象方法的接口。
作用:Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
函数描述符:函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符
。
5、@FunctionalInterface注解
被
@FunctionalInterface
注解标记的类型表明这是一个函数接口。如果你用@FunctionalInterface
定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。请注意,@FunctionalInterface
不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override
标注表示方法被重写了。
四、常用函数式接口
1、Predicate接口
java.util.function.Predicate<T>
接口定义了一个名叫test
的抽象方法,它接受泛型 T对象,并返回一个boolean
。
public class Test01 {
public static void main(String[] args) {
PredicateMethod(s -> s.length()>5,"hello");
}
private static void PredicateMethod(Predicate<String> predicate,String str){
boolean flag = predicate.test(str);
System.out.println("判断结果"+flag);
}
}
/** 运行结果 */
判断结果false
2、Consumer接口
java.util.function.Consumer<T>
定义了一个名叫accept
的抽象方法,它接受泛型T 的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口,消费数据。
public class Test02 {
public static void main(String[] args) {
List<String> list = Arrays.asList("1","2","3");
//Lambda是Consumer中accept方法的实现
forEach(list,(String i)->System.out.println(i));
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T i : list) {
c.accept(i);
}
}
}
/** 运行结果 */
1
2
3
3、Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有一个抽象apply
的方法,它接受一个 泛型T
的对象,并返回一个泛型R
的对象。
public class Test03 {
public static void main(String[] args) {
//Lambdas是Function接口的apply方法的实现
List<Integer> list = map(Arrays.asList("java", "c++"), (String s) -> s.length());
System.out.println(list);
}
private static <T,R> List<R> map(List<T> list, Function<T,R> function) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(function.apply(t));
}
return result;
}
}
/** 运行结果 */
[4, 3]
4、装箱与拆箱
装箱(boxing):将原始类型转换为对应的引用类型的机制。
拆箱(unboxing):与装箱相反,将引用类型转换为原始类型。
注意:装箱和拆箱操作是自动完成的,但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
5、常用的函数式接口及其函数描述符
函数式接口 | 函数描述符 | 原始类型转化 |
---|---|---|
Predicate | T -> boolean | IntPredicate,LongPredicate,DoublePredicate |
Consumer | T -> void | IntConsumer,LongConsumer,DoubleConsumer |
Function<T,R> | T -> R | IntFunction,IntToDoubleFunction,IntToLongFunction, LongFunction,LongToDoubleFunction,LongToIntFunction, DoubleFunction,ToIntFunction,ToDoubleFunction, ToLongFunction |
Supplier | () -> T | BooleanSupplier,IntSupplier,LongSupplier,DoubleSupplier |
UnaryOperator | T -> T | IntUnaryOperator,LongUnaryOperator,DoubleUnaryOperator |
BinaryOperator | (T, T) -> T | IntBinaryOperator,LongBinaryOperator,DoubleBinaryOperator |
BiPredicate<L,R> | (L, R) -> boolean | |
BiConsumer<T,U> | (T, U) -> void | ObjIntConsumer,ObjLongConsumer,ObjDoubleConsumer |
BiFunction<T,U,R> | (T, U) -> R | ToIntBiFunction<T,U>,ToLongBiFunction<T,U>,ToDoubleBiFunction<T,U> |