【Java】 Java 8 新特性笔记

Java 8 (又称 jdk 1.8)的好处

  • Java 5以来最具革命性的版本,带来大量新特性
  • 速度更快
  • 代码更少(Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn 引擎,允许在 JVM 上运行 JS 应用

Lambda 表达式

概述

Lambda 表达式的基础语法:Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符Lambda 操作符

  •                  箭头操作符将 Lambda 表达式拆分成两部分
    
  •                  左侧:Lambda 表达式的**参数列表**(其实就是接口中的抽象方法的形参列表)
    
  •                  右侧:Lambda 表达式中所需执行的功能, 即 **Lambda 体**(其实就是重写的抽象方法的方法体)
    
  •                  化简依据:函数式接口的方法只有固定一个,可以化简。参数类型由泛型固定,可以化简
    
  •                  **本质:作为==函数式接口的实例==**
    

语法格式一:无参数,无返回值

() -> System.out.println("Hello Lambda!");

语法格式二:有一个参数,并且无返回值

(x) -> System.out.println(x)

语法格式三:若只有一个参数,小括号可以省略不写

x -> System.out.println(x)

语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句

Comparator<Integer> com = (x, y) -> {

System.out.println("函数式接口");

return Integer.compare(x, y);

}; 

语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写(但是需要同时省略,不能只省略一个)

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”

(Integer x, Integer y) -> Integer.compare(x, y);

  •  上联:左右遇一括号省
    
  •  下联:左侧推断类型省
    
  •  横批:能省则省
    

Lambda 表达式需要“函数式接口”的支持

  •  函数式接口:接口中**只有一个抽象方法**的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰
    
  •  可以检查是否是函数式接口
    
public class TestLambda2 {
	
	@Test
	public void test1(){
		int num = 0;//jdk 1.7 前,必须是 final
		
        //原形式
		Runnable r = new Runnable() {
			@Override
			public void run() {
				System.out.println("Hello World!" + num);
			}
		};
		
		r.run();
		
		System.out.println("-------------------------------");
		//语法格式一
		Runnable r1 = () -> System.out.println("Hello Lambda!");
		r1.run();
	}
	
    //语法格式二、三
	@Test
	public void test2(){
		Consumer<String> con = x -> System.out.println(x);
		con.accept("我大尚硅谷威武!");
	}
	
    //语法格式四
	@Test
	public void test3(){
		Comparator<Integer> com = (x, y) -> {
			System.out.println("函数式接口");
			return Integer.compare(x, y);
		};
	}
	
    //语法格式五
	@Test
	public void test4(){
		Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	}
	
    //语法格式六
	@Test
	public void test5(){
//		String[] strs;
//		strs = {"aaa", "bbb", "ccc"};
		
		List<String> list = new ArrayList<>();
		
		show(new HashMap<>());
	}

	public void show(Map<String, Integer> map){
		
	}
	
	//需求:对一个数进行运算
	@Test
	public void test6(){
		Integer num = operation(100, (x) -> x * x);
		System.out.println(num);
		
		System.out.println(operation(200, (y) -> y + 200));
	}
	
	public Integer operation(Integer num, MyFun mf){
		return mf.getValue(num);
	}
}

函数式接口

定义

  • 接口里面只有一个抽象方法,该接口就被称为函数式接口
  • 可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda表达式抛出一个受检异常, 那么该异常需要在目标接口的抽象方法上进行声明)
  • 可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口, 同时 javadoc 也会包含一条声明, 说明这个接口是一个函数式接口。
  • 在java.util.function包下定义了Java 8 的丰富的函数式接口

如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还
    可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型–函数式接口
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
  • 所以以前用匿名内部类表示的现在都可以用Lambda表达式来写
    //代替匿名内部类
@Test
    public void test01(){
        //匿名内部类写法
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("消费:"+aDouble);
            }
        });

        System.out.println("------------");
		
        //Lambda表达式写法
        happyTime(400,x -> System.out.println("消费2:"+x));
    }
    

    public void happyTime(double money, Consumer<Double> con){
        con.accept(money);
    }

