Java 笔记 25:Lambda表达式,Stream,Optional

Lambda表达式

Lambda表达式是函数式编程的风格,是为了给SAM接口的变量和形参赋值的一种语法。

  • 目的:减少代码的冗余,增加可读性。

  • 示例1:

  • 开启一个线程,这个线程的任务:打印“hello”
    要求用实现Runnable接口的方式来创建多线程

    Runnable接口符合SAM特征: public abstract void run();
    Runnable接口唯一的抽象方法就是public abstract void run();

public class Test01 {	
	@Test
	public void test05(){
		//使用Lambda表达式
		new Thread(()->System.out.println("hello")).start();
	}
	
	@Test
	public void test04(){
		//匿名内部类,匿名对象
		new Thread(new Runnable(){
			@Override
			public void run() {
				System.out.println("hello");
			}
		}).start();
	}
	
	@Test
	public void test03(){
		//匿名内部类,匿名对象
		Thread t = new Thread(new Runnable(){
			@Override
			public void run() {
				System.out.println("hello");
			}
		});
		t.start();
	}
	
	@Test
	public void test02(){
		//匿名内部类,对象有名字
		Runnable my = new Runnable(){
			@Override
			public void run() {
				System.out.println("hello");
			}
		};
		Thread t = new Thread(my);
		t.start();
	}
	
	@Test
	public void test01(){
//		非Lambda表达式方式实现,并且有名字的实现类实现
		MyRunnable my = new MyRunnable();
		Thread t = new Thread(my);
		t.start();
	}
}
class MyRunnable implements Runnable{

	@Override
	public void run() {
		System.out.println("hello");
	}
	
}

示例2:

  • 有一个数组,存储了5个学生对象,这个学生类的自然排序是按照编号排序。但是我希望这个数组是按照姓名排序
  • Comparator符合SAM接口的特征: public abstract int compare(T t1, T t2)
public class Test02 {
	@Test
	public void test04(){
		Student[] arr = new Student[5];
		arr[0] = new Student(1,"zhangsan");
		arr[1] = new Student(2,"lisi");
		arr[2] = new Student(3,"wangwu");
		arr[3] = new Student(4,"zhaoliu");
		arr[4] = new Student(5,"qianqi");
		
		Arrays.sort(arr, (o1, o2)-> o1.getName().compareTo(o2.getName()));//按照元素的姓名定制排序
		for (Student student : arr) {
			System.out.println(student);
		}
	}
	
	@Test
	public void test03(){
		Student[] arr = new Student[5];
		arr[0] = new Student(1,"zhangsan");
		arr[1] = new Student(2,"lisi");
		arr[2] = new Student(3,"wangwu");
		arr[3] = new Student(4,"zhaoliu");
		arr[4] = new Student(5,"qianqi");
		
		Arrays.sort(arr, new Comparator<Student>(){
			@Override
			public int compare(Student o1, Student o2) {
				return o1.getName().compareTo(o2.getName());
			}
		});//按照元素的姓名定制排序
		for (Student student : arr) {
			System.out.println(student);
		}
	}
	
	@Test
	public void test02(){
		Student[] arr = new Student[5];
		arr[0] = new Student(1,"zhangsan");
		arr[1] = new Student(2,"lisi");
		arr[2] = new Student(3,"wangwu");
		arr[3] = new Student(4,"zhaoliu");
		arr[4] = new Student(5,"qianqi");
		
		Arrays.sort(arr, new NameComparator());//按照元素的姓名定制排序
		for (Student student : arr) {
			System.out.println(student);
		}
	}
	
	@Test
	public void test01(){
		Student[] arr = new Student[5];
		arr[0] = new Student(1,"zhangsan");
		arr[1] = new Student(2,"lisi");
		arr[2] = new Student(3,"wangwu");
		arr[3] = new Student(4,"zhaoliu");
		arr[4] = new Student(5,"qianqi");
		
		Arrays.sort(arr);//按照元素的自然顺序排列
		for (Student student : arr) {
			System.out.println(student);
		}
	}
}
class NameComparator implements Comparator<Student>{

	@Override
	public int compare(Student o1, Student o2) {
		return o1.getName().compareTo(o2.getName());
	}
	
}


class Student implements Comparable<Student>{
	private int id;
	private String name;
	public Student(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public Student() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}
	@Override
	public int compareTo(Student o) {
		return this.id - o.id;
	}
}

