Java8的新特性

未完待续

Lambda表达式

函数式接口

在Java8中,引入了函数式接口的概念,函数式接口是一个只有一个抽象方法的接口,通常用于Lambda表达式和方法引用,函数式接口可以有多个默认方法静态方法,但是必须只有一个抽象方法

1.语法

@FunctionalInterface
public interface MyPredicate<T> {
    
    boolean fun(T obj);
    
    default void other() {
        System.out.println("hello world");
    }

    static void staticMethod() {
        System.out.println("static method");
    }
}

@FunctionalInterface注解:这是一个可选的注解,它可以帮助编译器在编译时检查接口是否符合函数式接口的要求,即是否只有一个抽象方法,如不符合还加这个注解,会导致编译器报错。

2.函数式接口的使用

编写一个实体类Employee

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Employee {
    private String name;
    private Double salary;
    private Integer age;

    public Employee(Integer age) {
        this.age = age;
    }

    public Employee(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
}

新增一个按条件过滤的方法filter,将List<Employee>作为第一个参数,函数式接口MyPredicate<Employee>作为第二个参数传进filter()方法,方法体内循环将每个Employee对象一一作为参数传入接口的抽象方法fun()中,并调用,根据抽象方法运行后得到的布尔值判断是否过滤掉。

private List<Employee> filter(List<Employee>employees, MyPredicate<Employee> predicate) {
    List<Employee>list = new ArrayList<>();
    for (Employee e : employees) {
        if (predicate.fun(e)) {
            list.add(e);
        }
    }
    return list;
}

声明一个员工集合employees,插入5个对象,然后调用filter()方法,将employees作为第一个参数传入,然后直接new一个实现MyPredicate接口抽象方法的匿名内部类作为第二个参数传入,这样一来,调用时既告诉了目标方法filter()要处理的数据是employees,也一并将数据的具体处理规则obj.getAge() > 16告诉了目标方法,调用同一个方法可以有无数种处理数据的策略,这个实际上就是一种典型的策略模式,实际上Java8已经为我们写好了一种策略模式的函数式接口。

private List<Employee> employees = Arrays.asList(
    new Employee("soo", 8547.322, 17),
    new Employee("lili", 1000D, 15),
    new Employee("王萌", 2154D, 16),
    new Employee("张帆", 8547.322, 22),
    new Employee("goog", 353D, 12)
);

@Test
public void test3() {

    List<Employee>list = filter(employees, new MyPredicate<Employee>() {
        @Override
        public boolean fun(Employee obj) {
            return obj.getAge() > 16;
        }
    });

    System.out.println(list);
}

Java8中,通过将策略接口实现简写为Lambda表达式的方式,可以使得语法显得更加简洁

List<Employee>list2 = filter(employees, (e) -> e.getAge() < 16);

3.常用的内置函数式接口

Java8提供了一些预定义的函数式接口,位于java.util.function包中

  • java.util.function.Consumer 消费
  • java.util.function.Supplier 供给
  • java.util.function.Function 函数
  • java.util.function.Predicate 断言
  • java.util.function.BinaryOperator 不常用
  • java.util.function.UnaryOperator 不常用

编写4个将函数式接口作为参数的方法

private void testConsumer(String str, Consumer<String>consumer) {
    consumer.accept(str);
}

private String testSupplier(Supplier<String>supplier) {
    return supplier.get();
}

private Integer testFunction(String str, Function<String, Integer>function) {
    return function.apply(str);
}

private boolean testPredicate(String str, Predicate<String>predicate) { 
    return predicate.test(str); 
}

分别调用这些方法,按照业务逻辑通过匿名内部类的lambda表达式写法实现函数式接口的抽象方法,作为参数传入

@Test
public void test4() {

    testConsumer("hello lambda", (x) -> System.out.println(x));

    String str = testSupplier(() -> { return "hello world"; });
    System.out.println(str);

    Integer integer = testFunction("66", (x) -> Integer.valueOf(x));
    System.out.println(integer);

    boolean b = testPredicate("hello", (e) -> e.equals("hello"));
    System.out.println(b);

}

得到运行结果

hello lambda
hello world
66
true

还可以通过lambda表达式的方法引用和构造器引用将调用修改的更简洁一些

@Test
public void test2() {
    testConsumer("hello lambda", System.out::println);

    Integer integer = testFunction("66", Integer::valueOf);
}

Stream API

接口的默认方法

Java8前的接口,只能有两个成员,全局静态常量和抽象方法,Java8引入了接口的默认方法和静态方法作为新特性,它们的引入是为了增强接口的功能,特别是在接口的扩展性和灵活性方面。

接口中的默认方法,使用default修饰符修饰,可以带有实现,实现类可以直接继承使用,实现类可以选择重写默认方法,也可以直接使用。

接口中的静态方法只能通过接口名调用,不能通过接口的实现类或实例调用,为接口提供相关的工具性功能,而不需要依赖具体的实现类,静态方法不会被实现类继承,也不能被实现类重写。

案例1:接口的默认方法和静态方法

编写一个接口test.testinterface.MyInterface,拥有两个默认方法test()hello()和一个静态方法helloworld()

package test.testinterface;

public interface MyInterface {
    
    default String test() {
        System.out.println("default");
        return "default";
    }

    default void hello() {
        System.out.println("my interface");
    }
    
    static void helloworld() {
        System.out.println("hello java8!!!");
    }
}

编写一个类test.testinterface.SubClass,实现接口MyInterface

package test.testinterface;

public class SubClass  implements MyInterface {

    public static void main(String[] args) {
        SubClass subClass = new SubClass();
  
        subClass.hello();

        MyInterface.helloworld();
    }
}

不实现接口里面的hello()方法也可以直接调用默认方法hello(),而且可以通过接口名直接调用接口的静态方法helloworld(),程序输出:

my interface
hello java8!!!

案例2:默认方法冲突

编写另一个接口test.testinterface.OtherInterface,并实现一个默认方法hello

package test.testinterface;

public interface OtherInterface {
    default void hello() {
        System.out.println("other interface");
    }
}

令类test.testinterface.SubClass再实现一个接口OtherInterface,该接口含有和接口MyInterface一样定义的default方法hello(),就产生了接口冲突,当实现的多个接口中有相同签名的默认方法时,子类必须显式重写冲突的方法hello(),最终程序输出结果:”sub hello!”

package test.testinterface;

public class SubClass implements MyInterface, OtherInterface {
    /**
     * 多实现方法冲突,实现类必须实现
     **/
    @Override
    public void hello() {
        System.out.println("sub hello!");
    }

    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.hello();
    }
}

