Java8新特性Lambda、Stream

Java8新特性

​ Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Date Time API − 加强对日期与时间的处理。
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

其中核心:Lambda表达式和Stream API

第一节:Lambda表达式

​ Lambda表达式可以看成是匿名内部类,Lambda 允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递,使用 Lambda 表达式可以使代码变的更加简洁紧凑。

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

​ 基本语法:

 <函数式接口>  <变量名> = (参数1,参数2...) -> {
                    //方法体
 };

案例1:

//匿名内部类
		Runnable r=new Runnable() {
			@Override
			public void run() {
				System.out.println("hello");
			}
		};
//lambda表达式
Runnable r2=()->System.out.println("hello");

案例2:

//匿名内部类
		Comparator<String> comparator=new Comparator<String>() {

			@Override
			public int compare(String o1, String o2) {
				// TODO Auto-generated method stub
				return o1.length()-o2.length();
			}
		};
		TreeSet<String> treeSet=new TreeSet<String>(comparator);
//lambda表达式
		TreeSet<String> treeSet2=new TreeSet<>((o1,o2)->o1.length()-o2.length());
1.1为什么使用lambda表达式

​ 需求1:有一个员工集合,获取年龄大于25的员工信息


public class Employee {
	private String name;
	private int age;
	private double salary;
	public Employee() {
		// TODO Auto-generated constructor stub
	}
	
	public Employee(String name, int age, double salary) {
		this.name = name;
		this.age = age;
		this.salary=salary;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}
}
public static void main(String[] args) {
		List<Employee> employees=new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		
		List<Employee> list = filterEmployee(employees);
		for (Employee emp : list) {
			System.out.println(emp.toString());
		}
	}
	public static List<Employee> filterEmployee(List<Employee> employees) {
		List<Employee> list=new ArrayList<>();
		for (Employee e : employees) {
			if(e.getAge()>=25) {
				list.add(e);
			}
		}
		return list;
	}

需求2:获取工资大于10000的员工信息

public static List<Employee> filterEmployee2(List<Employee> employees) {
		List<Employee> list=new ArrayList<>();
		for (Employee e : employees) {
			if(e.getSalary()>=10000) {
				list.add(e);
			}
		}
		return list;
}

问题:如果再添加类似需求,需要再添加一个方法。如何解决?

1.使用策略设计模式

/**
*定义一个接口
*/
public interface MyPredicate<T> {
	boolean test(T t);
}
//两个实现类
//按照年龄过滤
class FilterEmployeeByAge implements MyPredicate<Employee>{

	@Override
	public boolean test(Employee t) {
		// TODO Auto-generated method stub
		return t.getAge()>=25;
	}

}
//按照工资过滤
class FilterEmployeeBySalary implements MyPredicate<Employee>{

	@Override
	public boolean test(Employee t) {
		// TODO Auto-generated method stub
		return t.getSalary()>=10000;
	}

}

public static void main(String[] args) {
		List<Employee> employees=new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		//按照年龄过滤
		System.out.println("-------------按照年龄过滤--------------");
		List<Employee> list=filterEmployee(employees,new FilterEmployeeByAge());
		for (Employee employee : list) {
			System.out.println(employee.toString());
		}
		System.out.println("-------------按照工资过滤--------------");
		//按照工资过滤
		List<Employee> list2=filterEmployee(employees,new FilterEmployeeBySalary());
		for (Employee employee : list2) {
			System.out.println(employee.toString());
		}
	}
	
	public static List<Employee> filterEmployee(List<Employee> employees,MyPredicate<Employee> predicate) {
		List<Employee> list=new ArrayList<>();
		for (Employee e : employees) {
			if(predicate.test(e)) {
				list.add(e);
			}
		}
		return list;
	}

2:使用Lambada表达式优化策略模式。

