Java8、9新特性

Java 8(2014年3月份发布):Lambda(函数式编程)
Java 9(2017年9月份发布):Jigsaw(模块化)

文章目录


编程思想转换
如果从北京,到上海,怎么去?

  • 走着去
  • 骑马
  • 马车
  • 自行车
  • 汽车、摩托车
  • 火车
  • 飞机

如何进行软件开发,怎么做?

  • 自己编写二进制: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() {
// 方法体
}

  1. 参数列表为空:不需要任何条件就可以执行该方法
  2. 没有返回值:方法不产生任何数据结果
  3. 方法体大括号:这才是关键的方法内容所在

Lambda表达式

() -> System.out.println("线程任务执行啦!")

  1. 前面一个小括号:不需要任何参数条件,即可直接执行
  2. 箭头指向后面要做的事情
  3. 箭头后面就好比是方法体大括号,代表具体要做的内容

Lambda表达式的标准格式

三要素

  1. 一些参数
  2. 一个箭头
  3. 一些代码

(参数类型 参数名称) -> { 一些代码 }

  1. 如果参数有多个,那么使用逗号分隔;如果参数没有,则留空。
  2. 箭头是固定写法
  3. 大括号其实就相当于是方法体。

Lambda表达式的使用前提

  1. 必须保证有一个接口,而且其中的抽象方法有且仅有一个 (函数式接口)
  2. 必须具有上下文环境,才能推导出来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表达式并不是匿名内部类的“语法糖”。

语法糖:代码的写法更加简便,但其实原理不变。
例如:

  1. 方法当中的可变参数,底层仍然是一个数组
  2. 增强for循环用于java.lang.Iterable实现类型时,底层仍然是一个迭代器
  3. 自动装箱、自动拆箱

Lambda表达式和匿名内部类存在根本区别,不是语法糖!

  1. 所需的类型不一样
    如果是匿名内部类,那么可以用接口、还可以用抽象类、甚至可以是普通的类。
    如果是Lambda表达式,那么必须是接口。

  2. 使用的限制不同
    如果接口当中有且仅有一个抽象方法,那么可以使用Lambda表达式,也可以使用匿名内部类。
    但是如果接口当中抽象方法不唯一,那么只能使用匿名内部类,不能使用Lambda表达式了。

  3. 实现原理也不同
    匿名内部类:其实就是一个类,编译之后,直接产生一个单独的.class字节码文件。
    Lambda表达式:编译之后,没有单独的.class字节码文件;对应的字节码会在运行的时候才会动态生成。

Lambda表达式的省略规则

  1. 参数的类型可以省略。但是只能同时省略所有参数的类型,或者干脆都不省略,不能只写个别参数的类型。
  2. 如果参数有且仅有一个,那么小括号可以省略。
  3. 如果大括号之内的语句有且仅有一个,那么无论有没有返回值,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 注解:用来检测一个接口是不是函数式接口。
编译的时候,写上这个注解:

  1. 如果是函数式接口,那么编译通过。
  2. 如果不是函数式接口,那么编译失败。
@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集合当中

条件:

  1. 名字必须是四个字
  2. 必须是女的
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);
	}

}

获取流的多种方式

获取流的常用三种方式

  1. 通过Collection:直接调用stream()方法
  2. 通过Map:并不是Collection的子接口,而且其中是一对儿一对儿的,不能直接获取,间接转换成为集合然后获取流。
  3. 通过数组:通过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当中的方法可以分成下面两类

  1. 链式方法: 返回值仍然是Stream接口自身,支持链式调用,只是在进行函数模型拼接。
  2. 终结方法: 返回值不再是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("张三");
  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建Person对象;
  7. 打印整个队伍的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);
	}

}

并发流的获取与使用

如何才能获取并发的流:

  1. 直接获取并发流:parallelStream方法
  2. 先获取普通流,然后变成并发的: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);
	}

}

接口的组成更新

接口的组成部分

  1. 常量
  2. 抽象方法
  3. 默认方法(Java 8)
  4. 静态方法(Java 8)
  5. 私有方法(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开始,接口当中允许定义私有方法

私有方法可以是:

  1. 成员私有方法
  2. 静态私有方法
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或者更老的版本中

  1. 常量:public static final(全都可以省略)
  2. 抽象方法:public abstract(全都可以省略)

Java 8新特性

  1. 默认方法:public default(public可以省略,default不能省略,必须有方法体)
  2. 静态方法:public static(public可以省略,static不能省略,必须有方法体)

Java 9新特性:

  1. 私有方法
    a.私有的成员方法:private(不能省略)
    b.私有的静态方法:private static(不能省略)
发布了160 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45730091/article/details/103904992