接口中默认方法修饰为普通方法
从JDK1.8开始,支持使用static和default修饰;可以写方法体,不需要子类重写。
普通方法:可以有方法体
抽象方法:没有方法体,需要子类去实现(重写)
接口定义方法:public 抽象方法 需子类实现
接口定义变量:public、static、final
实例
接口类
public interface JDK8Interface {
/**
* 默认的时候是public、abstract
*/
void add();
/**
* jdk8 提供的默认方法
*/
default void get(){
System.out.println("defaultGet");
}
static void del(){
System.out.println("staticDel");
}
}
接口实现类
public class JDK8InterfaceImpl implements JDK8Interface{
/**
* 定义子类实现JDK8Interface,没有强制要求重写default方法和静态方法
*/
@Override
public void add() {
System.out.println("add");
}
}
启动类
public class Test {
public static void main(String[] args) {
JDK8InterfaceImpl jdk8Interface = new JDK8InterfaceImpl();
jdk8Interface.add();
jdk8Interface.get();
JDK8Interface.del();
}
}
结果
Lambda
概念
Lambda表达式可以再一定程度上简化接口的实现。但是,并非所有的接口都可以使用lambda表达式来简洁实现的。
lambda表达式只是一个匿名方法。当实现的接口中方法过多或者过少的时候,lambda表达式均不适用。
lambda,只能实现函数式接口。
函数式接口
基础概念
若一个接口中,要求实现类必须实现的抽象方法,有且只有一个!这样的接口,就是函数式接口。
@FunctionalInterface
这个注解适用于接口之前,判断这个接口是否为一个函数式接口。如果是函数式接口,则没有任何问题,如果不是函数式接口,则会报错。功能类似@Override。
@FunctionalInterface
interface FunctionalInterfaceTest(){
void test();
}
Lambda表达式的语法
基础语法
从本质上来讲,lambda表达式就是一个匿名函数。因此再写lambda表达式的时候,不需要关心方法名,也不需要关心返回值类型。
我们只需要关注两部分内容即可:参数列表 和 方法体。
基础语法如下
(参数) -> {
方法体
};
- 参数部分:方法的参数列表,要求和要实现的接口中的方法的参数部分一致,包括参数的数量和类型。
- 方法体部分:方法的实现部分,如果接口中定义的方法有返回值,则再实现的时候,注意返回值的返回。
- -> : 分隔参数部分和方法体部分。
实例
public class BasicSyntax {
public static void main(String[] args) {
// 1.实现无参数,无返回值的函数式接口
NoneReturnNoneParameter lambda = () -> {
System.out.println("这是一个无参,无返回值的方法。");
};
lambda.test();
// 2.实现一个参数,无返回值的函数式接口
NoneReturnSingleParameter lambda1 = (int a) -> System.out.println("这是一个参数,无返回值的方法,参数a:"+ a);
lambda1.test(1);
// 3.实现一个参数,无返回值的函数式接口
NoneReturnMutipleParameter lambda2 = (a,b) -> System.out.println("这是多个参数,无返回值的方法,参数a:"+ a+"参数b:"+b);
lambda2.test(1,2);
// 4.实现无参数,有返回值的函数式接口
SingleReturnNoneParameter lambda3 = () -> {
System.out.println("这是一个无参,有返回值的方法。");
return 1;
};
int ret = lambda3.test();
System.out.println(ret);
// 5.实现一个参数,有返回值的函数式接口
SingleReturnSingleParameter lambda4 = (a) -> {
System.out.println("这是一个参数,有返回值的方法。");
return a;
};
int ret1 = lambda4.test(1);
System.out.println(ret1);
// 5.实现多个参数,有返回值的函数式接口
SingleReturnMutipleParameter lambda5 = (a,b) -> {
System.out.println("这是多个参数,有返回值的方法。");
return a * b;
};
int ret2 = lambda5.test(1,2);
System.out.println(ret2);
}
语法精简
参数部分
方法体部分
函数引用
Lambda表达式是为了简化接口的实现的。在Lambda表达式中,不应该出现比较复杂的逻辑。如果Lambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在Lambda表达式中需要处理的逻辑比较复杂,一般情况会单独的写一个方法。在Lambda表达式中直接引用这个方法即可。
或者,在有些情况下,我们需要在Lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已存在的方法即可。
函数引用:引用一个已经存在的方法,使其代替Lambda表达式完成接口的实现。
静态方法的引用
-
语法:
- 类::静态方法
-
注意事项:
- 再引用的方法后面,不要添加小括号
- 引用的这个方法,参数(数量,类型)和返回值,必须要和接口中定义的一致
-
实例
public class Lambda1 { private static interface Calculate{ int calculate(int x, int y); } public static void main(String[] args) { // Calculate calculate = ((x, y) -> calculate(x ,y)); // 这两个方式相同,下为静态函数引用 Calculate calculate = Lambda1::calculate; System.out.println(calculate.calculate(2,3)); } public static int calculate(int x, int y) { if (x > y){ return x - y; } else if (x < y){ return x + y; } return x * y; } }
非静态方法的引用
-
语法:
- 对象::静态方法
-
注意事项:
- 再引用的方法后面,不要添加小括号
- 引用的这个方法,参数(数量,类型)和返回值,必须要和接口中定义的一致
-
实例
public class Lambda1 { private static interface Calculate{ int calculate(int x, int y); } public static void main(String[] args) { // Calculate calculate = ((x, y) -> calculate(x ,y)); // 这两个方式相同,下为静态函数引用 Calculate calculate = Lambda1::calculate; System.out.println(calculate.calculate(2,3)); // 引用非静态方法 Calculate calculate1 = new Lambda1()::calculate2; System.out.println(calculate1.calculate(2,3)); } public static int calculate(int x, int y) { if (x > y){ return x - y; } else if (x < y){ return x + y; } return x * y; } private int calculate2(int a, int b){ if (a != b){ return a - b; } return a + b; } }
构造方法的引用
-
使用场景
- 如果某一个函数式接口中定义的方法,仅仅时为了得到一个类的对象.此时,我们可以使用构造方法的引用,简化这个方法的实现.
-
语法:
- 类名::new
-
注意事项:
- 可以通过接口中的方法的参数,区分引用不同的构造方法
-
实例
public class Lamdba2 { private static class Person{ String name; int age; public Person(){ System.out.println("Person类无参构造方法"); } public Person(String name){ this.name = name; System.out.println("Person类有参构造方法"); } public Person(String name, int age){ this.name = name; this.age = age; System.out.println("Person类两个有参构造方法"); } } private interface GetPersonWithNoneParameter { Person get(); } private interface GetPersonWithSingleParameter { Person get(String name); } private interface GetPersonWithMutipleParameter { Person get(String name, int age); } public static void main(String[] args) { // 调用构造函数,均用 类::new // 1.使用Lambda表达式,实现GetPersonWithNoneParameter接口 GetPersonWithNoneParameter getPerson = Person::new; // 2.使用Lambda表达式,实现GetPersonWithSingleParameter接口 GetPersonWithSingleParameter getPerson2 = Person::new; // 3.使用Lambda表达式,实现GetPersonWithMutipleParameter接口 GetPersonWithMutipleParameter getPerson3 = Person::new; getPerson.get(); getPerson2.get(""); getPerson3.get("",1); } }
对象方法的特殊引用
如果再使用Lambda表达式,实现某些接口的时候。Lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。其他的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。
Stream流
结合了lambada表达式,简化集合,数组的操作。
创建不可变集合
创建Stream流
public class StreamDemo1 {
public static void main(String[] args) {
/* 单列集合获取stream流 */
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a","ab","ac","ad","ae","af");
// 获取到stream流,并把集合中的数据放到流中
Stream<String> stream = arrayList.stream();
stream.forEach(new Consumer<String>() {
// s依次表示流水线上的每个数据
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 简化
arrayList.stream().forEach(s -> System.out.println(s));
/* 双列集合获取stream流 */
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("a","b");
hashMap.put("a1","b1");
hashMap.put("a2","b2");
hashMap.put("a3","b3");
// 打印所有key
hashMap.keySet().stream().forEach(s -> System.out.println(s));
// 打印键值对
hashMap.entrySet().stream().forEach(s -> System.out.println(s));
// 此处打印结果顺序不是按照put顺序的原因是因为底层是通过哈希值来存储的
/* 数组获取stream流 */
int[] arr = {1,2,3,4,5};
String[] arr1 = {"1","2","3","4","5"};
Arrays.stream(arr).forEach(s -> System.out.println(s));
Arrays.stream(arr1).forEach(s -> System.out.println(s));
// 这里的打印结果是一个地址,其原因是Stream接口中静态方法of的形参是一个可变参数,可以传递一堆数据,也可以传递数组
// 但是数组必须是引用类型的,如果传递基本数据类型,就会把整个数组当作一个元素,放入到stream流中
Stream.of(arr).forEach(ints -> System.out.println(ints));
Integer[] arr3 = {1,2,3,4,5};
// 这样就是正常打印
Stream.of(arr3).forEach(ints -> System.out.println(ints));
/* 零散数据获取stream流 */
Stream.of(1,2,3,4,5).forEach(s -> System.out.println(s));
Stream.of("1","2","3","4","5").forEach(s -> System.out.println(s));
}
}
Stream中间方法
- 中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
- 修改Stream流中的数据,不会影响原来集合或者数组中的数据
filter 过滤
/* filter 过滤 */
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a","ab","ac","b","da","bc");
// 过滤出a开头的
arrayList.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("a");
}
}).forEach(s -> System.out.println(s));
// 简化后
arrayList.stream().filter(s -> s.startsWith("a")).forEach(s -> System.out.println(s));
limit 获取x个数据
arrayList.stream().limit(3).forEach(s -> System.out.println(s));
skip 跳过x个数据
arrayList.stream().skip(3).forEach(s -> System.out.println(s));
sorted 排序
如果调用空参的sorted()方法,需要流中的元素实现了Comparable接口
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("zz", 10));
persons.add(new Person("xx", 20));
persons.add(new Person("cc", 30));
// 有参方法
persons.stream()
.sorted(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// 可以通过修改此处来改变升/降序排列
return o1.getAge() - o2.getAge();
}
})
.forEach(person -> System.out.println(person.getAge()));
// 简化
persons.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(person -> System.out.println(person.getAge()));
// 空参方法 需要流中的元素实现了Comparable接口
persons.stream().sorted().forEach(person -> System.out.println(person.getAge()));
// Person类
public class Person implements Comparable<Person>{
private String name;
private Integer age;
、、、
@Override
public int compareTo(Person o) {
return this.getAge() - o.getAge();
}
}
distinct 去重
依赖hashCode和equals方法
ArrayList<String> arrayList1 = new ArrayList<>();
Collections.addAll(arrayList1, "a","a","ab","ab","ba","bc");
arrayList1.stream().distinct().forEach(s -> System.out.println(s));
concat 合并两个流成为一个
最好保持两个流的类型一致,否则两个流形成一个新的父类而无法使用流的其他方法
Stream.concat(arrayList.stream(), arrayList1.stream()).forEach(s -> System.out.println(s));
map 转换流中的数据类型
ArrayList<String> arrayList2 = new ArrayList<>();
Collections.addAll(arrayList2, "a-1","b-2","c-3","d-4");
// 将-后面的数字转为Integer并输出
// Function里第一个类型是流原本的类型,第二个类型是要转成的类型
arrayList2.stream().map(new Function<String, Integer>() {
@Override
// apply的形参s表示流里的每一个数据,返回值表示转换后的数据
public Integer apply(String s) {
String[] arrs = s.split("-");
return Integer.parseInt(arrs[1]);
}// 当map方法执行完毕后,流上的数据就成流int类型,所以在forEach中的s也都是int类型
}).forEach(s -> System.out.println(s));
// 简化后
arrayList2.stream()
.map(s -> Integer.parseInt(s.split("-")[1]))
.forEach(s -> System.out.println(s));
// 同理也可以这样
arrayList2.stream().forEach(s -> System.out.println(Integer.parseInt(s.split("-")[1])));
flatMap 将一个对象转换为多个对象作为流中的元素
ArrayList<Person> persons1 = new ArrayList<>();
ArrayList<Food> foods = new ArrayList<>();
foods.add(new Food("apple", 3.3));
ArrayList<Food> foods1 = new ArrayList<>();
foods1.add(new Food("banana", 2.4));
foods1.add(new Food("pear", 5.5));
ArrayList<Food> foods2 = new ArrayList<>();
foods2.add(new Food("chicken", 4.5));
foods2.add(new Food("rice", 3.6));
persons1.add(new Person("zz", 10, foods));
persons1.add(new Person("xx", 20, foods1));
persons1.add(new Person("cc", 30, foods2));
// 获取每个Person下的所有Food
// Function里第一个类型是流原本的类型,第二个类型是要转成的流的类型
persons1.stream().flatMap(new Function<Person, Stream<Food>>() {
@Override
// 这里表示要返回的流的类型是哪一种
public Stream<Food> apply(Person person) {
return person.getFoods().stream();
}
}).forEach(new Consumer<Food>() {
@Override
public void accept(Food food) {
System.out.println(food.getName());
}
});
// 简化
persons1.stream().flatMap(person -> person.getFoods().stream()).forEach(food -> System.out.println(food.getName()));
Stream终结方法
forEach 遍历
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a","ab","ac","b","da","bc");
// Consumer的泛型表示流中数据的类型
arrayList.stream().forEach(new Consumer<String>() {
@Override
// accept的形参s表示流中的每一个数据
public void accept(String s) {
System.out.println(s);
}
});
// 简化
arrayList.stream().forEach(s -> System.out.println(s));
count 统计
返回为long类型
long count = arrayList.stream().count();
System.out.println(count);
min&max 取最值
// 此类方法适用于比较的数据为int类型
// Optional<Food> min = persons1.stream()
// .flatMap(person -> person.getFoods().stream())
// .min((o1, o2) -> o1.getPrice() - o2.getPrice());
// Optional<Food> max = persons1.stream()
// .flatMap(person -> person.getFoods().stream())
// .max((o1, o2) -> o1.getPrice() - o2.getPrice());
// 当要比较的数据为double、long等用Comparator中的方法
Optional<Food> min = persons1.stream().flatMap(person -> person.getFoods().stream()).min(Comparator.comparingDouble(Food::getPrice));
Optional<Food> max = persons1.stream().flatMap(person -> person.getFoods().stream()).max(Comparator.comparingDouble(Food::getPrice));
System.out.println(min.get().getName());
System.out.println(max.get().getName());
anyMatch 任意一个匹配
boolean flag = persons1.stream().anyMatch(person -> person.getAge() > 30);
System.out.println(flag);
allMatch 全部匹配
boolean flag = persons1.stream().allMatch(person -> person.getAge() >= 18);
System.out.println(flag);
noneMatch 全部不匹配
boolean flag = persons1.stream().noneMatch(person -> person.getAge() > 30);
System.out.println(flag);
findAny 获取流中任意一个元素
Optional<Person> person = persons1.stream().findAny();
System.out.println(person.toString());
findFirst 获取流中第一个元素
Optional<Person> person = persons1.stream().findFirst();
System.out.println(person.toString());
toArray 收集流中的数据,放到一个数组当中
Object[] array = arrayList.stream().toArray();
System.out.println(Arrays.toString(array));
// toArray方法的参数作用:负责创建一个指定类型的数组
// toArray方法的底层:会依次得到流里面的每一个数据,并把数据放到数组中
// IntFunction的泛型是具体类型的数组
String[] arr = Arrays.stream(array).toArray(new IntFunction<String[]>() {
@Override
// apply的形参:流中的数据的个数,要跟数组长度保持一致;返回值:具体类型的数组
public String[] apply(int value) {
return new String[value];
}
});
// 简化
String[] arr1 = arrayList.stream().toArray(value -> new String[value]);
collect 收集流中的数据,放到一个数组当中
ArrayList<String> arrayList2 = new ArrayList<>();
Collections.addAll(arrayList2, "@-a-1","@-a-1","!-a-2","#-b-3","$-b-4");
// 收集所有中间是a的
// 收集到list中
arrayList2.stream().filter(s -> "a".equals(s.split("-")[1])).collect(Collectors.toList());
// 收集到set中 根据set的特性,元素不会重复
arrayList2.stream().filter(s -> "a".equals(s.split("-")[1])).collect(Collectors.toSet());
// 收集到Map集合中
ArrayList<String> arrayList3 = new ArrayList<>();
Collections.addAll(arrayList3, "@-a-1","!-a-2","#-b-3","$-b-4");
// toMap: 参数一表示键的生成规则,参数二表示值的生成规则
Map<String, Integer> map = arrayList3.stream().collect(Collectors.toMap(
// 参数一的function:泛型一表示流中的每个数据的类型;泛型二表示Map集合中键的类型
new Function<String, String>() {
@Override
// apply的形参:依次表示流中的数据;方法体:生成键的代码;返回值:生成的键
public String apply(String s) {
return s.split("-")[0];
}
},
// 参数二的function:泛型一表示流中的每个数据的类型;泛型二表示Map集合中值的类型
new Function<String, Integer>() {
@Override
// apply的形参:依次表示流中的数据;方法体:生成值的代码;返回值:生成的值
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}
)
);
System.out.println(map);
// 简化
Map<String, Integer> map1 = arrayList3.stream().collect(Collectors.toMap(s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])));
System.out.println(map1);
reduce 归并
对流中的数据按照指定的计算方式计算出一个结果(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值的基础上进行计算,计算结果再和后面的元素计算。
- reduce两个参数的重载形式
内部的计算方式如下// identity就是通过方法参数传入的初始值 T result = identity; for(T element : this.stream) { // apply执行什么计算也是通过方法参数确定 result.accumuator.apply(result, element); } return result;
Integer sum = persons1.stream().map(person -> person.getAge()).reduce(0, new BinaryOperator<Integer>() { @Override // apply的第一个参数相当于初始值,第二个参数是本次遍历到的元素 public Integer apply(Integer result, Integer element) { return result + element; } }); System.out.println(sum); // 简化 // apply的第一个参数相当于初始值,第二个参数是本次遍历到的元素 Integer sum = persons1.stream() .map(person -> person.getAge()) .reduce(0, (result, element) -> result + element); System.out.println(sum); Integer max = persons1.stream() .map(person -> person.getAge()) .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result); System.out.println(max);
- reduce一个参数的重载形式
内部的计算方式如下boolean foundAny = false; T result = null; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; } else result = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty();
Optional<Integer> min = persons1.stream().map(person -> person.getAge()).reduce((result, element) -> result > element ? element : result); System.out.println(min.get());
并行流
当流中存在极大量元素时,可以使用并行流提高效率。
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5,6,7,8,9,10);
Optional<Integer> reduce = list.stream()
// 并行流方法
.parallel()
// 用于调试的方法
.peek(s -> System.out.println(s + ":" + Thread.currentThread().getName()))
.reduce((res, ele) -> res + ele);
System.out.println(reduce.get());
Optional
概述
在编写代码的时候,极其容易遇到做非空判断的时候,过多的非空判断会使代码臃肿,在JDK8中引入的Optional可以使得更优雅的避免非空异常。
创建对象
optional类似一个包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
ofNullable
我们一般使用Optional的静态方法ofNullable来把数据封装成一个optional对象。无论传入的参数是否为null都不会出现问题。
// 方法一
// public static void main(String[] args) {
// Person person = getPerson();
// Optional<Person> optionalPerson = Optional.ofNullable(person);
// optionalPerson.ifPresent(person -> System.out.println(person));
// }
//
// public static Person getPerson(){
// ArrayList<Food> foods1 = new ArrayList<>();
// foods1.add(new Food("banana", 2.4));
// foods1.add(new Food("pear", 5.5));
// Person person = new Person("zz", 10, foods1);
// return person;
// }
public static void main(String[] args) {
Optional<Person> optionalPerson = getOptionalPerson();
optionalPerson.ifPresent(person -> System.out.println(person));
}
// 方法二
public static Optional<Person> getOptionalPerson(){
ArrayList<Food> foods1 = new ArrayList<>();
foods1.add(new Food("banana", 2.4));
foods1.add(new Food("pear", 5.5));
Person person = new Person("zz", 10, foods1);
return Optional.ofNullable(person);
}
of
当确定传入的对象不是空的时候可以使用Optional的静态方法of来把数据封装成optional对象,传入的of的参数不能为null,否则会报错。
Person person = getPerson();
Optional<Person> optionalPerson = Optional.of(person);
empty
当方法返回值是optional类型时,且会返回null时,利用Optional的静态方法empty来把null封装成optional对象返回
public static Optional<Person> getOptionalPerson(){
Person person = new Person("zz", 10, null);
return person == null ? Optional.empty(): Optional.ofNullable(person);
}
安全消费值
Optional<Person> optionalPerson = getOptionalPerson();
optionalPerson.ifPresent(person -> System.out.println(person));
获取值
不推荐使用get()方法,当值为null时get会抛出NoSuchElementException
异常。
orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建对象作为默认值返回。
public static void main(String[] args) {
Optional<Person> optionalPerson = getOptionalPerson();
// 当对象中有值的时候就直接返回,对象为null的话就返回这里的默认值
Person person = optionalPerson.orElseGet(() -> new Person("ss", 33, null));
System.out.println(person);
}
orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出
Optional<Person> optionalPerson = getOptionalPerson();
Person person = null;
try {
person = optionalPerson.orElseThrow(new Supplier<Throwable>() {
@Override
public Throwable get() {
return new RuntimeException("数据为null");
}
});
// 简化
// person = optionalPerson.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("数据为null"));
} catch (Throwable e) {
throw new RuntimeException(e);
}
System.out.println(person);
过滤
当数据不符合filter的判断时,也会变成无数据的Optional对象
Optional<Person> optionalPerson = getOptionalPerson();
optionalPerson.filter(person -> person.getAge()>50).ifPresent(person -> System.out.println(person));
判断
使用isPresent判断数据是否为null
Optional<Person> optionalPerson = getOptionalPerson();
System.out.println(optionalPerson.isPresent());
数据转换
利用map转换后的数据依然是被optional包装的
Optional<Person> optionalPerson = getOptionalPerson();
optionalPerson.map(person -> person.getFoods()).ifPresent(foods -> System.out.println(foods));