函数式接口:SAM接口

  • 函数式接口:SAM接口
  • Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法(默认方法和静态方法,或者是从Object继承的方法)。
  • 回忆之前学过的接口:
  • (1)Runnable
  • (2)Comparable
  • (3)Comparator
  • (4)Iterable
  • (5)Iterator
  • (6)Collection,Set,List,Map,Queue,Deque…
  • (7)Serializable
  • (8)Externalizable
  • (9)FileFilter
  • (10)InvocationHandler
  • 哪些是符号SAM特征的呢?
  • (1)Runnable public void run()
  • (2)Comparable public int compareTo(T t)
  • (3)Comparator public int compare(T t1, T t2)
  • (4)Iterable public Iterator iterator()
  • (5)FileFilter public boolean accept(File pathname);
  • (6)InvocationHandler public Object invoke(Object proxy, Method method, Object[] args)
  • 按照语法来说,只要符号SAM特征的接口,都可以使用Lambda表达式。
  • 但是Java建议只针对标记了@FunctionalInterface这注解的SAM接口使用Lambda表达式
  • 上面哪些标记了@FunctionalInterface注解:
  • (1)Runnable public void run()
  • (3)Comparator public int compare(T t1, T t2)
  • (5)FileFilter public boolean accept(File pathname);
  • 如果没有标记@FunctionalInterface注解的,说明它考虑了以后可能增加抽象方法。目前使用没问题,就是以后可能有风险。
  • Java8增加了大量的函数式接口给我们使用,而且基本上能满足常规的所有情况。
  • 这些函数式接口集中在java.util.function包中。
  • 四大类:
  • (1)消费型接口
  •  它的抽象方法有一个特征:    有参无返回值		例如:void accept(T t)
    
  • (2)供给型接口
  •  它的抽象方法有一个特征:    无参有返回值		例如:T get()
    
  • (3)判断型接口
  •  它的抽象方法有一个特征:    有参有返回值,但是返回值类型是boolean   例如:boolean  test(T t)
    
  • (4)功能型接口
  •  它的抽象方法有一个特征:    有参有返回值	       例如: R apply(T t)
    

一、消费型接口

  • 经典代表:
  •  Consumer<T>   抽象方法  void accept(T t)  
    
  • 延伸代表:各种变形们

(1)BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
(2)
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
(3)
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值
*
*

  • 总结:(1)消费型接口,都以“Consumer”单词为结尾
  •       (2)Bi开头,传两个参数   Binary(二元的)
    
  •        (3)xxConsumer,前面的xx代表形参类型
    

二、供给型接口

  • 经典代表:
  • Supplier 抽象方法 T get()
  • 延伸的代表们:
    *(1)BooleanSupplier boolean getAsBoolean() 返回一个boolean值
    *(2)DoubleSupplier double getAsDouble() 返回一个double值
    *(3)IntSupplier int getAsInt() 返回一个int值
    *(4)LongSupplier long getAsLong() 返回一个long值
  • 总结:(1)供给型接口以“Supplier”单词结尾
  •  (2)xxSupplier说明返回xx类型的结果
    
  •  (3)供给型接口的抽象方法都是无参的
    

三、判断型接口

  • 经典代表:
  •  Predicate<T>  抽象方法    boolean test(T t)  
    
  • 延伸代表们:
    (1)BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
    (2)DoublePredicate boolean test(double value) 接收一个double值
    (3)IntPredicate boolean test(int value) 接收一个int值
    (4)LongPredicate boolean test(long value) 接收一个long值
  • 总结:(1)判断型接口以“Predicate”结尾
  •  (2)判断型接口抽象方法的返回值类型是固定的,是boolean
    
  •  (3)xxPredicate,说明形参是xx类型的
    

