Java 8十个常用特性

Java 8是Java的一次重大的版本升级,速度更快(hashmap添加红黑树)、代码更少、便于并行。本文综合了各种资料,整理了关于Java 8高级特性的内容,希望你有所收获。

public class TalkDemo {

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

	//需求:获取公司中年龄小于 35 的员工信息
	public List<Employee> filterEmployeeAge(List<Employee> emps){
		List<Employee> list = new ArrayList<>();
		
		for (Employee emp : emps) {
			if(emp.getAge() <= 35){
				list.add(emp);
			}
		}
		return list;
	}
	
	@Test
	public void test1(){
		List<Employee> list = filterEmployeeAge(emps);
		for (Employee employee : list) {
			System.out.println(employee);
		}
	}

	// 如果这时候又来一个需求:获取公司中工资大于 5000 的员工信息
	public List<Employee> filterEmployeeSalary(List<Employee> emps){
		List<Employee> list = new ArrayList<>();
		for (Employee emp : emps) {
			if(emp.getSalary() >= 5000){
				list.add(emp);
			}
		}
		return list;
	}
	
	//优化方式一:策略设计模式
	public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){
		List<Employee> list = new ArrayList<>();
		for (Employee employee : emps) {
			if(mp.test(employee)){
				list.add(employee);
			}
		}
		return list;
	}
	
	@Test
	public void test2(){
		List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());
		for (Employee employee : list) {
			System.out.println(employee);
		}
		System.out.println("------------------------------------------");
		List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());
		for (Employee employee : list2) {
			System.out.println(employee);
		}
	}
	
	//优化方式二:匿名内部类
	@Test
	public void test3(){
		List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
			@Override
			public boolean test(Employee t) {
				return t.getAge() <= 35;
			}
		});
		for (Employee employee : list) {
			System.out.println(employee);
		}
	}
	
	//优化方式三:Lambda 表达式
	@Test
	public void test4(){
		List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
		list.forEach(System.out::println);
		System.out.println("------------------------------------------");
		List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
		list2.forEach(System.out::println);
	}
	
	//优化方式四:Stream API
	@Test
	public void test5(){
		emps.stream()
			.filter((e) -> e.getAge() <= 35)
			.forEach(System.out::println);
	}
}
@FunctionalInterface
public interface MyPredicate<T> {
	public boolean test(T t);
}
public class FilterEmployeeForAge implements MyPredicate<Employee>{
	@Override
	public boolean test(Employee t) {
		return t.getAge() <= 35;
	}
}
public class FilterEmployeeForSalary implements MyPredicate<Employee> {
	@Override
	public boolean test(Employee t) {
		return t.getSalary() >= 5000;
	}
}

一、Lambda表达式(重点)

Lambda 是Java 8中最大的改变。Lambda 是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

1、从匿名内部类到Lambda表达式的转换

// 匿名内部类
Runnable r1 = new Runnable(){
	@Override
	public void run(){
		System.out.println("Hello World!");
	}
};

// Lambda表达式
Runnable r2 = () -> System.out.println("Hello Lambda!");
// 原来用匿名内部类作为参数传递
TreeSet<String> ts1 = new TreeSet<>(new Comparator<String>(){
	@Override
	public int compare(String o1, String o2){
		return Integer.compare(o1.length(),o2.length());
	}
});

// Lambda表达式作为参数传递
TreeSet<String> ts2 = new TreeSet<>(
	(o1,o2) -> Integer.compare(o1.length(),o2.length());
);

2、Lambda 表达式基础语法
Lambda 表达式引入了->(剪头操作符、Lambda 操作符),它将Lambda 分为两个部分:
① 左侧:Lambda 表达式的参数列表
② 右侧:Lambda 表达式需要执行的功能

// 语法1:无参、无返回值,Lambda体只需一条语句
Runnable r1 = () -> System.out.println("Hello world!");

// 语法2:需要一个参数
Consumer<String> fun2 = (x) -> System.out.println(x);
// 只需要一个参数时,参数的小括号可以省略
Consumer<String> fun2 = x -> System.out.println(x);

// 语法3:需要两个参数,并且有返回值
BinaryOperator<Long> bo3 = (Long x,Long y) -> {
    System.out.println("实现接口的方法");
    return x+y;
};
// 数据类型可以省略,因为可由JVM编译器上下文推断得出,称为“类型推断”
BinaryOperator<Long> bo3 = (x,y) -> {
    System.out.println("实现接口的方法");
    return x+y;
};