注:上面的代码中,

new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("消费:"+aDouble);
            }
        }

为函数式接口Consumer<T>的一个匿名实例,而在下面的Lambda表达式x -> System.out.println("消费2:"+x)代替了它,所以说==Lambda表达式的本质是函数式接口的实例==

Java 内置四大核心函数式接口(需要熟练掌握接口的方法是什么)

函数式接口参数类型返回类型用途
Consumer<T> 消费型接口Tvoid对类型为T的对象应用操作, 包含方法: void accept(T t)
Supplier<T > 供给型接口T返回类型为T的对象, 包含方法: T get();
Function<T, R>函数型接口TR对类型为T的对象应用操作, 并返回结果。 结果是R类型的对象。 包含方法: R apply(T t);
Predicate<T > 断定型接口Tboolean确定类型为T的对象是否满足某约束, 并返回boolean 值。 包含方法boolean test(T t);

其它接口

函数式接口参数类型返回类型用途
BiFunction<T, U, R> T, UR对类型为 T, U 参数应用操作, 返回 R 类型的结 果。包含方法为R apply(T t, U u);
UnaryOperator<T > (Function子接口)TT对类型为T的对象进行一元运算, 并返回T类型的结果。 包含方法为T apply(T t);
BinaryOperator<T> (BiFunction 子接口)T, TT对类型为T的对象进行二元运算, 并返回T类型的结果。 包含方法为T apply(T t1, T t2);
BiConsumer<T, U> T, Uvoid对类型为T, U 参数应用操作。 包含方法为void accept(T t, U u)
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T >Tint long double分别计算 int , long 、double、 值的函数
IntFunction<R> LongFunction<R> ``DoubleFunction<R>int long doubleR参数分别为int、 long、double 类型的函数
/*
 * Java8 内置的四大核心函数式接口
 * 
 * Consumer<T> : 消费型接口
 * 		void accept(T t);
 * 
 * Supplier<T> : 供给型接口
 * 		T get(); 
 * 
 * Function<T, R> : 函数型接口
 * 		R apply(T t);
 * 
 * Predicate<T> : 断言型接口
 * 		boolean test(T t);
 * 
 */
public class TestLambda3 {
	
	//Predicate<T> 断言型接口:
	@Test
	public void test4(){
		List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
		List<String> strList = filterStr(list, (s) -> s.length() > 3);
		
		for (String str : strList) {
			System.out.println(str);
		}
	}
	
	//需求:将满足条件的字符串,放入集合中
	public List<String> filterStr(List<String> list, Predicate<String> pre){
		List<String> strList = new ArrayList<>();
		
		for (String str : list) {
			if(pre.test(str)){
				strList.add(str);
			}
		}
		
		return strList;
	}
	
	//Function<T, R> 函数型接口:
	@Test
	public void test3(){
		String newStr = strHandler("\t\t\t 我大尚硅谷威武   ", (str) -> str.trim());
		System.out.println(newStr);
		
		String subStr = strHandler("我大尚硅谷威武", (str) -> str.substring(2, 5));
		System.out.println(subStr);
	}
	
	//需求:用于处理字符串
	public String strHandler(String str, Function<String, String> fun){
		return fun.apply(str);
	}
	
	//Supplier<T> 供给型接口 :
	@Test
	public void test2(){
		List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
		
		for (Integer num : numList) {
			System.out.println(num);
		}
	}
	
	//需求:产生指定个数的整数,并放入集合中
	public List<Integer> getNumList(int num, Supplier<Integer> sup){
		List<Integer> list = new ArrayList<>();
		
		for (int i = 0; i < num; i++) {
			Integer n = sup.get();
			list.add(n);
		}
		
		return list;
	}
	
	//Consumer<T> 消费型接口 :
	@Test
	public void test1(){
		happy(10000, (m) -> System.out.println("你们刚哥喜欢大宝剑,每次消费:" + m + "元"));
	} 
	
	public void happy(double money, Consumer<Double> con){
		con.accept(money);
	}
}

方法引用

  • 使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用

  • 方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例

  • 使用格式:类(或对象)::方法名

  • 具体分为如下的三种情况(注意没有对象::静态方法,因为没有意义)

    1. 对象::非静态方法(非静态方法也叫实例方法)
    2. 类::静态方法
    3. 类::非静态方法(难点)
  • 方法引用使用的要求:要求接口中的抽象方法的形参列表返回值类型与方法引用的方法的形参列表返回值类型相同(只针对前两种情况)

  • 理解:本质上是将已经有的方法替换对函数式接口里的方法的重写

    相比Lambda表达式:不用传入参数和标明返回值类型,因为形参列表返回值类型相同

情况一: 对象::非静态方法

// 情况一:  对象::非静态方法
//consumer中的void accept(T t)
//Printstream中的void println(T t)
//将已经有的Printstream类中的println方法替换对Consumer接口里的accept方法的重写
@Test
public void test1(){
    //Lambda表达式写法
	Consumer<String>conl=str -> System.out.println(str);
	con1.accept("北京");
     
    //方法引用写法
	Printstream ps = System.out;
	Consumer<String>con2 = ps::println;  //ps::println为对象ps的非静态方法println
	con2.accept("beijing");
    
    // 匿名内部类写法(传统)
Consumer<String> con3 = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};