案例3:类优先

编写一个类test.testinterface.MyClass,里面有一个方法String test(),并让SubClass类继承它,并执行subClass.test();,得到输出结果:”class”,但是SubClass实现的接口MyInterface里面也有个方法String test(),却没有被执行,而是执行了类里面的方法,说明类优先,如果类或其父类中已经提供了方法实现,则优先使用类的实现,而不是接口的默认方法。

package test.testinterface;

public class MyClass  {
    public String test() {
        System.out.println("class");
        return "class";
    }
}
package test.testinterface;

public class SubClass extends MyClass implements MyInterface, OtherInterface {
    // 多实现方法冲突,实现类必须实现
    @Override
    public void hello() {
        System.out.println("sub hello!");
    }

    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        // 类优先原则, 继承类的方法
        subClass.test();
    }
}

新的日期和时间API (java.time)

旧API的线程安全问题

旧的日期时间工具类java.text.SimpleDateFormat存在线程安全问题,例如SimpleDateFormat线程不安全,内部依赖一个Calendar实例来解析和格式化日期,而Calendar是线程不安全的,多线程格式化会并发更新Calendar状态会导致出现异常。

以下代码使用100个线程并发调用一个format对象进行日期解析操作,会导致出现错误。

package test.time;

import java.text.SimpleDateFormat;

public class Test1 {

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

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(format.parse("20191231"));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        for (int i=0; i<100; i++) {
            new Thread(r, "t"+i).start();
        }

    }
}

可以采取同步块,线程单独持有format对象,以及线程池内使用ThreadLocal的办法解决。采用同步代码块时,只能有一个线程执行parse方法,可以避免线程安全问题。

package test.time;

import java.text.SimpleDateFormat;

public class Test1 {

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

