java8新特性_java8新特性总结 - CSDN
精华内容
参与话题
  • JDK1.8,Java8常用新特性

    万次阅读 多人点赞 2020-07-01 15:48:23
    Java8,也就是jdk1.8,是意义深远的一个版本 是Java5之后一个大的版本升级,让Java语言和库仿佛获得了新生 新特性包含: a.随着大数据的兴起,函数式编程在处理大数据上的优势开始体现,引入了Lambada函数式...

    A.Lamdba表达式

    1.Java8概述

    Java8,也就是jdk1.8,是意义深远的一个新版本

    是Java5之后一个大的版本升级,让Java语言和库仿佛获得了新生

    新特性包含:

    a.随着大数据的兴起,函数式编程在处理大数据上的优势开始体现,引入了Lambada函数式编程

    b.使用Stream彻底改变了集合使用方式:只关注结果,不关心过程

    c.新的客户端图形化工具界面库:JavaFX

    d.良好设计的日期/时间API

    e.增强的并发/并行API

    f.Java与JS交互引擎 -nashorn

    g.其他特性

    2.什么是Lambda表达式

    带有参数变量的表达式,是一段可以传递的代码,可以被一次或多次执行

    是一种精简的字面写法,其实就是把匿名内部类中“一定”要做的工作省略掉

    然后由JVM通过推导把简化的表达式还原

    格式:  (parameters参数) -> expression表达式或方法体

    paramaters:

    类似方法中的形参列表,这里的参数是函数式接口里的参数

    ->:可理解为“被用于”的意思

    方法体:

    可以是表达式也可以代码块,是函数式接口里方法的实现

    如果负责运算的代码无法用表达式表示,可以使用编写方法实现

    但必须用{}包围并按需明确使用 return语句

    需求:对字符串数组按字符串长度排序

     

    package org.xxxx.demo01;
     
    import java.util.Arrays;
    import java.util.Comparator;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 定义字符串数组
    		String[] strArr = { "abc", "cd", "abce", "a" };
     
    		// 传统方法
    		// 匿名内部类
    		Arrays.sort(strArr, new Comparator<String>() {
     
    			@Override
    			public int compare(String s1, String s2) {
    				return Integer.compare(s2.length(), s1.length());
    			}
    		});
     
    		// 输出排序结果
    		for (String s : strArr) {
    			System.out.println(s);
    		}
    		System.out.println("---------------------");
     
    		// Lambda表达式
    		Arrays.sort(strArr, (s1, s2) -> Integer.compare(s2.length(), s1.length()));
     
    		// 输出
    		for (String s : strArr) {
    			System.out.println(s);
    		}
    

    统招全日制学历联系wx       YLWL9331

    需求:用Lambda实现多线程

    
    package org.xxxx.demo01;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// Lambda表达式
    		new Thread(() -> System.out.println(1 + "hello world")).start();
     
    		System.out.println("----------------");
     
    		// 方法体
    		new Thread(() -> {
    			for (int i = 0; i < 10; i++) {
    				System.out.println(2 + "hello world");
    			}
    		}).start();
    	}
    }

    3.何时使用

    通过上面的两个需求,发现Lamdba表达式很简单,那何时使用呢

    需要显示创建函数式接口对象的地方,都可以使用

    实际上函数式接口的转换是Lambda表达式唯一能做的事情

    即lambda必须和Functional Interface配套使用

    主要用于替换以前广泛使用的内部匿名类,各种回调

    比如事件响应器、传入Thread类的Runnable等

    4.函数式接口分类

    a.系统与定义函数接口(Comparator, Runnable)

    b.用户自定义函数接口(注解必须有,表达式是直接通过参数列表来实现的,只能有一个有效方法)

    
    @FunctionalInterface
    public interface MyInterface {
    	String info(String tip);
    }

     

    5.公共定义的函数式接口

    从jdk1.8开始为了方便用户开发专门提供了一个新的包:java.util.function

    在这个包里面针对于用户有可能做的函数式接口做了一个公共定义

    最为核心的有四个接口:

    a.功能性接口:Function<T, R>

    有输入参数,有返回值

    是对接收一个T类型参数,返回R类型的结果的方法的抽象

    通过调用apply方法执行内容

    需求:给定一个字符串,返回字符串长度

    package org.xxxx.demo01;
     
    import java.util.function.Function;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 定义字符串
    		String str = "helloworld";
    		
    		// 调用方法
    		// 在调用的时候写方法体,方法比较灵活
    		int length = testFun(str, (s) -> s.length());
    		
    		System.out.println(length);
    	}
    	
    	// 方法
    	/**
    	 * 
    	 * @param str 输入参数
    	 * @param fun 表达式 String 为输入类型,Integer为输出类型
    	 * @return 返回字符串长度
    	 */
    	public static int testFun(String str, Function<String, Integer> fun) {
    		// 执行
    		Integer length = fun.apply(str);
    		
    		return length;
    	}
    }

    统招全日制学历联系wx       YLWL9331

    b.消费型接口:Consumer<T>

    有输入参数,没返回值

    对应的方法类型为接收一个参数,没有返回值

    一般来说使用Consumer接口往往伴随着一些期望状态的改变

    或者事件的发生,典型的forEach就是使用的Consumer接口

    虽然没有任何的返回值,但是向控制台输出结果

    Consumer 使用accept对参数执行行为

    需求:输出字符串

    
    package org.xxxx.demo01;
     
    import java.util.function.Consumer;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建字符串
    		String str = "hello world";
    		
    		// 调用
    		testCon(str, (s) -> System.out.println(s));
    	}
    	
    	/**
    	 * 
    	 * @param str 传入参数
    	 * @param con
    	 */
    	public static void testCon(String str, Consumer<String> con) {
    		// 执行
    		con.accept(str);
    	}
    	
    }

     

    c.供给型接口:Supplier<T>

    无传入参数,有返回值

    该接口对应的方法类型不接受参数,但是提供一个返回值

    使用get()方法获得这个返回值

    package org.xxxx.demo01;
     
    import java.util.function.Supplier;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建字符串
    		String str = "hello world";
    		
    		// 调用
    		String sup = testSup(() -> str);
    		
    		System.out.println(sup);
    	}
    	
    	/**
    	 * 
    	 * @param sup
    	 * @return
    	 */
    	public static String testSup(Supplier<String> sup) {
    		// 执行
    		String s = sup.get();
    		return s;
    	}
    	
    }

     

    d.断言型接口:Predicate<T>

    有传入参数,有返回值Boolean

    该接口对应的方法为接收一个参数,返回一个Boolean类型值

    多用于判断与过滤,使用test()方法执行这段行为

    需求:输入字符串,判断长度是否大于0

    package org.xxxx.demo01;
     
    import java.util.function.Predicate;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建字符串
    		String str = "hello world";
    		
    		// 调用
    		boolean flag = testPre(str, (s) -> s.length() > 0);
    		
    		System.out.println(flag);
    	}
    	
    	/**
    	 * 
    	 * @param str
    	 * @param pre
    	 * @return
    	 */
    	public static boolean testPre(String str, Predicate<String> pre) {
    		// 执行
    		boolean flag = pre.test(str);
    		
    		return flag;
    	}
    	
    }

     

    6.Lambda的优点

    a.极大的减少代码冗余,同时可读性也好过冗长的匿名内部类

    b.与集合类批处理操作结合,实现内部迭代,并充分利用现代多核CPU进行并行计算。之前集合类的迭代都是外部的,即客户代码。而内部迭代意味着由Java类库来进行迭代,而不是客户代码

    7.和匿名内部类的区别

    a.在lambda中,this不是指向lambda表达式产生的那个对象,而是它的外部对象

    b.Java 编译器编译 Lambda 表达式并将他们转化为类里面的私有函数,它使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,但每一个匿名内部类编译器会为其创建一个类文件

    B.方法引用

    1.概述

    某些lambda表达式里面仅仅是调用了一个已存在的方法,在这种情况下

    直接通过方法名称引用方法的形式可读性更高一些,这种形式就是方法引用

    方法引用是一种更简洁易懂的lambda 表达式替换

    其唯一用途就是支持Lambda的简写

    函数接口中抽象方法的参数列表,必须与方法引用方法的参数列表保持一致

    方法引用中::后只是方法名,不能加();

    forEach()也是jdk8的新特性

    比如:list.forEach((s) -> System.out.println(s));---list.forEach(Syetem.out::println);

    2.类静态方法引用

    求绝对值,使用Function实现

    package org.xxxx.demo01;
     
    import java.util.function.Function;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 调用
    		long l1 = testAbs(-10L, (s) -> Math.abs(s));
    		
    		// 改进
    		long l2 = testAbs(-10, Math::abs);
    		
    		System.out.println(l1);
    		System.out.println(l2);
    	}
    	
    	public static long testAbs(long s, Function<Long, Long> fun) {
    		Long l = fun.apply(s);
    		return l;
    	}
    }

    统招全日制学历联系wx       YLWL9331

    3.实例方法引用

    循环集合中元素,使用forEach方法

    (s) -> System.out.println(s)的类型是Consumer类型

    其accept接受参数和println一致

    
    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建集合
    		List<String> list = new ArrayList<>();
    		
    		// 添加元素
    		list.add("e");
    		list.add("c");
    		list.add("a");
    		list.add("d");
    		list.add("b");
    		
    		// 排序
    		list.sort((s1, s2) -> s1.compareTo(s2));
    		
    		// 遍历
    		list.forEach((s) -> System.out.println(s));
    		list.forEach(System.out::println);
    	}
    }

     

    4.构造方法引用

    在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致

    格式为 类名::new

    
    package org.xxxx.demo01;
     
    import java.util.function.Supplier;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		getString(String :: new);
    	}
    	
    	public static void getString(Supplier<String> su) {
    		String s = su.get();
    		System.out.println(s == null);
    	}
    }

     

    5.任意对象的实例方法

    若Lambda表达式的参数列表的第一个参数,是实例方法的调用者

    第二个参数(或无参)是实例方法的参数时,就可以使用这种方法

    参考第3条中的排序

    C.Stream流

    1.思考

    计算字符串List中长度大于2的元素的数量

    分析:使用增强for循环

    a.代码冗余

    b.实现并行计算很麻烦

    c.代码无法及时传递程序员的意图 ,必须读完代码

    2.外部迭代

     forEach工作原理:代码底层使用Iterator进行迭代的,是串行化操作

    3.内部迭代

    4.Stream概述

    是用函数式编程方式在集合类上进行复杂操作的工具,更像一个高级版本的 Iterator

    原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作

    高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作

    Stream 会隐式地在内部进行遍历,做出相应的数据转换

    而和迭代器又不同的是,Stream 可以并行化操作

    借助于 Lambda 表达式,极大的提高编程效率和程序可读性

    5.常用操作

    a.forEach

    迭代集合中元素。接收一个 Lambda 表达式

    然后在 Stream 的每一个元素上执行该表达式

    此操作是及早求值方法

    b.collect(toList()) 

    由Stream 里的值生成一个列表,是一个及早求值操作

    很多Stream 操作都是惰性求值,因此调用Stream 一系列方法之后

    还需最后再调用一个类似collect 的及早求值方法返回集合

    开篇的例子:(再将符合要求的字符串放到一个新的集合里)

    使用filter过滤器:遍历数据并检查其中的元素

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建集合
    		List<String> list = new ArrayList<>();
     
    		// 添加元素
    		list.add("sdf");
    		list.add("a");
    		list.add("asdf");
    		list.add("d");
    		list.add("basdfgh");
     
    		// 统计长度大于2的
    		long count = list.stream().filter((s) -> s.length() > 2).count();
     
    		// 将符合要求的放入集合
    		List<String> list2 = list.stream().filter((s) -> s.length() > 2).collect(Collectors.toList());
     
    		System.out.println(count);
    		list2.forEach(System.out :: println);
    	}
     
    }

     

    6.map

    如果有一个函数可以将一种类型的值转换成另外一种类型

    map 操作就可以使用该函数,将一个流中的值转换成一个新的流

    需求:将字符串全转换成大写

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建集合
    		List<String> list = new ArrayList<>();
     
    		// 添加元素
    		list.add("sdf");
    		list.add("a");
    		list.add("asdf");
    		list.add("d");
    		list.add("basdfgh");
    		
    		// 转换为大写
    		List<String> list2 = list.stream().map((s) -> s.toUpperCase()).collect(Collectors.toList());
    		
    		list2.forEach(System.out :: println);
     
    	}
     
    }
    

     

    7.filter

    遍历数据并检查其中的元素。例如获取字符串List中以数字开始的字符集合

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 创建集合
    		List<String> list = new ArrayList<>();
     
    		// 添加元素
    		list.add("1sdf");
    		list.add("a");
    		list.add("2asdf");
    		list.add("d");
    		list.add("basdfgh");
     
    		// 获取数字开头的
    		List<String> list2 = list.stream().filter((s) -> Character.isDigit(s.charAt(0))).collect(Collectors.toList());
     
    		list2.forEach(System.out::println);
    	}
     
    }

     

    8.flatMap 

    可用Stream 替换值, 然后将多个Stream 连接成一个Stream

    map 操作可用一个新的值代替Stream 中的值

    若用户希望让map操作有点变化

    生成一个新的Stream 对象取而代之则flatMap适用

    假设有一个包含多个列表的流,现在希望得到所有数字的序列

    package org.xxxx.demo01;
     
    import java.util.Arrays;
    import java.util.stream.Stream;
     
    public class Demo01 {
    	public static void main(String[] args) {
     
    		Stream<?> flatMap = Stream.of(Arrays.asList("a", "b"), Arrays.asList(1, 2, 3)).flatMap((s) -> s.stream());
    		flatMap.forEach(System.out :: println);
    	}
     
    }

    统招全日制学历联系wx       YLWL9331

    9.max和min

    获取Stream中最大值或最小值,获取字符串List中长度最长的字符串长度

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		List<String> list = new ArrayList<>();
    		
    		list.add("abc");
    		list.add("ab");
    		list.add("abcd");
    		list.add("abcde");
    		
    		// 获取最大值
    		int max = list.stream().map((s) -> s.length()).max(Integer :: compareTo).get();
    		System.out.println(max);
    		
    		// 获取最小值,另一种方法
    		int min = list.stream().min(Comparator.comparing((s) -> s.length())).get().length();
    		System.out.println(min);
    	}
     
    }

     

    10.reduce

    通过指定的函数把stream中的多个元素汇聚为一个元素,min max等是它的特例

    格式:reduce(初始值,(r, i) -> r + i) 或者 reduce((r, i) -> r + "*" i)

    计算1~100的和

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		List<Long> list = new ArrayList<>();
    		
    		// 封装到集合
    		for (long i = 1; i <= 100; i++) {
    			list.add(i);
    		}
    		
    		// 计算
    		// reduce:参1,和的初始值
    		Long sum = list.stream().parallel().reduce(0L, (r, l) -> r + l);
    		
    		System.out.println(sum);
    	}
     
    

     

    11.练习

    a.获取Student集合中年龄小于20岁的集合中年龄最大的学生信息

    Student类省略

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		List<Student> list = new ArrayList<>();
     
    		Student s1 = new Student("张三", 21);
    		Student s2 = new Student("李四", 19);
    		Student s3 = new Student("王五", 18);
    		Student s4 = new Student("程六", 17);
    		Student s5 = new Student("赵七", 20);
     
    		list.add(s1);
    		list.add(s2);
    		list.add(s3);
    		list.add(s4);
    		list.add(s5);
     
    		// 筛选
    		Student student = list.stream().filter((s) -> s.getAge() < 20).max(Comparator.comparing((s) -> s.getAge()))
    				.get();
    		System.out.println(student);
    	}
     
    }

     b.查找集合中以a开头的字符的长度集合

    package org.xxxx.demo01;
     
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		List<Integer> list = Stream.of("abc", "b", "a", "abcd").filter((s) -> s.startsWith("a")).map((s) -> s.length())
    				.collect(Collectors.toList());
    		System.out.println(list);
    	}
     
    }

    D.并行API

    1.概述

    在Java7之前,并行处理数据基本是通过多线程来解决

    a.将数据分成部分

    b.给每个子部分分配一个子线程

    c.等待所有的子线程全部结束

    d.合并子线程

    这样的并行数据处理不稳定、易出错,在Java8中Stream接口应用分支/合并框架

    将一个数据内容分成多个数据块,并用不同的线程分别处理每个数据块流

    Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成

    而并行Stream则是在多个线程上同时执行

    并行是每个cpu运行一个程序

    2.使用方法

    通常编写并行代码很难而且容易出错,

    但使用 Stream API 无需编写一行多线程的代码

    就可以很方便地写出高性能的并发程序 

    a.调用Stream的parallel()方法

    b.调用Collection的parallelStream()方法

    c.parallel() 与 sequential() 可在并行流与顺序流之间切换

    package org.xxxx.demo01;
     
    import java.util.ArrayList;
    import java.util.List;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		List<Long> list = new ArrayList<>();
    		
    		// 封装到集合
    		for (long i = 1; i <= 100; i++) {
    			list.add(i);
    		}
    		
    		// 计算
    		// reduce:参1,和的初始值
    		Long sum = list.stream().parallel().reduce(0L, (r, l) -> r + l);
    		
    		System.out.println(sum);
    	}
     
    }

    统招全日制学历联系wx       YLWL9331

    3.使用并行的建议

    a.尽量使用基本类型的流  IntStream, LongStream, and DoubleStream

    b.有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst,依赖元素顺序的操作在并发流中是极其消耗性能的.findAny的性能就会好很多,因为不依赖顺序

    c.考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间, N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高

    d.数据量不大时使用并发流,性能得不到提升

    e.考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的

    f.传递给并行流的函数都是线程安全的,无副作用

    4.何时需要并行编程

    a.是否需要并行

    弄清楚你要解决的问题是什么,数据量有多大,计算的特点是什么

    并不是所有的问题都适合使用并发,比如当数据量不大时

    顺序执行往往比并行执行更快。

    准备线程池和其它相关资源也是需要时间的

    但当任务涉及到I/O操作并且任务之间不互相依赖时,那么适合并行化

    b.任务之间是否是独立的?是否会引起任何竞态条件

    如果任务之间是独立的

    并且代码中不涉及到对同一个对象的某个状态或者某个变量的更新操作

    那么就表明代码是可以被并行化的

    c.结果是否取决于任务的调用顺序

    由于在并行环境中任务的执行顺序是不确定的

    因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果

    E.Time日期时间

    1.旧的日期时间缺点

    a.非线程安全

     java.util.Date不是线程安全的

    因此开发者必须在使用日期处理并发性问题

    新的日期时间API是不可变的,并且没有setter方法

    b.设计不佳

    默认的开始日期从1900年,开始每月从1天从0开始,所以没有统一

    不直接使用方法操作日期。新的API提供了这样操作实用方法

    c. 困难的时区处理

    开发人员必须编写大量的代码来处理时区的问题

    新的API设计开发保持特定领域设计

    2.Instat

    a.概述

    时间戳,即时间轴上的一个点

    从元年1970-1-1 00:00:00到现在的nanosecond数

    b. Instant.now();获取当前时间

    package org.xxxx.demo01;
     
    import java.time.Instant;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 获取当前时间
    		Instant now = Instant.now();
    		
    		// T代表东西经0°经线区时:伦敦时间
    		System.out.println(now);// 2017-11-25T14:06:57.079Z
    	}
     
    }

     c.Instant.ofEpochMilli(new Date().getTime()); 旧日期转为新日期

    package org.xxxx.demo01;
     
    import java.time.Instant;
    import java.util.Date;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 获取当前时间
    		Instant instant = Instant.ofEpochMilli(new Date().getTime());
    		System.out.println(instant);
    	}
     
    }

    d. Instant.parse();日期文本格式转换为时间格式

    package org.xxxx.demo01;
     
    import java.time.Instant;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 获取当前时间
    		Instant instant = Instant.parse("1993-02-06T10:12:35Z");
    		System.out.println(instant);
    	}
     
    }
    

    3.LocalDate

    表示不带时区的日期,比如2014-01-14

    a.LocalDateTime.now();获取当前日期时间

    b.now.minusDays(2);将当前日期时间减去两天

    c.LocalDateTime.of(2016, 10, 23);构造一个指定日期时间的对象

    package org.xxxx.demo01;
     
    import java.time.LocalDate;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 当前时间
    		LocalDate now = LocalDate.now();
    		System.out.println(now);
    		
    		// 往前推两天
    		LocalDate date = now.minusDays(2);
    		System.out.println(date);
    		
    		// 制定一个日期
    		LocalDate localDate = LocalDate.of(1993, 2, 6);
    		System.out.println(localDate);
    	}
     
    }

     

    4.LocalTime

    表示不带时区的时间

    a. LocalTime.now();当前时间

    b. LocalTime.of(22, 33);确定的时间

    c.LocalTime.ofSecondOfDay(4503); 一天中的第4503秒

    package org.xxxx.demo01;
     
    import java.time.LocalTime;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 当前时间
    		LocalTime now = LocalTime.now();
    		System.out.println(now);
    		
    		// 22:33
    		LocalTime localTime = LocalTime.of(22, 33);
    		System.out.println(localTime);
    		
    		// 一天中的4503秒
    		LocalTime ofDay = LocalTime.ofSecondOfDay(4503);
    		System.out.println(ofDay);
    	}
     
    }

     

    5.LocalDateTim

     是LocalDate和LocalTime的组合体,表示的是不带时区的日期及时间

    a.LocalDateTime.now();当前时间

    b.localDateTime.plusHours(25).plusMinutes(3); 当前时间加上25小时3分钟

    c.LocalDateTime.of(2014, 1, 20, 3, 30, 20);转换

    package org.xxxx.demo01;
     
    import java.time.LocalDateTime;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 当前时间
    		LocalDateTime now = LocalDateTime.now();
    		System.out.println(now);
     
    		// 当前时间加上25小时3分钟
    		LocalDateTime plusMinutes = now.plusHours(25).plusMinutes(3);
    		System.out.println(plusMinutes);
     
    		// 转换
    		LocalDateTime of = LocalDateTime.of(1993, 2, 6, 11, 23, 30);
    		System.out.println(of);
    	}
     
    }

     

    6.ZoneDateTime

    含有时区信息的时间

    a.ZonedDateTime.now();获取包含时区的当前日期时间

    b.ZonedDateTime.of(LocalDateTime.of(2014, 1, 20, 3, 30, 20), ZoneId.of("+08"));创建时区的日期时间对象

    
    package org.xxxx.demo01;
     
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 当前时间
    		ZonedDateTime now = ZonedDateTime.now();
    		System.out.println(now);
     
    		// 创建时区的日期时间对象
    		ZonedDateTime of = ZonedDateTime.of(LocalDateTime.of(1993, 2, 6, 11, 23, 30), ZoneId.of("+08"));
    		System.out.println(of);
    	}
     
    }

     

    7.DateTimeFormatter

    格式化日期和解析日期格式字符串。DateTimeFormatter是不可变类

    a.格式化:日期对象转换为格式字符串

    package org.xxxx.demo01;
     
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 格式化
    		String time = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss").format(LocalDateTime.now());
    		System.out.println(time);
    	}
     
    }

     b.解析:格式字符串转换为日期对象

    package org.xxxx.demo01;
     
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		// 格式化
    		LocalDateTime parse = LocalDateTime.parse("2017.01.01 08:08:08", DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss"));
    		System.out.println(parse);
    	}
     
    }
    

    8. 遗留代码操作

    a.Date --> Instant

    b.Calendar-->Instant

    package org.xxxx.demo01;
     
    import java.time.Instant;
    import java.util.Calendar;
    import java.util.Date;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		Instant timestamp = new Date().toInstant(); 
    		
    		Instant instant = Calendar.getInstance().toInstant();
    		
    		System.out.println(timestamp);
    		System.out.println(instant);
    	}
     
    }
    

    F.其他

    1.默认方法

    接口和它的实现类保持兼容非常重要

    因为你开发的类库可能正在被多个开发者广泛的使用着

    若上层接口需要做改变的时候(如增加新的空方法)

    下层接口就需要实现新增的方法,在某些情况下,变得不灵活

    因此:使用默认方法

    接口中定义的default修饰的有具体实现的方法,是给接口增加的公用方法

    是对象方法,需要使用对象来进行访问

    添加默认方法,对以前代码不会有影响,避免其实现类都做出相应的改动

    且default方法通常使用已经实现类已存在的方法进行定义

    2.问:

    如果一个接口中定义一个默认方法

    另外一个父类或接口中定义同名方法(不管是否default)时候,子类如何处理

    a.选择父类方法。当父类提供具体实现时,接口同名同参方法被忽略

    b.覆盖父类方法。当一个父接口提供默认方法,另一个父接口提供同名同参方法时候

    3.静态方法

    接口中定义的static修饰的有具体实现的方法

    类似于default方法,但在实现类中不能覆盖(override)该方法

    接口的static方法只对接口里的方法可见,和其他静态方法类似

    可以使用接口名来调用接口中的静态方法

    接口的static方法有利于提供通用的方法,比如判空检查,集合排序等

    可以移除工具类中方法到接口static方法中

    例如:sList.sort((s,t)->Integer.compare(s.length(), t.length()));

    转换为静态方法:sList.sort(Comparator.comparing(String::length));

    4.String新增join(字符串集合或数组)方法

    使用指定连接符连接多个字符串

    package org.xxxx.demo01;
     
    public class Demo01 {
    	public static void main(String[] args) {
    		String[] str = {"a", "b", "c"};
    		
    		String join = String.join("-", str);
    		System.out.println(join);
    	}
    }
    

     

    5.可能错过的Java7特性

    try--with-resources

    在 JDK 7 之前,文件资源需要手动finally中关闭

    try-with-resources 能够很容易地关闭在 try-catch 语句块中使用的资源

    所谓的资源(resource)是指在程序完成后,必须关闭的对象

    try-with-resources 语句确保了每个资源在语句结束时关闭

    所有实现了 java.lang.AutoCloseable 接口的对象可以使用作为资源

    在try( ...)里声明的资源,会在try-catch代码块结束后自动关闭掉

     

     

     

    展开全文
  • JAVA8十大新特性详解

    万次阅读 2018-05-09 09:50:18
    转自https://blog.csdn.net/Mr_LI3306/article/details/80239339一、接口的默认方法Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:复制代码代码...

    转自https://blog.csdn.net/Mr_LI3306/article/details/80239339

    一、接口的默认方法

    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

    复制代码代码如下:                                                                                                             

    interface Formula {
        double calculate(int a);

        default double sqrt(int a) {
            return Math.sqrt(a);
        }
    }


    Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
    复制代码代码如下:

    Formula formula = new Formula() {
        @Override
        public double calculate(int a) {
            return sqrt(a * 100);
        }
    };

    formula.calculate(100);     // 100.0
    formula.sqrt(16);           // 4.0


    文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。

    译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。

    二、Lambda 表达式

    首先看看在老版本的Java中是如何排列字符串的:

    复制代码代码如下:

    List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

    Collections.sort(names, new Comparator<String>() {
        @Override
        public int compare(String a, String b) {
            return b.compareTo(a);
        }
    });


    只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。

    在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

    复制代码代码如下:

    Collections.sort(names, (String a, String b) -> {
        return b.compareTo(a);
    });

    看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:
    复制代码代码如下:

    Collections.sort(names, (String a, String b) -> b.compareTo(a));

    对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:
    复制代码代码如下:

    Collections.sort(names, (a, b) -> b.compareTo(a));

    Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:

    三、函数式接口

    Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

    我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

    示例如下:

    复制代码代码如下:

    @FunctionalInterface
    interface Converter<F, T> {
        T convert(F from);
    }
    Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
    Integer converted = converter.convert("123");
    System.out.println(converted);    // 123

    需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。

    译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。

    四、方法与构造函数引用

    前一节中的代码还可以通过静态方法引用来表示:

    复制代码代码如下:

    Converter<String, Integer> converter = Integer::valueOf;
    Integer converted = converter.convert("123");
    System.out.println(converted);   // 123

    Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
    复制代码代码如下:

     converter = something::startsWith;
    String converted = converter.convert("Java");
    System.out.println(converted);    // "J"

    接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
    复制代码代码如下:

    class Person {
        String firstName;
        String lastName;

        Person() {}

        Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }


    接下来我们指定一个用来创建Person对象的对象工厂接口:
    复制代码代码如下:

    interface PersonFactory<P extends Person> {
        P create(String firstName, String lastName);
    }

    这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
    复制代码代码如下:

    PersonFactory<Person> personFactory = Person::new;
    Person person = personFactory.create("Peter", "Parker");

    我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

    五、Lambda 作用域

    在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

    六、访问局部变量

    我们可以直接在lambda表达式中访问外层的局部变量:

    复制代码代码如下:

    final int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3


    但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
    复制代码代码如下:

    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3


    不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
    复制代码代码如下:

    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);
    num = 3;

    在lambda表达式中试图修改num同样是不允许的。

    七、访问对象字段与静态变量

    和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

    复制代码代码如下:
    class Lambda4 {
        static int outerStaticNum;
        int outerNum;

        void testScopes() {
            Converter<Integer, String> stringConverter1 = (from) -> {
                outerNum = 23;
                return String.valueOf(from);
            };

            Converter<Integer, String> stringConverter2 = (from) -> {
                outerStaticNum = 72;
                return String.valueOf(from);
            };
        }
    }



    八、访问接口的默认方法

    还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。
    Lambda表达式中是无法访问到默认方法的,以下代码将无法编译:
    复制代码代码如下:

    Formula formula = (a) -> sqrt( a * 100);
    Built-in Functional Interfaces

    JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
    Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

    Predicate接口

    Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):

    复制代码代码如下:

    Predicate<String> predicate = (s) -> s.length() > 0;

    predicate.test("foo");              // true
    predicate.negate().test("foo");     // false

    Predicate<Boolean> nonNull = Objects::nonNull;
    Predicate<Boolean> isNull = Objects::isNull;

    Predicate<String> isEmpty = String::isEmpty;
    Predicate<String> isNotEmpty = isEmpty.negate();



    Function 接口

    Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):

    复制代码代码如下:

    Function<String, Integer> toInteger = Integer::valueOf;
    Function<String, String> backToString = toInteger.andThen(String::valueOf);

    backToString.apply("123");     // "123"



    Supplier 接口

    Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
    复制代码代码如下:

    Supplier<Person> personSupplier = Person::new;
    personSupplier.get();   // new Person


    Consumer 接口

    Consumer 接口表示执行在单个参数上的操作。
    复制代码代码如下:

    Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
    greeter.accept(new Person("Luke", "Skywalker"));


    Comparator 接口

    Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
    复制代码代码如下:

    Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

    Person p1 = new Person("John", "Doe");
    Person p2 = new Person("Alice", "Wonderland");

    comparator.compare(p1, p2);             // > 0
    comparator.reversed().compare(p1, p2);  // < 0



    Optional 接口

    Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:

    Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。

    复制代码代码如下:

    Optional<String> optional = Optional.of("bam");

    optional.isPresent();           // true
    optional.get();                 // "bam"
    optional.orElse("fallback");    // "bam"

    optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"



    Stream 接口

    java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

    首先看看Stream是怎么用,首先创建实例代码的用到的数据List:

    复制代码代码如下:

    List<String> stringCollection = new ArrayList<>();
    stringCollection.add("ddd2");
    stringCollection.add("aaa2");
    stringCollection.add("bbb1");
    stringCollection.add("aaa1");
    stringCollection.add("bbb3");
    stringCollection.add("ccc");
    stringCollection.add("bbb2");
    stringCollection.add("ddd1");

    Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:

    Filter 过滤

    过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

    复制代码代码如下:

    stringCollection
        .stream()
        .filter((s) -> s.startsWith("a"))
        .forEach(System.out::println);

    // "aaa2", "aaa1"



    Sort 排序

    排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。

    复制代码代码如下:

    stringCollection
        .stream()
        .sorted()
        .filter((s) -> s.startsWith("a"))
        .forEach(System.out::println);

    // "aaa1", "aaa2"


    需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
    复制代码代码如下:

    System.out.println(stringCollection);
    // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1


    Map 映射

    中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
    复制代码代码如下:

    stringCollection
        .stream()
        .map(String::toUpperCase)
        .sorted((a, b) -> b.compareTo(a))
        .forEach(System.out::println);

    // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"



    Match 匹配

    Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

    复制代码代码如下:

    boolean anyStartsWithA = 
        stringCollection
            .stream()
            .anyMatch((s) -> s.startsWith("a"));

    System.out.println(anyStartsWithA);      // true

    boolean allStartsWithA = 
        stringCollection
            .stream()
            .allMatch((s) -> s.startsWith("a"));

    System.out.println(allStartsWithA);      // false

    boolean noneStartsWithZ = 
        stringCollection
            .stream()
            .noneMatch((s) -> s.startsWith("z"));

    System.out.println(noneStartsWithZ);      // true

    Count 计数

    计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

    复制代码代码如下:

    long startsWithB = 
        stringCollection
            .stream()
            .filter((s) -> s.startsWith("b"))
            .count();

    System.out.println(startsWithB);    // 3



    Reduce 规约

    这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:

    复制代码代码如下:

    Optional<String> reduced =
        stringCollection
            .stream()
            .sorted()
            .reduce((s1, s2) -> s1 + "#" + s2);

    reduced.ifPresent(System.out::println);
    // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"



    并行Streams

    前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

    下面的例子展示了是如何通过并行Stream来提升性能:

    首先我们创建一个没有重复元素的大表:

    复制代码代码如下:

    int max = 1000000;
    List<String> values = new ArrayList<>(max);
    for (int i = 0; i < max; i++) {
        UUID uuid = UUID.randomUUID();
        values.add(uuid.toString());
    }

    然后我们计算一下排序这个Stream要耗时多久,
    串行排序:
    复制代码代码如下:

    long t0 = System.nanoTime();

    long count = values.stream().sorted().count();
    System.out.println(count);

    long t1 = System.nanoTime();

    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
    System.out.println(String.format("sequential sort took: %d ms", millis));

    // 串行耗时: 899 ms
    并行排序:

    复制代码代码如下:

    long t0 = System.nanoTime();

    long count = values.parallelStream().sorted().count();
    System.out.println(count);

    long t1 = System.nanoTime();

    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
    System.out.println(String.format("parallel sort took: %d ms", millis));

    // 并行排序耗时: 472 ms
    上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。

    Map

    前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。

    复制代码代码如下:

    Map<Integer, String> map = new HashMap<>();

    for (int i = 0; i < 10; i++) {
        map.putIfAbsent(i, "val" + i);
    }

    map.forEach((id, val) -> System.out.println(val));
    以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。

    下面的例子展示了map上的其他有用的函数:

    复制代码代码如下:

    map.computeIfPresent(3, (num, val) -> val + num);
    map.get(3);             // val33

    map.computeIfPresent(9, (num, val) -> null);
    map.containsKey(9);     // false

    map.computeIfAbsent(23, num -> "val" + num);
    map.containsKey(23);    // true

    map.computeIfAbsent(3, num -> "bam");
    map.get(3);             // val33


    接下来展示如何在Map里删除一个键值全都匹配的项:
    复制代码代码如下:

    map.remove(3, "val3");
    map.get(3);             // val33

    map.remove(3, "val33");
    map.get(3);             // null


    另外一个有用的方法:
    复制代码代码如下:

    map.getOrDefault(42, "not found");  // not found

    对Map的元素做合并也变得很容易了:
    复制代码代码如下:

    map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
    map.get(9);             // val9

    map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
    map.get(9);             // val9concat


    Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

    九、Date API

    Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:

    Clock 时钟

    Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

    复制代码代码如下:

    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();

    Instant instant = clock.instant();
    Date legacyDate = Date.from(instant);   // legacy java.util.Date



    Timezones 时区

    在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。

    复制代码代码如下:

    System.out.println(ZoneId.getAvailableZoneIds());
    // prints all available timezone ids

    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    System.out.println(zone1.getRules());
    System.out.println(zone2.getRules());

    // ZoneRules[currentStandardOffset=+01:00]
    // ZoneRules[currentStandardOffset=-03:00]



    LocalTime 本地时间

    LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:

    复制代码代码如下:

    LocalTime now1 = LocalTime.now(zone1);
    LocalTime now2 = LocalTime.now(zone2);

    System.out.println(now1.isBefore(now2));  // false

    long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
    long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

    System.out.println(hoursBetween);       // -3
    System.out.println(minutesBetween);     // -239


    LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
    复制代码代码如下:

    LocalTime late = LocalTime.of(23, 59, 59);
    System.out.println(late);       // 23:59:59

    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedTime(FormatStyle.SHORT)
            .withLocale(Locale.GERMAN);

    LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
    System.out.println(leetTime);   // 13:37


    LocalDate 本地日期

    LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

    复制代码代码如下:

    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
    LocalDate yesterday = tomorrow.minusDays(2);

    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();


    System.out.println(dayOfWeek);    // FRIDAY
    从字符串解析一个LocalDate类型和解析LocalTime一样简单:
    复制代码代码如下:

    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedDate(FormatStyle.MEDIUM)
            .withLocale(Locale.GERMAN);

    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
    System.out.println(xmas);   // 2014-12-24



    LocalDateTime 本地日期时间

    LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

    复制代码代码如下:

    LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

    DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
    System.out.println(dayOfWeek);      // WEDNESDAY

    Month month = sylvester.getMonth();
    System.out.println(month);          // DECEMBER

    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
    System.out.println(minuteOfDay);    // 1439


    只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
    复制代码代码如下:

    Instant instant = sylvester
            .atZone(ZoneId.systemDefault())
            .toInstant();

    Date legacyDate = Date.from(instant);
    System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014


    格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
    复制代码代码如下:

    DateTimeFormatter formatter =
        DateTimeFormatter
            .ofPattern("MMM dd, yyyy - HH:mm");

    LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
    String string = formatter.format(parsed);
    System.out.println(string);     // Nov 03, 2014 - 07:13


    和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
    关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html

    十、Annotation 注解

    在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。
    首先定义一个包装类Hints注解用来放置一组具体的Hint注解:

    复制代码代码如下:

    @interface Hints {
        Hint[] value();
    }

    @Repeatable(Hints.class)
    @interface Hint {
        String value();
    }


    Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。

    例 1: 使用包装类当容器来存多个注解(老方法)

    复制代码代码如下:

    @Hints({@Hint("hint1"), @Hint("hint2")})
    class Person {}

    例 2:使用多重注解(新方法)
    复制代码代码如下:

    @Hint("hint1")
    @Hint("hint2")
    class Person {}

    第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:
    复制代码代码如下:

    Hint hint = Person.class.getAnnotation(Hint.class);
    System.out.println(hint);                   // null

    Hints hints1 = Person.class.getAnnotation(Hints.class);
    System.out.println(hints1.value().length);  // 2

    Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
    System.out.println(hints2.length);          // 2


    即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。
    另外Java 8的注解还增加到两种新的target上了:
    复制代码代码如下:

    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    @interface MyAnnotation {}

    关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如Arrays.parallelSort, StampedLock和CompletableFuture等等。
    展开全文
  • Java8 新特性学习总结

    千次阅读 2017-05-21 22:40:02
    Java8新特性

    Lambda 表达式

    Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
    Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
    - 左侧:指定了 Lambda 表达式需要的所有参数
    - 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。

    Lambda 表达式语法

    语法格式一:无参,无返回值,Lambda 体只需一条语句

    Runnable runnable = () -> System.out.println("hello Lambda");
    

    语法格式二:Lambda 需要一个参数

    Consumer<String> con = (t) -> System.out.println(t);
    

    语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略

    Consumer<String> con = t -> System.out.println(t);
    

    语法格式四:Lambda 需要两个参数,并且有返回值

    Comparator<Integer> comparator = (x,y) -> {
            System.out.println("相加结果是:"+(x+y));
            return Integer.compare(x,y);
        };
    

    语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略

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

    语法格式六:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

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

    类型推断

    上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

        /**
         * Lambda表达式基础语法,Java8中引入新的操作符"->"
         * Lambda表达式分为左右两部分:
         *  左:Lambda表达式的参数列表
         *  右:Lambda表达式中所需执行的功能,即Lambda体
         */
        public class LambdaDemo {
    
            @Test
            public void test1(){
                Runnable r = new Runnable() {
    
                    @Override
                    public void run() {
                        System.out.println("hello world");
                    }
                };
                r.run();
                System.out.println("------------");
    
                //Lambda表达式
                Runnable runnable = () -> System.out.println("hello Lambda");
                runnable.run();
            }
    
            @Test
            public void test2(){
                Consumer<String> consumer = new Consumer<String>() {
    
                    @Override
                    public void accept(String t) {
                        System.out.println(t);
                    }
                };
                consumer.accept("hello consumer !");
                System.out.println("---------------");
    
                //Lambda表达式
        //      Consumer<String> con = (t) -> System.out.println(t);
                Consumer<String> con = t -> System.out.println(t);
                con.accept("hello Lambda !");
            }
    
            @Test
            public void test3(){
                Comparator<Integer> comparator = (x,y) -> {
                    System.out.println("相加结果是:"+(x+y));
                    return Integer.compare(x,y);
                };
    
                int compare = comparator.compare(1, 2);
                System.out.println(compare);
            }
    
            @Test
            public void test4(){
                Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
    
                int compare = comparator.compare(1, 2);
                System.out.println(compare);
            }
    
            @Test
            public void test5(){
                show(new HashMap<>());
            }
    
            public void show(Map<String,Integer> map){
    
            }
        }
    

    函数式接口

    • 只包含一个抽象方法的接口,称为函数式接口。
    • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
    • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

    Java 内置四大核心函数式接口

        /**
         * 函数式接口
         */
        public class TestFunction {
    
            //Consumer<T>消费型接口
            @Test
            public void test1(){
                cost(8888, (m) -> System.out.println("共消费:" + m + "元"));
            }
    
            public void cost(double money,Consumer<Double> con){
                con.accept(money);
            }
    
            //Supplier<T> 供给型接口
            @Test
            public void test2(){
                List<Integer> list = getNumList(8, () -> (int)(Math.random() * 100));
                for (Integer integer : list) {
                    System.out.println(integer);
                }
            }
    
            //产生指定数量的整数,放入集合中
            public List<Integer> getNumList(int num,Supplier<Integer> sup){
                List<Integer> list = new ArrayList<Integer>();
    
                for (int i = 0; i < num; i++) {
                    Integer n = sup.get();
                    list.add(n);
                }
    
                return list;
            }
    
            //Function<T,R> 函数型接口
            @Test
            public void test3(){
                String string = strHandler(" 函数型接口测试 ", (str) -> str.trim().substring(0, 5));
                System.out.println(string);
            }
    
            //用于处理字符串
            public String strHandler(String str,Function<String, String> fun){
                return fun.apply(str);
            }
    
            //Predicate<T> 断言型接口
            @Test
            public void test4(){
                List<String> list = Arrays.asList("hello","Lambda","ok");
                List<String> strList = filterStr(list, (s) -> s.length() > 3);
                for (String string : strList) {
                    System.out.println(string);
                }
            }
    
            //将满足条件的字符串,放入集合中
            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;
            }
        }
    

    其他接口

    方法引用与构造器引用

    方法引用

    当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。

    如下三种主要使用情况:
    - 对象::实例方法
    - 类::静态方法
    - 类::实例方法

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

    构造器引用

    与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
    格式: ClassName::new

    数组引用

    格式: type[] :: new

        /**
         * 方法引用:若Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用"
         */
        public class TestMethodRef {
    
            @Test
            public void test1(){
                Consumer<String> con = (x) -> System.out.println(x);
                con.accept("shuai");
    
                //方法引用,对象::实例方法名
                Consumer<String> consumer = System.out::println;
                consumer.accept("test");
            }
    
            @Test
            public void test2(){
                Person person = new Person();
                Supplier<String> supplier = () -> person.getName();
                String str = supplier.get();
                System.err.println(str);
    
                //方法引用,对象::实例方法名
                Supplier<Integer> sup = person::getAge;
                Integer age = sup.get();
                System.out.println(age);
            }
    
            //类::静态方法名
            @Test
            public void test3(){
                Comparator<Integer> com = (x,y) -> Integer.compare(x, y);
                //使用前提,compare的参数和返回值与Comparator一致
                Comparator<Integer> comparator = Integer :: compare;
            }
    
            //类::实例方法名
            @Test
            public void test4(){
                BiPredicate<String, String> bp = (x,y) -> x.equals(y);
                //使用条件:第一个参数是实例方法调用者,第二个参数是实例方法的参数
                BiPredicate<String, String> biPredicate = String :: equals;
            }
    
            //构造器引用
            @Test
            public void test5(){
                Supplier<Person> sup = () -> new Person();
    
                //构造器引用方式
                Supplier<Person> supplier = Person :: new;
                Person person = supplier.get();
                System.out.println(person);
            }
    
            //构造器引用
            @Test
            public void test6(){
                Function<Integer, Person> fun = (x) -> new Person(x);
    
                Function<Integer, Person> function = Person :: new;
                Person person = function.apply(2);
                System.out.println(person);
                System.out.println("--------------------");
    
                BiFunction<String, Integer, Person> biFunction = Person :: new;
                Person person2 = biFunction.apply("张三", 23);
                System.out.println(person2);
            }
    
            //数组引用
            @Test
            public void test7(){
                Function<Integer, String[]> fun = (x) -> new String[x]; 
                String[] strs = fun.apply(8);
                System.out.println(strs.length);
    
                Function<Integer, String[]> function = String[] :: new;
                String[] strArray = function.apply(6);
                System.out.println(strArray.length);
            }
        }
    

    Stream API

    Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

    Stream

    是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
    “集合讲的是数据,流讲的是计算!”
    1. Stream 自己不会存储元素。
    2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
    3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

    Stream 的操作三个步骤

    • 创建 Stream
      一个数据源(如:集合、数组),获取一个流
    • 中间操作
      一个中间操作链,对数据源的数据进行处理
    • 终止操作(终端操作)
      一个终止操作,执行中间操作链,并产生结果

    创建 Stream

    Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
    - default Stream stream() : 返回一个顺序流
    - default Stream parallelStream() : 返回一个并行流

    Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
    - static Stream stream(T[] array): 返回一个流

    可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
    - public static Stream of(T… values) : 返回一个流

    可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。
    - 迭代
    public static Stream iterate(final T seed, final UnaryOperator f)
    - 生成
    public static Stream generate(Supplier s) :

        //创建Stream
        @Test
        public void test1(){
            //1.可以通过Collection系列集合提供的stream() 或parallelStream()
            List<String> list = new ArrayList<>();
            Stream<String> stream = list.stream();
    
            //2.通过Arrays中静态方法 stream() 获取数组流
            Person[] persons = new Person[10];
            Stream<Person> stream2 = Arrays.stream(persons);
    
            //3.通过Stream类中的静态方法 of()
            Stream<String> stream3 = Stream.of("a","b","c");
    
            //4.创建无限流
            //迭代
            Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
            stream4.limit(8).forEach(System.out :: println);
    
            //生成
            Stream.generate(() -> Math.random()).limit(6)
                .forEach(System.out :: println);
        }       
    

    Stream 的中间操作

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

    筛选与切片

    映射

    排序

        /**
         * Stream API的中间操作
         */
        public class TestSteamAPI2 {
    
            List<Person> persons = Arrays.asList(
                    new Person(2, "钱四", 24),
                    new Person(1, "张三", 33),
                    new Person(2, "李四", 24),
                    new Person(3, "王五", 65),
                    new Person(4, "赵六", 26),
                    new Person(4, "赵六", 26),
                    new Person(5, "陈七", 27)
            );
    
            //内部迭代,由Stream API完成
            @Test
            public void test1(){
                //中间操作,不会执行任何操作
                Stream<Person> stream = persons.stream()
                                            .filter((e) -> {
                                                System.out.println("Stream的中间操作");
                                                return e.getAge() > 25;
                                            });
                //终止操作,一次性执行全部内容,即"惰性求值"
                stream.forEach(System.out :: println);
            }
    
            //外部迭代
            @Test
            public void test2(){
                Iterator<Person> iterator = persons.iterator();
                while (iterator.hasNext()) {
                    System.out.println(iterator.next());
                }
            }
    
            //limit,截断
            @Test
            public void test3(){
                persons.stream()
                    .filter((e) -> {
                        System.out.println("迭代操作"); //短路
                        return e.getAge() > 24;
                    })
                    .limit(2)
                    .forEach(System.out :: println);
            }
    
            //跳过skip,distinct去重(要重写equals和hashcode)
            @Test
            public void test4(){
                persons.stream()
                        .filter((e) -> e.getAge() > 23)
                        .skip(2)
                        .distinct()
                        .forEach(System.out :: println);
            }
    
            //映射
            @Test
            public void test5(){
                List<String> list = Arrays.asList("a","bb","c","d","e");
    
                list.stream().map((str) -> str.toUpperCase())
                    .forEach(System.out :: println);
                System.out.println("---------------");
    
                persons.stream().map((Person :: getName)).forEach(System.out :: println);
                System.out.println("---------------");
    
                Stream<Stream<Character>> stream = list.stream()
                    .map(TestSteamAPI2 :: filterCharacter);
    
                stream.forEach((s) -> {
                    s.forEach(System.out :: println);
                });
                System.out.println("-----------------");
    
                //flatMap
                Stream<Character> stream2 = list.stream()
                    .flatMap(TestSteamAPI2 :: filterCharacter);
                stream2.forEach(System.out :: println);
            }
    
            //处理字符串
            public static Stream<Character> filterCharacter(String str){
                List<Character> list = new ArrayList<>();
    
                for (Character character : str.toCharArray()) {
                    list.add(character);
                }
                return list.stream();
            }
    
            //排序
            @Test
            public void test6(){
                List<String> list = Arrays.asList("bb","c","aa","ee","ddd");
    
                list.stream()
                    .sorted() //自然排序
                    .forEach(System.out :: println);
                System.out.println("------------");
    
                persons.stream()
                        .sorted((p1,p2) -> {
                            if (p1.getAge() == p2.getAge()) {
                                return p1.getName().compareTo(p2.getName());
                            } else {
                                return p1.getAge() - p2.getAge();
                            }
                        }).forEach(System.out :: println);
    
            }
    
        }
    

    Stream 的终止操作

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

    查找与匹配

    归约



    map 和 reduce 的连接通常称为 map-reduce 模式。

    收集


    Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。

        /**
         * Stream API的终止操作
         */
        public class TestSteamAPI3 {
    
            List<Person> persons = Arrays.asList(
                    new Person(2, "钱四", 24,Status.YOUNG),
                    new Person(1, "张三", 23,Status.YOUNG),
                    new Person(2, "李四", 24,Status.YOUNG),
                    new Person(3, "王五", 65,Status.OLD),
                    new Person(4, "赵六", 26,Status.MIDDLE),
                    new Person(4, "赵六", 56,Status.OLD),
                    new Person(5, "陈七", 27,Status.MIDDLE)
            );
    
            //查找与匹配
            @Test
            public void test1(){
                boolean b = persons.stream()
                                    .allMatch((e) -> e.getStatus().equals(Status.YOUNG));
                System.out.println(b);
    
                boolean b2 = persons.stream()
                        .anyMatch((e) -> e.getStatus().equals(Status.YOUNG));
                System.out.println(b2);
    
                boolean b3 = persons.stream()
                        .noneMatch((e) -> e.getStatus().equals(Status.MIDDLE));
                System.out.println(b3);
    
                Optional<Person> op = persons.stream()
                        .sorted((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge()))
                        .findFirst();
                System.out.println(op.get());
    
                Optional<Person> optional = persons.stream()
                        .filter((e) -> e.getStatus().equals(Status.OLD))
                        .findAny();
                System.out.println(optional.get());
            }
    
            //最大,最小
            @Test
            public void test2(){
                long count = persons.stream()
                        .count();
                System.out.println(count);
    
                Optional<Person> optional = persons.stream()
                        .max((e1,e2) -> Integer.compare(e1.getId(), e2.getId()));
                System.out.println(optional.get());
    
                Optional<Integer> op = persons.stream()
                        .map(Person :: getAge)
                        .min(Integer :: compare);
                System.out.println(op.get());
            }
    
            //归约
            @Test
            public void test3(){
                List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8);
    
                Integer sum = list.stream()
                    .reduce(0, (x,y) -> x + y);
                System.out.println(sum);
                System.out.println("------------");
    
                Optional<Integer> optional = persons.stream()
                        .map(Person :: getAge)
                        .reduce(Integer :: sum);
                System.out.println(optional.get());
            }
    
            //收集
            @Test
            public void test4(){
                List<String> list = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toList());
                list.forEach(System.out :: println);
                System.out.println("------------");
    
                Set<String> set = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toSet());
                set.forEach(System.out :: println);
                System.out.println("------------");
    
                HashSet<String> hashSet = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toCollection(HashSet :: new));
                hashSet.forEach(System.out :: println);
            }
    
            @Test
            public void test5(){
                Long count = persons.stream()
                        .collect(Collectors.counting());
                System.out.println("总人数="+count);
                System.out.println("----------------");
    
                //平均值
                Double avg = persons.stream()
                        .collect(Collectors.averagingInt(Person :: getAge));
                System.out.println("平均年龄="+avg);
                System.out.println("---------------");
    
                //总和
                Integer sum = persons.stream()
                        .collect(Collectors.summingInt(Person :: getAge));
                System.out.println("年龄总和="+sum);
                System.out.println("----------------");
    
                //最大值
                Optional<Person> max = persons.stream()
                        .collect(Collectors.maxBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
                System.out.println("最大年龄是"+max.get());
                System.out.println("----------------");
    
                //最小值
                Optional<Person> min = persons.stream()
                        .collect(Collectors.minBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
                System.out.println("最小年龄是"+min.get());
            }
    
            //分组
            @Test
            public void test6(){
                Map<Status, List<Person>> map = persons.stream()
                        .collect(Collectors.groupingBy(Person :: getStatus));//根据年龄层分组
                System.out.println(map);
            }
    
            //多级分组
            @Test
            public void test7(){
                Map<Status, Map<String, List<Person>>> map = persons.stream()
                        .collect(Collectors.groupingBy(Person :: getStatus ,Collectors.groupingBy((e) -> {
                            if (e.getId()%2 == 1) {
                                return "单号";
                            } else {
                                return "双号";
                            } 
                        })));
                System.out.println(map);
            }
    
            //分区
            @Test
            public void test8(){
                Map<Boolean, List<Person>> map = persons.stream()
                        .collect(Collectors.partitioningBy((e) -> e.getAge() > 30));
                System.out.println(map);
            }
    
            //IntSummaryStatistics
            @Test
            public void test9(){
                IntSummaryStatistics iss = persons.stream()
                        .collect(Collectors.summarizingInt(Person :: getAge));
                System.out.println(iss.getSum());
                System.out.println(iss.getAverage());
                System.out.println(iss.getMax());
            }
    
            @Test
            public void test10(){
                String str = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.joining(",","人员名单:","等"));
                System.out.println(str);
            }
        }
    

    并行流与串行流

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
    Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换。

        /**
         * FrokJoin框架
         */
        public class ForkJoinCalculate extends RecursiveTask<Long>{
            private static final long serialVersionUID = 1L;
    
            private long start;
            private long end;
    
            private static final long THRESHOLD = 10000;
    
            public ForkJoinCalculate() {
    
            }
    
            public ForkJoinCalculate(long start, long end) {
                this.start = start;
                this.end = end;
            }
    
    
            @Override
            protected Long compute() {
                long length = end - start ;
                if (length <= THRESHOLD) {
                    long sum = 0;
                    for (long i = start; i <= end; i++) {
                        sum += i;
                    }
                    return sum;
                }else {
                    long middle = (start + end) / 2; 
                    ForkJoinCalculate left = new ForkJoinCalculate();
                    left.fork();//拆分子任务,同时压入线程队列
    
                    ForkJoinCalculate right = new ForkJoinCalculate();
                    right.fork();
                    return left.join() + right.join();
                }
            }
    
        }
    

    测试并行流

        public class TestForkJoin {
    
            /**
             * FrokJoin框架
             */
            @Test
            public void test1(){
                Instant start = Instant.now();
    
                ForkJoinPool pool = new ForkJoinPool();
                ForkJoinTask<Long> task = new ForkJoinCalculate(0,10000000000L);
                Long sum = pool.invoke(task);
                System.out.println(sum);
    
                Instant end = Instant.now();
                System.out.println(Duration.between(start,end).toMillis());
            }
    
            /**
             * for循环
             */
            @Test
            public void test2(){
                Instant start = Instant.now();
                long sum = 0L;
    
                for (long i = 0; i <= 10000000000L; i++) {
                    sum += i;
                }
                System.out.println(sum);
    
                Instant end = Instant.now();
                System.out.println(Duration.between(start, end).toMillis());
            }
    
            /**
             * Java8并行流
             */
            @Test
            public void test3(){
                Instant start = Instant.now();
                LongStream.rangeClosed(0, 10000000000L)
                            .parallel()
                            .reduce(0,Long :: sum);
                Instant end = Instant.now();
                System.out.println(Duration.between(start, end).toMillis());
            }
        }
    

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

    接口中的默认方法

    Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。
    接口默认方法的”类优先”原则
    若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
    - 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
    - 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。

    接口中的静态方法

    Java8 中,接口中允许添加静态方法。

        public interface MyInterface {
    
            default String getName(){
                return "接口测试";
            }
    
            public static void show(){
                System.out.println("接口中的静态方法");
            }
        }
    

    新时间日期 API

    LocalDate、LocalTime、LocalDateTime

    LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

    传统写法:

        public class DateFormatThreadLocal {
    
            private static final ThreadLocal<DateFormat> tl = new ThreadLocal<DateFormat>(){
                protected DateFormat initialValue() {
                    return new SimpleDateFormat("yyyyMMdd");
                }
            };
    
            public static Date convert(String source) throws ParseException{
                return tl.get().parse(source);
            }
        }       
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
    //      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    
            Callable<Date> callable = new Callable<Date>() {
    
                @Override
                public Date call() throws Exception {
    //              return sdf.parse("20170521");
                    return DateFormatThreadLocal.convert("20170521");
                }
    
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<Date>> results = new ArrayList<>();
            for (int i = 0; i < 8; i++) {
                results.add(pool.submit(callable));
            }
            for (Future<Date> future : results) {
                System.out.println(future.get());
            }
    
            //关闭资源
            pool.shutdown();
        }
    

    新特性:

        public static void main(String[] args) throws InterruptedException, ExecutionException {
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
            Callable<LocalDate> callable = new Callable<LocalDate>() {
    
                @Override
                public LocalDate call() throws Exception {
                    return LocalDate.parse("20170521",dtf);
                }
    
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<LocalDate>> results = new ArrayList<>();
            for (int i = 0; i < 8; i++) {
                results.add(pool.submit(callable));
            }
            for (Future<LocalDate> future : results) {
                System.out.println(future.get());
            }
    
            //关闭资源
            pool.shutdown();
    }
    

    LocalDate LocalTime LocalDateTime

        //1.LocalDate LocalTime LocalDateTime
        @Test
        public void test1(){
            LocalDateTime ldt = LocalDateTime.now();
            System.out.println(ldt);
    
            LocalDateTime ldt2 = LocalDateTime.of(2017, 05, 21, 21, 43, 55, 33);
            System.out.println(ldt2);
    
            LocalDateTime ldt3 = ldt.plusYears(3);
            System.out.println(ldt3);
    
            LocalDateTime ldt4 = ldt.minusMonths(5);
            System.out.println(ldt4);
        }
    

    Instant 时间戳

    用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算。

        //2.Instant
        @Test
        public void test2(){
            Instant now = Instant.now();
            System.out.println(now);
    
            OffsetDateTime atOffset = now.atOffset(ZoneOffset.ofHours(6));
            System.out.println(atOffset);
    
            Instant ins = Instant.ofEpochSecond(60);
            System.out.println(ins);
        }
    

    Duration 和 Period

    • Duration:用于计算两个“时间”间隔
    • Period:用于计算两个“日期”间隔

      //3.Duration 
      @Test
      public void test3(){
          Instant now = Instant.now();
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          Instant now2 = Instant.now();
          //计算时间差
          Duration duration = Duration.between(now, now2);
          System.out.println(duration.getSeconds());
          System.out.println("----------------");
      
          LocalTime lt1 = LocalTime.now();
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          LocalTime lt2 = LocalTime.now();
          System.out.println(Duration.between(lt1, lt2).toMillis());
      }
      
      //4.Period
      @Test
      public void test4(){
          LocalDate ld1 = LocalDate.of(2017, 1, 1);
          LocalDate ld2 = LocalDate.now();
          Period period = Period.between(ld1, ld2);
          System.out.println(period.getYears());
          System.out.println(period.getMonths());
          System.out.println(period.getDays());
      }
      

    日期的操作

    • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
    • TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。

      //5.TemporalAdjuster:时间校正器
      @Test
      public void test5(){
          LocalDateTime ldt = LocalDateTime.now();
          System.out.println(ldt);
      
          LocalDateTime ldt2 = ldt.withDayOfMonth(8);
          System.out.println(ldt2);
      
          LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
          System.out.println(ldt3);
      
          //自定义
          LocalDateTime ldt5 =  ldt.with((l) -> {
              LocalDateTime ldt4 = (LocalDateTime) l;
              DayOfWeek dow = ldt4.getDayOfWeek();
              if (dow.equals(DayOfWeek.FRIDAY)) {
                  return ldt4.plusDays(3);
              }else if (dow.equals(DayOfWeek.SATURDAY)) {
                  return ldt4.plusDays(2);
              } else {
                  return ldt4.plusDays(1);
              }
          });
          //下个工作日
          System.out.println(ldt5);
      }
      

    解析与格式化

    java.time.format.DateTimeFormatter 类
    该类提供了三种格式化方法:
    - 预定义的标准格式
    - 语言环境相关的格式
    - 自定义的格式

        //DateTimeFormatter:格式化时间/日期
        @Test
        public void test6(){
            DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
            LocalDateTime ldt = LocalDateTime.now();
    
            System.out.println(ldt);
            String format = ldt.format(dtf);
            System.out.println(format);
            System.out.println("------------");
            DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
            String format2 = dtf2.format(ldt);
            System.out.println(format2);
    
            LocalDateTime ldt2 = ldt.parse(format2,dtf2);
            System.out.println(ldt2);
        }
    

    时区的处理

    Java8 中加入了对时区的支持,带时区的时间为分别为:
    ZonedDate、ZonedTime、ZonedDateTime
    其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式
    例如 :Asia/Shanghai 等
    ZoneId:该类中包含了所有的时区信息
    - getAvailableZoneIds() : 可以获取所有时区时区信息
    - of(id) : 用指定的时区信息获取 ZoneId 对象

        //ZonedDate ZoneTime ZoneDateTime
        @Test
        public void test7(){
            LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
            System.out.println(ldt);
    
            LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
            ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Asia/Shanghai"));
            System.out.println(zdt);
        }
    

    与传统日期处理的转换

    其他新特性

    HashMap

    HashMap:减少碰撞,位置相同时,条件达到链表上超过8个,总数超过64个时,数据结构改为红黑树。

    ConcurrentHashMap:取消锁分段,与HashMap相同,达到条件时,数据结构改为红黑树。

    Optional 类

    Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
    常用方法:
    - Optional.of(T t) : 创建一个 Optional 实例
    - Optional.empty() : 创建一个空的 Optional 实例
    - Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
    - isPresent() : 判断是否包含值
    - orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
    - orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
    - map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
    - flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

        /**
         * Optional类
         */
        public class TestOptional {
    
            @Test
            public void test1(){
                //参数不能为空
                Optional<Person> op = Optional.of(new Person());
    
                Person person = op.get();
                System.out.println(person);
            }
    
            @Test
            public void test2(){
                //构建空optional
                Optional<Person> op = Optional.empty();
                System.out.println(op.get());
            }
    
            @Test
            public void test3(){
                //如果为null,调用empty,如果不为null,调用of
                Optional<Person> op = Optional.ofNullable(null);
        //      Optional<Person> op = Optional.ofNullable(new Person());
                if (op.isPresent()) {
                    System.out.println(op.get());
                }
    
                //有值就用值,没值就替代
                Person person = op.orElse(new Person("张三", 23));
                System.out.println(person);
            }
        }
    

    重复注解与类型注解

    Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

        @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface MyAnnotations {
    
            MyAnnotation[] value();
        }
    
        @Repeatable(MyAnnotations.class)
        @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ElementType.TYPE_PARAMETER})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface MyAnnotation {
    
            String value() default "aric";
        }
    
        /**
         * 重复注解与类型注解
         */
        public class TestAnnotation {
    
            @MyAnnotation("hello")
            @MyAnnotation("test")
            public void show(@MyAnnotation("a") String str){
                System.out.println(str);
            }
        }
    
    展开全文
  • JDK8主要新特性介绍(一)

    万次阅读 2019-06-21 17:13:50
    1、语言新特性1.1接口新增默认方法与静态方法 1.1.1 Interface Default Method:For creating a default method in java interface, we need to use “default” keyword with the method signature. For example: ...

    1、语言新特性
    1.1接口新增默认方法与静态方法

    1.1.1  Interface Default Method:For creating a default method in java interface, we need to use “default” keyword with the method signature. For example:

    package com.java8.defaultmethod;
    
    public interface Interface1 {
    
    	void method1(String str);
    	//a default method
    	default void log(String str){
    		System.out.println("I1 logging::"+str);
    	}
    }
    

    Notes:类继承多个Interface接口同名方法(如show())时,必须在子类中@Override重写父类show()方法。

    1.1.2 Interface Static Method:interface static method is similar to default method except that we can’t override them in the implementation classes.for example:

    package com.java8.staticmethod;
    public interface MyData {
            static boolean isNull(String str) {
    	 System.out.println("Interface Null Check");
    	 return str == null ? true : "".equals(str) ? true : false;
            }
    }

    1.1.2.1 Functional Interfaces:含有一个显式声明函数(抽象方法)的接口称为函数接口,注释@FunctionalInterface用作检查代码块,包package java.util.function,通常使用lambda expressions来实体化函数接口,for example:

    特性说明:

        1,函数式接口仅仅只有一个方法(非默认或静态方法),用于显示转换成ladbma表达式。

        2, java.lang.Runnable接口 java.util.concurrent.Callable接口是两个最典型的函数式接口。

        3.如果一个函数式接口添加一个普通方法,就变成了非函数式接口(一般定义的接口)。

                  4.Jdk8 规范里添加了注解@FunctionalInterface来限制函数式接口不能修改为普通的接口.

      新增了四个重要的函数式接口:函数形接口 、供给形接口消费型接口、判断型接口

             

     

    1.2 新增Lambda表达式

     1.2.1 Lambda表达式(基于函数的匿名表达式)

      

      语法:( object str,....)[参数列表]   ->[箭头符号]     代码块或表达式

      特性:Lambda 的类型是从使用 Lambda 的上下文推断出来的。上下文中 Lambda 表达式需要的类型称为目标类型(一个 Lambda表达式所在的类的类型。并且必须存在一个目标类型);  匿名、函数、传递、简洁。

    1.3 新增方法引用

    1)构造器引用

                

    2)静态方法引用

                  

    3)(任意对象)的方法引用

                    

    4)实例对象的方法引用

               

    5)数组引用

              

    1.4 重复注解

           Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。重复注解机制本身必须用@Repeatable注解.

                 

    1.5 扩展注解的支持

          jdk 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。

                 

     

    PS:分析的代码见‘https://www.journaldev.com/592/java-try-with-resources

    展开全文
  • Java8新特性

    2019-08-06 14:26:27
    Java8发布到现在至少3年了,但是对Lambda表达式不熟悉、看不懂、不会用的现象非常常见。 即使是升级到JDK1.8了,但是很多开发者依然是停留在1.8之前的开发方式,使用的也是非常老旧和过时的API,遇到函数式接口...
  • java8 新特性 面试问题

    千次阅读 2019-03-13 21:39:44
    java8 新特性介绍 1 lambda表达式。 2 注解拓展,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类。 3 类型推断的能力更加强大。 4 使用@Repeatable注解定义重复注解,可以在同一个地方多次...
  • Java 8有哪些新特性

    千次阅读 2019-01-08 21:06:21
    (1)Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用Lambda 表达式可以使代码变的更加简洁紧凑。 (2) 方法引用...
  • Java8新特性一览

    2019-04-22 10:38:55
    HashMap当单链表数量大于8时转为红黑树 ConcurrentHashMap去掉了分段锁的设计,改为使用CAS实现 取消了JVM内存中的方法区,使用元空间替代(Matespace),使用直接内存 二、Lambad表达式 可以将一个方法作为参数进行...
  • Java版本:JDK8的十大新特性介绍

    万次阅读 多人点赞 2019-05-14 09:24:35
    JDK8新特性: 1.Lambda表达式 2.的日期API 3.引入Optional 4.使用Base64 5.接口的默认方法和静态方法 6.新增方法引用格式 7.新增Stream类 8.注解相关的改变 9.支持并行(parallel)数组 10.对...
  • JAVA8 十大新特性详解

    万次阅读 多人点赞 2018-07-25 21:10:14
    一、接口的默认方法在接口中新增了default方法和static方法,这两种方法可以有方法体 1、static方法 示例代码:public interface DefalutTest { static int a =5; default void defaultMethod(){ ...
  • Java 8新特性—终极版

    万次阅读 多人点赞 2016-03-15 15:38:14
    前言: Java 8 已经发布...在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versus
  • Java 8、9、10、11 新特性

    万次阅读 2018-10-16 08:54:50
    不知不觉Java已经发布到了第11版,兄弟们恐怕还不知道他们的新特性吧。快来一起瞧瞧吧。 Java 8 1.Lambdas表达式与Functional接口 2.接口的默认与静态方法 ...详细网址:Java8 新特性   Java 9 ...
  • Java5、6、7、8、9的新特性

    万次阅读 2017-07-22 10:28:59
    Java5java5是很重要的一个版本,有泛型、枚举、自动装箱/拆箱、可变参数、注解、foreach循环、静态导入、的... Java6的新特性Java7java7有一些比较重要的更新,如异常处理增加了被抑制的异常、捕获多异常、try-with-r
  • Java8新特性–Interface中的default方法(接口默认方法)在Java8新特性中有一个新特性为接口默认方法,该新特性允许我们在接口中添加一个非抽象的方法实现,而这样做的方法只需要使用关键字default修饰该默认实现...
  • java8十大新特性

    千次阅读 2018-08-23 09:51:36
    学习地址:https://blog.csdn.net/cdw8131197/article/details/68553148  
  • 使用Java8新特性对List进行排序

    万次阅读 2018-08-23 10:15:56
    在项目开发中往往会遇到各种数据需要排序展示在页面上,常见的从数据库查使用数据库的排序,还有一种就是使用我们的开发语言进行排序,这里给大家演示使用java8新特性进行排序,众所周知java8带来了函数式编程和...
  • 截止稿前,java10都已经发布了,现在我们来讨论一下java8。 本文的讨论,只是一个简述,更加具体的,各位可以参照各自的专题讲解 说在前面 Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库...
  • Java11的8新特性

    万次阅读 热门讨论 2018-09-27 13:36:36
    Java11又出新版本了,我还在Java8上停着。不过这也挡不住我对他的热爱,忍不住查看了一下他的性能,由于自己知识有限,只总结了以下八个特性; 1、本地变量类型推断 什么是局部变量类型推断? var javastack =...
  • 面试必知的java8新特性-stream

    千次阅读 2016-12-17 22:36:21
    面试必知的java8新特性-stream java作为开发语言中的元老已经度过了很多年,最新的java8为我们带来了一些新特性,这些特性可以在以后的工作中为我们的开发提供更多的便捷,现在就让我们看看最新的函数...
  • Java技术栈www.javastack.cn优秀的Java技术公众号以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期...
1 2 3 4 5 ... 20
收藏数 389,589
精华内容 155,835
关键字:

java8新特性