Java 8(2014年3月份发布):Lambda(函数式编程)
Java 9(2017年9月份发布):Jigsaw(模块化)
文章目录
- Lambda(函数式编程)
- Lambda表达式入门
- Lambda表达式的标准格式
- Lambda表达式的使用前提
- Lambda表达式必须有上下文推导
- Lambda表达式的参数和返回值
- Lambda与匿名内部类的区别
- Lambda表达式的省略规则
- 函数式接口的定义和使用
- Lambda延迟执行
- Lambda作为方法参数
- Lambda作为方法返回值
- Lambda使用局部变量
- 方法引用
- 常用的函数式接口
- Lambda、方法引用和函数式接口综合练习
- Stream API
- Stream入门体验
- 获取流的多种方式
- Stream的常用方法:过滤filte
- Stream的常用方法:统计个数count
- Stream的常用方法:取用前几个limit
- Stream的常用方法:跳过前几个skip
- Stream的常用方法:映射map
- Stream的常用方法:组合concat
- Stream的常用方法:逐一消费forEach
- 链式方法与终结方法
- 综合练习
- 并发流的获取与使用
- 收集Stream结果到集合中
- 收集Stream结果到数组中
- 接口的组成更新
编程思想转换
如果从北京,到上海,怎么去?
- 走着去
- 骑马
- 马车
- 自行车
- 汽车、摩托车
- 火车
- 飞机
- …
如何进行软件开发,怎么做?
- 自己编写二进制:10101010……
- 汇编语言
- 面向过程
- 面向对象
- 函数式编程思想
1. 面向对象强调“一切皆对象”,如果要想做事情,必须找到对象来做。
2. 函数式编程思想强调“做什么,而不是怎么做”。
Lambda(函数式编程)
Lambda表达式入门
public static void main(String[] args) {
// 匿名内部类对象
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行啦!");
}
};
new Thread(task).start();
}
public static void main(String[] args) {
new Thread(() -> System.out.println("线程任务执行啦!")).start();
}
Runnable接口当中的run方法语义分析
public void run() {
// 方法体
}
- 参数列表为空:不需要任何条件就可以执行该方法
- 没有返回值:方法不产生任何数据结果
- 方法体大括号:这才是关键的方法内容所在
Lambda表达式
() -> System.out.println("线程任务执行啦!")
- 前面一个小括号:不需要任何参数条件,即可直接执行
- 箭头指向后面要做的事情
- 箭头后面就好比是方法体大括号,代表具体要做的内容
Lambda表达式的标准格式
三要素
- 一些参数
- 一个箭头
- 一些代码
(参数类型 参数名称) -> { 一些代码 }
- 如果参数有多个,那么使用逗号分隔;如果参数没有,则留空。
- 箭头是固定写法
- 大括号其实就相当于是方法体。
Lambda表达式的使用前提
- 必须保证有一个接口,而且其中的抽象方法有且仅有一个 (函数式接口)。
- 必须具有上下文环境,才能推导出来Lambda对应的接口。
Cook接口
/*
使用Lambda表达式的必要前提:
1. 必须有一个接口
2. 接口当中必须保证有且仅有一个抽象方法
*/
public interface Cook {
// 唯一的抽象方法
void makeFood();
}
Cook接口实现类
public class CookImpl implements Cook {
@Override
public void makeFood() {
System.out.println("吃饭啦!(实现类方法)");
}
}
使用Cook实现类
public class Demo01UseImpl {
public static void main(String[] args) {
method(new CookImpl());
}
private static void method(Cook cook) {
cook.makeFood();
}
}
使用匿名内部类
public class Demo02UseInnerClass {
public static void main(String[] args) {
method(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭啦!(匿名内部类方法)");
}
});
}
private static void method(Cook cook) {
cook.makeFood();
}
}
使用Lambda表达式
// Lambda的标准格式:(参数类型 参数名称) -> {语句代码}
public class Demo03UseLambda {
public static void main(String[] args) {
method(() -> {
System.out.println("吃饭啦(Lambda表达式)");
});
}
private static void method(Cook cook) {
cook.makeFood();
}
}
Lambda表达式必须有上下文推导
public interface Cook {
void makeFood();
}
public class Demo01Lambda {
public static void main(String[] args) {
method(() -> System.out.println("吃饭啦!"));// Cook接口
}
private static void method(Cook cook) {
cook.makeFood();
}
}
public class Demo02Lambda {
public static void main(String[] args) {
Runnable task = () -> System.out.println("多线程任务执行!");// 使用Runnable接口
new Thread(task).start();
// () -> System.out.println("多线程任务执行!");
// 直接使用Lambda报错,不能根据局部变量的赋值来推导得知Lambda对应的接口。
}
}
Lambda表达式的参数和返回值
数组排序练习
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
public class Demo01PersonSort {
public static void main(String[] args) {
Person[] array = {
new Person("迪丽热巴", 18),
new Person("古力娜扎", 16),
new Person("玛尔扎哈", 35)
};
// 看看数组本来的样子
System.out.println(Arrays.toString(array));
Arrays.sort(array, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
});
// 看看数组排序之后的样子
System.out.println(Arrays.toString(array));
}
}
public class Demo02PersonLambda {
public static void main(String[] args) {
Person[] array = {
new Person("迪丽热巴", 18),
new Person("古力娜扎", 16),
new Person("玛尔扎哈", 35)
};
// 看看数组本来的样子
System.out.println(Arrays.toString(array));
Arrays.sort(array, (Person p1, Person p2) -> {
return p1.getAge() - p2.getAge();
});
// 看看数组排序之后的样子
System.out.println(Arrays.toString(array));
}
}
Lambda与匿名内部类的区别
Lambda表达式并不是匿名内部类的“语法糖”。
语法糖:代码的写法更加简便,但其实原理不变。
例如:
- 方法当中的可变参数,底层仍然是一个数组
- 增强for循环用于java.lang.Iterable实现类型时,底层仍然是一个迭代器
- 自动装箱、自动拆箱
Lambda表达式和匿名内部类存在根本区别,不是语法糖!
-
所需的类型不一样
如果是匿名内部类,那么可以用接口、还可以用抽象类、甚至可以是普通的类。
如果是Lambda表达式,那么必须是接口。 -
使用的限制不同
如果接口当中有且仅有一个抽象方法,那么可以使用Lambda表达式,也可以使用匿名内部类。
但是如果接口当中抽象方法不唯一,那么只能使用匿名内部类,不能使用Lambda表达式了。 -
实现原理也不同
匿名内部类:其实就是一个类,编译之后,直接产生一个单独的.class字节码文件。
Lambda表达式:编译之后,没有单独的.class字节码文件;对应的字节码会在运行的时候才会动态生成。
Lambda表达式的省略规则
- 参数的类型可以省略。但是只能同时省略所有参数的类型,或者干脆都不省略,不能只写个别参数的类型。
- 如果参数有且仅有一个,那么小括号可以省略。
- 如果大括号之内的语句有且仅有一个,那么无论有没有返回值,return、大括号和分号,都可以省略。
public interface Calculator {
// 有两个参数,返回值是int类型,计算二者之和
int sum(int a, int b);
}
public class Demo01CalculatorLambda {
public static void main(String[] args) {
method((int a, int b) -> {
return a + b;
});
}
private static void method(Calculator calc) {
int result = calc.sum(1234, 9876);
System.out.println("结果是:" + result);
}
}
public class Demo02LambdaFormat {
public static void main(String[] args) {
method((a, b) -> a + b);
}
private static void method(Calculator calc) {
int result = calc.sum(1234, 9876);
System.out.println("结果是:" + result);
}
}
函数式接口的定义和使用
函数式接口:接口当中有且仅有一个抽象方法
@FunctionalInterface
注解:用来检测一个接口是不是函数式接口。
编译的时候,写上这个注解:
- 如果是函数式接口,那么编译通过。
- 如果不是函数式接口,那么编译失败。
@FunctionalInterface
public interface MyInterface {
void method();
}
注意:@FunctionalInterface
注解是可选的,就算不用这个注解,只要保证接口满足函数式接口的定义要求,也照样是函数式接口。
public static void main(String[] args) {
MyInterface lambda = () -> System.out.println("Lambda表达式!");
lambda.method();
}
Lambda延迟执行
@FunctionalInterface
public interface MsgBuilder {
String buildMsg();
}
public class Demo01Logger {
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
logger(1, msgA + msgB + msgC);
// Lambda的延迟执行,level不等于1时,Lambda不会进行字符串的拼接buildMsg方法不会执行
loggerLambda(2, () -> msgA + msgB + msgC);
loggerLambda(1, () -> {
System.out.println("Lambda执行啦!");
return msgA + msgB + msgC;
});
}
private static void logger(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
private static void loggerLambda(int level, MsgBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMsg());
}
}
}
Lambda作为方法参数
案例1
public class Demo01LambdaParam {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类执行!");
}
}).start();
new Thread(() -> System.out.println("Lambda执行!")).start();
}
}
案例2
@FunctionalInterface
public interface MySupplier {
Object get();
}
public class Demo02LambdaParam {
public static void main(String[] args) {
method(() -> "Hello");
}
private static void method(MySupplier supplier) {
System.out.println(supplier.get());
}
}
Lambda作为方法返回值
public class Demo03LambdaReturn {
public static void main(String[] args) {
String[] array = { "abcd", "12", "ABC" };
Arrays.sort(array, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
// Comparator<String> comp = getComparator();
// Arrays.sort(array, comp);
Arrays.sort(array, getComparator());
System.out.println(Arrays.toString(array));
}
private static Comparator<String> getComparator() {
// Comparator<String> comp = (o1, o2) -> o1.length() - o2.length();
// return comp;
return (o1, o2) -> o1.length() - o2.length();
}
}
Lambda使用局部变量
Lambda使用局部变量的要求:如果Lambda需要使用外部局部变量,局部变量必须是有效final的。(和匿名内部类的要求一样。)
public class Demo04LambdaLocalvariable {
public static void main(String[] args) {
int[] array = { 5, 15, 20, 13, 21, 50, 49 };
// 使用Lambda的时候,要注意:如果Lambda需要使用外部局部变量,局部变量必须是有效final的。(和匿名内部类的要求一样。)
// array=null;
method(() -> {
int max = array[0];
for (int num : array) {
if (max < num) {
max = num;
}
}
return max;
});
}
private static void method(MySupplier supplier) {
int max = (int) supplier.get();
System.out.println("最大值:" + max);
}
}
方法引用
通过方法引用改进代码
从Java 8开始,引入了一个全新的运算符,方法引用符
(两个冒号连写“::
”)。
所在的表达式就是一个方法引用。
方法引用和Lambda本质是完全一样的,目的就是为了简化Lambda表达式的写法。
两种写法完全等效
- Lambda写法:
s -> System.out.println(s)
- 方法引用写法:
System.out::println
@FunctionalInterface
public interface Printer {
void print(String str);
}
public class Demo01Printer {
public static void main(String[] args) {
// method(s -> System.out.println(s));
method(System.out::println);
}
private static void method(Printer printer) {
printer.print("Hello, World!!!");
}
}
通过对象名引用成员方法
如果一个对象当中有一个成员方法,正好就是Lambda表达式所唯一希望使用的内容,那么这时候就可以使用方法引用。
格式:对象名称::方法名称
注意:只有方法名称,没有方法的参数小括号。
@FunctionalInterface
public interface StringPrinter {
void printUpperCase(String str);
}
public class MethodRefObject {
// 成员方法
public void printStringUpper(String str) {
System.out.println(str.toUpperCase());
}
}
public class Demo01MethodRef {
public static void main(String[] args) {
method(str -> System.out.println(str.toUpperCase())); // Lambda表达式的写法
method(System.out::println); // 原样输出
MethodRefObject obj = new MethodRefObject(); // 已经存在的一个含有指定功能(成员方法)的对象
method(obj::printStringUpper); // 与Lambda等效的方法引用写法
}
private static void method(StringPrinter printer) {
printer.printUpperCase("Hello");
}
}
通过类名称引用静态方法
如果Lambda表达式需要做的事情,正好就是一个的类当中的静态方法内容。
那么可以使用方法引用的又一种简写方式:类名称::静态方法名
@FunctionalInterface
public interface Calculator {
// 求出绝对值
int getAbs(int num);
}
public class Demo01MethodRef {
public static void main(String[] args) {
// 首先使用Lambda写法
method(n -> Math.abs(n));
// 方法引用,类名称::静态方法名
method(Math::abs);
}
private static void method(Calculator calculator) {
int result = calculator.getAbs(-20);
System.out.println("结果是:" + result);
}
}
通过super引用父类方法
如果Lambda表达式要做的事情,正好就是父类当中的方法内容。
那么可以通过super关键字来引用父类当中的方法,格式:super::父类方法名称
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
public class Man extends Human {
@Override
public void sayHello() {
method(() -> System.out.println("Hello!"));
method(() -> super.sayHello());
method(super::sayHello);
}
private void method(Greetable lambda) {
lambda.greet(); // Hello!
System.out.println("I am a man!");
}
}
@FunctionalInterface
public interface Greetable {
void greet();
}
public class Demo01MethodRef {
public static void main(String[] args) {
Man man = new Man();
man.sayHello();
}
}
通过this引用本类方法
如果Lambda表达式要做的事情,正好就是本类当中的方法内容。
那么可以使用方法引用,借助this关键字,格式:this::本类方法名称
@FunctionalInterface
public interface Richable {
void buyHouse();
}
public class Husband {
public void marry(Richable lambda) {
lambda.buyHouse();
}
// 本类当中已经存在的方法
public void buy() {
System.out.println("买一个大房子!");
}
public void beHappy() {
// marry(() -> System.out.println("买一个大房子!"));
// marry(() -> this.buy());
marry(this::buy);
}
}
public class Demo01MethodRef {
public static void main(String[] args) {
Husband husband = new Husband();
husband.beHappy();
}
}
类的构造器引用
如果Lambda表达式要做的事情就是构造方法的内容。
那么可以使用构造器引用,格式:类名称::new
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@FunctionalInterface
public interface PersonBuilder {
// 根据一个字符串当做是名字,然后创建一个Person对象
Person build(String name);
}
public class Demo01MethodRefConstructor {
public static void main(String[] args) {
// method(name -> new Person(name)); // Lambda写法
method(Person::new); // 构造器引用
}
private static void method(PersonBuilder builder) {
Person person = builder.build("赵丽颖");
System.out.println("名字叫:" + person.getName());
}
}
数组的构造器引用
如果Lambda表达式要做的事情,正好就是数组的构造器要做的内容。
那么可以使用数组的构造器引用,格式:元素类型[]::new
@FunctionalInterface
public interface ArrayBuilder {
int[] build(int length);
}
public class Demo01MethodRefArray {
public static void main(String[] args) {
// method(length -> new int[length]); // Lambda的写法
method(int[]::new);
}
private static void method(ArrayBuilder builder) {
int[] array = builder.build(10);
array[0] = 10;
array[1] = 20;
System.out.println("数组的长度:" + array.length);
System.out.println("数组的内容:" + Arrays.toString(array));
}
}
常用的函数式接口
为了更好地支持函数式编程,所以JDK给我们内置了很多常用的函数式接口,一般都放在java.util.function包中
Supplier接口
Supplier接口的抽象方法:get
java.util.function.Supplier接口作用:“向外提供”一个数据。
public class Demo01Supplier {
public static void main(String[] args) {
method(() -> "Hello");
}
private static void method(Supplier<String> supplier) {
String str = supplier.get();
System.out.println("字符串内容:" + str);
}
}
练习:求出数组元素最大值
public class Demo02ArrayMax {
public static void main(String[] args) {
int[] array = { 5, 15, 200, 13, 21, 50, 49 };
// 使用Lambda的时候,要注意:如果Lambda需要使用外部局部变量,局部变量必须是有效final的。(和匿名内部类的要求一样。)
// array=null;
method(() -> {
int max = array[0];
for (int num : array) {
if (max < num) {
max = num;
}
}
return max;
});
}
private static void method(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println("最大值:" + max);
}
}
Consumer接口
Consumer接口的抽象方法:accept
public class Demo03Consumer {
public static void main(String[] args) {
method(s -> System.out.println(s));
method(System.out::println);
}
private static void method(Consumer<String> function) {
function.accept("Hello");
}
}
Consumer接口的默认方法:andThen
public class Demo04ConsumerAndThen {
public static void main(String[] args) {
// 有两个Lambda表达式
// 第一个打印大写字母
// 第二个打印小写字母
// 两个Lambda操作其实消费的是同一个数据。
method(s -> System.out.println(s.toUpperCase()), s -> System.out.println(s.toLowerCase()));
}
private static void method(Consumer<String> one, Consumer<String> two) {
// 先做one操作,然后再紧跟着做two操作,将二者拼接到一起:函数模型拼接
one.andThen(two).accept("Hello");
}
}
练习:分步打印信息
public class Demo05NameAndGender {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "玛尔扎哈,男" };
method(s -> System.out.print("姓名:" + s.split(",")[0] + " "),
s -> System.out.println("性别:" + s.split(",")[1]), array);
}
// 第一个参数one代表根据字符串打印其中的姓名部分
// 第二个参数two代表根据字符串打印其中的性别部分
private static void method(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info);
}
}
}
Predicate接口
Predicate接口的抽象方法:test
java.util.function.Predicate函数式接口的作用:对指定类型的对象进行操作,得到一个boolean值结果。
public class Demo06Predicate {
public static void main(String[] args) {
// 现在需要传递的是判断条件的规则内容
method(s -> s.length() > 3);
}
private static void method(Predicate<String> predicate) {
// 根据Predicate判断一下Hello字符串是不是特别长
boolean veryLong = predicate.test("Hello");
System.out.println("长不长:" + veryLong);
}
}
Predicate接口的默认方法:and、or、negate
Predicate接口当中有3个常用的默认方法(函数模型拼接)
- and 与,并且 &&
- or 或,或者 ||
- negate 非,取反 !
这三个默认方法返回值仍然还是Predicate接口自身,说明这只是在拼接函数模型而已。
只有当最终调用test抽象方法的时候,拼接好的若干步骤的函数模型,才会一次性执行。
public class Demo07PredicateDefault {
public static void main(String[] args) {
// 条件一:必须包含大写字母H
// 条件二:必须包含大写字母W
// 两个条件缺一不可
methodAnd(s -> s.contains("H"), s -> s.contains("W")); // false
// 两个条件满足至少一个即可
methodOr(s -> s.contains("H"), s -> s.contains("W")); // true
// 取反
methodNegate(s -> s.length() > 3);
}
private static void methodNegate(Predicate<String> function) {
boolean valid = function.negate().test("Hello");
System.out.println("结果是:" + valid);
}
private static void methodOr(Predicate<String> one, Predicate<String> two) {
boolean valid = one.or(two).test("Helloworld");
System.out.println("结果是:" + valid);
}
private static void methodAnd(Predicate<String> one, Predicate<String> two) {
boolean valid = one.and(two).test("Helloworld");
System.out.println("结果是:" + valid);
}
}
练习:集合信息筛选
题目:
- 有一个字符串数组,元素内容形如:“迪丽热巴,女”……
- 需要对数组当中的元素进行过滤,根据下面的两个条件(并且)
- 将满足条件的元素添加到List集合当中
条件:
- 名字必须是四个字
- 必须是女的
public class Demo08PredicateFilter {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "玛尔扎哈,男", "赵丽颖,女" };
List<String> result = filter(array, s -> s.split(",")[0].length() == 4, s -> "女".equals(s.split(",")[1]));
System.out.println(result);
}
private static List<String> filter(String[] array, Predicate<String> one, Predicate<String> two) {
// 对数组当中的每个元素进行挨个逐一处理
// 两个条件缺一不可
// 满足条件的,放入集合中;不满足的,不放。
List<String> list = new ArrayList<>();
for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
}
Function接口
Function接口的抽象方法:apply
java.util.function.Function<T,R>
- 第一个泛型T代表:函数的参数类型
- 第二个泛型R代表:函数的返回值类型
public class Demo09Function {
public static void main(String[] args) {
method(s -> Integer.parseInt(s));
method(Integer::parseInt);
methodString(s -> s.split(",")[0]);
}
private static void methodString(Function<String, String> function) {
String name = function.apply("赵丽颖,18");
System.out.println("姓名:" + name);
}
// 本来是String,需要转换成为Integer
private static void method(Function<String, Integer> function) {
int num = function.apply("20");
num += 100;
System.out.println("结果是:" + num);
}
}
Function接口的默认方法:andThen、compose
Function接口当中有两个默认方法
- andThen 先做自己,再做其他
- compose 先做其他,再做自己
二者的方向正好相反。
//String --> split --> String --> Integer.parseInt --> int
//"赵丽颖,20" "20" 20
public class Demo10FunctionDefault {
public static void main(String[] args) {
methodAndThen(s -> s.split(",")[1], Integer::parseInt);
methodCompose(s -> s.split(",")[1], Integer::parseInt);
}
private static void methodAndThen(Function<String, String> one, Function<String, Integer> two) {
int age = one.andThen(two).apply("赵丽颖,20");
age += 1;
System.out.println("年龄是:" + age);
}
private static void methodCompose(Function<String, String> one, Function<String, Integer> two) {
int age = two.compose(one).apply("赵丽颖,20");
age += 1;
System.out.println("年龄是:" + age);
}
}
Lambda、方法引用和函数式接口综合练习
题目:
- 有一个字符串数组,里面的元素形如:“赵丽颖,20”、“柳岩,18”……
- 将每一个字符串元素对照着创建成为Person对象(包含姓名、年龄)
- 要求最终得到List集合结果
- 全程使用Lambda、方法引用和函数式接口
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
@FunctionalInterface
public interface PersonBuilder {
Person build(String name, int age);
}
public class DemoCustomFunction {
public static void main(String[] args) {
String[] array = { "赵丽颖,20", "柳岩,18" };
List<Person> personList = getPersonList(array, Person::new, s -> s.split(",")[0], s -> s.split(",")[1],
Integer::parseInt);
System.out.println(personList);
}
private static List<Person> getPersonList(String[] array, PersonBuilder builder,
Function<String, String> nameGetter, Function<String, String> ageGetter,
Function<String, Integer> ageParser) {
List<Person> list = new ArrayList<>();
for (String info : array) {
// 对数组当中的字符串逐一处理
// 1、根据完整字符串拿出来一个姓名字符串
String name = nameGetter.apply(info);
// 2、根据完整字符串拿出来一个年龄数字(两个步骤)
int age = ageGetter.andThen(ageParser).apply(info);
Person person = builder.build(name, age);
list.add(person);
}
return list;
}
}
Stream API
Java 8全新引入的Stream API
核心接口:java.util.stream.Stream<T>
Stream入门体验
打印集合中名字为三个字且姓张的人
传统方式
public class Demo01Collection {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("赵丽颖");
list.add("鹿晗");
list.add("迪丽热巴");
list.add("张三");
list.add("张三丰");
list.add("张无忌");
list.add("赵敏");
for (String name : list) {
if (name != null && name.length() == 3 && name.startsWith("张")) {
System.out.println(name);
}
}
}
}
多步循环遍历
public class Demo02Collection {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("赵丽颖");
list.add("鹿晗");
list.add("迪丽热巴");
list.add("张三");
list.add("张三丰");
list.add("张无忌");
list.add("赵敏");
// 拆成一步一步:首先过滤出来三个字的,然后过滤姓张的,再进行打印
// 1. 过滤三个字的
List<String> threeList = new ArrayList<>();
for (String name : list) {
if (name != null && name.length() == 3) {
threeList.add(name);
}
}
// 2. 过滤姓张的
List<String> zhangList = new ArrayList<>();
for (String name : threeList) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 3. 打印
for (String name : zhangList) {
System.out.println(name);
}
}
}
Stream的更优写法
public class Demo03Stream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("赵丽颖");
list.add("鹿晗");
list.add("迪丽热巴");
list.add("张三");
list.add("张三丰");
list.add("张无忌");
list.add("赵敏");
// 1. 过滤只要三个字的
// 2. 过滤只要姓张的
// 3. 挨个儿打印
list.stream().filter(s -> s.length() == 3).filter(s -> s.startsWith("张")).forEach(System.out::println);
}
}
获取流的多种方式
获取流的常用三种方式
- 通过Collection:直接调用stream()方法
- 通过Map:并不是Collection的子接口,而且其中是一对儿一对儿的,不能直接获取,间接转换成为集合然后获取流。
- 通过数组:通过Arrays.stream方法,或者Stream.of方法
public class Demo01GetStream {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
Stream<String> stream = collection.stream();
Map<String, Integer> map = new HashMap<>();
// 获取所有的键对应的流
Stream<String> streamKey = map.keySet().stream();
// 获取所有的值对应的流
Stream<Integer> streamValue = map.values().stream();
// 获取所有的KV键值对儿对应的流
Stream<Map.Entry<String, Integer>> streamEntry = map.entrySet().stream();
String[] array = { "张三丰", "灭绝师太" };
Stream<String> streamArray = Arrays.stream(array);
Stream<String> StreamArray2 = Stream.of(array);
int[] arrayInt = new int[3];
IntStream streamInt = Arrays.stream(arrayInt);
Double[] arrayDouble = new Double[3];
Stream<Double> streamDouble = Stream.of(arrayDouble);
}
}
Stream的常用方法:过滤filte
filter过滤方法,参数必须是一个Predicate接口实例。
方法的调用会返回一个全新的Stream接口,原来的不变。
Stream是一次性使用的
,每次调用方法之后都会返回一个新的Stream接口实例。
public class Demo02StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("How are you");
list.add("World");
// Stream<String> stream = list.stream(); // 获取流
// 参数是一个Predicate,也就是能产生boolean结果的过滤规则
// Stream<String> streamResult = stream.filter(s -> s.contains("H"));
// 一个Stream只能使用唯一的一次,阅后即焚
// Stream<String> streamResult2 = stream.filter(s -> s.length() == 5);
Stream<String> resultStream = list.stream().filter(s -> s.contains("H")).filter(s -> s.length() == 5);
}
}
Stream的常用方法:统计个数count
Stream当中统计元素个数:count方法,返回的不是Stream接口自身,而是元素个数数字。
public class Demo03StreamCount {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("How are you");
list.add("World");
long count = list.stream().filter(s -> s.length() <= 5).count();
System.out.println("满足要求的元素个数有:" + count);
}
}
Stream的常用方法:取用前几个limit
Stream当中的元素如果希望只用前n个,那么可以使用limit方法
- 参数:需要指定的元素个数(前几个)
- 返回值:仍然是一个Stream接口(支持链式调用)
public class Demo04StreamLimit {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add("Stream-" + i);
}
long count = list.stream().limit(10).count();
System.out.println(count);
list.stream().limit(10).forEach(System.out::println);
}
}
Stream的常用方法:跳过前几个skip
如果希望跳过前几个元素,那么可以使用方法:skip
- 参数:需要跳过的元素个数
- 返回值:仍然是一个Stream接口(支持链式调用)
public class Demo05StreamSkip {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("Stream-" + i);
}
long count = list.stream().skip(10).count();
System.out.println("剩余个数:" + count);
list.parallelStream().skip(10).forEach(System.out::println);
}
}
Stream的常用方法:映射map
在流当中,如果希望进行映射操作,可以使用map方法。
map方法参数是一个Function<T,R>接口。
返回值仍然是一个Stream接口。
public class Demo06StreamMap {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("10");
list.add("20");
list.add("30");
// 将字符串"10"转换成为数字10
// 然后再累加1000,最终得到<Integer>
list.stream().map(Integer::parseInt).map(i -> i + 1000).forEach(System.out::println);
}
}
Stream的常用方法:组合concat
concat方法可以将两个流合并成为一个整体。
注意事项:这个concat方法是Stream接口当中的静态方法,可以静态调用。
public class Demo07StreamConcat {
public static void main(String[] args) {
String[] array1 = { "迪丽热巴", "古力娜扎", "玛尔扎哈" };
String[] array2 = { "鹿晗", "吴亦凡", "张艺兴" };
Stream<String> stream1 = Stream.of(array1);
Stream<String> stream2 = Arrays.stream(array2);
Stream<String> stream = Stream.concat(stream1, stream2);
stream.forEach(System.out::println);
}
}
Stream的常用方法:逐一消费forEach
如果希望对流当中的元素进行逐一挨个儿的消费处理,那么可以使用forEach方法,参数是一个Consumer接口。
public class Demo08StreamForEach {
public static void main(String[] args) {
String[] array = { "洪七公", "欧阳锋", "段智兴", "黄药师", "王重阳" };
Arrays.stream(array).forEach(System.out::println);
System.out.println("=====================");
Arrays.stream(array).forEach(Demo08StreamForEach::methodConsume);
}
private static void methodConsume(String str) {
System.out.println("高手高手高高手:" + str);
}
}
链式方法与终结方法
Stream当中的方法可以分成下面两类
- 链式方法: 返回值仍然是Stream接口自身,支持链式调用,只是在进行函数模型拼接。
- 终结方法: 返回值不再是Stream接口自身,不支持链式调用,会将所有的操作全都触发执行。
链式方法
- filter过滤
- limit取用前几个
- skip跳过前几个
- map映射
- concat组合拼接
终结方法
- count统计个数
- forEach逐一消费
Stream本身并不是集合,并不会存储任何元素,本身就是一个函数模型。
调用链式方法的时候,就是在拼接Stream模型。
Stream和Lambda一样,也有延迟执行的效果。
综合练习
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
one.add("黄药师");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建Person对象;
- 打印整个队伍的Person对象信息。
传统集合的元素处理
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + '}';
}
}
public class Demo01Collection {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
one.add("黄药师");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// 1. 第一个队伍只要名字为3个字的成员姓名;
List<String> nameThreeList = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
nameThreeList.add(name);
}
}
// 2. 第一个队伍筛选之后只要前3个人;
List<String> oneList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneList.add(nameThreeList.get(i));
}
// 3. 第二个队伍只要姓张的成员姓名;
List<String> zhangList = new ArrayList<>();
for (String name : two) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 4. 第二个队伍筛选之后不要前2个人;
List<String> twoList = new ArrayList<>();
for (int i = 2; i < zhangList.size(); i++) {
twoList.add(zhangList.get(i));
}
// 5. 将两个队伍合并为一个队伍;
List<String> totalList = new ArrayList<>();
totalList.addAll(oneList);
totalList.addAll(twoList);
// 6. 根据姓名创建Person对象;
List<Person> personList = new ArrayList<>();
for (String name : totalList) {
personList.add(new Person(name));
}
// 7. 打印整个队伍的Person对象信息。
for (Person person : personList) {
System.out.println(person);
}
}
}
Stream的元素处理
public class Demo02Stream {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
one.add("黄药师");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
Stream<String> oneStream = one.stream().filter(s -> s.length() == 3).limit(3);
Stream<String> twoStream = two.stream().filter(s -> s.startsWith("张")).skip(2);
Stream.concat(oneStream, twoStream).map(Person::new).forEach(System.out::println);
}
}
并发流的获取与使用
如何才能获取并发的流:
- 直接获取并发流:parallelStream方法
- 先获取普通流,然后变成并发的:parallel方法
并发流背后使用的是:Fork/Join框架
public class Demo03ParallelStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("Stream-" + i);
}
// 1. 直接获取并发流
// list.parallelStream().forEach(System.out::println);
// 2. 首先获取普通流,然后变成并发的
list.stream().parallel().forEach(System.out::println);
}
}
收集Stream结果到集合中
Stream当中收集结果需要使用 collect
方法,方法的参数是一个Collector接口。
Collector接口通常不需要自己实现,借助工具类Collectors其中的toList、toSet等方法即可。
public class Demo01StreamCollect {
public static void main(String[] args) {
String[] array = { "Hello", "World", "Java" };
Stream<String> stream = Arrays.stream(array);
// ...
List<String> list = stream.collect(Collectors.toList());
Set<String> set = Arrays.stream(array).collect(Collectors.toSet());
}
}
收集Stream结果到数组中
public class Demo02StreamArray {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// ...
Object[] array1 = list.toArray();
Object[] array2 = Stream.of("AAA", "BBB", "CCC").toArray();
}
}
解决泛型数组的限制
public class Demo03StreamArray {
public static void main(String[] args) {
Stream<String> stream = Stream.of("AAA", "BBB", "CCC");
String[] array = stream.toArray(String[]::new);
}
}
接口的组成更新
接口的组成部分
- 常量
- 抽象方法
- 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
从Java 8开始,接口当中允许定义默认方法
接口的实现类当中必须对接口所有的抽象方法都要覆盖重写,除非实现类是一个抽象类。
接口升级:本来是2个抽象方法,现在需要变成3个抽象方法。
设计模式当中的开闭原则:对扩展开放,对修改关闭。
从Java 8开始,接口当中允许定义default默认方法。
- 常量的修饰符:public static final(都可以省略)
- 抽象方法的修饰符:public abstract(都可以省略)
默认方法的修饰符:public default(public可以省略,default不能省略)
默认方法可以有方法体实现
默认方法也可以进行覆盖重写:
去掉default关键字,重新指定大括号方法体。
public interface MyInterface {
void method1(); // 本来已经存在的抽象方法
void method2(); // 本来已经存在的抽象方法
// 现在需要新定义一个方法
// void methodNew();
public default void methodNew() {
System.out.println("接口的默认方法执行!");
}
}
public class MyInterfaceImplA implements MyInterface {
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void methodNew() {
System.out.println("实现类A当中覆盖重写了接口的默认方法!");
}
}
public class MyInterfaceImplB implements MyInterface {
@Override
public void method1() {
}
@Override
public void method2() {
}
}
public interface MyNewInterface extends MyInterface {
// 在继承了两个老的抽象方法同时,定义了一个新的抽象方法
void methodNew();
}
public class Demo01Main {
public static void main(String[] args) {
MyInterface objB = new MyInterfaceImplB();
objB.methodNew();
}
}
从Java 8开始,接口当中允许定义静态方法
静态方法的修饰符:public static(public可以省略,static不能省略)
public interface Animal {
public abstract void eat(); // 抽象方法:吃东西
public static Animal getAnimal() {
return new Dog();
}
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
/*
左边是接口类型,说明我并不关心到底是猫还是狗,只要是动物就行。
*/
public class Demo01Animal {
public static void main(String[] args) {
// Animal animal = new Dog();
// animal.eat();
Animal animal = Animal.getAnimal();
animal.eat();
}
}
接口当中的静态方法可以作为“工厂”使用,可以让main方法和Cat,Dog进行“解耦”。高内聚,低耦合。
集合接口的工厂静态方法:of
Java 8当中接口中可以定义的静态方法,
这个特性在 Java 9
当中得以广泛应用。
public class Demo02Collection {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("迪丽热巴");
list1.add("古力娜扎");
list1.add("玛尔扎哈");
System.out.println(list1);
List<String> list2 = new ArrayList<>() {
{
add("迪丽热巴");
add("古力娜扎");
add("玛尔扎哈");
}
};
System.out.println(list2);
List<String> list3 = List.of("迪丽热巴", "古力娜扎", "玛尔扎哈");
System.out.println(list3);
Set<String> set = Set.of("洪七公", "黄药师", "欧阳锋", "段智兴");
System.out.println(set);
Map<String, Integer> map1 = Map.of("赵丽颖", 100);
System.out.println(map1);
Map<String, Integer> map2 = Map.of("柳岩", 98, "高圆圆", 95);
System.out.println(map2);
}
}
从Java 9开始,接口当中允许定义私有方法
私有方法可以是:
- 成员私有方法
- 静态私有方法
public interface MyInterface {
default void method1() {
System.out.println("方法1执行!");
makeFood();
}
default void method2() {
System.out.println("方法2执行!");
makeFood();
}
private void makeFood() {
System.out.println("起火");
System.out.println("上锅");
System.out.println("放油");
System.out.println("炒菜");
System.out.println("装盘");
}
static void methodA() {
System.out.println("静态方法A!");
methodCommon();
}
static void methodB() {
System.out.println("静态方法B!");
methodCommon();
}
private static void methodCommon() {
System.out.println("Hello");
System.out.println("World");
System.out.println("Java");
}
}
public class Demo01Main {
public static void main(String[] args) {
// MyInterface.methodA();
// MyInterface.methodB();
MyInterface obj = new MyInterfaceImpl();
obj.method1();
obj.method2();
}
}
接口的组成梳理
定义一个接口,基本组成都有
Java 7或者更老的版本中
- 常量:public static final(全都可以省略)
- 抽象方法:public abstract(全都可以省略)
Java 8新特性
- 默认方法:public default(public可以省略,default不能省略,必须有方法体)
- 静态方法:public static(public可以省略,static不能省略,必须有方法体)
Java 9新特性:
- 私有方法
a.私有的成员方法:private(不能省略)
b.私有的静态方法:private static(不能省略)