        Runnable r = new Runnable() {
            @Override
            public void run() {
                synchronized (format) {
                    try {
                        System.out.println(format.parse("20191231"));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }

            }
        };

        for (int i=0; i<100; i++) {
            new Thread(r, "t"+i).start();
        }

    }
}

采用线程独自持有format对象的方法解决,每个线程执行时创建一个format对象,每个线程单独持有,防止线程安全问题。

package test.time;

import java.text.SimpleDateFormat;

public class Test1 {

    public static void main(String[] args)  {

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20191231"));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        for (int i=0; i<100; i++) {
            new Thread(r, "t"+i).start();
        }

    }
}

线程池+ThreadLocal,10个线程同时派发100个格式化任务,可以为每个线程绑定一个format对象,各自使用,也可以避免线程安全问题。

package test.time;


import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class Test1 {

    
    public static void main(String[] args)  {

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(threadLocal.get().parse("20191231"));
                }
                catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };


        for (int i=0; i<100; i++) {
            executorService.submit(runnable, "t"+i);
        }

        executorService.shutdown();

    }
}

新的日期时间API

Java 8通过发布新的Date-TimeAPI(JSR310)进一步加强了对日期与时间的处理。

首先,在旧版的Java中,日期时间API存在诸多问题,首先java.util.Date是非线程安全的,所有的日期类都是可变的。

其次,Java的日期/时间类的定义并不一致,在java.utiljava.sql的包中都有日期类,负责格式化和解析的类又在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,而且放进sql包下并不合理。

而且,无法更好的处理时区,日期类不能国际化,没有时区支持,因此Java引入了java.util.Calendarjava.util.TimeZone,但是它们仍然存在一样的问题。

于是Java8引入了新的日期时间API,位于java.time包下,该包下有几个重要的类:

  • java.time.Instant 时间戳
  • java.time.Duration 时间差
  • java.time.LocalDate 只包含日期,例如2011-07-11
  • java.time.LocalTime 只包含时间,例如09:00:01
  • java.time.LocalDateTime 同时包含日期和时间,例如2024-11-30 04:09:45
  • java.time.Period 时间段
  • java.time.OffsetDateTime 带有时区偏移量的日期和时间,是LocalDateTime和ZoneOffset的结合体,更适用于需要精确到时间和偏移量的场景,尤其当你关心的只是某个时间点相对于 UTC 的偏移。例如,在处理需要表示时间差(例如时间戳、系统日志等)时,OffsetDateTime 比较合适。
  • java.time.ZoneOffset 时区偏移量,比如+8:00
  • java.time.ZonedDateTime 带有时区的日期和时间,是LocalDateTimeZoneId的组合,ZonedDateTime更适用于需要考虑时区历史和夏令时等复杂问题的场景。例如,如果你需要表示某个特定时区(如America/New_York)的时间,并且要处理夏令时,ZonedDateTime会更加准确
  • java.time.Clock 时钟
package test.time;

import org.junit.Test;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.*;
import java.util.Date;

public class Test4 {

    /**
     * java8 API获取当前时间
     */
    @Test
    public void current() {
        Instant instant = Instant.now();

        LocalDate localDate = LocalDate.now();

        LocalTime localTime = LocalTime.now();

        LocalDateTime localDateTime = LocalDateTime.now();

        ZonedDateTime zonedDateTime = ZonedDateTime.now();

        System.out.println(instant);
        System.out.println(localDate);
        System.out.println(localTime);
        System.out.println(localDateTime);
        System.out.println(zonedDateTime);

    }


    
    /**
     * Instant的常见方法
     */
    @Test
    public void testInstant() {
        //通过Instant获取当前时间戳,格林威治时间
        Instant now = Instant.now();
        System.out.println(now);

        //添加时区,转换为带时区的时间:OffsetDateTime
        OffsetDateTime us = now.atOffset(ZoneOffset.ofHours(-4));
        System.out.println(us);//US

        //设置偏移量
        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(+8));
        System.out.println(offsetDateTime);//CN
        System.out.println(now.atOffset(ZoneOffset.ofHours(+9)));//JP
        System.out.println(now.atOffset(ZoneOffset.ofHours(+10)));//AU

        //根据给定的Unix时间戳(即自1970年1月1日00:00:00 UTC起的秒数)创建一个Instant对象
        Instant instant = Instant.ofEpochSecond(1);//开始于1970
        System.out.println(instant);

