文章目录
一.Lambda表达式
引入:以Thread类为例
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("----" + Thread.currentThread());
}
}).start();
// 改造
new Thread(() -> {System.out.println("--------" + Thread.currentThread()); }).start();
}
lambda表达式的优点:简化匿名内部类的使用,语法更加简洁
匿名内部类语法冗余,体验了Lambda表达式之后,发现Lambda表达式是简化匿名内部类的一种方式
Lambda表达式的规则:
- 所包含的一个参数类型是一个接口 并且此接口只有一个抽象方法
- 由 括号(())、箭头( ->)、花括号( {})组成
练习:有参数又有返回值
public static void main(String[] args) {
ArrayList<Person> personArrayList = new ArrayList<>();
personArrayList.add(new Person("张三",33,"男"));
personArrayList.add(new Person("李四",13,"女"));
personArrayList.add(new Person("王五",36,"男"));
personArrayList.add(new Person("田七",25,"男"));
// Collections.sort(personArrayList, new Comparator<Person>() {
// @Override
// public int compare(Person o1, Person o2) {
// return o1.getAge()-o2.getAge();
// }
// });
Collections.sort(personArrayList, (Person o1, Person o2) ->{return o1.getAge()-o2.getAge(); });
System.out.println(personArrayList);
}
对于自定义的接口当作参数的注意事项:
- 必须保证此接口只有一个抽象方法
- 可以在接口类上加上 @FunctionalInterface 注解 用于检查此接口只有一个抽象方法
Lambda表达式的省略写法
在lambda表达式的标准写法基础上
- 小括号内的参数类型可以省略
- 如果小括号内如果仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return,分号;
例如:
m1(new IUserService() {
@Override
public void show(String desc) {
}
});
m1((String desc)->{
System.out.println("我是一个标准的Lambda");
});
m1(desc->System.out.println("我是一个简化的Lambda"));
new Thread(()->add(dest,"a")).start();
Lambda的使用前提
Lambda表达式的语法是非常简洁的,但是Lambda不是随意使用的,使用时需要注意几个条件
- 方法的参数或局部变量类型必须为接口才能使用lambda表达式
- 接口中有且仅有一个抽象方法(@FunctionalInterfaces)
Lambda和匿名内部类的区别
- 所需参数类型不一样
- 匿名内部类的类型可以是 类、抽象类、接口
- Lambda表达式所需要的类型必须是接口
- 抽象方法的数量不一样
- 匿名内部类所需的接口中的抽象方法的数量是随意的
- Lambda表达式所需要的接口中只能有一个抽象方法
- 实现原理不一样
- 匿名内部类是在编译后生成一个class
- Lambda表达式是在程序运行的时候动态生成class
二.接口中新增的方法
1.默认方法
为什么要使用默认方法:一个接口需要实现类实现才可以使用,如果需要对接口进行扩展,比如说增加一个方法,那么就需要在所有的实现类中重写此方法,造成代码的冗余。
解决:在接口中使用默认方法
格式:
interface A{
defalut void 方法名(){};
}
特点:
- 实现类直接调用接口的默认方法
- 实现类重写接口中的默认方法
2.静态方法
作用: 同样是为了扩展
格式:
interface A{
static void 方法名(){};
}
特点:
接口中静态方法在实现类中是不能被重写的,调用的话只能通过接口名来实现:接口名.静态方法名();
3.默认方法与静态方法的区别
- 默认方法通过对象调用,静态方法通过接口名调用
- 默认方法可以被继承,实现类可以调用或重写接口里的默认方法
- 静态方法不可被继承,实现类不能调用和重写接口里的静态方法,只能通过接口名调用
三.函数式接口
我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表。因此为了让我们使用Lambda表达式更好的方法,在JDK中提供了大量的常用的函数式接口。
@FunctionalInterface
public interface Operate {
Integer sum();
}
public class FunctionInterfaceTest {
public static void main(String[] args) {
sum(()->{
int[] nums = {1,2,3,4,5,6,7,8,9};
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum+= nums[i];
}
return sum;
});
}
public static void sum(Operate operate){
System.out.println(operate.sum());
}
}
在 JDK 中有函数式接口,主要在java.util.function包中
Supplier:无参数有返回值,用于生产数据
public static void main(String[] args) {
max(()->{
int[] nums = {1,2,3,4,5};
Arrays.sort(nums);
return nums[nums.length-1];
});
}
public static void max(Supplier<Integer> supplier){
Integer max = supplier.get();
System.out.println("最大值"+max);
}
Consumer:有参数无返回值,用于消费数据
public static void main(String[] args) {
sqrt((Integer num)->{
System.out.println(Math.sqrt(num));
});
}
public static void sqrt (Consumer<Integer> consumer){
consumer.accept(3);
}
andThen 方法:
public static void consumer(Consumer<Integer> c1,Consumer<Integer> c2){
// c1.accept(3);
// c2.accept(3);
c1.andThen(c2).accept(2);
// 先调用 c1 在 调用 c2
}
Function:有参数有返回值
public static void main(String[] args) {
estimate((Integer)->{
return 3 > 0?true:false;
});
}
public static void estimate(Function<Integer,Boolean> function){
System.out.println(function.apply(3));
}
Predicate:有参数返回值为bool值
public static void main(String[] args) {
equalsFive(num->{return num == 5;});
}
public static void equalsFive(Predicate<Integer> predicate){
System.out.println(predicate.test(5));
}
四.方法引用
1为什么要有方法引用
方法引用是为了更好的使用 Lambda 表达式
例如:
public class MethodReference {
public static void main(String[] args) {
Integer total = sum((nums) -> {
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum += nums[i];
}
return sum;
});
System.out.println(total);
}
public static Integer sum(Function<int[],Integer> function){
int[] nums = {1,2,3,4,5};
return function.apply(nums);
}
public static Integer sumMethod(int[] nums){
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
}
当使用一个 Lambda 表达式时,发现已经有一个写好的方法,这时候就可以用方法引用。
public class MethodReference {
public static void main(String[] args) {
Integer total = sum(MethodReference::sumMethod);
System.out.println(total);
}
public static Integer sum(Function<int[],Integer> function){
int[] nums = {1,2,3,4,5};
return function.apply(nums);
}
public static Integer sumMethod(int[] nums){
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
}
2方法引用的格式
符号表示:::
符号说明: 双冒号为方法引用运算符,而他所在的表达式被称为方法引用
应用场景:如果Lambda 表达式所要实现的方案,已经有其他方法存在相同的方案
常见的引用方式:
方法引用在JDK8 中使用相当灵活,有以下几种使用形式:
InstanceName::methodName 对象::方法名
ClassName:: staticMethodName 类名:: 静态方法名
ClassName::methodName 类名:: 普通方法名
ClassName::new 类名::new 调用的构造器
TypeName[]::new String[]:new 调用数组的构造方法
-
InstanceName::methodName
public static void main(String[] args) { Date date = new Date(); // Lambda 表达式 Supplier<Long> supplier = ()->date.getTime(); System.out.println(supplier.get()); // 方法引用 Supplier<Long> supplier1 = date::getTime; System.out.println(supplier1.get()); }
-
ClassName:: staticMethodName
public static void main(String[] args) { Supplier<Long> s1 = ()->System.currentTimeMillis(); Supplier<Long> s2 = System::currentTimeMillis; System.out.println(s1.get()); System.out.println(s2.get()); }
-
ClassName::methodName
同上
-
ClassName::new : 返回一个指定类型的对象
public static void main(String[] args) { BiFunction<String,Integer,Person> f1 = (name,age)->new Person(name,age); Person p1 = f1.apply("张三",13); System.out.println(p1); BiFunction<String,Integer,Person> f2 = Person::new; Person p2 = f2.apply("李四", 14); System.out.println(p2); }
-
ClassName[]::new. : 返回值一个指定长度的数组
public static void main(String[] args) { Function<Integer,String[]> f1 = len->new String[len]; String[] a1 = f1.apply(3); System.out.println(a1.length); Function<Integer,String[]> f2 = String[]::new; String[] a2 = f2.apply(4); System.out.println(a2.length); }
小结:
方法引用是对 Lambda表达式特定情况下的一种缩写方式,他使得我们的Lambda表达式更加精简,也可以理解为Lambda表达式的缩写形式, 不过要注意的是方法引用只能引用已存在的方法
五.StreamAPI
先看一个例子:
public static void main(String[] args) {
List<String> list = Arrays.asList("张三","张三丰","李四","王五");
List<String> new1 = new ArrayList<>();
for (String s : list) {
if(s.startsWith("张")){
new1.add(s);
}
}
ArrayList<String> new2 = new ArrayList<>();
for (String s : new1) {
if (s.length() == 2) {
new2.add(s);
}
}
System.out.println(new2);
}
为了减少for循环的使用,引入了 StreamAPI
public static void main(String[] args){
List<String> list = Arrays.asList("张三","张三丰","李四","王五");
list.stream().filter(s->s.startsWith("张"))
.filter(s->s.length() == 2)
.forEach(System.out::println);
}
一.Stream流式思想的概述
注意: Stream 和 IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象
Stream流式思想类似工厂车间的“生产流水线”,Stream不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
StreamAPI可以快速完成许多复杂的操作,如筛选,切片,映射,查找,去除重复,统计,匹配,和规约。
二.流的获取方式
1.根据Collection获取
首先,java.util.Collection接口中加入了default方法 stream,也就是说Collection接口下的所有实现都可以通过stream方法来获取Stream流
public static void main(String[] args) {
List<String> list = Arrays.asList("1","2");
Set<String> set = new HashSet<>();
Stream<String> listS = list.stream();
Stream<String> setS = set.stream();
listS.forEach(System.out::println);
setS.forEach(System.out::println);
}
但是Map接口没有实现Collection接口,? 我们可以根据Map获取对应的key value集合。
Map<String,Integer> map = new HashMap<>();
Set<String> strings = map.keySet();
Collection<Integer> values = map.values();
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 然后在获取流
2.通过Stream的of方法获取
Stream 提供了对于数组的 直接操作方法
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
stream.forEach(System.out::println);
三.Stream中的常用方法介绍
常用方法
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用。
非终结方法:返回值类型仍然是 Stream 类的方法,支持链式调用
Stream注意事项(重要)
1.Stream 智能操作一次
2.Stream 方法返回的是新的流
3.Stream不调用终结方法,中间的操作不会执行
常用方法介绍:
1.foreach
void forEach(Consumer<? super T> action);
2.count
long count();
3.filter
Stream<T> filter(Predicate<? super T> predicate);
4.skip
Stream<T> skip(long n);
5.limit
Stream<T> limit(long maxSize);
Stream.of("a1", "a2", "b1", "b2", "b3").filter(s -> s.startsWith("b"))
.skip(1)
.limit(2)
.forEach(System.out::println);
6.map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream.of("1","2","3")
// .map(str->Integer.parseInt(str))
.map(Integer::parseInt)
.forEach(System.out::println);
7.sorted
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
Stream.of("1","2","3","6")
// .map(str->Integer.parseInt(str))
.map(Integer::parseInt)
.sorted((o1,o2)->o2-o1)
.forEach(System.out::println);
8.distinct
Stream<T> distinct();
基本数据类型可以直接去重,对于引用类型来说需要 重写 equals 和 hashcode 方法
Stream.of("1","3","3","6")
// .map(str->Integer.parseInt(str))
.map(Integer::parseInt)
.sorted((o1,o2)->o2-o1)
.distinct()
.forEach(System.out::println);
9.match:终结方法
10.find:终结方法
11.max min:终结方法
12reduce:求和 最大值 最小值。终结方法
13mapToInt:代表方法,因为包装类使用时浪费内存,用于转换为对应的基本数据类型
14concat: 静态方法,用于将流 进行 合并