public static void main(String[] args) {
		List<Employee> employees=new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		//按照年龄过滤
		System.out.println("-------------按照年龄过滤--------------");
		List<Employee> list=filterEmployee(employees,(e)->e.getAge()>=25);
		for (Employee employee : list) {
			System.out.println(employee.toString());
		}
		System.out.println("-------------按照工资过滤--------------");
		//按照工资过滤
		List<Employee> list2=filterEmployee(employees,(e)->e.getSalary()>=10000);
		for (Employee employee : list2) {
			System.out.println(employee.toString());
		}
	}
	
	public static List<Employee> filterEmployee(List<Employee> employees,MyPredicate<Employee> predicate) {
		List<Employee> list=new ArrayList<>();
		for (Employee e : employees) {
			if(predicate.test(e)) {
				list.add(e);
			}
		}
		return list;
	}

3:使用Stream API再优化lambda表达式

public static void main(String[] args) {
		List<Employee> employees = new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		// 按照年龄过滤
		System.out.println("-------------按照年龄过滤--------------");
		employees
			.stream()
			.filter((e) -> e.getAge() >= 25)
			.forEach(System.out::println);
		// 按照工资过滤
		System.out.println("-------------按照工资过滤--------------");
		employees
			.stream()
			.filter((e) -> e.getSalary() >= 10000)
			.forEach(System.out::println);

	}
1.2使用lambda表达式注意事项:
Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分
左侧:(参数1,参数2…)表示参数列表;
右侧:{}内部是方法体 

1、形参列表的数据类型会自动推断; 
2、如果形参列表为空,只需保留(); 
3、如果形参只有1个,()可以省略,只需要参数的名称即可; 
4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句; 
5、lambda不会生成一个单独的内部类文件; 
6、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错。

1.3练习

需求1:使用Collections.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比),使用Lambda作为参数传递。
需求2:声明函数式接口,接口中声明抽象方法,public String getValue(String s);
	  编写测试类,类中编写方法使用接口作为参数,将一个字符串转成大写,并作为方法的返回值。
1.3函数式接口

​ 如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口可以使用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

​ Java为了程序员方便使用Lambda表达式,Java内置了四个核心函数式接口

函数式接口 参数类型 返回类型 说明
Consume 消费型接口 T void void accept(T t);对类型为T的对象应用操作
Supplier 供给型接口 T T get(); 返回类型为T的对象
Function<T,R> 函数型接口 T R R apply(T t);对类型为T的对象应用操作,并返回类型为R类型的对象。
Predicate 断言型接口 T boolean boolean test(T t);确定类型为T的对象是否满足条件,并返回boolean类型。

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

public class Test {
	public static void main(String[] args) {
		happy(1000,(m)->System.out.println("吃烤全羊,消费"+m));
		happy(10000,(m)->System.out.println("旅游消费"+m));
		
		List<Integer> numbers = getNumbers(10, ()->new Random().nextInt(100));
		for (Integer n : numbers) {
			System.out.println(n);
		}
		
		System.out.println(stringOpe("hello", (s)->s.toUpperCase()));
		System.out.println(stringOpe("   hello    ", (s)->s.trim()));
		
		List<String> names=new ArrayList<>();
		names.add("张三");
		names.add("张三丰");
		names.add("张无忌");
		names.add("李四");
		names.add("王五");
		List<String> names2=filterString(names,s->s.startsWith("张"));
		for (String string : names2) {
			System.out.println(string);
		}
		
	}
	//消费型接口
	public static void happy(double money,Consumer<Double> consumer) {
		consumer.accept(money);
	}
	//供给型接口
	public static List<Integer> getNumbers(int count,Supplier<Integer> supplier) {
		List<Integer> numbers=new ArrayList<>();
		for(int i=0;i<count;i++) {
			numbers.add(supplier.get());
		}
		return numbers;
	}
	
	//函数型接口
	public static String stringOpe(String s,Function<String, String> fun) {
		return fun.apply(s);
	}
	
	//断言型接口
	
	public static List<String> filterString(List<String> list,Predicate<String> p) {
		List<String> list2=new ArrayList<>();
		for (String string : list) {
			if(p.test(string)) {
				list2.add(string);
			}
		}
		return list2;
 	}
}

第二节:方法引用

​ 方法引用是lambda表达式的一种简写形式。 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。