// 语法4:当Lambda体只有一条语句时,return与大括号可以省略
BinaryOperator<Long> bo2 = (x,y) -> x+y;

3、Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
	( String e ) -> System.out.print( e + separator ) );
-----------------------
final String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
	( String e ) -> System.out.print( e + separator ) );

输出结果:

a,b,c,

4、Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

Arrays.asList( "a", "b", "c" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
-----------------------
Arrays.asList( "a", "b", "c" ).sort( ( e1, e2 ) -> {
	int result = e1.compareTo( e2 );
	return result;
	} );

5、类型推断:指的是参数类型都是由编译器推断得出的。例如,Lambda 表达式中无需指定类型,程序依然可以编译,这是因为Lambda 表达式的类型依赖于上下文环境,在后台推断出了参数的类型。Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。

二、函数式接口

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

函数式接口:只包含一个抽象方法的接口。这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。

在实际生活中,只要某个开发者在该接口中再添加一个函数,则该接口就不再是函数式接口,进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8提供了一个特殊的注解@FunctionalInterface,用于检查它是否是函数式接口。

1、举个简单的函数式接口的定义:

	@FunctionalInterface 
    public interface Functional {
        public void method();
    }

2、并且,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

	@FunctionalInterface 
    public interface Functional {
        public void method();
        default void defaultMethod() {}
    }

3、为了将Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。

@FunctionalInterface
public interface MyFunc<T> {
    public T getValue(T t);
}

public static String toUpperString(MyFunc<String> mf,String str){
    return mf.getValue(str);
}

public static void main(String[] args) {
    String newStr = toUpperString((str) -> str.toUpperCase(),"dfasadasfag");
    System.out.println(newStr);
}

输出结果:

DFASADASFAG

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

函数式接口 参数类型 返回类型 用途
Consumer 消费型接口(有去无回) T void 操作类型为T的对象,包含方法:void accept(T t);
Supplier 供给型接口(用于产生对象) T 返回类型为T的对象,包含方法:T get();
Function 函数型接口 T R 操作类型为T的对象并返回结果,结果是R类型的对象,包含方法:R apply(T t);
Predicate 断言型接口(用于判断) T boolean 确定类型为T的对象是否满足某约束,返回boolean值,包含方法:boolean test(T t);

其他接口

函数式接口 参数类型 返回类型 用途
BiConsumer T,U void 对类型为T,U参数应用操作,包含方法为void accept(Tt,Uu)
UnaryOperator (Function子接口) T T 对类型为T的对象进行一元运算,并返回T类型的结果,包含方法:T apply(Tt);
ToIntFunctionToLongFunctionToDoubleFunction T int、long、double 返回值类型为int、long、double的函数
IntFunction LongFunctionDoubleFunction int、long、double R 参数为int、long、double类型的函数
BiFunction T,U R 对类型为T,U参数应用操作,返回R类型的结果,包含方法:R apply(Tt,Uu);
BinaryOperator (BiFunction子接口) T,T T 对类型为T的对象进行二元运算,并返回T类型的结果,包含方法:T apply(Tt1,Tt2);

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

3.1 方法引用

当要传递给Lambda体的方法已经有实现了,可以使用方法引用(实现抽象方法的参数列表必须与方法引用方法的参数列表保持一致!)

可以将方法引用理解为 Lambda 表达式的另外一种表现形式

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法、实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁。

方法引用使用操作符::将方法名和对象或类的名字分隔开来:
对象::实例方法名
类::静态方法名
类::实例方法名

例如1:

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

等同1:

Consumer<String> con = System.out::println;

例如2:

BinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);

等同2:

BinaryOperator<Double> bo = Math::pow;

例如3:

BiPredicate<String, String> bp = (x, y) -> x.equals(y);

等同3:

// 当引用方法的第一个参数是调用者,且第二个参数是要调用的参数(或无参)时:ClassName::methodName
BiPredicate<String, String> bp = String::equals;

注意:
① 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致
② 若Lambda 的参数列表的第一个参数是实例方法的调用者,第二个参数是实例方法的参数(或无参)时,格式: ClassName::MethodName

3.2 构造器引用

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

构造器的参数列表,需要与函数式接口中参数列表保持一致!

例如:

Function<Integer,MyClass> fun = (n) -> new MyClass(n);

等同:

Function<Integer,MyClass> fun = MyClass::new;

