JDK8的那些骚操作
不是吧啊sir,这都JDK14.0.2了你还在这讲JDK8?!!!
1.静态方法和默认方法
在JDK8中,允许在接口中增加静态方法和默认方法。理论上讲,没有什么不妥,只不过这种做法确实有违将接口作为抽象规范的初衷。
先让我们来回忆一下接口的概念:接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定,一个类可以实现一个或多个接口。可以将接口看成是没有实例域的抽象类。接口中可以定义变量和方法,接口中可以包含方法和常量,方法和常量的修饰符是:public abstract和public static final,这两个修饰符是默认的,也就是说我们不需要去主动添加。
JDK8之前定义接口的写法:
代码语言:javascript复制public interface Student {
//定义常量
String name = "西瓜籽";
public static final String aliasName="黑籽";
int age = 18;
//定义方法
void playing();
public abstract void learning();
}
JDK8之后可以这样写:
代码语言:javascript复制public interface Students {
//定义常量
String name = "西瓜籽";
public static final String aliasName="黑籽";
int age = 18;
//静态方法
static void playing(){
System.out.println("在看大西瓜的推文");
}
//默认方法
default void learning(){
System.out.println("在和大西瓜一起学Java");
}
}
我们实现该接口:
代码语言:javascript复制public class ImpStudent implements Students {
@Override
public void learning() {
System.out.println("在教大西瓜学Java");
}
}
我们发现default方法是可以重写的,而static方法不能重写。在IDEA的工具中也可以发现可实现的方法也只有default关键字修饰的learning()方法。
2.Lambda表达式和接口式函数
lambda表达式是继泛型后,引入的另一让人眼前一亮的特性。lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
lambda表达式形式:
(参数) -{表达式} 注意:参数可以为空,参数的类型无需指定,lambda根据上下文自动获取;表达式是要实现的内容,但是单个返回值可以不用{}
接口式函数:
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。函数式接口带有@FunctionalInterface的标注。
西瓜籽:“ 为什么接口式函数必须要有一个抽象方法?”
大西瓜:“ 因为接口完全有可能重新声明Object类的方法,如toString()和clone(),这些声明有可能会让方法不再是抽象的。更重要的是我们前面小结说讲的现在接口中已经可以存在‘静态方法和默认方法’ ”。
下面我们用Arrays.sort()方法,来展示一下lambda表达式:
代码语言:javascript复制public static void main(String[] args) {
Integer[] a = {2,1,4,3,5};
Integer[] b = {2,1,4,3,5};
//传统写法,需定义一个实现了Compartor<T>接口的DESCComparator<T>类
Arrays.sort(b,new DESCComparator());
for (int i : b){
System.out.print(i " ");
}
System.out.println();
//Lambda写法,无需指定就i,j类型,将自动化为Integer类型
Arrays.sort(a,(i,j)->j-i);
for (int i : a){
System.out.print(i " ");
}
}
//传统写法的内部类
static class DESCComparator implements Comparator<Integer>{
@Override
public int compare(Integer A, Integer B) {
return B-A;
}
}
代码语言:javascript复制 运行结果:
5 4 3 2 1
5 4 3 2 1
Lambda表达式作用域:
代码语言:javascript复制lambda表达式访问外层作用域和匿名对象的方式很相似。可以直接访问标记了final的外层局部变量、实例的字段以及静态全局变量。不过这里并不建议大家使用后俩,有一条规则:“lambda表达式中捕获的变量必须是实际上的最终变量(final的)”,这样也可以保证并发执行时的安全。在Java中,lambda表达式就是闭包。
public class Scope {
//自定义一个接口式函数
static interface MySystem{
void print();
}
//Error:(10, 52) java: 无法从静态上下文中
//Integer A=10;引用非静态 变量 A
public static Integer A=10;
public static void main(String[] args) {
//对于lambda而言,默认为final类型
Integer a=10;
MySystem mySystem =()-> System.out.println(a);
mySystem.print();
}
}
总结:
lambda表达式可以使我们的代码更加简洁,可是同时也使得代码得可读性变差。而且我们最好把lambda表达式看作是一个函数,而不是一个对象,另外要接受lambda表达式可以传递到函数式接口。实际上在Java中,对lambda表达式所能做的也只是能转换为函数式接口。甚至不能把lambda表达式赋给类型为Object的变量,因为Object不是一个函数式接口。
3.方法引用和构造器引用
方法引用:
有时,可能已经有现成的方法可以完成你想要传递到其他代码的某个动作。就拿我们上面引入lambda表达式时的排序代码来进行对比讲解,之前我们是这样写的:
代码语言:javascript复制Arrays.sort(a,(i,j)->j-i)
上述代码刨根问底到源码也就是都使用了compare方法来进行比较的,恰巧我们的Interger类里面有现成的compare方法,所以我们这时候就可以这样写了:
代码语言:javascript复制Arrays.sort(a,Integer::compareTo);
从上面可以看出方法引用的操作符为"::",用来分隔方法名与对象或类名。主要有三种情况:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
构造器引用:
构造器引用与方法引用很类似,只不过方法名为new,我们可以像其他方法引用一样创建构造函数的方法引用。需要注意的是,需要调用的构造器参数列表要和函数式接口中抽象方法的参数列表保持一致。
代码语言:javascript复制//lambda表达式写法
Supplier<Student> studentSupplier1 = ()->new Student();
//构造器写法
Supplier<Student> studentSupplier2 = Student::new;
4.JDK8 新增的日期时间API
在JDK8之前,处理日期时间,我们主要使用3个类, Date、 SimpleDateFormat 和 Calendar。不过要注意的是SimpleDateFormat不是线程安全的,Data和Calendar的月份是0~11,让人使用起来多少感觉有些不舒服。在JDK8推出了全新的日期时间处理类,有效的解决了上述问题。下面我们就来讨论一下这些新增类:
LocalDate:只有日期
LocalTime:只有时间
LocalDateTime:有时间和日期
Instat:时间戳
DateTimeFormatter:日期格式化
LocalDate:
代码语言:javascript复制LocalDate封装了实例域来维护所设置的日期。如果不查看源代码,就不可能知道类内部的日期表示。当然,封装的意义在于,这一点并不重要,重要的是类对外提供的方法。LocalDate类没有更改器方法,与之不同,Date类有一个更改器方法setTime,可以在这里设置毫秒数。Date对象是可变的,这一点就破坏了封装性!
@Test
public void LocalDateTest(){
//获取当前日期
LocalDate localDate = LocalDate.now();
//年
int year = localDate.getYear();
//月
int mouth = localDate.getMonthValue();
//日
int day = localDate.getDayOfMonth();
//获取指定日期
LocalDate AppointDate = LocalDate.of(2020,9,9);
System.out.println(localDate);
System.out.println("year:" year);
System.out.println("mouth:" mouth);
System.out.println("day:" day);
System.out.println("指定日期:" AppointDate);
}
LocalTime
代码语言:javascript复制封装性
@Test
public void LocalTimeTest(){
//获取当前时间
LocalTime localTime = LocalTime.now();
//小时
int hour = localTime.getHour();
//分钟
int minute = localTime.getMinute();
//秒
double second = localTime.getSecond();
System.out.println("当前时间:" localTime);
System.out.println("小时:" hour);
System.out.println("分钟:" minute);
System.out.println("秒:" second);
}
LocalDateTime
代码语言:javascript复制上两个的合体版
@Test
public void LocalDateTimeTest(){
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
}
Instant:
代码语言:javascript复制在JDK8中Instant可以替代Date。时间上的瞬时点,这个类对时间线上的一个瞬时点进行建模。可以用作时间戳。推荐用Instant类代替Date类。
@Test
public void Instant(){
//获取当前时间
Instant instant = Instant.now();
System.out.println(instant);
//获取时间戳,单位为秒
System.out.println(instant.getEpochSecond());
//获得时间戳,单位为毫秒
System.out.println(instant.toEpochMilli());
}
DateTimeFormatter:
代码语言:javascript复制JDK8新增用DateTimeFormatter来处理日期格式化问题,阿里也建议使用DateTimeFormatter来代替SimpleDateFormat。
@Test
public void DateTimeFormatter(){
LocalDate localDate = LocalDate.now();
System.out.println("yyyy/MM/dd:" localDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
}
总结:将时间和日历分开是一种很好的面向对象设计。通常,最好使用不同的类表示不同的概念。不要使用构造器来构造LocalDate这些类的对象。
还有些我不太熟悉,等我熟悉了咱再聊
- END -