四、功能型接口

  • 经典代表:

  •  Function<T,R>   抽象方法    R apply(T t)  
    
  • 延伸代表:

  • (1)UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果

  • (2)

  • DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
    IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
    LongFunction R apply(long value) 接收一个long值,返回一个R类型对象

  • (3)

  • ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
    ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
    ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long

  • (4)
    DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
    DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
    IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
    IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
    LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
    LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果

  • (5)
    DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
    IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
    LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果

  • (6)BiFunction<T,U,R> R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果

  • (7)BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
    (8) ToDoubleBiFunction<T,U> double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
    ToIntBiFunction<T,U> int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
    ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long

    (9) DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
    IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
    LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

  • 总结:(1)以Unary开头的,表示一元的,泛型的类型只有一个,形参和返回值都是同一种类型

  •  (2)xxFunction,说明形参的类型是xx类型的
    
  •  (3)toXXFunction,说明返回值类型是xx类型
    
  •  (4)xxToyyFunction,说明形参的类型是xx类型的,返回值类型是yy类型
    
  •  (5)xxUnary开头的,表示一元,形参类型和返回值类型都是xx
    
  •  (6)Bi开头,表示二元,形参类型是2个
    
  •  (7)BinaryOperator,既是Bi开头表示两个形参,又是Operator结尾,表示形参和返回值类型是一样
    
  •  (8)toXXBi开头的,表返回值类型是xx,并且形参是两个
    
  •  (9)xxBinaryOperator,表示两个形参,又是Operator结尾,表示形参和返回值类型是一样
    

Lambda表达式

Lambda表达式是给函数式接口(SAM接口)的变量或形参赋值的表达式。

  • Lambda表达式替代了原来使用匿名内部类的对象给函数式接口(SAM接口)的变量或形参赋值的形式。
  • 匿名内部类:实现了这个接口,重写了接口的抽象方法,同时创建了对象。
  • Lambda表达式也要实现这个接口,重写接口的抽象方法,隐含的创建了对象。
  • 既然Lambda表达式是SAM接口的变量或形参赋值的,那么实现接口这部分语法可以省略,SAM接口的抽象方法只有一个,因此方法签名可以省略一部分。
  • 例如:修饰符,返回值类型、方法名可以省略,但是其中的形参列表不能省略,因此这里表表现形参类型和形参名,这个在方法体的实现中要使用。
  • Lambda表达式语法格式:
  • (形参列表) -> {Lambda体}
  • 说明:(1)(形参列表)就是SAM接口的抽象方法的形参列表
  •      (2){Lambda体}就是实现SAM接口的抽象方法的方法体
    
  •     (3)->称为Lambda操作符,由“减号”和“大于号”构成,中间不能有空格
    
  • 优化:
  • (1)如果{Lambda体}只有一句语句,可以省略{}以及{}中的语句的结束;,如果{}没有省略的话,;就不能省略
  • (2)如果(形参列表)中形参的类型是已知的,获取可以推断,那么数据类型可以省略
  • (3)如果(形参列表)只有一个形参,并且数据类型也已经省略了,那么这个()可以省略了,如果数据类型没有省略,()不能省略
  • (4)如果{Lambda体}只有一句语句,并且是一个return语句,那么可以省略{}以及{}中的语句的结束;还有return。
  • (5)如果没有形参,()不能省略
public class Test03Lambda {
	@Test
	public void test01(){
		//使用Lambda表达式给Runnable接口的形参赋值,这个线程要打印“AT"
		//构造器:new Thread(Runnable target)
		//现在要用Lambda表达式给Runnable类型的target形参赋值
		
		/*
		 * 写好一个Lambda表达式关注两个事情:
		 * (1)抽象方法长什么样		-->形参列表怎么写
		 * 	  public void run()
		 * (2)抽象方法怎么实现,即这个抽象方法要做什么事情 -->Lambda体
		 */
		//new Thread(() -> {System.out.println("AT");}).start();
		
		//优化
		new Thread(() -> System.out.println("AT")).start();
	}
	
	@Test
	public void test02(){
		/*
		 * 在Java8版本,给Iterable<T>这个接口增加一个默认方法
		 *   default void forEach(Consumer<? super T> action)  这个方法可以遍历容器中的元素,做xx事情
		 * 
		 * Collection<E>接口继承了Iterable<T>接口,说明Collection系列的集合都有这个方法,例如:ArrayList
		 *
		 */
		ArrayList<String> list = new ArrayList<>();
		list.add("hello");
		list.add("world");
		list.add("java");
		list.add("at");
		
		/*
		 * forEach方法的形参是Consumer类型,它是消费型接口的类型,是SAM接口,就可以使用Lambda表达式赋值
		 * (1)接口的抽象方法
		 * 		void  accept(T t)  这里方法名不重要,只看返回值类型和形参列表
		 * (2)要如何实现这个抽象方法,例如:我这里要打印元素
		 */
//		list.forEach((String str) -> {System.out.println(str);});
		
//		优化
//		list.forEach((String str) -> System.out.println(str));
		
		//再优化
		list.forEach((str) -> System.out.println(str));
		
		//再优化
		list.forEach(str -> System.out.println(str));
	}
	
