一、引言
JDK1.8出来已经一段时间了,现在1.9也已经出来了,有部分公司不太愿意升级到高版本的JDK1.8,有些项目之前使用到是低版本JDK,如果升级维护老版本项目就比较麻烦,需要连带升级其他到框架。今天抽了一段时间把这些都总结一下
二、新特性
1、关键字 default 关键字
在JDK1.8之前,项目中我们定义的接口中一般都只有抽象的方法不能有方法体,其实则不然,接口里面可以定义静态的具体方法(使用static关键字定义方法),而JDK1.8之后可以通过关键字default定义具体方法,如下
public interface NewCharacter {
/**
*一般抽象方法没有实现体到方法
*/
public void test1();
/**
*通过static修饰有方法体到方法
*/
public static void test2() {
System.out.print("方法test2");
}
/**
*JDK1.8引入到新特性定义到方法
*/
public default void test3(){
System.out.println("方法test3");
}
}
那这么定义一个方法的作用是什么呢?为什么不在接口的实现类里面再去实现方法呢?
一般情况在接口里面定义到抽象方法,都需要子类重写父类到抽象方法,在接口里面如果定义到抽象方法是一个算法或者别的方法,需要子类都来重写,并且实现到方式都一样,这样就造成代码到冗余,而且如果修改每个实现类都需要修改,重复的工作量(实际开发中可以通过工具类的定义全局的,每个方法调用即可),这样很麻烦
其实这么定义一个方法的主要意义是定义一个默认方法,也就是说这个接口的实现类实现了这个接口之后,不用管这个default修饰的方法,也可以直接调用,如下。
class NewCharacterImpl implements NewCharacter {
public void test1() {
System.out.println("test1");
}
@Override
public void test3() {
System.out.println("重写父类default方法test3");
}
public static void main(String[] args) {
NewCharacter nca = new NewCharacterImpl();
nca.test1();
nca.test3();
}
}
这样在接口里面定义方法目的是,当实现到方法中相同到代码,不需要再所有到子类中都去重复到写一遍,直接在接口中定义dufault 方法中编写具体代码子类就可以直接使用这个方法了,这样不管是开发还是维护项目,都会大大简化代码量。
2.lambda
Lambda可以将方法或者代码片段作为数据进行参数传递。
lambda运算符 " -> ",运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体(可以是表达式也可以是代码块,实现函数式接口中的方法)
1.不接受参数,直接返回值1
()-> 1
2.接收两个int类型到参数,返回两个参数到和
(int a, int b) -> a + b
3.接收a, b两个参数,JVM会根据上下文推断参数到类型,返回两个数到和
(a, b) -> a + b
4. 接收一个字符串参数,打印该字符串,没有返回值
(String name) -> System.out.println(name)
5.接收一个参数,JVM根据上下文推断参数到类型,打印该字符串,没有返回值
(name) -> System.out.println(name)
6.接收两个字符串,分别打印结果,没有返回值
(name,address) -> {System.out.println(name);System.out.pirintln(address);}
7. 接收一个参数,返回它本身2倍
x -> 2*x
案例测试
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
使用 Lambda 表达式需要注意以下两点:
-
Lambda
表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。 -
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
3 . 函数式接口
函数式接口(Functional Interface)特点:
1.接口内有且只有一个抽象方法
2.可以有多个默认方法(default修饰的方法),即可以有多个非抽象方法的接口。
3.java.lang.Object的public方法不算
4.函数式接口可以被隐式转换为 lambda 表达式。
JDK 1.8 之前已有的函数式接口
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
java.util.function
测试案例
import java.util.Arrays;
import java.util.List;
public class LamdbaTest {
// 计算余数等于1的值
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 4, 2, 5, 7);
// 定义算法
Eval eval = a -> a % 2 == 1;
// 调用方法执行
out(list, eval);
}
public static void out(List<Integer> list, Eval eval) {
for (Integer integer : list) {
if(eval.bool(integer)) {
System.out.println(integer);
}
}
}
interface Eval {
boolean bool(Integer a);
}
}
执行以上脚本,输出结果为
$ javac LamdbaTest.java
$ java LamdbaTest
输出所有数据:
1
5
7
4. Java 8 Stream
Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
import java.util.Arrays;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
public class StreamList {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "", "c", "e", "", "f");
// forEach 遍历集合数据
list.forEach(str -> System.out.println(str));
// or
list.forEach(str -> {
System.out.println(str);
});
// map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数
List<Integer> list1 = Arrays.asList(1, 2, 5, 3, 4);
// 把集合中的元素处理后赋值给新的集合
List<Integer> collect = list1.stream().map(i -> i * i)
.collect(Collectors.toList());
collect.forEach(str -> System.out.println(str));
// filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
list = list.stream().filter(str -> !str.isEmpty())
.collect(Collectors.toList());
list.forEach(str -> System.out.println(str));
// limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据
list.stream().limit(2).forEach(str -> System.out.println(str));
// sorted 方法用于对流进行排序
// 升序
list1.stream().sorted().forEach(str -> System.out.println(str));
// 降序
list1.stream().sorted(Comparator.reverseOrder())
.forEach(str -> System.out.println(str));
List<Person> pList = Arrays.asList(new Person(1, 2), new Person(2, 1),
new Person(1, 3));
// 对象属性使用Comparator 来排序一个list
pList.stream().sorted(Comparator.comparing(Person::getAge))
.sorted(Comparator.comparing(Person::getNumber).reversed())
.forEach(str -> System.out.println(str));
//
// 对象属性把上面的元素逆序
pList.stream().sorted(Comparator.comparing(Person::getAge).reversed())
.forEach(str -> System.out.println(str));
// 大小写转换
list.stream().map(String::toUpperCase)
.forEach(str -> System.out.println(str));
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x)
.summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
}
}
class Person {
private Integer age;
private Integer number;
public Person(Integer age, Integer number) {
super();
this.age = age;
this.number = number;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@Override
public String toString() {
return "Person [age=" + age + ", number=" + number + "]";
}
}
5. Java 8 Date-Time
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
1. 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
2. 设计很差 −Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和
解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入
java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
3. 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,
但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API
Local(本地) − 简化了日期时间的处理,没有时区的问题。
Zoned(时区) − 通过制定的时区处理日期时间。
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testLocalDateTime();
}
public void testLocalDateTime(){
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
当前时间: 2016-04-15T16:55:48.668
date1: 2016-04-15
月: APRIL, 日: 15, 秒: 48
date2: 2012-04-10T16:55:48.668
date3: 2014-12-12
date4: 22:15
date5: 20:15:30
使用时区的日期时间API
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testZonedDateTime();
}
public void testZonedDateTime(){
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
ZoneId: Europe/Paris
当期时区: Asia/Shanghai