JDK新特性之 JDK8

前言

Java 8于2015年3月19发布,是自Java 5以来最具革命性的版本。
Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性
特点:

HashMap底层数据结构由链表改成了红黑树
内存模型中用元空间代替了永久带:

lamdba


语法:

demo:

//lambda表达式
public class Test01 {
	
	@Test
	public void test01(){
		//1无参无返回值
		Runnable r1=()-> System.out.println("hello lambda");
		r1.run();
		
		//2 有一个参数无返回值
		// java8中首次出现Consumer接口,用于接收一个输入参数并处理,不返回任何值
		Consumer c=(x)-> System.out.println(x);
		//accept方法处理输入的参数
		c.accept("哈哈");
		//只有一个参数小括号可以省略不写
		Consumer c1= x-> System.out.println(x);
		//accept方法处理输入的参数
		c.accept("呵呵");
	}
	@Test
	public void test02(){
		//两个参数,多条语句并且有返回值
		Comparator<Integer> com=(x, y)->{
			System.out.println("比较器");
			return Integer.compare(x, y);
		};
		System.out.println(com.compare(5, 2));// 1
		
		//一条语句,并且有一条语句,有返回值,大括号和return可以省略
		Comparator<Integer> com1=(x, y)-> Integer.compare(x, y);
		System.out.println(com1.compare(5, 5));// 0
	}
	
	@Test
	public void test03(){
		//使用lambda表达式重写接口方法
		Integer result = oper(100, x->x+100);
		System.out.println(result); // 200
	}
	//定义运算方法,接受接口实现接口运算
	public Integer oper(Integer num, MyOperation mp){
		return mp.operation(num);
	}
	
	/**
	 * 此注解修饰的是函数式接口
	 */
	@FunctionalInterface
	public interface MyOperation {
		//只能有一个抽象方法
		Integer operation(Integer num);
	}
}

应用demo:

public class TestLambda {
	List<Employee> emps= Arrays.asList(
			new Employee("哈哈",18),
			new Employee("呵呵",16),
			new Employee("嘿嘿",20),
			new Employee("呼呼",18)
	);
	@Test
	public void test01(){
		//对员工排序,先按年龄排序,如果相等,再按名字排序
		//lambda实现排序器
		Collections.sort(emps, (e1, e2)->{
			if(e1.getAge()==e2.getAge()){
				return e1.getName().compareTo(e2.getName());
			}else{
				return Integer.compare(e1.getAge(), e2.getAge());
			}
		});
		//输出集合,foreach参数需要一个函数式接口的实现
		emps.forEach(x-> System.out.println(x));
	}
	
	@Test
	public void test02(){
		//1 字符串转大写
		String result01 = strOperation("houzheng", String::toUpperCase);
		System.out.println(result01);// HOUZHENG
		//2 截取子字符串,索引2到4 (左闭右开)
		String result02=strOperation("houzheng", str-> str.substring(2, 4));
		System.out.println(result02);// uz
	}
	
	//字符串操作方法
	public String strOperation(String string, MyStringOperation msp){
		return msp.getValue(string);
	}
	
	@Test
	public void test03(){
		//1求和,输出String类型
		String add = getResult(100,200,(x, y)->x+y+"");
		System.out.println(add);
		//2求积
		String product = getResult(100,200,(x, y)->x*y+"");
		System.out.println(product);
	}
	//传入两个Integer数,求运算结果 ,泛型必须传对象,不能是基本类型
	public String getResult(int t1, int t2, MyNumOperation<Integer, String> mlp){
		return mlp.getValue(t1, t2);
	}
	@Test
	public void test04(){
		//四大内置函数可以直接使用而不必去声明函数式接口,除非有特殊需求需要单独声明
		//1 消费型接口,传入参数无返回
		Consumer<String> com= x-> System.out.println(x);
		com.accept("哈哈");//哈哈
		
		//2供给型接口,无参,有返回值
		Supplier<String> sup=()->"9527";
		System.out.println(sup.get());//9527
		
		//3 函数型接口,有参数有返回值
		Function<String, Integer> fun=(s)-> Integer.valueOf(s);
		System.out.println(fun.apply("666"));//666
		
		//4断言型接口,传入参数,返回boolean
		Predicate<String> pre= x->x!="";
		System.out.println(pre.test(""));//false
	}
}