使用“::”操作符将方法名和对象或类的名字分隔开来。以下是四种使用情况:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法
  • 类::new

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

import com.qf.day15_3.Employee;

public class Test2 {
	public static void main(String[] args) {
		//1方法引用  对象::方法名
		Consumer<String> con=(s)->System.out.println(s);
		//等价于
		Consumer<String> con2=System.out::println;
		con.accept("hello");
		con2.accept("world");
		
		Employee e=new Employee("张亮", 20, 10000);
		Supplier<String> sup=()->e.getName();
		//等价于
		Supplier<String> sup2=e::getName;
		System.out.println(sup2.get());
		
		
		//2方法引用 类名::静态方法
		//Comparator<Integer> comparator=(x,y)->Integer.compare(x, y);
		//等价于
		Comparator<Integer> comparator=Integer::compare;
		TreeSet<Integer> treeSet=new TreeSet<>(comparator);
		
		
		//3方法引用: 类名::实例方法名
		
		BiPredicate<String, String> bpre=(x,y)->x.equals(y);
		BiPredicate<String, String> bpre2=String::equals;
		
		//4构造方法引用:
		Supplier<Employee> sup3=()->new Employee();
		//等价于
		Supplier<Employee> sup4=Employee::new;//默认构造方法
		
	}
}

第三节:Stream API

​ Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。

​ 简单应用:统计一个字符串类型集合中,所有长度大于3的元素个数。

public static void main(String[] args) {
		//传统实现
		List<String> data=new ArrayList<>();
		data.add("hello");
		data.add("world");
		data.add("ni");
		data.add("apple");
		data.add("china");
		int count = 0;
        for (String s : data) {
            if (s.length() > 3)
                count++;
        }
        System.out.println(count);
        //Stream API
       long count2= data
    		   .stream()
    		   .filter(s->s.length()>3)
    		   .count();
       System.out.println(count2);
	}

上面代码中stream方法会为字符串列表生成一个Streamfilter方法会返回只包含字符串长度大于3的一个Stream,然后通过count方法计数。

3.1 什么是Stream?

​ 一个Stream表面上与一个集合很类似,集合中保存的是数据,而流中对数据的操作。

注意:

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

Stream遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的。
3.2 如何使用Stream API

使用Stream,会有三个阶段(步骤):

  1. 创建一个Stream。 (创建)
  2. 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作。 (中间操作)
  3. 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。(终止操作)
3.2.1 Stream的创建方法
// 1. Stream.of方法
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays.of方法
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. 集合方法
List<String> list = Arrays.asList(strArray);
stream = list.stream();
stream = list.parallelStream();//并行流

//4.创建无限流
//迭代
Stream<Integer> stream = Stream.iterate(0, (x)->x+2);
stream.limit(10)
  .forEach(System.out::println);
//生成
Stream<Double> stream2 = Stream.generate(()->Math.random());
stream2.limit(5)
  .forEach(System.out::println);
3.2.2 Stream中间操作

中间操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。

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

筛选和切片

public static void main(String[] args) {
		
		List<Employee> employees=new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		//employees.add(new Employee("李四", 32, 22000));
		//1 筛选和切片
		// filter---从流中排除元素
		// limit——截断流,使其元素不超过给定数量。
		// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
		// distinct——筛选,通过流所生成元素的 equals() 去除重复元素
		System.out.println("-----------filter------------");
		Stream<Employee> stream = employees.stream().filter((e)->e.getAge()>=25);
		stream.forEach(System.out::println);
		
		System.out.println("---------limit-------");
		Stream<Employee> stream2 = employees.stream().limit(3);
		stream2.forEach(System.out::println);
		
		System.out.println("---------skip-------");
		Stream<Employee> stream3= employees.stream().skip(2);
		stream3.forEach(System.out::println);
		
		System.out.println("---------distinct-------");//通过equals方法去重复
		Stream<Employee> stream4= employees.stream().distinct();
		stream4.forEach(System.out::println);
			
	}

映射