3.3 方法引用与构造器引用的区别

Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。

	public static class Car {
        public static Car create( final Supplier< Car > supplier ) {
            return supplier.get();
        }

        public static void collide( final Car car ) {
            System.out.println( "Collided " + car.toString() );
        }

        public void follow( final Car another ) {
            System.out.println( "Following the " + another.toString() );
        }

        public void repair() {
            System.out.println( "Repaired " + this.toString() );
        }
    }

① 第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

② 第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

③ 第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:

cars.forEach( Car::repair );

④ 第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

输出结果(Car实例可能不同):

Collided JDK8.Demo$Car@5fd0d5ae
Repaired JDK8.Demo$Car@5fd0d5ae
Following the JDK8.Demo$Car@5fd0d5ae

3.4 数组引用

格式: type[] :: new
例如:

Function<Integer,Integer[]> fun = (n) -> new Integer[n];

等同:

Function<Integer,Integer[]> fun = Integer[]::new;

四、Stream API(重点)

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

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中,极大地简化了集合操作(非常复杂的查找、过滤和映射数据等)和并行操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。

三步骤:创建Stream -> 中间操作 -> 终止操作(终端操作)

1、创建Stream:创建一个数据源(如:集合、数组),从而获取一个流
① Collection 提供了两个方法:stream() 与 parallelStream()

List<String> list = new ArrayList<>();
	
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

② 通过 Arrays 中的 stream() 获取一个数组流

Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);

③ 通过 Stream 类中静态方法 of()

Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

④ 创建无限流

//迭代
Stream<Integer> stream1 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream1.forEach(System.out::println);
		
//生成
Stream<Double> stream2 = Stream.generate(Math::random).limit(2);
stream2.forEach(System.out::println);

2、中间操作(例如filter、map):对数据源的数据进行处理,执行一个中间操作并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam中。

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!

方法 描述
筛选与切片
filter(Predicate p) 从流中排除某些元素
distinct() 筛选,根据流所生成元素的hashCode() 和equals() 来去除重复元素
limit(Long maxSize) 截断流:使其元素不超过给定数量
skip(Long n) 扔掉前n个元素的流:若流中元素不足n个则返回一个空流,与limit(n) 互补
映射
map(Function f) 接收一个函数(Lambda)作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
排序
sorted() 按自然顺序排序产生一个新流
sorted(Comparator comp) 按比较器顺序排序产生一个新流
// 【内部迭代】:迭代操作由Stream API内部完成
// 中间操作:不会做任何的处理
// filter需要的是断言型接口Predicate
Stream<Employee> stream = emps.stream()
	.filter((e) -> {
		System.out.println("测试中间操作");
		return e.getAge() <= 35;
	});
// 终止操作:所有的中间操作会一次性的全部执行,称为“惰性求值”
stream.forEach(System.out::println);

// 【外部迭代】
Iterator<Employee> it = emps.iterator();	
while(it.hasNext()){
	System.out.println(it.next());
	}
// 映射
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
		
Stream<String> stream = strList.stream()
	.map(String::toUpperCase)
	.forEach(System.out::println);
// 排序:先按年龄排,年龄一样按姓名排
emps.stream()
	.sorted((x, y) -> {
		if(x.getAge() == y.getAge()){
			return x.getName().compareTo(y.getName());
		}else{
			return Integer.compare(x.getAge(), y.getAge());
		}
	}).forEach(System.out::println);

3、终止操作(例如forEach、sum):执行中间操作链,并产生结果。在执行晚期操作后,steam就不能使用了。

方法 描述
查找与匹配
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用Collection接口做迭代的称为外部迭代)
归约
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来得到一个值,返回类型为T
reduce(BinaryOperator b) 可以将流中元素反复结合起来得到一个值,返回类型为Optional
收集
collect(Collector c) 将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

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

方法 返回类型 作用 示例
toList List 把流中元素收集到List List emps= list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set Set emps= list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到创建的集合 Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和 inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 计算流中元素Integer属性的平均值 doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值 IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串 String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional 根据比较器选择最大值 Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional 根据比较器选择最小值 Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转换函数 inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map 根据某属性值对流分组,属性为K,结果为V Map map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map 根据true或false进行分区 Mapvd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
boolean bl1 = emps.stream()
	.allMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(bl1);
			
boolean bl2 = emps.stream()
	.anyMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(bl2);
			
boolean bl3 = emps.stream()
	.noneMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(bl3);

long count = emps.stream()
	.filter((e) -> e.getStatus().equals(Status.FREE))
	.count();
System.out.println(count);
		
Optional<Double> op1 = emps.stream()
	.map(Employee::getSalary)
	.max(Double::compare);
System.out.println(op1.get());
		