函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以在调用时,被隐式转换为 lambda 表达式。
demo:

//声明函数式接口
@FunctionalInterface
public interface MyStringOperation {	
	String getValue(String str);
}

内置函数式接口:


java8还内置了非常多的函数接口,如果没有合适自己业务的,也可以自定义!
函数式接口其实就是策略模式的语法糖实现,可以将参数相同的各种算法接口,
定义成一个函数式接口,从而将算法交给调用法,自己不必再实现,以不变应万变

方法引用与构造器引用

方法引用:如果Lambda体中的功能,已经有方法提供了实现,就可以使用方法引用
可以将方法引用理解为lambda表达式的另外一种表现形式
作用: 使语言的构造更紧凑简洁,减少冗余代码。支持Lambda的简写,使用方法名称来表示Lambda
demo:

/**
 * 1 对象的引用:: 实例方法名
 * 2  类名:: 静态方法名
 * 3  类名:: 实例方法名
 * 注: 1 方法引用所引用的参数列表与返回值类型,必须要和函数式接口的一致
 */
public class TestMethodRef01 {
	
	//1 对象的引用::实例方法名
	@Test
	public void test01(){
		//System.out实例是打印流对象
		PrintStream ps = System.out;
		Consumer<String> com1= x->ps.println(x);
		
		//上面可以写成方法引用: 不需要写->和参数
		Consumer<String> com2=ps::println;
		Consumer<String> com3= System.out::println;
		com1.accept("hello1");
		com2.accept("hello2");
		com3.accept("hello3");
	}
	@Test
	public void test02(){
		Employee emp=new Employee("哈哈",18);
		Supplier<String> sup1=()->emp.getName();
		//getName已经实现,可以使用方法引用
		Supplier<String> sup2=emp::getName;
		System.out.println(sup1.get());//哈哈
		System.out.println(sup2.get());//哈哈
	}
	//2 类名::静态方法名
	@Test
	public void test03(){
		//返回两个值中大的一个  BiFunction:接受两个类型参数,返回一个类型值
		BiFunction<Double, Double, Double> bif1=(x, y)-> Math.max(x,y);
		Double apply = bif1.apply(1.5, 2.8);//2.8
		System.out.println(apply);
		//使用方法引用
		BiFunction<Double, Double, Double> bif2= Math::max;
		System.out.println(bif2.apply(1.5, 2.8));//2.8
		
	}
	
	//3 类名::实例方法名
	@Test
	public void test04(){
		//比较两个字符串是否相等 BiPredicate: 传入两个参数
		BiPredicate<String, String> bq1=(x, y)->x.equals(y);
		bq1.test("哈哈","哈哈");
		//如果lambda的第一个参数是方法的调用者,第二个参数没有或者是实例方法,可以直接用类名::实例方法
		BiPredicate<String, String> bq2= String::equals;
		bq2.test("哈哈","哈哈");
	}
	//4 构造方法引用    :构造器的参数列表需要与函数式接口一致
	@Test
	public void test05(){
		// 1 类名::new   
		Supplier<Employee> sup1=()->new Employee();
		//引用构造器  无参构造器
		Supplier<Employee> sup2=Employee::new;
		//一个参数构造器
		Function<String,Employee> sup3=Employee::new;
		//两个参数构造器
		BiFunction<String, Integer,Employee> sup4=Employee::new;
	}
	//数组引用
	@Test
	public void test06(){
		//创建数组
		Function<Integer, String[]> fun= length->new String[length];
		String[] apply = fun.apply(10);
		System.out.println(apply.length);//10
		//引用数组
		Function<Integer, String[]> fun1= String[]::new;
		String[] apply1 = fun.apply(5);
		System.out.println(apply1.length);//5
	}
}

Stream API

Stream是java8中处理集合的关键抽象概念,可以对集合执行非常复杂的操作,提供了一种高效易于处理数据的方式
注意:流只能使用一次,进行了终止操作后无法再次使用
操作流程:


demo:

//StreamAPI
public class TestStreamAPI {
	//1 创建Stream
	@Test
	public void test01(){
		List<String> list=new ArrayList<>();
		//获取顺序流
		Stream<String> stream = list.stream();
		//获取并行流
		Stream<String> parallelStream = list.parallelStream();
		/**
		 * 2 获取数组流
		 */
		Function<Integer, Integer[]> fun= Integer[]::new;
		Integer[] nums = fun.apply(10);//lmabda创建数组
		//使用Arrays.stream() 获取数组stream流
		Stream<Integer> stream2 = Arrays.stream(nums);
		/**
		 * 3 通过Stream的静态方法of获取任意数据类型的流
		 */
		//参数可以是一个或多个任意类型的数据
		Stream<Integer> of = Stream.of(1,2,3,4,5);
		Stream<String> of2 = Stream.of("yaozhen");
		Stream<String> of3 = Stream.of("yozhen","何毅","崔杰");
		/**
		 * 4 创建无限流
		 */
		//参数是一个T类型,一个Function<T,T>接口的实现UnaryOperator<T,T>,即lambda,同类型参数返回同类型值
		Stream<Integer> iterate = Stream.iterate(0, (x)->x+2).limit(10); //.limit(10) 只获取前十个
		//无限流,会从0开始一直输入偶数,无限输出
		iterate.forEach(System.out::println);
		//无限生成随机数     .limit(5)取前五个
		Stream<Double> generate = Stream.generate(Math::random).limit(5).skip(2);//跳过前两个
		generate.forEach(System.out::println);
	}
	
	@Test
	public void test04(){
		//distinct:根据hascode()和equals去重,对象中需要重写方法
		emps.stream().distinct().forEach(System.out::println);
	}
	/**2 中间操作
	 * 多个中间操作可以连接起来形成一个流水线,除非流水线触发终止操作,
	 * 否则中间操作不会执行任何处理,而在终止操作时一次性全部处理,成为"惰性求值"
	 */
	@Test
	public void test02(){
		//1 筛选与切片
		Stream<Employee> stream = emps.stream().//创建流
		//filter: 接受一个断言型接口实现lambda,过滤数据,返回一个新流
		filter(e->e.getAge()<=35);
		//终止操作时 ,一次性执行中间操作
		stream.forEach(System.out::println);
	}
	@Test
	public void test03(){
		Stream<Employee> stream = emps.stream().
		filter(e->e.getSalary()>=5000)
		.skip(1)   //跳过前1个
		.limit(2);   //只取前两个,与skip合同可达到分页效果
		//终止操作时 ,一次性执行中间操作
		stream.forEach(System.out::println);
	}

	//映射
	@Test
	public void test09(){
		//map:接受一个函数,将流中每一个元素转换成其他形式,或者提取其中一部分信息,形成一个新的元素
		emps.stream().map(e->e.getName()).forEach(System.out::println);
		//将String的list转为大写输出
		List<String> list = Arrays.asList("aa","bb","cc","dd","ee","ff","gg");
		list.stream().map(String::toUpperCase).forEach(System.out::println);

		//将String转换为char输出   TestStreamAPI2::filterCharacter:返回含有char的流
		//即map返回一个流,流中的每个元素又是一个流
		Stream<Stream<Character>> map = list.stream().map(TestStreamAPI2::filterCharacter);
		//双重循环输出字符
		map.forEach((sm)->{
			//每个含有字符的流再循环
			sm.forEach(System.out::println);
		});
		//可以使用flatMap直接 完成     flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
		list.stream().flatMap(TestStreamAPI::filterCharacter).forEach(System.out::println);
	}

	@Test
	public void test07(){
		//进行reducing计算,工资求和1
		Optional<Double> sum = emps.stream()
				.map(Employee::getSalary)
				.collect(Collectors.reducing(Double::sum));
		System.out.println(sum.get());
		//用map-reduce计算出流中有多少个员工
		Optional<Integer> reduce = emps.stream().map((x)->1).reduce(Integer::sum);
		System.out.println(reduce.get());  //7
	}

	//将字符串转换成单个字符,返回含有字符的流
	public static Stream<Character> filterCharacter(String str){
		List<Character> list=new ArrayList<>();
		for (Character character : str.toCharArray()) {
			list.add(character);
		}
		return list.stream();
	}

	private List<Employee> emps = Arrays.asList(
			new Employee(102, "李四", 59, 6666.66),
			new Employee(101, "张三", 18, 9999.99),
			new Employee(103, "王五", 28, 3333.33),
			new Employee(104, "赵六", 8, 7777.77),
			new Employee(104, "赵六", 8, 7777.77),
			new Employee(104, "赵六", 8, 7777.77),
			new Employee(105, "田七", 38, 5555.55)
	);
}

并行流

java8并行流底层额采用forkJoin框架

	//使用并行流计算数的累加;CPU使用率剧增
	@Test
	public void test3(){
		long start = System.currentTimeMillis();
		
		Long sum = LongStream.rangeClosed(0L, 10000000000L)  //数值越大.效率越高
							 .parallel()   //切换成并行流
							 .sum();
		System.out.println(sum);
		long end = System.currentTimeMillis();
		System.out.println("耗费的时间为: " + (end - start)); //1551
	}

接口中的默认方法与静态方法

Java 8 新增了接口的默认方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。这样要增加一个庞大的接口方法时,不需要子类再全部实现,即可增加
继承规则:

demo:

![](https://img2020.cnblogs.com/blog/1370059/202108/1370059-20210814183220288-1692611942.png)

新时间日期API

public class TestNewTimeAPI {
	
	//1 LocalDate  LocalTime LocalDateTime
	@Test
	public void test01(){
		//获取当前日期时间
		LocalDateTime ldt= LocalDateTime.now();
		System.out.println(ldt);//2018-03-26T15:29:22.363
		//获取指定日期时间          年月日时分秒
		LocalDateTime of = LocalDateTime.of(2018, 3, 26, 15, 15, 15);
		System.out.println(of);
		//计算: 
		ldt.plusYears(20); //增加20年
		ldt.minusMonths(5);//减少5个月
		//获取
		System.out.println(ldt.getYear());
		System.out.println(ldt.getDayOfYear()); //今年的第85天
		System.out.println(ldt.getDayOfWeek()); // MONDAY 星期一
		System.out.println(ldt.getMonth());     //MARCH 三月
		System.out.println(ldt.getMonthValue()); //3
	}
	
	//2. Instant : 时间戳。 (使用 Unix 元年  1970年1月1日 00:00:00 所经历的毫秒值)
	@Test
	public void test2(){
		//默认使用 UTC 时区:协调世界时,又称世界统一时间,世界标准时间,国际协调时间,简称UTC不属于任意时区
		Instant ins = Instant.now();
		System.out.println(ins);//2018-03-26T09:29:58.422Z
		//时间偏移8小时
		OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
		System.out.println(odt);//2018-03-26T01:29:58.422-08:00
		//获取纳秒
		System.out.println(ins.getNano());//422000000
		
		//元年时间偏移5秒
		Instant ins2 = Instant.ofEpochSecond(5);
		System.out.println(ins2);//1970-01-01T00:00:05Z
	}
	
	//3.
	//Duration : 用于计算两个“时间”间隔
	//Period : 用于计算两个“日期”间隔
	@Test
	public void test3(){
		Instant ins1 = Instant.now();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		Instant ins2 = Instant.now();
		//时间间隔
		System.out.println("所耗费时间为:" + Duration.between(ins1, ins2));
		
		LocalDate ld1 = LocalDate.now();
		LocalDate ld2 = LocalDate.of(2011, 1, 1);
		// 日期间隔
		Period pe = Period.between(ld2, ld1);
		System.out.println(pe.getYears());
		System.out.println(pe.getMonths());
		System.out.println(pe.getDays());
	}
		
	//6.ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
	@Test
	public void test7(){
		//获取某个时区的时间
		LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
		System.out.println(ldt);
		//信息后面显示时区信息
		ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
		System.out.println(zdt);
	}
	
	@Test
	public void test6(){
		//获取所有的时区
		Set<String> set = ZoneId.getAvailableZoneIds();
		set.forEach(System.out::println);
	}

		
	//5. DateTimeFormatter : 解析和格式化日期或时间
	@Test
	public void test5(){
		//标准时间格式
//			DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
		//自定义时间格式
		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
		LocalDateTime ldt = LocalDateTime.now();
		String strDate = ldt.format(dtf);//格式化输出字符串
//			/2018年03月26日 17:36:19 星期一
		System.out.println(strDate);
		//字符串解析成日期
		LocalDateTime newLdt = ldt.parse(strDate, dtf);
		System.out.println(newLdt);//2018-03-26T17:36:19
	}
	
	//4. TemporalAdjuster : 时间校正器
	@Test
	public void test4(){
		LocalDateTime ldt = LocalDateTime.now();
		//with 需要接受一个TemporalAdjuster接口的实现类
		//TemporalAdjusters内置了许多实现可以直接使用
		//获取这周的周日
		LocalDateTime ldt2 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
		System.out.println(ldt2);
	}
}

CompletableFuture

在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,
并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法

public class CompletableFutureDemo {

    /**
     * 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,
     * 并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法
     *  注意: 方法中有Async一般表示另起一个线程,没有表示用当前线程
     */
    @Test
    public void test01() throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        /**
         *  supplyAsync用于有返回值的任务,
         *  runAsync则用于没有返回值的任务
         *  Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池
         */
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "侯征";
        }, service);
        CompletableFuture<Void> data = CompletableFuture.runAsync(() -> System.out.println("侯征"));
        /**
         * 计算结果完成回调
         */
        future.whenComplete((x,y)-> System.out.println(x+","+y)); //执行当前任务的线程执行继续执
        data.whenCompleteAsync((x,y)-> System.out.println(x+","+y)); // 交给线程池另起线程执行
        future.exceptionally(Throwable::toString);
        //System.out.println(future.get());
        /**
         * thenApply,一个线程依赖另一个线程可以使用,出现异常不执行
         */
        //第二个线程依赖第一个的结果
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 5).thenApply(x -> x);

        /**
         * handle 是执行任务完成时对结果的处理,第一个出现异常继续执行
         */
        CompletableFuture<Integer> future2 = future1.handleAsync((x, y) -> x + 2);
        System.out.println(future2.get());//7
        /**
         * thenAccept 消费处理结果,不返回
         */
        future2.thenAccept(System.out::println);
        /**
         * thenRun  不关心任务的处理结果。只要上面的任务执行完成,就开始执行
         */
        future2.thenRunAsync(()-> System.out.println("继续下一个任务"));
        /**
         * thenCombine 会把 两个 CompletionStage 的任务都执行完成后,两个任务的结果交给 thenCombine 来处理
         */
        CompletableFuture<Integer> future3 = future1.thenCombine(future2, Integer::sum);
        System.out.println(future3.get()); // 5+7=12
        /**
         * thenAcceptBoth : 当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行消耗
         */
        future1.thenAcceptBothAsync(future2,(x,y)-> System.out.println(x+","+y)); //5,7
        /**
         * applyToEither
         * 两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作
         */
        CompletableFuture<Integer> future4 = future1.applyToEither(future2, x -> x);
        System.out.println(future4.get()); //5
        /**
         * acceptEither
         * 两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的消耗操作
         */
        future1.acceptEither(future2, System.out::println);
        /**
         * runAfterEither
         * 两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable
         */
        future1.runAfterEither(future,()-> System.out.println("有一个完成了,我继续"));
        /**
         * runAfterBoth
         * 两个CompletionStage,都完成了计算才会执行下一步的操作(Runnable)
         */
        future1.runAfterBoth(future,()-> System.out.println("都完成了,我继续"));
        /**
         * thenCompose 方法
         * thenCompose 方法允许你对多个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作
         * thenApply是接受一个函数,thenCompose是接受一个future实例,更适合处理流操作
         */
        future1.thenComposeAsync(x-> CompletableFuture.supplyAsync(()->x+1))
                .thenComposeAsync(x-> CompletableFuture.supplyAsync(()->x+2))
                .thenCompose(x-> CompletableFuture.runAsync(()-> System.out.println("流操作结果:"+x)));
        TimeUnit.SECONDS.sleep(5);//主线程sleep,等待其他线程执行
    }
}

posted @ 2021-08-14 17:29  侯小厨  阅读(1411)  评论(0编辑  收藏  举报
Fork me on Gitee