public static void main(String[] args) {
		List<Employee> employees = new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		// map——接收 Lambda ,
		// 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
		System.out.println("----------获取员工姓名-----------");
		Stream<String> str = employees.stream().map((e) -> e.getName());
		str.forEach(System.out::println);
		System.out.println("-------------------转成大写------------------------");
		List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
		Stream<String> stream = strList.stream().map(String::toUpperCase);

		stream.forEach(System.out::println);
	}

排序

public static void main(String[] args) {
		List<Employee> employees = new ArrayList<>();
		employees.add(new Employee("xxx", 30, 10000));
		employees.add(new Employee("yyy", 29, 8000));
		employees.add(new Employee("zzz", 22, 12000));
		employees.add(new Employee("张三", 21, 20000));
		employees.add(new Employee("李四", 32, 22000));
		/*
			sorted()——自然排序
			sorted(Comparator com)——定制排序
		 */
		System.out.println("-----------------自然排序-------------------");
		employees.stream().map(Employee::getName).sorted().forEach(System.out::println);
		System.out.println("-----------------定制排序-------------------");
		employees.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.2.3 Stream 的终止操作

终止操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。

遍历

forEach

查找和匹配

allMatch——检查是否匹配所有元素
anyMatch——检查是否至少匹配一个元素
noneMatch——检查是否没有匹配的元素
findFirst——返回第一个元素
findAny——返回当前流中的任意元素
count——返回流中元素的总个数
max——返回流中最大值
min——返回流中最小值
public static void main(String[] args) {
		List<Employee> employees = new ArrayList<>();
		employees.add(new Employee("xxx", 30,"男",10000));
		employees.add(new Employee("yyy", 29,"男",8000));
		employees.add(new Employee("zzz", 22, "男",12000));
		employees.add(new Employee("张三", 21,"男",20000));
		employees.add(new Employee("李四", 32,"男",22000));
		//-----------foreach--------------
		employees.stream().forEach(System.out::println);
		/**
		 *  allMatch——检查是否匹配所有元素
		    anyMatch——检查是否至少匹配一个元素
			noneMatch——检查是否没有匹配的元素
			findFirst——返回第一个元素
			findAny——返回当前流中的任意元素
			count——返回流中元素的总个数
			max——返回流中最大值
			min——返回流中最小值
		 */
		
		boolean b = employees.stream().allMatch((e)->e.getSex().equals("男"));
		boolean b2 = employees.stream().anyMatch((e)->e.getSex().equals("女"));
		boolean b3=employees.stream().noneMatch((e)->e.getSex().equals("女"));
		System.out.println(b);
		System.out.println(b2);
		System.out.println(b3);
		Optional<Employee> findFirst = employees.stream().findFirst();
		System.out.println(findFirst.get());
		Optional<Employee> findAny = employees.parallelStream().findAny();
		System.out.println(findAny.get());
		Optional<Employee> max = employees.stream().max((o1,o2)->o1.getAge()-o2.getAge());
		System.out.println(max.get());
		Optional<Double> min = employees.stream().map(Employee::getSalary).min(Double::compareTo);
		System.out.println(min.get());
		
	}

归约和收集

public static void main(String[] args) {
		List<Employee> employees = new ArrayList<>();
		employees.add(new Employee("xxx", 30, "男", 10000));
		employees.add(new Employee("yyy", 29, "男", 8000));
		employees.add(new Employee("zzz", 22, "男", 12000));
		employees.add(new Employee("张三", 21, "男", 20000));
		employees.add(new Employee("李四", 32, "男", 22000));
		// -----------foreach--------------
		employees.stream().forEach(System.out::println);
		/**
		 * reduce归约 reduce(T identity, BinaryOperator) / reduce(BinaryOperator)
		 * ——可以将流中元素反复结合起来,得到一个值
		 */
		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);

		System.out.println("----------------------------------------");

		Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);

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

		/**
		 * collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
		 * 
		 */
		System.out.println("-------------List集合---------------------");

		List<String> list2 = employees.stream().map(Employee::getName).collect(Collectors.toList());

		list2.forEach(System.out::println);

		System.out.println("---------------Set集合-------------------");

		Set<String> set = employees.stream().map(Employee::getName).collect(Collectors.toSet());

		set.forEach(System.out::println);


	}