Optional<Employee> op2 = emps.stream()
	.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op2.get());

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);	
Integer sum = list.stream()
	.reduce(0, (x, y) -> x + y);
System.out.println(sum);
		
Optional<Double> op3 = emps.stream()
	.map(Employee::getSalary)
	.reduce(Double::sum);
System.out.println(op3.get());

List<String> list = emps.stream()
	.map(Employee::getName)
	.collect(Collectors.toList());
list.forEach(System.out::println);

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

五、串行流和并行流

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

	@Test
	public void test5(){
		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)); //2061-2053-2086-18926
	}

六、Optional类

Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

Optional 类(java.util.Optional)是一个容器类,代表一个值存在或不存在。原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念,并且可以避免空指针异常。

1、常用方法 :
Optional.of(T t):创建一个Optional 实例
Optional.empty() :创建一个空的Optional 实例
Optional.ofNullable(T t):若t不为null则创建Optional实例,否则创建空实例
isPresent() :判断是否包含非空值,有则返回true,否则返回false
orElse(T t) :如果调用对象包含值则返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值则返回该值,否则返回s获取的值
map(Function f): 如果有值对其处理会返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper):与map 类似,要求返回值必须是Optional

接下来看一点使用Optional的例子:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );        
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

输出结果:

Full Name is set? false
Full Name: [none]
Hey Stranger!

再看下另一个简单的例子:

Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );        
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

输出结果:

First Name is set? true
First Name: Tom
Hey Tom!

七、Date/Time API

LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象(解决线程安全问题),分别表示使用ISO-8601日历系统(ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法)的日期、时间、日期和时间。它们提供了简单的日期或时间,但是不包含当前的时间信息,也不包含与时区相关的信息。

方法 描述 示例
now() 根据当前时间创建对象 LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now();
of() 根据指定日期/时间创建对象 LocalDate localDate = LocalDate.of(2020, 07, 10);LocalTime localTime = LocalTime.of(02, 22, 22);LocalDateTime localDateTime = LocalDateTime.of(2020, 07,10, 02, 22, 22);
plusDays, plusWeeks,plusMonths, plusYears 向当前LocalDate对象添加几天、几周、几月、几年
minusDays, minusWeeks,minusMonths, minusYears 从当前LocalDate对象减去几天、几周、几月、几年
plus, minus 添加或减少一个Duration或Period
withDayOfMonth,withDayOfYear,withMonth,withYear 将月份天数、年份天数、月份、年份修改为指定的值并返回新的LocalDate 对象
getDayOfMonth 获得月份天数(1-31)
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个DayOfWeek枚举值)
getMonth 获得月份,返回一个Month的枚举值
getMonthValue 获得月份(1-12)
getYear 获得年份
until 获得两个日期之间的Period 对象,或者指定ChronoUnits 的数字
isBefore, isAfter 比较两个LocalDate
isLeapYear 判断是否是闰年

Java 8中新的时间和日期管理API深受第三方库Joda-Time的影响。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性,如果某个实例需要修改,则返回一个新的对象。

① Instant:基于时间戳的运算,从Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始。
② Duration:用于计算两个时间间隔
③ Period:用于计算两个日期间隔
④ 日期的操纵:使用时间校正器TemporalAdjuster来调整日期。TemporalAdjusters类是通过静态方法提供了大量的常用TemporalAdjuster的实现。