	@Test
	public void test03(){
		//从集合中删除字符个数超过6个的
		ArrayList<String> list = new ArrayList<>();
		list.add("hello");
		list.add("world");
		list.add("java");
		list.add("at");
		
		/*
		 * Java8中Collection接口增加了一个方法
		 * 		default boolean removeIf(Predicate<? super E> filter)
		 * 这个方法的形参是Predicate判断型接口,是SAM接口,可以使用Lambda表达式赋值
		 * 
		 * Predicate<T>判断型接口
		 * (1)抽象方法
		 * 		boolean test(T t)
		 * (2)如何实现抽象方法
		 * 		这里判断条件是  字符个数>6
		 */
/*		list.removeIf((String str) -> {
				if(str.length()>6){
					return true;
				}else{
					return false;
				}
			});*/
		
		//代码优化
		list.removeIf((String str) -> {return str.length()>6;});
		
		//再优化
		list.removeIf(str -> str.length()>6);
	}
	
}

Supplier和Consumer

public void test01(){
		/*
		 * Java8中,有一个新的类:Stream类型,它代表一个数据加工流
		 * java.util.stream.Stream<T>接口
		 * 		static <T> Stream<T> generate(Supplier<T> s) 
		 * 
		 *  这个静态方法的形参:Supplier<T>供给型接口的类型,SAM接口,可以使用Lambda表达式赋值
		 *  接口的静态方法通过“接口名.静态方法”调用
		 *  
		 *  Supplier<T>接口
		 *  (1)抽象方法		T get()
		 *  (2)抽象方法如何实现
		 *  		例如:在抽象方法中实现,随机产生一个小数[0,1),返回值类型double
		 */
//		Stream<Double> stream = Stream.generate(() -> {return Math.random();});
		
		//优化
		Stream<Double> stream = Stream.generate(() -> Math.random());
		
		/*
		 * 为了看效果,我再调用Stream<T>接口的void forEach(Consumer<? super T> action) 
		 * 这个方法形参是Consumer<T>消费型接口类型,SAM接口,也可以使用Lambda表达式赋值
		 * 
		 * Consumer<T>消费型接口:
		 * (1)抽象方法
		 * 		void accept(T t)
		 * (2)如何实现抽象方法
		 * 		例如:打印流中的元素
		 * 
		 */
//		stream.forEach((Double num) -> {System.out.println(num);});
		//优化
		stream.forEach(num -> System.out.println(num));
	}

JDK1.8时Map接口增加了一个方法:

  • default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
public class Test05Lambda3 {
	@Test
	public void test01(){
		HashMap<String,Employee> map = new HashMap<>();
		map.put("张三", new Employee("张三", 8000));
		map.put("李四", new Employee("李四", 9000));
		map.put("王五", new Employee("王五", 12000));
		map.put("赵六", new Employee("赵六", 11000));
		
		//把原来薪资低于10000元的,修改为薪资为10000元,其他的不变
		/*
		 * replaceAll(BiFunction<? super K,? super V,? extends V> function)
		 * 
		 * replaceAll的形参是BiFunction<T,U,R>接口类型,SAM接口,可以使用Lambda表达式赋值
		 * 
		 * BiFunction<T,U,R>接口:
		 * (1)抽象方法
		 * 		R apply(T t, U u) 
		 * (2)如何实现抽象方法
		 * 		例如:把原来value对象的薪资<10000元的替换(覆盖)为薪资是10000元
		 * 
		 * 如果抽象方法有返回值,那么实现抽象方法时,需要return语句。
		 * 这里看返回值类型是V类型,是value的类型
		 */
		map.replaceAll((String key,Employee emp) -> {
			if(emp.getSalary()<10000){
				emp.setSalary(10000);
			}
			return emp;
		});
		
		//优化
		map.replaceAll((key,emp) -> {
			if(emp.getSalary()<10000){
				emp.setSalary(10000);
			}
			return emp;
		});
	}
}
class Employee {
	private String name;
	private double salary;
	public Employee(String name, double salary) {
		super();
		this.name = name;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + "]";
	}
}

自定义函数式接口:

  • 1、声明一个接口,只能包含一个抽象方法
  • 2、给这个接口加@FunctionalInterface
