Java8新特性_其他总结

212 阅读11分钟

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

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 NumberFormatExceptionNullPointerException等),以提高代码质量。这就是类型注解的作用。

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)-> 直接使用物理内存 -> 加载类文件