// 例如获取下个周日
LocalDate nextSunday = LocalDate.now().with(
	TemporalAdjusters.next(DayOfWeek.SUNDAY)
);

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

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

	//1. LocalDate、LocalTime、LocalDateTime
	@Test
	public void test1(){
		LocalDateTime ldt = LocalDateTime.now();
		System.out.println(ldt);//2020-07-13T21:09:18.106
		
		LocalDateTime ld2 = LocalDateTime.of(2016, 11, 21, 10, 10, 10);
		System.out.println(ld2);//2016-11-21T10:10:10
		
		LocalDateTime ldt3 = ld2.plusYears(20);
		System.out.println(ldt3);//2036-11-21T10:10:10
		
		LocalDateTime ldt4 = ld2.minusMonths(2);
		System.out.println(ldt4);//2016-09-21T10:10:10
		
		System.out.println(ldt.getYear());//2020
		System.out.println(ldt.getMonthValue());//7
		System.out.println(ldt.getDayOfMonth());//13
		System.out.println(ldt.getHour());//21
		System.out.println(ldt.getMinute());//9
		System.out.println(ldt.getSecond());//18
	}

	//2. Instant:时间戳(使用Unix元年 1970年1月1日 00:00:00 所经历的毫秒值)
	@Test
	public void test2(){
		Instant ins = Instant.now();  //默认使用UTC时区
		System.out.println(ins);//2020-07-13T13:12:47.710Z
		
		OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
		System.out.println(odt);//2020-07-13T21:12:47.710+08:00
		
		System.out.println(ins.getNano());//710000000
		
		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();
		System.out.println("--------------------");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		Instant ins2 = Instant.now();
		System.out.println("所耗费时间为:" + Duration.between(ins1, ins2).toMillis());//所耗费时间为:1000
		System.out.println("----------------------------------");
		
		LocalDate ld1 = LocalDate.now();
		LocalDate ld2 = LocalDate.of(2011, 1, 1);
		
		Period pe = Period.between(ld2, ld1);
		System.out.println(pe.getYears());//9
		System.out.println(pe.getMonths());//6
		System.out.println(pe.getDays());//12
	}

	//4. TemporalAdjuster:时间校正器
	@Test
	public void test4(){
		LocalDateTime ldt = LocalDateTime.now();
		System.out.println(ldt);//2020-07-13T21:22:44.429
		LocalDateTime ldt2 = ldt.withDayOfMonth(10);
		System.out.println(ldt2);//2020-07-10T21:22:44.429
		LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
		System.out.println(ldt3);//2020-07-19T21:22:44.429
		//自定义:下一个工作日
		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);//2020-07-14T21:22:44.429
	}
	
	//5. DateTimeFormatter:解析和格式化日期或时间
	@Test
	public void test5(){
		DateTimeFormatter dtf0 = DateTimeFormatter.ISO_LOCAL_DATE;
		
		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
		
		LocalDateTime ldt = LocalDateTime.now();
		String strDate = ldt.format(dtf);
		
		System.out.println(strDate);//2020年07月13日 21:23:38 星期一
		
		LocalDateTime newLdt = ldt.parse(strDate, dtf);
		System.out.println(newLdt);//2020-07-13T21:23:38
	}
	
	@Test
	public void test6(){
		Set<String> set = ZoneId.getAvailableZoneIds();
		set.forEach(System.out::println);//输出所有时区
	}
	
	//6.ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
	@Test
	public void test7(){
		LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
		System.out.println(ldt);//2020-07-13T21:25:15.880
		
		ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
		System.out.println(zdt);//2020-07-13T06:25:15.886-07:00[US/Pacific]
	}

如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:

		ZonedDateTime zonedDatetime = ZonedDateTime.now();
        final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
        final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
        System.out.println( zonedDatetime );
        System.out.println( zonedDatetimeFromClock );
        System.out.println( zonedDatetimeFromZone );

输出结果:

2020-07-08T16:23:38.909+08:00[Asia/Shanghai]
2020-07-08T08:23:38.909Z
2020-07-08T01:23:38.910-07:00[America/Los_Angeles]

八、Base64

对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:

		final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
                .getEncoder()
                .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
                Base64.getDecoder().decode( encoded ),
                StandardCharsets.UTF_8 );
        System.out.println( decoded );

输出结果:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

九、接口的默认方法和静态方法

Java 8扩展了接口的含义:默认方法和静态方法。

1、默认方法使得开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的具有具体实现的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法使用default 关键字修饰。默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

	private interface Defaulable {
        // 使用关键字default定义了一个默认方法notRequired()
        default String notRequired() {
            return "Default implementation";
        }
    }
    // 默认继承了接口中的默认方法
    private static class DefaultableImpl implements Defaulable {
    }
    // 覆写了接口的默认方法
    private static class OverridableImpl implements Defaulable {
        @Override
        public String notRequired() {
            return "Overridden implementation";
        }
    }

2、Java 8带来的另一个特性是在接口中可以定义静态方法

	private interface Named {
        public Integer myFun();

		default String getName(){
			return "Hello getName!";
		}
		
        static void show() {
            System.out.println("Hello show!");
        }
    }

3、下面的代码片段整合了默认方法和静态方法的使用场景:

	public static void main(String[] args) {
        Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
        System.out.println( defaulable.notRequired() );

        defaulable = DefaulableFactory.create( OverridableImpl::new );
        System.out.println( defaulable.notRequired() );
    }

输出结果:

Default implementation
Overridden implementation

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()parallelStream()forEach()removeIf()等等。
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。

十、新的Java工具

1、Nashorn引擎:jjs
jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。
2、类依赖分析器:jdeps
jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

你可能感兴趣的:(Java笔记)