public class TestDefineFunctionalInterface {
	public static void main(String[] args) {
		//IntCalc tools形参,函数式接口,可以Lambda表达式赋值
		/*
		 * (1)抽象方法:int cal(int a , int b);
		 * (2)如何实现抽象方法
		 * 		例如:求a+b的和
		 */
		getProduct(1,2, (int a , int b) -> {return a+b;});//new了一个IntCalc的实现类的对象,而且实现了IntCalc抽象方法
		
		getProduct(1,2, (a ,b) ->a*b);
	}

	//2、在测试类中定义public static void getProduct(int a , int b ,IntCalc tools), 
	//该方法的预期行为打印使用tools的cal(a,b)的计算结果
	public static void getProduct(int a , int b ,IntCalc tools){
		int result = tools.cal(a, b);
		System.out.println("结果:" + result);
	}
}
//1、定义一个函数式接口IntCalc,其中抽象方法int cal(int a , int b),使用注解@FunctionalInterface
@FunctionalInterface
interface IntCalc{
	int cal(int a , int b);
}

lambda表达式的方法引用

lambda表达式是用来简化匿名内部类的一种函数式编程的语法。

  •  只有SAM接口才能使用lambda表达式
    
  • 方法引用和构造器引用是用来简化lambda表达式。
  •  只有当lambda表达式满足一些特殊情况时,才能使用方法引用和构造器引用。
    
  • 条件:
  • (1)当Lambda体的实现是通过调用一个现有的方法来完成功能时
  • (2)要求函数式接口的抽象方法返回值类型与该方法的返回值类型要对应。
  • (3)要求函数式接口的抽象方法的形参列表与该方法的形参列表对应
  • 或者
  • 要求函数式接口的抽象方法的形参列表的第一个形参是作为调用方法的对象,剩余的形参正好是给这个方法的实参列表
  • 方法引用的语法格式:
  • (1)对象::实例方法名
  • (2)类名::静态方法名
  • (3)类名::实例方法名
  • 条件:
  • (1)当Lambda体的实现是通过创建一个对象来实现的
  • (2)要求函数式接口的抽象方法返回值类型与该对象的类型一致
  • (3)要求函数式接口的抽象方法的形参列表与该对象创建时的构造器的形参列表对应
  • 构造器引用语法格式:
  • (1)类名::new
  • (2)数组类型::new
public class TestFunctionReference {
	@Test
	public void test06(){
		new Thread(() -> System.out.println("hello")).start();//不能使用方法引用来优化,因为“hello”加在了println里面,就变成了有参。对象::方法,这里面方法之后不跟东西
	}
	
	@Test
	public void test05(){
		//调用createArray这个方法,创建一个2的n次方长度的数组
		/*
		 * public <R> R[] createArray(Function<Integer,R[]> fun,int length)
		 * 第一个参数:Function<Integer,R[]>接口  ,是SAM接口,可以赋值为Lambda表达式
		 * Function<T,R>接口的抽象方法:	R apply(T t)
		 * 
		 * 					R[] apply(Integer i)
		 * 如何实现,创建一个长度为i的R类型的数组
		 * 
		 */
//		String[] arr = createArray((Integer i) -> new String[i], 10);
		//优化为
		
		String[] arr = createArray(String[]::new, 10);
		System.out.println(Arrays.toString(arr));
		System.out.println(arr.length);
	}
	
	/*
	 * 模仿HashMap的源码实现的一个方法,功能是,可以创建一个2的n次方长度的数组
	 * 例如:这个length是你希望的数组的长度,但是它可能不是2的n次方,那么我会对length进行处理,纠正为一个2的n次方长度,并且创建这个数组
	 */
	public <R> R[] createArray(Function<Integer,R[]> fun,int length){
		int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
		return fun.apply(length);
	}
	
	@Test
	public void test04(){
		//lambda表达式也可以用于给SAM接口的变量赋值
		/*
		 * Supplier<T> 接口的抽象方法   T get()
		 * 如何实现这个抽象方法,例如:这里要new一个String的空对象
		 */
//		Supplier<String> s = () -> new String();
		
		//就可以使用构造器引用,来创建对象
		Supplier<String> s = String::new;
	}
	