        //设置时区
        ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("GMT+9"));
        LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
        System.out.println(localDateTime);
    }

    /**
     * LocalDateTime LocalDate LocalTime 的常见方法和使用
     */
    @Test
    public void testLocalDateTime() {

        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);

        //构造时间
        LocalDateTime localDateTime = LocalDateTime.of(2019,8,8,12,23,50);
        System.out.println(localDateTime);

        //从LocalDate和LocalTime构造时间
        System.out.println(LocalDateTime.of(LocalDate.now(), LocalTime.now()));

        // 获取年月日时分秒
        System.out.println(localDateTime.getYear());
        System.out.println(localDateTime.getDayOfYear());
        System.out.println(localDateTime.getDayOfMonth());

        //星期
        DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
        System.out.println(dayOfWeek);

        //当前时间的纳秒部分,表示这个时间点内的精细时间
        System.out.println(localDateTime.getNano());

        //时间计算
        System.out.println(LocalDateTime.now().plusMonths(2));
        System.out.println(LocalDateTime.now().minusYears(2));
        System.out.println(LocalDateTime.now().plusHours(24));
        System.out.println(LocalDateTime.now().plusNanos(500));

        System.out.println(LocalDateTime.now().plusYears(2).plusMonths(8).plusDays(9));

        // Period.of 用于创建一个表示特定时间间隔的Period对象
        System.out.println(LocalDateTime.now().plus(Period.of(3, 5, 20))); ;

        // ChronoUnit.DECADES代表十年
        System.out.println(LocalDateTime.now().plus(3, ChronoUnit.DECADES)) ;


        // 时间修改
        System.out.println(LocalDateTime.now().withMonth(2));
        System.out.println(LocalDateTime.now().withDayOfMonth(25));
        System.out.println(LocalDateTime.now().withSecond(22));
        System.out.println(LocalDateTime.now().with(ChronoField.DAY_OF_MONTH, 2));
        System.out.println(LocalDateTime.now().with(ChronoField.MONTH_OF_YEAR, 8));

        // LocalDate LocalTime
        System.out.println(LocalDate.of(2020, 1, 19));
        System.out.println(LocalDate.of(2020, Month.AUGUST, 19));
        System.out.println(LocalDate.of(2020, Month.of(12), 19));
        System.out.println(LocalTime.of(20, 0));

        System.out.println(LocalDate.now().withMonth(8));
        System.out.println(LocalDate.of(2020, Month.AUGUST, 19).plusDays(5));
        System.out.println(LocalDate.of(2020, Month.of(12), 19));


        System.out.println( LocalTime.of(20, 0).plusHours(8) );

        // LocalDate的方法,判断当前年份是否为闰年
        System.out.println(LocalDate.now().isLeapYear());

    }

    /**
     * TemporalAdjusters 时间校正器
     */
    @Test
    public void testTemporalAdjusters() {
        // 下一个周四
        LocalDateTime dateTime = LocalDateTime.now();

        dateTime.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));
        System.out.println(dateTime);

        dateTime.with(TemporalAdjusters.previous(DayOfWeek.THURSDAY));
        System.out.println(dateTime);

        dateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY));
        System.out.println(dateTime);

        dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.THURSDAY));
        System.out.println(dateTime);

        System.out.println(LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)));

        // 获取月份第一天
        System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()));


        System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()));

        // 自定义 计算下一个工作日
        LocalDateTime nextWorkDay = LocalDateTime.now().with((e) -> {
            LocalDateTime temp = LocalDateTime.from(e);
            DayOfWeek dayOfWeek = temp.getDayOfWeek();
            if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
                return temp.plusDays(3);
            } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
                return temp.plusDays(2);
            } else {
                return temp.plusDays(1);
            }
        });

        System.out.println(nextWorkDay);
    }



    public void test() {
        System.out.println(Year.now());
        System.out.println(YearMonth.now());
        System.out.println(MonthDay.now());
    }


    /**
     * 计算时间间隔:武汉封了多少天,多少小时
     */
    @Test
    public void testChronoUnit() {

        LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 23, 10, 0,0);
        LocalDateTime to = LocalDateTime.of(2020, Month.APRIL, 8, 0, 0,0);

        long days = ChronoUnit.DAYS.between(from, to);
        long hours = ChronoUnit.HOURS.between(from, to);

        System.out.println( days );
        System.out.println( hours );
    }


    /**
     * 使用 TemporalQuery 来计算当前时间与一个指定时间点(2020年1月19日10:00:00)之间的小时差,
     * 并将其作为 long 类型的值返回
     */
    @Test
    public void testTemporalQuery() {
        long l = LocalDateTime.now().query(new TemporalQuery<Long>() {

            @Override
            public Long queryFrom(TemporalAccessor temporal) {
                LocalDateTime now = LocalDateTime.from(temporal);
                LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 19, 10, 0,0);
                return ChronoUnit.HOURS.between(from, now);
            }
        });

        System.out.println(l);
    }


    /**
     * Duration类,只能计算时间差异
     */
    @Test
    public void testDurationPeriod() {
        LocalTime start = LocalTime.of(20, 0);
        LocalTime end = LocalTime.of(21, 30);

        // 时间间隔
        Duration between = Duration.between(start, end);
        System.out.println(between.toHours());
        System.out.println(between.toMinutes());

    }


    /**
     * 格式化 DateTimeFormatter
     */
    @Test
    public void testDateTimeFormatter() {

        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
        System.out.println(LocalDateTime.now().format(formatter));

        LocalDate localDate = LocalDate.parse("2009-12-31", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        System.out.println(localDate);

        LocalDateTime localDateTime = LocalDateTime.parse("2009-12-31 01:01:02", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println(localDateTime);

        // 2024年12月1日 星期日
        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));

        // 2024年12月1日
        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));

        // 24-12-1
        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));

        // 2024-12-1
        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
    }


    @Test
    public void getAvailableZoneIds() {
        // 当前系统时区
        System.out.println(ZoneId.systemDefault());

        // 打印java8中所有支持时区
        ZoneId.getAvailableZoneIds().forEach(System.out::println);
    }


    /**
     * OffsetDateTime
     */
    @Test
    public void testOffsetDateTime() {
        OffsetDateTime offsetDateTime = new Date().toInstant().atOffset(ZoneOffset.of("-4"));
        System.out.println(offsetDateTime);
        System.out.println(offsetDateTime.toLocalDateTime());

        OffsetDateTime of = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4"));
        System.out.println(of);

    }

    /**
     * ZonedDateTime
     */
    @Test
    public void testZonedDateTime() {
        // 当前时间转换为东京时间是几时
        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println(zonedDateTime);
        System.out.println(zonedDateTime.toLocalDateTime());

        ZonedDateTime of = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Tokyo"));
        System.out.println(of);

        // 为当前时间带上时区
        ZonedDateTime tokyo = LocalDateTime.now().atZone(ZoneId.of("Asia/Tokyo"));
        System.out.println(tokyo);
        System.out.println(tokyo.toLocalDateTime());

        // 将一个时区时间转换为同一时刻另一个时区时间
        ZonedDateTime beijing = tokyo.withZoneSameInstant(ZoneId.of("GMT+8"));
        System.out.println(beijing);

        ZonedDateTime usa = LocalDateTime.now()
                .atZone(ZoneId.systemDefault())
                .withZoneSameInstant(ZoneId.of("GMT-4"));

        System.out.println(usa);

    }

}

新API和旧的Date之前的互转

package test.time;

import org.junit.Test;

import java.sql.Timestamp;
import java.time.*;
import java.util.Calendar;
import java.util.Date;

public class Test5 {

    /**
     * 将 LocalDateTime 和系统默认时区结合,转换为 ZonedDateTime
     * 再将 ZonedDateTime 转换为 Instant,这是一个包含 UTC 时间戳的对象。
     * Date.from():将 Instant 转换为 java.util.Date 对象
     */
    @Test
    public void toDate() {
        LocalDateTime localDateTime = LocalDateTime.now();
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date date = Date.from(instant);
        System.out.println(date);
    }


    @Test
    public void toLocalDateTime() {
        Date date = new Date();
        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        System.out.println(dateTime);

    }


    /**
     * java.sql.Date 转换 LocalDateTime
     */
    @Test
    public void sqlDate() {
        java.sql.Date date = new java.sql.Date(System.currentTimeMillis());
        LocalDate localDate = date.toLocalDate();
        System.out.println(localDate);

        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
        LocalDateTime localDateTime = timestamp.toLocalDateTime();
        System.out.println(localDateTime);

    }

    /**
     * Calendar 转换 LocalDateTime
     */
    @Test
    public void calendarToLocalDateTime() {
        Calendar calendar = Calendar.getInstance();
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
        System.out.println(zonedDateTime.toLocalDateTime());

    }

}

