二、JDK8新特性
JDK8新特性(一)
新特性如下:
- 函数式接口
- Lambda 表达式
- 集合的流式操作
- 注解
- 安全性
- IO/NIO
- 全球化功能
1、函数式编程
函数式编程并不是Java新提出的概念,其与指令编程相比,强调函数的计算比指令的计算更重要;与过程化编程相比,其中函数的计算可以随时调用。
1.1 接口的默认方法(Default Methods for Interfaces)
接口可以定义静态属性(常量)和静态方法和抽象方法,接口不能直接实例化(new),静态方法和静态属性属于类,普通方法属于实例对象。
public interface Person {
//1、静态属性
public static final String state = "已审核";
//2、静态方法
public static void play(){
System.out.println("play");
}
//3、抽象方法
void say();
}
Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现,此功能也称作虚拟扩展方法。
public interface Person {
void say();//抽象方法
//默认方法
default void play(){
System.out.println("play");
}
//默认方法
default void eat(){
System.out.println("eat");
}
}
方法调用:
public class Tom implements Person {
@Override
public void say() {
System.out.println("say");
}
public static void main(String[] args) {
Person p = new Tom();
p.eat();
p.play();
p.say();
//匿名内部类
Person p2 = new Person() {
@Override
public void say() {
System.out.println("匿名内部类");
}
};
p2.say();
p2.play();
p2.eat();
}
}
不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创 建对象。对于上面通过匿名内部类方式访问接口,可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后让接口的引用来指向这个对象。
1.2 函数式接口
**“函数式接口”指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是默认方法)的接口。**关于接口的变动,与其他接口的区别就是:
- 函数式接口中只能有一个抽象方法(我们在这里不包括与Object的方法重名的方法);
- 可以有从Object继承过来的抽象方法,因为所有类的最终父类都是Object;
- 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持Lambda表达式。
Java 8增加了一种特殊的注解 @FunctionalInterface ,但是这个注解通常不是必须的(某些情况建议使 用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用 @FunctionalInterface 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于 一个抽象方法的时候会报错的。
@FunctionalInterface
public interface Person {
void say();//抽象方法
//可以有多个默认方法
}
1.3 Lambda表达式
//函数式接口
@FunctionalInterface
public interface Person {
String say(String msg);//抽象方法
}
public class Tom {
public static void tomSay(Person p){
String helloJerry = p.say("hello jerry");
System.out.println(helloJerry);
}
public static void main(String[] args) {
/**
* 等价
* Tom.tomSay(new Person() {
* @Override
* public String say(String msg) {
* return "jerry say: "+msg;
* }
* });
*/
Tom.tomSay(msg ->"jerry say:"+msg );//lambda表达式
}
}
函数式编程调用外部局部变量,建议加上final,外部局部变量不可变。
1.4 内置的函数式接口
内置常用函数式接口
1.4.1 Predicates
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非),该方法是接受一个传入类型,返回一个布尔值,此方法应用于判断。
1.4.2 Functions
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):
public class Tom {
public static void tomSay(Function<String,String> function, String msg){
System.out.println(function.apply(msg));
}
public static void main(String[] args) {
Tom.tomSay(s-> "转化了"+s,"jerry");
/**
* 等价于静态内部类
* Tom.tomSay(new Jerry(),"jerry");
* }
* // 静态内部类
* private static class Jerry implements Function<String,String> {
* @Override
* public String apply(String s) {
* return "转化了"+s;
* }
*/
/**
* 等价于匿名内部类
* Tom.tomSay(new Function<String, String>() {
* @Override
* public String apply(String s) {
* return "转化了"+s;
* }
* },"jerry");
*/
}
}
1.4.3 Suppliers
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。
1.4.4 Consumers
Consumer 接口表示要对单个输入参数执行的操作。
public class Tom {
public static void tomSay(Consumer<String> consumer,String msg){
consumer.accept(msg);
}
public static void main(String[] args) {
Tom.tomSay(msg-> System.out.println("消费了"+msg),"jerry");
/**
* 等价于
* Tom.tomSay(new Consumer<String>() {
* @Override
* public void accept(String msg) {
* System.out.println("消费了"+msg);
* }
* },"jerry");
*/
}
}
1.4.5 Comparators
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
2、Stream
2.1 基本概念
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最 终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多 个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。
Stream VS List
- Stream 可以是无限的
- Stream可并行处理
- Stream可能延迟处理
2.2 基本操作
Filter(过滤): 过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在 过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次 执行。
Sorted(排序): 排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会 使用默认排序。
Sorted(排序): 排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会 使用默认排序。
Match(匹配): Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最 终操作 ,并返回一个 boolean 类型的值。
Count(计数): 计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。
Reduce(规约): 这是一个 最终操作 ,允许通过指定的函数来讲stream中的多个元素规约为一个元素
2.3 实例
public class Tom {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 21, 12, 4, 5, 78, 12, 23);
// Optional<Integer> reduce =
list.stream()
.map(item -> item * 10)//映射:每个数扩大三倍
.filter(item -> item > 30)//过滤:保留大于30的数字
.sorted((num1, num2) -> num1 - num2)//排序:由小到大
.distinct()//去重
// .reduce((num1, num2) -> num1 + num2);//规约:总和
// System.out.println(reduce.get());
.forEach(System.out::println);
}
public class Tom {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("tom",12,175),
new Person("jerry",12,176),
new Person("kitty",18,175),
new Person("david",19,175),
new Person("lucy",10,175)
);
// List<Person> collect = people.stream()
// .filter(p -> p.getAge() > 12)//筛选
// .sorted((p1,p2)->p1.getAge()-p2.getAge())
// .collect(Collectors.toList());
// System.out.println(collect);
// people.sort((p1,p2)->p1.getAge()-p2.getAge());
// people.sort(Comparator.comparing(p->p.getAge()));
people.sort(Comparator.comparing(Person::getAge).reversed().thenComparing(Person::getHeight));
people.forEach(System.out::println);
}
}
2.4 并行流
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream 则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
public class test {
public static void main(String[] args) {
List<Double> nums= new ArrayList<>();
// 随机生成10000000个数字,进行排序
for (int i=0;i<10000000;i++){
double random = Math.random();
nums.add(random);
}
// 串行流
Long start = System.currentTimeMillis();
List<Double> collect = nums.stream().sorted().collect(Collectors.toList());
Long end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println("-----------------------------");
// 并行流
Long start1 = System.currentTimeMillis();
List<Double> collect1 = nums.parallelStream().sorted().collect(Collectors.toList());
Long end1 = System.currentTimeMillis();
System.out.println(end1-start1);
}
}
执行结果:
3、Date API(日期相关API)
3.1 Clock
Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用 Instant 类 来表示, Instant 类也可以用来创建旧版本的 java.util.Date 对象。
3.2 Timezones(时区)
在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类 ZoneId (在 java.time 包中)表示一个区域标识符。 它有一个名为 getAvailableZoneIds 的静态 方法,它返回所有区域标识符。
3.3 LocalTime(本地时间)
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点或者 17:30:15。
3.4 LocalDate(本地日期)
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本 一致。下
3.5 LocalDateTime(本地日期时间)
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。
public static void main(String[] args) {
// Clock clock = Clock.systemDefaultZone();
// System.out.println(clock.getZone());
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = now.format(dateTimeFormatter);
System.out.println(format);
}
执行结果: