持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 2 天,点击查看活动详情
5、Optional
5.1 常用方法
5.1.1 of
为非null的值创建一个Optional。of方法通过工厂方法创建Optional类。需要注意的是,创建对象时传入的参数不能为null。如果传入参数为null,则抛出 NullPointerException 。
5.1.2 ofNullable
为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。
5.1.3 isPresent
如果值存在返回true,否则返回false。
5.1.4 get
如果Optional有值则将其返回,否则抛出NoSuchElementException。
@Test
public void isPresentTest() throws Exception {
String name = "present";
Optional<String> optional = Optional.of(name);
if (optional.isPresent()){
System.out.println(optional.get());
}
}
5.1.5 ifPresent
如果Optional实例有值则为其调用consumer,否则不做处理。
5.1.6 orElse
如果有值则将其返回,否则返回指定的其它值。
5.1.7 orElseGet
orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。示例如(代码示例 5.1.8 )。
5.1.8 orElseThrow
如果有值则将其返回,否则抛出supplier接口创建的异常。 代码示例 5.1.8 :
@Test
public void orElseTest() throws Exception {
String v1 = null;
String v2 = "orElseTest";
Optional<String> o1 = Optional.ofNullable(v1);
Optional<String> o2 = Optional.ofNullable(v2);
//orElse
System.out.println(o1.orElse("v1 的值为 null"));
//orElseGet
System.out.println(o2.orElseGet(() -> "v2 的值为 null"));
//orElseThrow
try {
o1.orElseThrow(() -> new RuntimeException("v1 null "));
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
5.1.9 map
如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。 map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。
5.1.10 flatMap
如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。 flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。
@Test
public void mapTest() throws Exception {
// map
Optional<String> optional = Optional.ofNullable(null);
optional = optional.map(v -> v.toUpperCase());
System.out.println(optional.orElse("null"));
// flatMap
Optional<String> o = Optional.of("world");
o = o.flatMap(v1 -> Optional.of(v1.toUpperCase()));
System.out.println(o.orElse("null"));
}
5.1.11 filter
如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。对于filter函数我们应该传入实现了Predicate接口的lambda表达式,filter个方法通过传入限定条件对Optional实例的值进行过滤。
@Test
public void filterTest() throws Exception {
Optional<String> optional = Optional.of("hello");
System.out.println(optional.filter(v -> v.length()> 3).orElse("字符串长度小于等于3"));
System.out.println(optional.filter(v -> v.length() > 10).orElse("字符串长度小于等于10"));
}
5.2 应用
Optional 解决NPE(java.lang.NullPointerException)问题,它就是为NPE而生的,其中可以包含空值或非空值。
@Test
public void test() throws Exception {
Zoo zoo = new Zoo();
Optional.ofNullable(zoo).map(o -> o.getDog()).map(dog -> dog.getAge()).ifPresent(
age -> System.out.println(age)
);
Zoo zoo1 = new Zoo();
Dog dog = new Dog();
dog.setAge(23);
zoo1.setDog(dog);
Optional.ofNullable(zoo1).map(Zoo::getDog).map(Dog::getAge).ifPresent(System.out::println);
}
class Zoo {
private Dog dog;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
class Dog {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
解决空集合获取元素下标越界异常:
@Test
public void nullTest() throws Exception {
List<Map<String,String>> list = new ArrayList<Map<String,String>>();
Map<String,String> m1 = new HashMap<String,String>();
m1.put("a", "b");
list.add(m1);
Optional.ofNullable(list).map(v -> v.isEmpty() ? null : v.get(0)).ifPresent(map -> System.out.println(map));
}
6、Date-Time API
这是对 java.util.Date 强有力的补充,解决了Date类大部分痛点:
- 非线程安全
- 时间处理麻烦
- 各种格式化、时间计算繁琐
- 设计缺陷,Date类同时包含日期和时间;还有一个 java.sql.Date 容易混淆
6.1 java.time 主要的类
- LocalDateTime.class // 日期+时间 yyyy-MM-ddTHH:mm:ss.SSS
- LocalTime.class // 时间 yyyy-MM-dd
- LocalDate.class // 日期 HH:mm:ss
6.2 格式化
@Test
public void formatTest() throws Exception {
LocalDate date = LocalDate.now();
System.out.printf("date format: %s%n", date);
//.withNano(0) 设置纳秒
LocalTime time = LocalTime.now().withNano(0);
System.out.printf("time format: %s%n", time);
//yyyy-MM-dd HH:mm:ss
LocalDateTime dateTime = LocalDateTime.now();
System.out.printf("default dateTime format: %s%n",dateTime);
String format = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.printf("datetime format: %s%n",format);
}
6.3 字符串转日期格式
Java 8 之前 转换都需要借助 SimpleDateFormat 类,而Java 8 之后只需要 LocalDate、LocalTime、LocalDateTime的 of 或 parse 方法。
@Test
public void strToDateTimeTest() throws Exception {
LocalDate of = LocalDate.of(2022, 10, 8);
LocalDate parse = LocalDate.parse("2022-10-01");
LocalDateTime dateTime = LocalDateTime.of(2022,10,1,11,12,18);
LocalDateTime parse1 = LocalDateTime.parse("2022-10-09 12:23:22");
}
6.4 日期计算
下面仅以一周后日期为例,其他单位(年、月、日、1/2 日、时等等)大同小异。另外,这些单位都在 java.time.temporal.ChronoUnit 枚举中定义。
@Test
public void computeDateTest() throws Exception {
//TODO:计算一周后的日期
LocalDate localDate = LocalDate.now();
// 方式1
LocalDate after1 = localDate.plus(1, ChronoUnit.WEEKS);
// 方式2
LocalDate after2 = localDate.plusWeeks(1);
System.out.println("一周后的日期:"+after1);
//TODO:计算两个日期间隔多少天
oday.with(TemporalAdjusters.firstDayOfMonth());
System.out.printf("当月第一天:%s%n",firstDayOfMonth);
// 当月最后一天
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.printf("当月最后一天:%s%n",lastDayOfMonth);
// 当年最后一天
LocalDate oldDate = LocalDate.parse("1998-09-29");
// 方式1 间隔几年几月几天
Period period = Period.between( oldDate,localDate);
System.out.printf("localDate 与 oldDate 间隔: %s年%s月%s天%n",period.getYears(), period.getMonths(), period.getDays());
// 方式2 总天数
long day = localDate.toEpochDay() - oldDate.toEpochDay();
System.out.println(localDate+"和"+oldDate+" 相差"+day+"天");
}
6.5 获取指定日期
@Test
public void getDate() throws Exception {
LocalDate today = LocalDate.now();
// 明天
LocalDate plusDays = today.plusDays(1);
System.out.printf("明天:%s%n",plusDays);
// TemporalAdjusters 时间调整器
// 当月第一天
LocalDate firstDayOfMonth = t
System.out.printf("当年最后一天:%s%n",today.with(TemporalAdjusters.lastDayOfYear()));
//TODO: 2021年最后一个周日
LocalDate with = LocalDate.parse("2022-12-10").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
System.out.printf("2021年最后一个周日:%s%n",with);
}
6.6 JDBC 与 Java 8 时间类型对应关系
现在 jdbc 时间类型和 java8 时间类型对应关系是:
- Date ---> LocalDate
- Time ---> LocalTime
- Timestamp ---> LocalDateTime
- Java 8之前统统对应 Date,也只有 Date。
6.7 时区
时区:正式的时区划分为每隔经度 15° 划分一个时区,全球共 24 个时区,每个时区相差 1 小时。但为了行政上的方便,常将 1 个国家或 1 个省份划在一起,比如我国幅员宽广,大概横跨 5 个时区,实际上只用东八时区的标准时即北京时间为准。 java.util.Date 对象实质上存的是 1970 年 1 月 1 日 0 点( GMT)至 Date 对象所表示时刻所经过的毫秒数。也就是说不管在哪个时区 new Date,它记录的毫秒数都一样,和时区无关。但在使用上应该把它转换成当地时间,这就涉及到了时间的国际化。java.util.Date 本身并不支持国际化,需要借助 TimeZone。
//北京时间:Wed Jan 27 14:05:29 CST 2021
Date date = new Date();
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//北京时区
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("毫秒数:" + date.getTime() + ", 北京时间:" + bjSdf.format(date));
//东京时区
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); // 设置东京时区
System.out.println("毫秒数:" + date.getTime() + ", 东京时间:" + tokyoSdf.format(date));
//如果直接print会自动转成当前时区的时间
System.out.println(date);
//Wed Jan 27 14:05:29 CST 2021
在新特性中引入了 java.time.ZonedDateTime 来表示带时区的时间。它可以看成是 LocalDateTime + ZoneId。
@Test
public void zonedTest() throws Exception {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.printf("当前时区时间:%s%n",zonedDateTime);
ZoneId zoneId = ZoneId.of(ZoneId.SHORT_IDS.get("JST"));
ZonedDateTime tokyoTime = zonedDateTime.withZoneSameInstant(zoneId);
System.out.printf("东京时间:%s%n",tokyoTime);
LocalDateTime localDateTime = tokyoTime.toLocalDateTime();
System.out.printf("东京时间转本地时间:%s%n",localDateTime);
ZonedDateTime localZonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
System.out.printf("本地时区时间:%s%n",localZonedDateTime);
}
7、类型注解
7.1 什么是类型注解
注解是从Java 5开始引入的新特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置。在Java 8之前,注解只能在声明的地方使用,比如类、方法、属性;而在Java 8 中,注解可以应用在任何地方,比如
创建实例
new @Interned MyObject();
类型映射
myString = (@NonNull String) str;
接口实现
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { … }
throw exception 声明
void monitorTemperature() throws @Critical TemperatureException { … }
类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。
7.2 类型注解的作用
类型注解被用来支持在Java的程序中做强类型检查。配合插件式的check framework,可以在编译的时候检测出runtime error,以提高代码质量。这就是类型注解的作用了。 check framework是第三方工具,配合Java的类型注解效果就是1+1>2。它可以嵌入到javac编译器里面,可以配合ant和maven使用, 地址是types.cs.washington.edu/checker-fra…
8、重复注解
允许在同一申明类型(类,属性,或方法)上多次使用同一个注解。 Java 8之前,由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解。
public class OldRepeatAnnoTest {
@AuthoritiesOne({@AuthorityOne(role = "Admin"),@AuthorityOne(role = "Manager")})
public void doSomeThing() {
}
}
@interface AuthorityOne {
String role();
}
@interface AuthoritiesOne {
AuthorityOne[] value();
}
Java 8之后,创建重复注解AuthorityTwo时,加上@Repeatable,指向存储注解AuthoritiesTwo,在使用时候,直接可以重复使用AuthorityTwo注解。这样可读性更强。
public class NewRepeatAnnoTest {
@AuthorityTwo(role = "admin")
@AuthorityTwo(role = "message")
public void doSomeThing() {
}
}
@Repeatable(AuthoritiesTwo.class)
@interface AuthorityTwo {
String role();
}
@interface AuthoritiesTwo {
AuthorityTwo[] value();
}
9、类型推断优化(泛型)
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。通俗点将就是“类型的变量”。这种类型变量可以用在类、接口和方法的创建中。 泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但也有尴尬的地方,就是每次定义时都要写明泛型的类型,这样显示指定不仅感觉有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。
9.1 Java 7 优化
在Java 7以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。
// java 7 之前
Map<String, String> map = new HashMap<String, String>();
// java 7 之后
Map<String, String> map = new HashMap<>();
编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。再次提醒一定要注意new HashMap后面的“<>”,只有加上这个“<>”才表示是自动类型推断,否则就是非泛型类型的HashMap,并且在使用编译器编译源代码时会给出一个警告提示。
9.2 Java 8 优化
Java8里面泛型的目标类型推断主要2个:
支持通过方法上下文推断泛型目标类型 支持在方法调用链路当中,泛型类型推断传递到最后一个方法
class List<E> {
static <Z> List<Z> nil() { ... };
static <Z> List<Z> cons(Z head, List<Z> tail) { ... };
E head() { ... }
}
//通过方法赋值的目标参数来自动推断泛型的类型
List<String> l = List.nil();
//而不是显示的指定类型
//List<String> l = List.<String>nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());
//而不是显示的指定类型
//List.cons(42, List.<Integer>nil());
10、锁优化 StampedLock
10.1 synchronized
在java5之前,实现同步主要是使用synchronized。它是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。有四种不同的同步块:
- 实例方法
- 静态方法
- 实例方法中的同步块
- 静态方法中的同步块
public class LockTest {
private int count;
private static int sum = 0;
public static void main(String[] args) {
}
/**
* 实例方法同步
* Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。
* 只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。
* @param value
*/
public synchronized void add(int value) {
this.count += value;
}
/**
* 静态方法同步
* 静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
* @param value
*/
public static synchronized void addSum(int value) {
sum += value;
}
/**
* 实例方法中的同步块(与实例方法同步类似)
* @param value
*/
public void add1(int value) {
synchronized (this) {
this.count += value;
}
}
/**
* 静态方法中的同步块(与静态方法同步类似)
* @param value
*/
public static synchronized void addSum1(int value) {
synchronized (LockTest.class) {
sum += value;
}
}
}
10.2 Lock
它是Java 5在java.util.concurrent.locks新增的一个API。 Lock是一个接口,核心方法是lock(),unlock(),tryLock(),实现类有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock; ReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义。 与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。Lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
private double x, y;
private final StampedLock sl = new StampedLock();
void m(double deltaX, double deltaY) {
long s = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(s);
}
}
10.3 StampedLock
它是java8在java.util.concurrent.locks新增的一个API。
ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。
然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。
StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量。