JDK8 新特性学习笔记

1,192 阅读7分钟

java8新特性学习

java8的特点

  1. 速度更快(修改了HasMap、HasSet、CurrentHasMap等存储结构)
  2. 代码更少(增加了新的语法Lambda表达式)
  3. 强大的Stream API
  4. 便于并行
  5. 最大化减少空指针异常

1.Lambda表达式

1.1、Lambda表达式是什么?

Lambda是一个匿名函数,我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

1.2、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);

源代码

package com.zgy.deom1;

import java.util.Comparator;
import java.util.function.Consumer;

import org.junit.jupiter.api.Test;

public class TestLambda2 {

	/*
	 * Lambda基本语法:参数列表 -> 方法体
	 */
	
	/*
	 * 语法格式一:无参数,无返回值
	 * 	() -> System.out.println("Hello World");
	 */
	@Test
	public void test1() {
		int num = 0; //jdk8中当匿名内部类中使用的变量,会自动隐式添加final关键字,匿名内部类中依然不能将变量进行加减操作。
		Runnable r = new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("hello world"+num);
			}
		};
		r.run();

		System.out.println("=============================");
		
		Runnable r1 = () -> System.out.println("hello lambda"+num);
		r1.run();
	}
	
	/*
	 * 语法格式二:有一个参数,没有返回值
	 */
	@Test
	public void test2() {
		Consumer<String> c = (x) -> System.out.println(x);
		c.accept(1+"");
		
		//Lambda表达式中如果参数只有一个,那么括号可以省略不写
		Consumer<Object> c1 = x -> System.out.println("哈哈哈");
		c1.accept("ABC");
	}
	
	/*
	 * 语法格式三:有两个参数,有返回值,Lambda体中有多条语句
	 */
	@Test
	public void test3() {
		Comparator<Integer> com = (x,y) -> {
			System.out.println("好好学习,天天向上!");
			return x+y;
		};
		
		int result = com.compare(20, 30);
		System.out.println("result:"+result);
		
		//如果Lambda体中只有一条语句,可以省略“{}”和return
		Comparator<Integer> c = (Integer x,Integer y) -> Integer.compare(x, y);
		int num = c.compare(30, 30);
		System.out.println("maxNub"+num);
		
		//由于JVM的类型推断,所以Lambda参数列表的数据类型,可以省略不写
		Comparator<Integer> c1 = (x,y) -> Integer.compare(x, y);
		int num1 = c1.compare(30, 31);
		System.out.println("maxNub"+num1);
	}
	
	/*
	 * 调用自定的函数式接口
	 */
	@Test
	public void test4() {
		MyFun myFun = x -> x+100;
		int value = myFun.getValue(100);
		System.out.println("value:"+value);
	}
	
}
package com.zgy.deom1;

@FunctionalInterface
public interface MyFun {

	int getValue(int x);
}

练习代码

package com.zgy.deom1;


import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.junit.jupiter.api.Test;

public class TestLambda {

	
	List<Employee> emps = Arrays.asList(
			new Employee(101,"张三",18,9999.99),
			new Employee(102,"李四",59,6666.66),
			new Employee(103,"王五",28,3333.33),
			new Employee(104,"赵六",8,7777.77),
			new Employee(105,"田七",38,5555.55)
			);
	
	/*
	 * 使用Collections对员工列表进行排序,按工资从多到少比
	 * 关于Collections.sort()自定义排序规则,两个参数x和y,如果要按照升序,就是x在前面;相反如果要按照降序,就是y在前面。
	 */
	@Test
	public void test1() {
		
		Collections.sort(emps, (x,y) -> Double.compare(y.getSalary(), x.getSalary()));
		for (Employee employee : emps) {
			System.out.println(employee);
		}
	}
	
	/*
	 * 定义一个函数式接口,传如一个参数,返回该参数的大写形式
	 */
	@Test
	public void test2() {
		
		String str = strHandler("zgy", x -> x.toUpperCase());
		System.out.println("str:"+str);
	}
	
	//小写转大写的方法
	public String strHandler(String str,MyFunction mf) {
		return mf.getValue(str);
	}
	
	/*
	 * 声明一个带两个泛型的函数式接口,泛型类型为<T,R> T为参数,R为返回值,接口中声明对应的抽象方法
	 * 再使用接口作为参数,计算两个long型参数的和
	 * 再计算两个long型参数的积
	 */
	@Test
	public void test3() {
		
		//计算和
		System.out.println(getValue(100L, 200L, (x,y) -> x+y));
		
		//计算积
		System.out.println(getValue(500L, 900L, (x,y) -> x*y));
	}
	
	public long getValue(Long x,Long y,MyFunction2<Long, Long> mf) {
		
		return mf.getValue(x, y);
	}
}
package com.zgy.deom1;

public class Employee {

	private int id;
	private String name;
	private int age;
	private double salary;
	
	public Employee() {}
	
	public Employee(int id, String name, int age, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}

	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;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}
	
}
package com.zgy.deom1;

@FunctionalInterface
public interface MyFunction {

	public String getValue(String x);
}
package com.zgy.deom1;

@FunctionalInterface
public interface MyFunction2<T,R> {

	public R getValue(T t1, T t2);
}

1.3、函数式接口

函数式接口:接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface修饰,该注解的作用是可以检查该接口是否为函数式接口。