Optional

java.util.Optional是一个容器类,用来表示可能包含或者不包含值的对象。它提供了一种优雅的方式来避免出现空指针,从而帮助开发者更安全、更清晰地处理可能为NULL的值。

1.创建一个Optional对象

包装一个非空的值,如果传入的变量为null会直接抛出空指针异常,如果直接写死null进去,IDEA可能直接编译出错

Optional<String> optional = Optional.of("Hello, World!");

ofNullable方法允许填充一个可能为空的值进去

Optional<String> optional = Optional.ofNullable(null);

空的Optional对象

Optional<String> optional = Optional.empty();

2.检查值是否存在

可以使用isPresent()方法判断

Optional<String> optional = Optional.empty();

if (optional.isPresent()) {
    System.out.println("Value: " + optional.get());
} else {
    System.out.println("No value present");
}

还可以采用ifPresent()避免if显式调用

optional.ifPresent(value -> System.out.println("Value: " + value));

3.默认值

如果为空,提供一个默认值

Optional<String> optional = Optional.empty();

String value = optional.orElse("Default Value");

还可以通过提供的Supplier函数式接口生成默认值

Optional<String> optional = Optional.empty();

String value = optional.orElseGet(() -> "Generated Default Value");
// optional.orElseGet(String::new);

如果值不存在,可以抛出自定义异常

Optional<String> optional = Optional.empty();

String value = optional.orElseThrow(() -> new RuntimeException("Value is missing!"));

4.转换

map() 如果有值进行处理,并返回处理后的Optional对象,否则返回Optional.empty()

空值,不执行输出

Optional<String> optional = Optional.empty();
Optional<String> upperCase = optional.map(String::toUpperCase);
upperCase.ifPresent(System.out::println); 

非空,处理后返回新的Optional,输出:HELLO WORLD

Optional<String> optional = Optional.ofNullable("hello world");

Optional<String> upperCase = optional.map(String::toUpperCase);

upperCase.ifPresent(System.out::println); 

使用flatMap()进一步防止空指针异常,如果optional中的值为null,flatMap()直接返回Optional.empty(),否则,它返回一个包含e.getName()的Optional对象

Employee employee = new Employee();

employee.setName("XXX");

Optional<Employee> optional = Optional.ofNullable(employee);

Optional<String> s = optional.flatMap((e) -> Optional.of(e.getName()));

s.ifPresent(System.out::println);

重复注解 (Repeating Annotations)

1.首先创建一个容器注解,这个注解类型包含一个注解数组,存储多个相同类型的注解

package test.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

2.定义一个重复注解,并使用@Repeatable标记

package test.anno;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import static java.lang.annotation.ElementType.TYPE_PARAMETER; // 类型注解

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value() default "hello world";
}

3.测试,通过反射访问方法上的注解,由于MyAnnotation是重复注解,所以一个方法加上多个也不会语法报错,然后提取其中的多个MyAnnotation注解。

package test.anno;

import java.lang.reflect.Method;
import java.util.Arrays;

public class TestAnnotation {
    
    @MyAnnotation("hello")
    @MyAnnotation("world")
    public void test(String s) {
        
    }

    public static void main(String[] args) {
        Class<TestAnnotation> clazz = TestAnnotation.class;
        try {
            Method method = clazz.getMethod("test", String.class);
            MyAnnotation[] annotations = method.getAnnotationsByType(MyAnnotation.class);
            Arrays.stream(annotations).map(MyAnnotation::value).forEach(System.out::println);
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
        
        
    }
}

Nashorn JavaScript引擎


Java8的新特性
https://blog.liuzijian.com/post/86955c3b-9635-47a0-890c-f1219a27c269.html
作者
Liu Zijian
发布于
2022年6月20日
许可协议