一、接口的默认方法和静态方法
1、默认方法
早期JDK版本,接口类中只允许有全局静态常量和抽象方法。Java JDK8发布后,我们可以使用default关键字在接口类添加非抽象的方法,改功能也称作虚拟扩展方法。
public interface AnimalInterface {
void sound();
default void run() {
System.out.println("animal is running");
}
}
接口默认方法的“类优先”原则:
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
eg.AnimalInterface接口中定义了sound默认方法,而父类DogClass提供了sound的实现方法,则Husky会实现父类的方法。
public interface AnimalInterface {
default void sound() {
System.out.println("animal is sound");
}
}
public class DogClass {
public void sound() {
System.out.println("dog is sound");
}
}
public class Husky extends DogClass implements AnimalInterface{
public static void main(String[] args) {
Husky husky = new Husky();
// 输出:dog is sound
husky.sound();
}
}
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。
eg.AnimalInterface接口中定义sound默认方法,而另一个接口CatInterface也写个相同方法,这时候实现这两个接口的Persian类就需要重新实现sound方法。
public interface AnimalInterface {
default void sound() {
System.out.println("animal is sound");
}
}
public interface CatInterface {
void sound();
}
public class Persian implements AnimalInterface, CatInterface{
@Override
public void sound() {
System.out.println("Persian is sound");
}
public static void main(String[] args) {
Persian persian = new Persian();
// 输出:Persian is sound
persian.sound();
}
}
2、静态方法
在Java JDK8版本后,在接口中可以添加静态方法。
AnimalInterface接口中定义了sound静态方法,调用时直接使用类名+静态方法名来使用。
public interface AnimalInterface {
static void sound() {
System.out.println("animal is sound");
}
}
public class DogClass {
public static void main(String[] args) {
AnimalInterface.sound();
}
}
二、方法和构造函数引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法,提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。
在Java JDK8中,我们会使用Lambda表达式创建匿名方法,但是有时候我们仅仅是调用该方法而已并没有其他操作,所以引入了方法引用这个特性,提高代码可读性。
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
// 猫实体
public class Cat {
private String name;
private Integer age;
// 比较猫年龄方法
public static int compareByAge(Cat cat1, Cat cat2) {
return cat1.age.compareTo(cat2.age);
}
// 猫的sound方法
public String sound() {
return "Miao~";
}
// 猫的run方法
public static String run(String catName) {
return catName + " is run!";
}
// 猫的isAdult方法
public boolean isAdult(int age) {
if (this.age >= age) {
return true;
}
return false;
}
}
演变过程
// 对猫数组数据,根据年龄进行排序
public class CatTest {
Cat[] cats = new Cat[] {
new Cat("black", 2),
new Cat("orange", 1),
new Cat("persia", 3)
};
@Test
public void test() {
// 匿名内部类
Arrays.sort(cats, new Comparator<Cat>() {
@Override
public int compare(Cat o1, Cat o2) {
return o1.getAge().compareTo(o2.getAge());
}
});
}
@Test
public void test2() {
// Lambda表达式,普通方法
Arrays.sort(cats, (Cat o1, Cat o2) -> {
return o1.getAge().compareTo(o2.getAge());
});
}
@Test
public void test3() {
// Lambda表达式,Cat类的静态compareByAge方法
Arrays.sort(cats, (o1, o2) -> Cat.compareByAge(o1, o2));
}
@Test
public void test4() {
// Lambda表达式,方法引用
Arrays.sort(cats, Cat::compareByAge);
}
}
方法引用,若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用时Lambda表达式的另一种表现形式),其中方法引用的操作符是双冒号::。
方法引用分类
在双冒号的左边是类名或者某个对象的引用,二右边是方法名,方法引用主要有以下五种方式:
- 对象引用::实例方法名
- 类名::静态方法名
- 类名::实例方法名
- 类名::new
- 类型[]::new
public static void main(String[] args) {
/**
* 1.对象引用::实例方法名
*/
Cat catEntity = new Cat("black", 2);
Supplier catSoundSupplier = catEntity::sound;
System.out.println(catSoundSupplier.get());
// 打印结果:Miao~
/**
* 2.类名::静态方法名
*/
Function<String, String> catRunFaction = Cat::run;
System.out.println(catRunFaction.apply("orange"));
// 打印结果:orange is run!
/**
* 3.类名::实例方法名
*/
BiPredicate<Cat, Integer> isAdultPredicate = Cat::isAdult;
System.out.println(isAdultPredicate.test(catEntity, 3));
// 打印结果:false
/**
* 4.类名::new
*/
Supplier<Cat> catConstructor = Cat::new;
Cat cat = catConstructor.get();
System.out.println(cat);
// 打印结果:Cat(name=null, age=null)
/**
* 5.类型[]::new
*/
Function<Integer, Cat[]> catArrayFunction = Cat[]::new;
Cat[] cats = catArrayFunction.apply(3);
Arrays.asList(cats).stream().forEach(System.out::println);
// 打印结果:null
// null
// null
}
三、重复注解与类型注解
Java JDK 8 对注解处理提供了两点改进:可重复的注解及可用于类型的注解。
1.重复注解
Java 8引入了重复注解的概念,允许在同一个地方使用同一个注解,使用@Repeatable注解定义重复注解。
注解类
@Repeatable(MyAnnotationContainer.class)
//注解容器类,需要这个才能使用重复注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
注解容器类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationContainer {
MyAnnotation[] value();
}
测试
@MyAnnotation("窗前明月光")
@MyAnnotation("疑是地上霜")
public void annotationFunction(){}
@MyAnnotationContainer({@MyAnnotation("举头望明月"), @MyAnnotation("低头思故乡")})
public void annotationFunction2(){}
public static void main(String[] args) throws NoSuchMethodException {
Class<AnnotationTest> clazz = AnnotationTest.class;
Method annotationFunction = clazz.getMethod("annotationFunction");
MyAnnotation[] annotations = annotationFunction.getAnnotationsByType(MyAnnotation.class);
Arrays.asList(annotations).stream().forEach(e -> System.out.println(e.value()));
// 打印结果:窗前明月光
// 疑是地上霜
Method annotationFunction2 = clazz.getMethod("annotationFunction");
MyAnnotation[] annotations2 = annotationFunction2.getAnnotationsByType(MyAnnotation.class);
Arrays.asList(annotations2).stream().forEach(e -> System.out.println(e.value()));
// 打印结果: 窗前明月光
// 疑是地上霜
}
2.类型注解
1)Java 8的类型注解扩展了注解使用的范围,从原来只能在生命的地方使用,变为了可以应用在任何地方。
类型注解只是语法而不是语义,并不会影响Java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。
2)Java 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
@Target的取值
- TYPE:标明该注解可以用于类、接口(包括注解类型)或enum声明
- FIELD:标明该注解可以用于字段(域)声明,包括enum实例
- METHOD:标明该注解可以用于方法声明
- PARAMETER:标明该注解可以用于参数声明
- CONSTRUCTOR:标明注解可以用于构造函数声明
- LOCAL_VARIABLE:标明注解可以用于局部变量声明
- ANNOTATION_TYPE:标明注解可以用于注解声明(应用于另一个注解上)
- PACKAGE:标明注解可以用于包声明
- TYPE_PARAMETER:标明注解可以用于类型参数声明(1.8新加入)
- TYPE_USER:类型使用声明(1.8新加入)
// 泛型
class MyClass<@Parameter T> { }
// 创建实例
new @Interned MyObject();
// 类型映射
myString = (@NonNull String) str;
// implements 语句中
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }
// throw exception声明
void monitorTemperature() throws @Critical TemperatureException { ... }
3)类型注解的使用
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework可以在编译的时候检测出Runtime Error(UnsupportedOperationException、 NumberFormatException、NullPointerException等),以提高代码质量。这就是类型注解的作用。
import org.checkerframework.checker.nullness.qual.*;
public class GetStarted {
void sample() {
@NonNull Object ref = null;
}
}
GetStarted.java:5: 错误: [assignment.type.incompatible] incompatible types in assignment.
@NonNull Object ref = null;
^
found : null
required: @UnknownInitialization @NonNull Object
1 个错误
四、 新时间日期API
1.多线程安全问题
在旧版的 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类,但他们同样存在上述所有的问题。
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
System.out.println(format.parse("2020-10-28 11:11:11"));
} catch (ParseException e) {
e.printStackTrace();
}
}, "thread" + i).start();
}
// 报错:Exception in thread "thread3" java.lang.NumberFormatException: For input string: "E282"
}
2.API演示
LocalDate、LocalTime、LocalDateTime
| 方法 | 描述 |
|---|---|
| now() | 静态方法,根据当前时间创建对象 |
| of() | 静态方法,根据指定日期/时间创建对象 |
| 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 | 判断是否是闰年 |
// LocalDate、LocalTime、LocalDateTime
public static void main(String[] args) {
// 1.获取当前日期时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
// 打印结果:2020-10-26T19:55:10.464
// 2.指定一个日期时间
LocalDateTime ldt2 = LocalDateTime.of(2020, 10, 26, 20, 11, 01);
System.out.println(ldt2);
// 打印结果:2020-10-26T20:11:01
// 3.当前日期时间加1年
System.out.println(ldt.plusYears(1));
// 打印结果:2021-10-26T20:26:08.794
// 4.当前日期时间减2月
System.out.println(ldt.minusMonths(2));
// 打印结果:2020-08-26T20:26:08.794
// 5.获取当前日式数据
System.out.println(ldt.getYear()); // 打印结果:2020
System.out.println(ldt.getMonthValue()); // 打印结果:10
System.out.println(ldt.getDayOfMonth()); // 打印结果:26
System.out.println(ldt.getHour()); // 打印结果:20
System.out.println(ldt.getMinute()); // 打印结果:29
System.out.println(ldt.getSecond()); // 打印结果:4
}
Instant
// Instant:时间戳(以Unix元年: 1970年1月1日00:00:00到某个时间之间的毫秒值)
public static void main(String[] args) {
// 获取默认时区
Clock defaultClock = Clock.systemDefaultZone();
System.out.println(defaultClock);
// 打印:SystemClock[Asia/Shanghai]
// 获取UTC时区的微秒数
Clock clock1 = Clock.systemUTC();
System.out.println(clock1.millis());
// 打印:1603894073531
// 获取UTC的初始时间
System.out.println(Instant.EPOCH);
// 打印:1970-01-01T00:00:00Z
// 获取默认UTC的当前时间
Instant ins = Instant.now();
System.out.println(ins);
// 打印:2020-10-28T10:50:20.991Z
// 添加8小时的偏移,获取东8区的时间
OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt.toLocalDateTime());
// 打印:2020-10-28T19:43:03.744+08:00
// 获取与Unix元年间隔毫秒数
System.out.println(ins.toEpochMilli());
// 打印:1603884542494
// 获取Unix元年后60s的时间
Instant instant = Instant.ofEpochSecond(60);
System.out.println(instant);
// 打印:1970-01-01T00:01:00Z
}
Duration、Period
// Duration:计算两个时间之间的间隔,Period:计算两个日期之间的间隔
public static void main(String[] args) throws InterruptedException {
// Instant
Instant ins1 = Instant.now();
TimeUnit.SECONDS.sleep(1);
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println(duration.toMillis());
// 打印:1001
// LocalTime
LocalTime lt1 = LocalTime.now();
TimeUnit.SECONDS.sleep(1);
LocalTime lt2 = LocalTime.now();
System.out.println(Duration.between(lt1, lt2).toMillis());
// 打印:1001
// LocalDate
LocalDate ld1 = LocalDate.of(2019, 1, 1);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1, ld2);
System.out.println(period.getYears()); // 打印:1
System.out.println(period.getMonths()); // 打印:9
System.out.println(period.getDays()); // 打印:28
}
Temporal Adjuster
// TemporalAdjuster:时间校验器
public static void main(String[] args) throws InterruptedException {
// 获取当前时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
// 打印:2020-10-29T10:47:35.952
// 修改为12月的时间
LocalDateTime ldt2 = ldt.withDayOfMonth(12);
System.out.println(ldt2);
// 打印:2020-10-12T10:47:35.952
// 修改为下周天的时间
LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(ldt3);
// 打印:2020-11-01T10:47:35.952
// 修改为下个工作日的时间
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-10-30T10:47:35.952
}
DateTimeFormatter
// DateTimeFormatter:格式化时间/日期
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
// 格式化当前时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.format(dtf));
// 打印:2020-10-29
// 自定义格式化格式
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String dateStr = dtf2.format(ldt);
System.out.println(dateStr);
// 打印:2020年10月29日 10时53分44秒
// 指定格式解析字符串获得LocalDateTime类型
LocalDateTime newDate = ldt.parse(dateStr, dtf2);
System.out.println(newDate);
// 打印:2020-10-29T10:53:44
}
3.旧版PAI转化
| 类 | 转旧版 | 转新版 |
|---|---|---|
| java.time.Instant java.util.Date | Date.from(instant) | date.toInstant() |
| java.time.Instant java.sql.Timestamp | Timestamp.from(instant) | timestamp.toInstant() |
| java.time.ZonedDateTime java.util.GregorianCalendar | GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
| java.time.LocalDate java.sql.Time | Date.valueOf(localDate) | date.toLocalDate() |
| java.time.LocalTime java.sql.Time | Date.valueOf(localDate) | date.toLocalTime() |
| java.timeLocalDateTime java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
| java.time.ZoneId java.util.TimeZone | Timezone.getTimeZone(id) | timeZone.toZoneId() |
| java.time.format.DateTimeFormatter java.text.DateFormat | formatter.toFormat() | 无 |
五、Java 8 新特性总结
-
Lambda 表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
-
Stream API
新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
-
Optional 类
Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
-
默认方法
默认方法就是一个在接口里面有了一个实现的方法。
-
方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
Date Time API
加强对日期与时间的处理。
-
底层数据结构修改,速度更快
- HashMap(数组-链表-红黑树)
- HashSet、ConcurrentHashMap(CAS算法)
-
修改垃圾回收机制
取消堆中的永久区(PremGen)-> 回收条件苛刻,使用元空间(MetaSpace)-> 直接使用物理内存 -> 加载类文件