// 调用 accept 方法
con3.accept("beijing");
// 情况一:  对象::非静态方法
//supplier中的T get()
//Employee中的string getName()
@Test
public void test2(){
Employee emp = new Employee( id: 1001,name:"Tom",age: 23, salary: 5680);//另外有Employee类,这里省略
    
    //Lambda表达式写法,将Supplier接口里的get方法重写,内容是执行emp对象的getName方法
	Supplier<String>sup1= () -> emp.getName();
	System.out.println(sup1.get());
	
    //方法引用写法,直接将重写内容改为了执行emp对象的getName方法
	Supplier<string>sup2 = emp::getName;
	System.out.println(sup2.get());
    
     // 匿名内部类写法
      Supplier<String> sup3 = new Supplier<String>() {
            @Override
            public String get() {
                return emp.getName();
            }
        };

        System.out.println(sup3.get());

情况二:类 ::静态方法

// 情况二:类 ::静态方法
//Comparator中的int compare(T t1, T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3(){
    //Lambda表达式写法
	Comparator<Integer>com1 = (t1,t2) -> Integer.compare(t1,t2);
	System.out.println(com1.compare(12,21));
	//方法引用写法
	Comparator<Integer>com2 =Integer::compare;
	System.out.println(com2.compare(12,3));
//Function中ER apply(T t)
//Math中的Long round(Double d)
@Test
public void test4(){
     // 匿名内部类写法
	Function<Double,Long>func = new Function<Double, Long>(){
	@Override
	public Long apply(Double d){
	return Math.round(d);
	};
	//Lambda表达式写法
	Function<Double,Long>func1=d-> Math.round(d);
	System.out.println(func1.apply( t: 12.3));
	//方法引用写法
	Function<Double,Long>func2 = Math::round;
	System.out.println(func1.apply(t:12.6));

情况三:类::实例方法

理解:传入的参数作为一个对象,用这个对象调用方法,此时调用的为非静态方法。但是写函数式接口的代码时还不知道传入的参数(对象)是什么,故用类名放在前面表示

类名或对象名:

  • String:: 表示正在引用 String 类中的方法。

方法名:

  • compareToString 类中的一个方法。

如何工作

当使用 String::compareTo 作为方法引用时,它意味着想要调用 String 类中的 compareTo 方法。这里的关键点是,compareTo 方法是一个实例方法,它需要一个 String 对象来调用它。

传入的参数

Comparator<String> 接口中,compare 方法有两个参数 t1t2。当使用 String::compareTo 作为方法引用时,这两个参数会被自动映射到 String 类中的 compareTo 方法的调用上。具体来说:

  • 第一个参数 t1 将作为 compareTo 方法的调用者。
  • 第二个参数 t2 将作为 compareTo 方法的参数。
//情况三:类::非静态方法 
//Comparator中的int comapre(T t1,T t2)
//String中的int t1.compareTo(t2)
@Test
public void test5(){
    //Lambda表达式写法
	Comparator<string>com1=(s1,s2)->s1.compareTo(s2);
	System.out.println(com1.compare("abc","abd"));
	//方法引用写法
	Comparator<String>com2 = String ::compareTo;
	System.out.println(com2.compare("abd","abm"));
//情况三:类::非静态方法 
//BiPredicate中的boolean test(T t1,T t2);
//string中的boolean t1.equals(t2)
@Test
public void tesi6(){
    //Lambda表达式写法
	BiPredicate<string,string>pre1=(s1,s2)->s1.equals(s2);
	System.out.println(pre1.test(t:"abc",u:"abc"));
    //方法引用写法
	BiPredicate<string,string>pre2=string ::equals;
	System.out.println(pre1.test( t:"abc",u: "abd"));
//情况三:类::非静态方法 
// Function中的R apply(T t)
// Employee中String getName();
@Test
public void test7(){
	Employee employee = new Employee( id: 1001, name: "Jerry", age: 23,salary:6000);
    //Lambda表达式写法
	Function<Employee,string>func1=e->e.getName(
	System.out.println(func1.apply(employee));
	//方法引用写法
	Function<Employee,string>func2 = Employee::getName;
	System.out.println(func2.apply(employee));

构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型

//构造器引用
//Supplier中的T get()
//Employee的空参构造器:Employee()
@Test
public void test1(){
    // 匿名内部类写法
	Supplier<Employee>sup = new supplier<Employee>(){
	@Override
	public Employee get(){
		return new Employee();
	};
	//Lambda表达式写法
	Supplier<Employee>sup1=() -> new Employee();
	System.out.println(sup1.get());
    //构造器引用写法
	Supplier<Employee>sup2 =Employee :: new;
	system.out.println(sup2.get());
//Function中的R apply(T t)
@Test
public void test2(){
    //Lambda表达式写法
	Function<Integer,Employee>func1 =id -> new Employee(id);
	Employee employee = func1.apply( t: 1001);
	System.out.println(employee);
	//构造器引用写法
	Function<Integer,Employee>func2 = Employee :: new;
	Employee employee1=func2.apply( t:1002);
	System.out.println(employee1);
//BiFunction中的R appLy(T t,U u)
@Test
	public void test3(){
	BiFunction<Integer,string,Employee>func1 =(id,name) -> new Employee(id,name)
	System.out.println(func1.apply(t:1001,u:"Tom"));

	BiFunction<Integer,string,Employee>func2 = Employee :: new;
	System.out.println(func2.apply( t: 1682,u: "Tom"));

数组引用

把数组看做是一个特殊的类,则写法与构造器引用一致

//数组引用
//Function中的R apply(T t)
@Test
	public void test4(){
	Function<Integer,string[]> func1 = length -> new string[length];
	string[] arr1 = func1.apply(t:5);
	System.out.println(Arrays.tostring(arr1));

	Function<Integer,string[]>func2 = string[] :: new;
	String[] arr2 =func2.apply( t: 10);
	System.out.println(Arrays.tostring(arr2));

Stream API

概述

  • Stream关注的是对数据的运算,与CPU打交道

    集合关注的是数据的存储,与内存打交道

  • 注意:

    1. Stream自己不会存储元素(类似迭代器,只处理集合数据,不存储集合数据)

    2. 不会改变源对象。相反,他们会返回一个持有结果的新Stream

    3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

  • Stream 执行流程

    1. Stream的实例化——一个中间操作链,对数据源的数据进行处理
    2. 一系列的中间操作(过滤、映射、.·)
    3. 终止操作——一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

Stream 的实例化

方式一:通过集合

stream()parallelstream()

//创建 stream方式-:通过集合
@Test
public void test1(){
	List<Employee>employees =EmployeeData.getEmployees()
	//default stream<E>stream():返回一个顺序流
	Stream<Employee>stream = employees.stream();
    
	//default stream<E>parallelstream():返回一个并行流
	Stream<Employee> parallelstream = employees.parallelstream();}

方式二:通过数组

stream(T[] array)

//创建 stream 方式二:通过数组
@Test
public void test2(){
	int[] arr = new int[]{1,2,3,4,5,6};
	//调用Arrays类的static<T>stream<T>stream(T[] array):返回一个流
	IntStream stream =Arrays.stream(arr);
    
	Employee e1 = new Employee( id:1001,name:"Tom");
	Employee e2 = new Employee( id: 1002,name: "Jerry");
	Employee[l arr1 = new Employee[]{e1,e2};
	Stream<Employee>stream1 = Arrays.stream(arr1);}

方式三:过stream的of()

stream.of()

//创建 stream方式三:通过stream的of()
@Test
public void test3(){
	Stream<Integer>stream = stream.of(123456)
}

方式四:创建无限流(略)

//创建 stream方式四:创建无限流
@Test
public void test4(){
    //迭代
	//public static<T> stream<T> iterate(final T seed, final Unaryoperator<T> f)
	//遍历前10个偶教
	Stream.iterate(seed:0,t->t+ 2).limit(10).forEach(System.out::println);
	//生成
	//public static<T>stream<T> generate(supplier<T> s)
	Stream.generate(Math::random).limit(10).forEach(System.out::println)}

Stream 的中间操作

多个中间操作可以连接起来形成一个流水线, 除非流水线上触发终止操作, 否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理, 称为“惰性求值”

1-筛选与切片

方 法描 述
filter(Predicate p)接收 Lambda , 从流中排除某些元素。
distinct()筛选, 通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流, 使其元素不超过给定数量(截断后面)
skip(long n)跳过元素, 返回一个扔掉了前 n 个元素的流。 若流中元素不足 n 个, 则返回一个空流。 与 limit(n) 互补(截断前面)
public void test1(){
	List<Employee>list = EmployeeData.getEmployees();
    
	//filter(Predicate p)-接收Lambda, 从流中排除某些元素。
	Stream<Employee>stream=list.stream();
	//查询员工表中薪资大于7000的员工信息
    //foreach方法:遍历,可以看作for循环,这里直接用方法引用(这里foreach算是一个终止操作)
	stream.filter(e->e.getsalary() > 7000).forEach(System.out::println).
	System.out.println();
    
	//limit(n)-截断流,使其元素不超过给定数量。
	stream,limit(3).forEach(system.out::println);
     System.out.println();
         
	//skip(n)- 跳过元素,返回一个扔掉了前 n 个元素的流。
	list.stream().skip(3).forEach(system.out::println);
    
	//distinct()-筛选,通过流所生成元素的 hashcode()和 equals()去除重复元素
	list.add(new Employee( id: 1010, name:"刘强东",age: 40,salary:8000));
	list.add(new Employee( id: 1010, name:"刘强东",age:8000));
	list.stream().distinct().forEach(system.out::println);

2-映射

方 法描 述
map(Function f)接收一个函数作为参数, 该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap(Function f)接收一个函数作为参数, 将流中的每个值都换成另一个流,然后把所有流连接成一个流
public void test2(){
	List<string>list =Arrays.asList("aa","bb","cc""dd");
    //例子1:将集合中的字母全部变成大写
	list.stream().map(str -> str.toUpperCase()).forEach(system.out::println);
	
    //例子2:获取员工姓名长度大于3的员工的姓名
	List<Emplloyee>employees = EmployeeData.getEmployees();
    //得到的是处理过后的新的流
	Stream<String> stringStream = employees.stream().map(Employee::getName);
    //再将这个流过滤
    namesStream.filter(name ->name.length()>3).forEach(System.out::println);

区别:map() 方法接收一个函数作为参数,并将该函数应用于流中的每个元素,然后返回一个新的流,其中包含应用函数后的结果。换句话说,map() 会将流中的每个元素转换为另一个类型的单个元素。

示例代码:

假设我们有一个整数流,我们想要将每个整数转换为它的平方值:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> squares = numbers.stream()
                               .map(n -> n * n)
                               .collect(Collectors.toList());

// 输出: [1, 4, 9, 16, 25]

在这个例子中,map(n -> n * n) 将每个整数 n 映射到 n * n 的结果,生成一个新的流,最终收集为一个列表。

flatMap() 方法接收一个函数作为参数,该函数返回一个流。flatMap() 方法会将原始流中的每个元素转换为一个流,然后将这些流扁平化为一个单一的流。这意味着 flatMap() 可以将嵌套的流转换为一个单一的流。

示例代码:

假设我们有一个整数列表的列表,我们想要将其扁平化为一个单一的整数列表:

List<List<Integer>> listOfLists = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5, 6),
    Arrays.asList(7, 8, 9)
);

List<Integer> flattened = listOfLists.stream()
                                     .flatMap(list -> list.stream())
                                     .collect(Collectors.toList());

// 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
//注:如果用map的操作,会得到类似[[1,2],3,4,5,[6,7,8,9]]的输出

在这个例子中,flatMap(list -> list.stream()) 将每个内部列表转换为其自身的流,然后将所有这些流合并成一个单一的流。

总结

  • map(): 用于一对一的转换,即将一个元素转换为另一个元素。
  • flatMap(): 用于一对多的转换,即将一个元素转换为多个元素(通过先转换为流再合并的方式)。

在实际应用中,当你需要处理嵌套的数据结构时,例如列表的列表、集合的集合等,flatMap() 通常是非常有用的工具。而当你只需要对每个元素进行简单的转换时,map() 就足够了。

3-排序

方 法描 述
sorted()产生一个新流, 其中按自然顺序排序
sorted(Comparator comp)产生一个新流, 其中按比较器顺序排序
public void test4(){
	//sorted()一自然排序
	List<Integer>list=Arrays.aslist(12,43,6534,87,0,-98,7);
	list.stream().sorted().forEach(system.out::println);
    
	//抛异常,原因:Employee没有实现Comparable接口
	//List<Employee>employees =EmployeeData.getEmployees();
	//employees.stream().sorted().forEach(System.out::println).sorted(Comparator com)——定制排序
    
        List<Employee>employees = EmployeeData.getEmployees();
	   employees.stream().sorted((e1,e2)->{
	int ageValue = Integer.compare(e1.getAge(),e2.getAge());
	if(ageValue != 0){
		return ageValue;
	}else{
		return pouble.compare(e1.getSalary(),e2.getSalary());
	}).forEach(system.out::println);
}

Stream 的终止操作

终端操作会从流的流水线生成结果。 其结果可以是任何不是流的值, 例如: List、 Integer, 甚至是 void

1-匹配与查找

方 法描 述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。 相反, Stream API 使用内部迭代——它帮你把迭代做了)
//以第一个为例
public void test1(){
	List<Employee>employees =EmployeeData.getEmployees();
	//allMatch(Predicate p)-检查是否匹配所有元素。
	//练习:是否所有的员工的年龄都大于18
	boolean allMatch = employees.stream().allMatch(e ->e.getAge()> 18);
	System.out.println(allMatch);
}

2-归约

reduce(T identity, BinaryOperator b)可以将流中元素反复结合起来, 得到一个值(identity)。返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 Optional

注: map 和 reduce 的连接通常称为map-reduce 模式, 因 Google 用它来进行网络搜索而出名

//练习1:计算1-10的自然数的和
	List<Integer> list = Arrays.aslist(1,2,3,4,5,6,7,8,9,10);
	Integer sum= list.stream().reduce(identity:0, Integer::sum);
	System.out.println(sum);
//练习2:计算公司所有员工工资的总和
	List<Employee>employees= EmployeeData.getEmployees();
	Stream<Double>salarystream = employees.stream().map(Employee::getSalary).
	//直接用方法引用的话:Optional<Double>sumMoney = salarystream.reduce(Double::sum);
	Optional<Double>sumMoney= salaryStream.reduce((d1,d2)-> d1 + d2);
	System.out.println(sumMoney);

3-收集

方 法描 述
collect(Collector c) 将流转换为其他形式。 接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)

但是 Collectors 实用类提供了很多静态方法, 可以方便地创建常见收集器实例, 具体方法与实例如下表 :

方法返回类型作用
toListList把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
toSetSet把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollectionCollection把流中元素收集到创建的集合
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
summingIntInteger对流中元素的整数属性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntDouble计算流中元素Integer属性的平均值
double avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingIntIntSummaryStatistics收集流中Integer属性的统计值。如: 平均值
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningString连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional根据比较器选择最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByOptional根据比较器选择最小值
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing归约产生的类型从一个作为累加器的初始值开始, 利用BinaryOperator与流中元素逐个结合, 从而归约成单个值
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器, 对其结果转换函数
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<K, List>根据某属性值对流分组, 属性为K, 结果为V
Map<Emp.Status, List<Emp>> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningByMap<Boolean, List>根据true或false进行分区
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));

Optional 类

Optional 类的作用

  1. 避免空指针异常:
    • Optional 可以用来封装可能为 null 的值,从而避免在使用这些值时产生 NullPointerException
    • 使用 Optional 可以明确地表示某个变量可能没有值,从而让调用者能够更容易地处理这种情况。
  2. 提供标准的方法来处理 null:
    • Optional 提供了一系列方法来安全地处理可能为 null 的值,如 ifPresent, orElse, orElseGet, orElseThrow 等。
    • 这些方法允许你在不检查 null 的情况下进行操作,使得代码更加简洁和易于理解。
  3. 减少条件判断:
    • 在使用 Optional 之前,通常需要显式地检查一个变量是否为 null
    • 使用 Optional 可以减少这些条件判断,使得代码更加简洁。
  4. 提供更好的可读性和可维护性:
    • Optional 的使用使得代码更加清晰,意图更加明显。
    • 通过使用 Optional 的方法,可以减少不必要的嵌套条件语句,使代码结构更加清晰。

常用方法

  • 创建Optional类对象的方法:
Optional.of(T t)创建一个 Optional 实侧,t必须非空
Optional.empty()创建一个空的 Optional 实例
Optional.ofNullable(T t)t 可以为 null
  • 判断Optional容器中是否包含对象:
boolean isPresent( )判断是否包含对象
void ifPresent(Consumer<? superT> consumer)如果有值,就执行`Consumer接口的实现代码,并且该值会作为参数传给它。
  • 获取Optional容器的对象:
T get()如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)如果有值则将其返回,否则返回指定的other对象
T orElseGet(Supplier <? extends T> other)如果有值则将其返回,否则返回由Supplier接口实现提供的对象
T orElseThrow(Supplier< ? extends X >exceptionSupplier)如果有值则将其返回,否则抛出由Supplier接口实现提供的异常

代码举例

获取到boy对象里的girl对象里的属性girlName

//1.直接写法:如果boy、girl对象不存在或为空,会报异常
public void test3(){
	Boy boy = new Boy();
	boy = null;
	String girlName= getGirlName(boy);
	System.out.println(girlName);
}

//2.优化以后的getGirlName():
public string getGirlNamel(Boy boy){
	if(boy != null){
		Girl girl = boy.getGir1();
		if(girl != null){
			return girl.getName();
        }
      }
return null;
}

//3.使用optional类的getGirlName(),这样能保证没有空指针异常,保底会输出默认值
public string getGirlName2(Boy boy){
	Optional<Boy> boyoptional = optional.ofNullable(boy);
	//此时的boy1一定非空
	Boy boy1 = boyoptional.orElse(new Boy(new Girl( name:"迪丽热巴")));
	Girl girl = boy1.getGir1();
	Optional<Girl> girloptional = optional.ofNullable(gir1);
	//girl1一定非空
	Girl girl1 = girloptional.orElse(new Girl( name:"古力娜扎"));
	return girl1.getName();
}

默认方法

概念

  • 接口中的“默认方法”(default method)是一种特殊类型的方法,它允许在接口中定义一个具有实现体的方法。这为接口提供了一种向后兼容的方式,可以在不破坏现有实现类的前提下添加新的方法

  • 可以在接口中声明一个方法,并使用default关键字来指定其为默认方法。这样,任何实现了该接口的类都可以选择性地覆盖这个方法,如果不覆盖,则会继承接口中的默认实现

  • 默认方法语法格式如下:

    public interface Vehicle {
       default void print(){
          System.out.println("我是一辆车!");
       }
    }
    

作用

  1. 向后兼容性
    • 在Java 8之前,一旦一个接口被发布并且被广泛采用,就不能轻易地向这个接口添加新方法,因为这会导致所有已经存在的实现类都需要显式地实现这个新方法,否则它们将不再符合接口的要求。
    • 默认方法使得可以安全地向现有的接口中添加新方法,而不会破坏那些已经存在的实现类,因为它们可以继续使用默认实现而不必进行任何更改
  2. 框架和库的更新
    • 随着时间的推移,库和框架需要添加新的功能或行为,但又不能破坏现有的用户代码。默认方法提供了一种机制,使得可以在不影响现有代码的情况下扩展接口的功能
  3. 减少重复代码
    • 在Java 8之前,如果需要在多个接口中提供相同的默认行为,那么必须在每个接口中都复制相同的方法实现。默认方法允许在父接口中定义一次,然后在子接口中通过继承重用

拓展

多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}
 
public interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法

public class Car implements Vehicle, FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}

第二种解决方案可以使用 super 来调用指定接口的默认方法

public class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
   }
}

静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
    // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

Java 8 日期时间 API

旧版日期时间API的问题

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的(可以修改对象的值,而不是新建一个对象),这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

新的API

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testLocalDateTime();
   }
    
   public void testLocalDateTime(){
    
      // 获取当前的日期时间
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("当前时间: " + currentTime);
        
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);
        
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
        
      System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
        
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2);
        
      // 12 december 2014
      LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
      System.out.println("date3: " + date3);
        
      // 22 小时 15 分钟
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4);
        
      // 解析字符串
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
   }
}

执行结果:

$ javac Java8Tester.java 
$ java Java8Tester
当前时间: 2016-04-15T16:55:48.668
date1: 2016-04-15: APRIL,: 15,: 48
date2: 2012-04-10T16:55:48.668
date3: 2014-12-12
date4: 22:15
date5: 20:15:30

使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:

import java.time.ZonedDateTime;
import java.time.ZoneId;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testZonedDateTime();
   }
    
   public void testZonedDateTime(){
    
      // 获取当前时间日期
      ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
      System.out.println("date1: " + date1);
        
      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);
        
      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("当期时区: " + currentZone);
   }
}

执行结果:

$ javac Java8Tester.java 
$ java Java8Tester
date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
ZoneId: Europe/Paris
当期时区: Asia/Shanghai
  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值