	@Test
	public void test03(){
		String[] arr = {"hello","Hi","world","chailinyan","at"};
		
		//不区分大小写的排序
		/*
		 * public static <T> void sort(T[] a, Comparator<? super T> c)
		 * 第二个形参:Comparator接口,SAM接口,赋值为Lambda表达式
		 * 
		 * Comparator<String>接口的抽象方法:
		 * 		int compare(String s1, String s2)
		 * 如何实现?不区分大小写比较字符串
		 */
//		Arrays.sort(arr, (s1,s2) ->  s1.compareToIgnoreCase(s2));
		/*
		 * Comparator<String>接口    int compare(String s1, String s2)
		 * String类的 		     int compareToIgnoreCase(String str)  此处,s1正好是调用compareToIgnoreCase的对象,s2是给它的参数
		 */
		//使用方法引用优化
		Arrays.sort(arr, String::compareToIgnoreCase);
		
		System.out.println(Arrays.toString(arr));
	}
	
	@Test
	public void test02(){
		/* 
		 * Stream的
		 * public static<T> Stream<T> generate(Supplier<T> s)
		 * 形参是Supplier接口类型,赋值为Lambda表达式
		 * 
		 * (1)抽象方法   T get()
		 * (2)如何实现,  用Math.random()随机产生一个[0,1)范围的数
		 */
//		Stream<Double> stream = Stream.generate(() -> Math.random());
		
		/*
		 * Supplier接口的抽象方法   T get()   Double get()
		 * 调用的是						double Math.random()  
		 */
		//使用方法引用
		Stream<Double> stream = Stream.generate(Math::random);
		stream.forEach(System.out::println);
	}
	
	
	@Test
	public void test01(){
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		
		/*
		 * default void forEach(Consumer<? super T> action)
		 * 形参的类型是Consumer接口,可以使用Lambda表达式赋值
		 * Consumer<T>  void accept(T t)
		 * 
		 */
		//list.forEach(num -> System.out.println(num));
		
		/*
		 * (1)这里的lambda表达式是通过调用一个现有的方法来完成的,是out对象的println()方法来完成
		 * (2)Consumer<T>  			public void accept(T t)
		 * 	  PrintStream类的out对象	public void println(Object x)
		 */
		list.forEach(System.out::println);
	}
	
	
}

Stream

StreamAPI:

  • StreamAPI是用来处理数据,处理集合等容器中的数据,处理操作有:查询、筛选、删除、过滤、统计、映射等。
  • 希望能够用类似于SQL语法的形式对Java内存中的数据进行处理。
  • SQL是对数据库中的数据进行处理。
  • Stream本身不负责存储数据,存储数据是用集合,数组等数据结构。
  • Stream只负责对数据进行处理、加工。
  • Stream的操作分为三步:
  • 1、创建Stream:告知Stream数据的来源是哪里?
  • 2、加工处理:这个过程可以很多步 0~n
  • 3、终结操作:收集结果
  • 一旦终结就不能再加工了,如果要加工需要重新创建Stream。
  • Stream的特点:
  • (1)Stream本身不负责存储数据,存储数据是用集合,数组等数据结构。
  • (2)Stream是不可变,一旦修改,就会产生新的Stream对象。Stream不会修改数据源的数据。
  • (3)Stream的操作是一个延迟操作。所有的操作都必须延迟到终结操作时,一起处理。
	public void test01(){
		ArrayList<Employee> list= new ArrayList<>();
		list.add(new Employee(1,"张三"));
		list.add(new Employee(1,"张三"));
		
		Stream<Employee> stream = list.stream();
		stream = stream.distinct();//处理,中间操作 //不会修改数据源,只会把统计结果显示出来 
		long count = stream.count();//统计个数   终结操作
		System.out.println(count);
		System.out.println("over");
		System.out.println("--------------------");
		//重新遍历list
		for (Employee employee : list) {
			System.out.println(employee);
		}
		
	}

Stream接口:

  • 实现类
  • IntStream
  • DoubleStream
  • LongStream

一、创建Stream

  • 1、方式一:通过集合创建
  •  集合对象.stream()
    
  • 2、方式二:通过数组工具类Arrays
  • Arrays.stream(数组对象)
    
  • 3、方式三:Stream接口的静态方法of方法,产生一个有限流
  •  Stream.of(...)
    
  • 4、方式四:Stream接口的静态方法
  • (1)generate方法,产生无限流
  • (2)Stream iterate(T seed, UnaryOperator f)