3.2.4 并行操作

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要耗时多久:

public static void main(String[] args) {
		int max = 1000000;
		List<String> values = new ArrayList<>(max);
		for (int i = 0; i < max; i++) {
		    UUID uuid = UUID.randomUUID();
		    values.add(uuid.toString());
		}

		System.out.println("----------串行------------");
		long t0 = System.currentTimeMillis();
		long count = values.stream().sorted().count();
		System.out.println(count);
		long t1 = System.currentTimeMillis();
		long millis = t1-t0;
		System.out.println(millis);
		
//		System.out.println("-------------并行----------------");
//		long t0 = System.currentTimeMillis();
//		long count = values.parallelStream().sorted().count();
//		System.out.println(count);
//		long t1 = System.currentTimeMillis();
//		long millis = t1-t0;
//		System.out.println(millis);
		
	}

第四节:新时间日期API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

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

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

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

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

线程安全问题演示:

public static void main(String[] args) throws Exception{
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

		Callable<Date> task = new Callable<Date>() {

			@Override
			public Date call() throws Exception {
				return sdf.parse("20161121");
			}

		};

		ExecutorService pool = Executors.newFixedThreadPool(10);

		List<Future<Date>> results = new ArrayList<>();

		for (int i = 0; i < 10; i++) {
			results.add(pool.submit(task));
		}

		for (Future<Date> future : results) {
			System.out.println(future.get());
		}

		pool.shutdown();
	}

使用新时间日期API解决

public static void main(String[] args) throws Exception{
		
		DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyyMMdd");
		Callable<LocalDate> task = new Callable<LocalDate>() {

			@Override
			public LocalDate call() throws Exception {
				return LocalDate.parse("20161002", formatter);
			}

		};

		ExecutorService pool = Executors.newFixedThreadPool(10);

		List<Future<LocalDate>> results = new ArrayList<>();

		for (int i = 0; i < 10; i++) {
			results.add(pool.submit(task));
		}

		for (Future<LocalDate> future : results) {
			System.out.println(future.get());
		}

		pool.shutdown();
	}
4.1 本地化日期时间 API

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

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

public static void main(String[] args) {
		//当前时间
		LocalDateTime ldt=LocalDateTime.now();
		System.out.println(ldt);
		//其他时间
		LocalDateTime ldt2=LocalDateTime.of(2012, 10, 1, 10, 10,10);
		System.out.println(ldt2);
		
		//加时间
		LocalDateTime ldt3=ldt2.plusDays(2);
		System.out.println(ldt3);
		//减时间
		LocalDateTime ldt4 = ldt3.minusHours(2);
		System.out.println(ldt4);
		//获取时间部分
		System.out.println(ldt.getYear());
		System.out.println(ldt.getMonthValue());
		System.out.println(ldt.getDayOfMonth());
		System.out.println(ldt.getHour());
		System.out.println(ldt.getMinute());
		System.out.println(ldt.getSecond());
	}
4.2 Instant、ZoneId

Instant 时间戳

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

ZoneId 时区

public static void main(String[] args) throws Exception {
		//时间戳
		Instant instant=Instant.now();
		System.out.println(instant);
		Thread.sleep(1000);
		Instant instant2=Instant.now();
		
		long millis = Duration.between(instant, instant2).toMillis();
		System.out.println(millis);
		
		//时区
		System.out.println(ZoneId.getAvailableZoneIds());

		ZoneId zone1 = ZoneId.of("Europe/Berlin");
		ZoneId zone2 = ZoneId.of("Brazil/East");
		System.out.println(zone1.getRules());
		System.out.println(zone2.getRules());
	}
4.3 时间矫正器 TemporalAdjuster

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

LocalDate localDate=LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(localDate);
4.4 DateTimeFormatter

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

DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);
LocalDateTime newLdt = ldt.parse(strDate, dtf);
System.out.println(newLdt);

猜你喜欢

转载自blog.csdn.net/qq_39513430/article/details/105719604