1.4、Java8内置四大核心函数式接口

Consumer:消费型接口

​ void accept(T t);

Supplier:供给型接口

​ T get();

Function<T, R>:函数型接口

​ R apply(T t);

Predicate:断言型接口

​ boolean test(T t);

其它子接口如下图

源代码

package com.zgy.deom1;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.junit.jupiter.api.Test;

/*
 * JDK8 内置四大核心函数式接口
 */
public class TestLambda3 {

	/*
	 * 消费式接口:传一个参数,没有返回值
	 * Consumer<T>  void accept(T t);
	 */
	@Test
	public void test1() {
		say("好好学习,天天向上!", x -> System.out.println(x+new Date()));
	}
	
	public void say(String str, Consumer<String> c) {
		c.accept(str);
	}
	
	/*
	 * 供给式接口:没有参数,返回一个结果
	 * Supplier<T>  T get();
	 */
	@Test
	public void test2() {
		List<Integer> listResult = getNumberArr(() -> {
			List<Integer> list = new ArrayList<>();
			for(int i=0; i<10; i++) {				
				list.add(new Random().nextInt());
			}
			return list;
		});
		
		for (Integer integer : listResult) {
			System.out.println("DATA:"+integer);
		}
	}
	
	public List<Integer> getNumberArr(Supplier<List<Integer>> s){
		return s.get();
	} 
	
	/*
	 * 函数式接口:传入一个参数,返回一个结果
	 * Function<T,R>  R apply(T t);
	 */
	@Test
	public void test3() {
		System.out.println(apply("ZGY:", (x) -> x+new Date()));
	}
	
	public String apply(String a, Function<String, String> f) {
		return f.apply(a);
	};
	
	/*
	 * 断言型接口:传入一个参数,返回一个布尔值
	 * Predicate<T>  boolean test(T t);
	 */
	@Test
	public void test4() {
		System.out.println(test(40, x -> x>0));
	}
	
	public String test(int x, Predicate<Integer> p) {
		if(p.test(x)) {
			return "大于0";
		}else {
			return "小于0";
		}
	}
}

1.5、方法引用与构造器引用

  1. 方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用是Lambda表达式的另外一种表现形式)

    主要有三种语法格式:

    1. 对象 :: 实例方法名
    2. 类 :: 静态方法名
    3. 类 :: 实例方法名

    注意:

    1. Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
    2. 若Lambda参数列表中的第一个参数是实例方法的调用者,第二个参数是实例方法的参数,可以用使用ClassName :: method
  2. 构造器引用:

    格式:ClassName :: new

    注意:需要调用的构造器参数列表要与函数式接口中抽象方法的参数列表保持一致

  3. 数组引用:

    格式:Type :: new;

源代码

package com.zgy.deom1;

import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.junit.jupiter.api.Test;

public class TestMethodRef {

	/*
	 * 方法引用和构造器引用
	 */
	
	@Test
	public void test1() {
		//对象 :: 实例方法名
		Consumer<String> c = System.out::println;
		c.accept("ZGY");
		
		Employee e = new Employee(111,"ZGY",22,1000);
		Supplier<String> s = e::getName;
		System.out.println(s.get());
	}
	
	@Test
	public void test2() {
		//类名::方法名
		Comparator<Integer> c = Integer::compare;
		System.out.println(c.compare(800, 210));
	}
	
	@Test
	public void test3() {
		//类名::实例方法名
		BiPredicate<String, String> bp = String::equals;
		System.out.println(bp.test("AAA", "AAA"));
	}
	
	@Test
	public void test4() {
		//构造器引用
		Supplier<Employee> e = Employee::new;
		Employee employee = e.get();
		employee.setName("ZGY");
		System.out.println(employee);
	}
}

2、Stream API

2.1、什么是Stream

流(Stream)到底是什么?

流,是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,而流讲的是计算!

注意:

  1. Stream自己不会存储元素。
  2. Stream不会改变源对象,相反他们会返回一个持有结果的新Stream。
  3. Stream是延迟执行的,这意味着它们会等到需要结果的时候才会执行。

2.2、Stream的操作三个步骤

  1. 创建Stream源码

    package com.zgy.deom1;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    import org.junit.jupiter.api.Test;
    
    /*
     * 创建Stream的几种方式
     */
    public class TestStreamAPI1 {
    
    	@Test
    	public void test1() {
    		//1.可以通过Collection系列集合提供的stream()或parallelStream()
    		List<String> list = new ArrayList<>();
    		Stream<String> stream = list.stream();
    		
    		//2.通过Arrays中的静态方法stream()获取数组流
    		Employee[] employees = new Employee[10];
    		Stream<Employee> stream2 = Arrays.stream(employees);
    		
    		//3.通过Stream类中的静态方法of()
    		Stream<String> stream3 = Stream.of("AA","BB","CC","DD");
    		
    		//4.通过迭代的方式创建无限流
    		Stream<Integer> stream4 = Stream.iterate(0, x -> x+2);
    		stream4.limit(5).forEach(System.out::println);
    		
    		//5.通过生成的方式创建无限流
    		Stream<Double> stream5 = Stream.generate(() -> Math.random());
    		//下面limit、forEach分别是
    		stream5.limit(20).forEach(System.out::println);
    	}
    }