public class Test07StreamCreate {
	@Test
	public void test06(){
		/*
		 * Stream<T> iterate(T seed, UnaryOperator<T> f)  
		 * UnaryOperator接口,SAM接口,抽象方法:
		 * 
		 * UnaryOperator<T> extends Function<T,T>
		 * 		T apply(T t)
		 */
		Stream<Integer> stream = Stream.iterate(1, num -> num+=2);
//		stream = stream.limit(10);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test05(){
		Stream<Double> stream = Stream.generate(Math::random);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test04(){
		Stream<Integer> stream = Stream.of(1,2,3,4,5);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test03(){
		String[] arr = {"hello","world"};
		Stream<String> stream = Arrays.stream(arr);
	}
	
	@Test
	public void test02(){
		int[] arr = {1,2,3,4,5};
		IntStream stream = Arrays.stream(arr);
	}
	
	@Test
	public void test01(){
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		
		//JDK1.8中,Collection系列集合增加了方法
		Stream<Integer> stream = list.stream();
	}
}

二、中间的加工操作

  • (1)filter(Predicate p):过滤
  • (2)distinct():去重
  • (3)limit(long maxSize):取有限的几个
  • (4)skip(long n):跳过n个
  • (5)peek(Consumer action) 接收Lambda表达式,对流中的每个数据执行Lambda体操作
  • (6)sorted():排序,按照自然排序
  • sorted(Comparator com):排序,按照定制排序
  • (7)map(Function f):接收Lambda表达式,对流中的每个数据,执行Lambda体操作,返回新的数据构成新的流
  • (8)flatMap(Function f)
  • map(Function<? super T,? extends R> mapper) map操作流中的把T对象变成R对象,由R对象构成新的流
  • flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
  • flatMap把流中的数据T对象压扁变成一个Stream,然后把一个个的Stream最后再合成一个大的Stream
public class Test08StreamMiddle {
	
	@Test
	public void test12(){
		String[] arr = {"hello","world","java"};
		Stream<String> flatMap = Arrays.stream(arr)
			.flatMap(t -> Stream.of(t.split("|")));//Function<T,R>接口抽象方法 R apply(T t)  现在的R是一个Stream
		flatMap.forEach(System.out::println);
	}
	
	
	@Test
	public void test11(){
		String[] arr = {"hello","world","java"};
		
		Arrays.stream(arr)
		
			.map(t->t.toUpperCase())
			.forEach(System.out::println);
	}
	
	@Test
	public void test10(){
		Stream.of(1,2,3,4,5)
			.map(t -> t+=1)//Function<T,R>接口抽象方法 R apply(T t)
			.forEach(System.out::println);
	}
	
	@Test
	public void test09(){
		//希望能够找出前三个最大值,前三名最大的,不重复
		Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
			.distinct()
			.sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口  int compare(T t1, T t2)
			.limit(3)
			.forEach(System.out::println);
	}
	
	@Test
	public void test08(){
		long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.distinct()
			.peek(System.out::println)  //Consumer接口的抽象方法  void accept(T t)
			.count();
		System.out.println("count="+count);
	}
	
	
	@Test
	public void test07(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.skip(5)
			.distinct()
			.filter(t -> t%3==0)
			.forEach(System.out::println);
	}

	@Test
	public void test06(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.skip(5)
			.forEach(System.out::println);
	}
	
	@Test
	public void test05(){
		Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
			.distinct()  //(1,2,3,4,5,6,7)
			.filter(t -> t%2!=0) //(1,3,5,7)
			.limit(3)
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test04(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.limit(3)
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test03(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.distinct()
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test02(){
		Stream.of(1,2,3,4,5,6)
			.filter(t -> t%2==0)
			.forEach(System.out::println);
	}
	
	@Test
	public void test01(){
		//1、创建Stream
		Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
		
		//2、加工处理
		//过滤:filter(Predicate p)
		//把里面的偶数拿出来
		/*
		 * filter(Predicate p)
		 * Predicate是函数式接口,抽象方法:boolean test(T t)
		 */
		stream = stream.filter(t -> t%2==0);
		
		//3、终结操作:例如:遍历
		stream.forEach(System.out::println);
	}
}

创建:一步

  • 中间:0~n步
  • 终结:一步

三、终结操作

  • 1、void forEach(Consumer ):遍历流中的数据
  • 2、long count():统计个数
  • 3、boolean allMatch(Predicate p):是否全部满足xx条件
  • boolean anyMatch(Predicate p):是否有一个满足xx条件
  • boolean noneMatch(Predicate p):是否全部都不满足xx条件
  • 4、 Optional findFirst():返回第一个
  • Optional findAny():返回任意一个
  • 5、Optional max(Comparator c):找出最大的
  • Optional min(Comparator c) :找出最小的
  • 6、T reduce(T iden, BinaryOperator b) 通过反复的运算,留下最后一个结果
  • Optional reduce(BinaryOperator b)
  • 7、R collect(Collector c):把流中的数据最后都收集到一起
  • Collector接口
  • BiConsumer 抽象方法 void accept(R r,T t)
public class Test09StreamEnding {
	
	@Test
	public void test14(){
		List<Integer> list = Stream.of(1,2,4,5,7,8)
				.filter(t -> t%2==0)
				.collect(Collectors.toList());
		
		System.out.println(list);
	}
	
	
	@Test
	public void test13(){
		Optional<Integer> reduce = Stream.of(1,2,4,5,7,8)
			 .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口   T apply(T t1, T t2)
		System.out.println(reduce);
	}
	
	@Test
	public void test12(){
		Integer reduce = Stream.of(1,2,4,5,7,8)
			 .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口   T apply(T t1, T t2)
		System.out.println(reduce);
	}
	
	@Test
	public void test11(){
		Optional<Integer> max = Stream.of(1,2,4,5,7,8)
				.max((t1,t2) -> Integer.compare(t1, t2));
		System.out.println(max);
	}
	
	@Test
	public void test10(){
		Optional<Integer> opt = Stream.of(1,2,4,5,7,8)
				.filter(t -> t%3==0)
				.findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test09(){
		Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9)
				.filter(t -> t%3==0)
				.findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test08(){
		Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test04(){
		boolean result = Stream.of(1,3,5,7,9)
			.anyMatch(t -> t%2==0);
		System.out.println(result);
	}
	
	
	@Test
	public void test03(){
		boolean result = Stream.of(1,3,5,7,9)
			.allMatch(t -> t%2!=0);
		System.out.println(result);
	}
	
	@Test
	public void test02(){
		long count = Stream.of(1,2,3,4,5)
				.count();
		System.out.println("count = " + count);
	}
	
	
	@Test
	public void test01(){
		Stream.of(1,2,3,4,5)
				.forEach(System.out::println);
	}
}

Optional

Optional实际上是个容器,它是一个装一个对象的容器。这个对象可能是个空,可能是非空。

  • 数组和集合是装多个对象的容器。
  • 1、创建Optional对象的方法:
  • (1)Optional.of(xx); 只能装非空对象
  • (2)Optional.ofNullable(x); 装可以是null的对象
  • (3)Optional empty() 直接返回一个空箱子对象
  • 2、如何取出Optional容器中的对象
  • (1)get() 必须配合of(xx)使用,因为这里面的对象不能是null
  • (2)orElse(T other) 如果Optional容器中的对象是空的,用other代替
  • (3)orElseGet(Supplier<? extends T> other) 如果Optional容器中的对象是空的,用other这个供给型接口提供的对象代替
  • (4)orElseThrow(Supplier<? extends X> exceptionSupplier)
  • 3、其他操作
  • Optional filter(Predicate<? super T> predicate)
public class TestOptional {
	@Test
	public void test9(){
		String str = "hello";
		Optional<String> opt = Optional.ofNullable(str);
		Optional<String> opt2 = opt.filter(s->s.length()>5);
		System.out.println(opt2);
	}
	
	
	@Test
	public void test8(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
		System.out.println(string);
	}
	
	
	@Test
	public void test7(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseGet(String::new);
		System.out.println(string);
	}
	
	@Test
	public void test6(){
		String str = "hello";
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElse("at");
		System.out.println(string);
	}
	
	@Test
	public void test5(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
	}
	
	@Test
	public void test4(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);

		String string = opt.get();
		System.out.println(string);
	}
	
	
	@Test
	public void test3(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		System.out.println(opt);
	}
	
	@Test
	public void test2(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);
		System.out.println(opt);
	}
	
	
	@Test
	public void test1(){
		ArrayList<Student> list = new ArrayList<>();
		list.add(new Student("张三", 23));
		//...
		
		//取出流中第一个年龄大于30岁的学生
		Optional<Student> opt = list.stream()
			.filter(s -> s.getAge()>30)
			.findFirst();
	
		
		//打印该学生的年龄
		Student stu = opt.orElse(new Student());
		System.out.println("学生的年龄:" + stu.getAge());
	}
}
class Student{
	private String name;
	private int age;
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Student() {
		super();
	}
	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 "Student [name=" + name + ", age=" + age + "]";
	}
	
}

猜你喜欢

转载自blog.csdn.net/qq_40473204/article/details/107772321