Java8新特性
Java8
(1)Interface
Interface 修改的时候,实现它的类也必须跟着改。为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用default
或 static
修饰,这样就可以有方法体,实现类也不必重写此方法。
一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。
default
修饰的方法,是普通实例方法,可以用this
调用,可以被子类继承、重写。static
修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface
调用。
public interface InterfaceNew { static void sm() { System.out.println("interface提供的方式实现"); } default void def() { System.out.println("interface default方法"); } //须要实现类重写 void f(); } public interface InterfaceNew1 { default void def() { System.out.println("InterfaceNew1 default方法"); } }
如果有一个类既实现了 InterfaceNew
接口又实现了 InterfaceNew1
接口,它们都有def()
,并且 InterfaceNew
接口和 InterfaceNew1
接口没有继承关系的话,这时就必须重写def()
。不然的话,编译的时候就会报错。
public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{ public static void main(String[] args) { InterfaceNewImpl interfaceNew = new InterfaceNewImpl(); interfaceNew.def(); } @Override public void def() { InterfaceNew1.super.def(); } @Override public void f() { } }
在 Java 8 ,接口和抽象类有什么区别的?
-
nterface 和 class 的区别,主要有:
- 接口多实现,类单继承
- 接口的方法是 public abstract 修饰,变量是 public static final 修饰。 abstract class 可以用其他修饰符
-
interface 的方法是更像是一个扩展插件。而 abstract class 的方法是要继承的。
(2)functional interface 函数式接口
定义:也称 SAM 接口,即 Single Abstract Method interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口。
在 java 8 中专门有一个包放函数式接口java.util.function
,该包下的所有接口都有 @FunctionalInterface
注解,提供函数式编程。
在其他包中也有函数式接口,其中一些没有@FunctionalInterface
注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface
注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
(3)Lambda 表达式
Lambda 表达式是推动 Java 8 发布的最重要新特性。是继泛型(Generics
)和注解(Annotation
)以来最大的变化。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。
Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。
# 语法格式
(parameters) -> expression 或
(parameters) ->{ statements; }
Lambda 实战
# 替代匿名内部类
过去给方法传动态参数的唯一方法是使用内部类。比如
1.Runnable
接口
new Thread(new Runnable() { @Override public void run() { System.out.println("The runable now is using!"); } }).start(); //用lambda new Thread(() -> System.out.println("It's a lambda function!")).start();
2.Comparator
接口
List<Integer> strings = Arrays.asList(1, 2, 3); Collections.sort(strings, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2;} }); //Lambda Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2); //分解开 Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1 - o2; Collections.sort(strings, comparator);
3.Listener
接口
JButton button = new JButton(); button.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { e.getItem(); } }); //lambda button.addItemListener(e -> e.getItem());
4.自定义接口
上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。@FunctionalInterface public interface Comparator<T>{} @FunctionalInterface public interface Runnable{}
我们自定义一个函数式接口
@FunctionalInterface public interface LambdaInterface { void f(); } //使用 public class LambdaClass { public static void forEg() { // lambdaInterfaceDemo(new LambdaInterface() { // @Override // public void f() { // System.out.println("自定义函数式接口"); // } // }); lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口")); } //函数式接口参数 static void lambdaInterfaceDemo(LambdaInterface i){ i.f(); } }
#集合迭代
void lamndaFor() { List<String> strings = Arrays.asList("1", "2", "3"); //传统foreach for (String s : strings) { System.out.println(s); } //Lambda foreach strings.forEach((s) -> System.out.println(s)); //or strings.forEach(System.out::println); //map Map<Integer, String> map = new HashMap<>(); map.forEach((k,v)->System.out.println(v)); }
# 方法的引用
Java 8 允许使用 ::
关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
public class LambdaClassSuper { LambdaInterface sf(){ return null; } } public class LambdaClass extends LambdaClassSuper { public static LambdaInterface staticF() { return null; } public LambdaInterface f() { return null; } void show() { //1.调用静态函数,返回类型必须是functional-interface LambdaInterface t = LambdaClass::staticF; //2.实例方法调用 LambdaClass lambdaClass = new LambdaClass(); LambdaInterface lambdaInterface = lambdaClass::f; //3.超类上的方法调用 LambdaInterface superf = super::sf; //4. 构造方法调用 LambdaInterface tt = LambdaClassSuper::new; } }
# 访问变量
int i = 0; Collections.sort(strings, (Integer o1, Integer o2) -> o1 - i); //i =3;
lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属性,不能被修改,如果修改,编译时就报错。
(4)Stream
java 新增了 java.util.stream
包,它和之前的流大同小异。之前的资源流,比如java.io.FileInputStream
,通过流把文件从一个地方输入到另一个地方,对文件内容不做任何CRUD。Stream
依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。它的源数据可以是 Collection
、Array
等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
流类型
- stream 串行流
- parallelStream 并行流,可多线程执行
并行 parallelStream 在使用方法上和串行一样。主要区别是 parallelStream 可多线程执行,是基于 ForkJoin 框架实现的.
延迟执行
在执行返回 Stream
的方法时,并不立刻执行,而是等返回一个非 Stream
的方法后才执行。因为拿到 Stream
并不能直接用,而是需要处理成一个常规类型。
Stream特点:
- 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
- 方法参数都是函数式接口类型
- 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
- Stream 不保存数据,不改变数据源
@Test public void test() { List<String> strings = Arrays.asList("abc", "def", "gkh", "abc"); //返回符合条件的stream Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s)); //计算流符合条件的流的数量 long count = stringStream.count(); //forEach遍历->打印元素 strings.stream().forEach(System.out::println); //limit 获取到1个元素的stream Stream<String> limit = strings.stream().limit(1); //toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环 String[] array = limit.toArray(String[]::new); //map 对每个元素进行操作返回新流 Stream<String> map = strings.stream().map(s -> s + "22"); //sorted 排序并打印 strings.stream().sorted().forEach(System.out::println); //Collectors collect 把abc放入容器中 List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList()); //把list转为string,各元素用,号隔开 String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(",")); //对数组的统计,比如用 List<Integer> number = Arrays.asList(1, 2, 5, 4); IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : "+statistics.getMax()); System.out.println("列表中最小的数 : "+statistics.getMin()); System.out.println("平均数 : "+statistics.getAverage()); System.out.println("所有数之和 : "+statistics.getSum()); //concat 合并流 List<String> strings2 = Arrays.asList("xyz", "jqx"); Stream.concat(strings2.stream(),strings.stream()).count(); //注意 一个Stream只能操作一次,不能断开,否则会报错。 Stream stream = strings.stream(); //第一次使用 stream.limit(2); //第二次使用 stream.forEach(System.out::println); //报错 java.lang.IllegalStateException: stream has already been operated upon or closed //但是可以这样, 连续使用 stream.limit(2).forEach(System.out::println); }
(5)Optional
传统解决 NPE 的办法如下:
Zoo zoo = getZoo(); if(zoo != null){ Dog dog = zoo.getDog(); if(dog != null){ int age = dog.getAge(); System.out.println(age); } }
Optional
是这样的实现的:
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
System.out.println(age)
);
(6)Date-Time API
这是对java.util.Date
强有力的补充,解决了 Date 类的大部分痛点:- 非线程安全
- 时区处理麻烦
- 各种格式化、和时间计算繁琐
- 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。
java.time 主要类
java.util.Date
既包含日期又包含时间,而 java.time
把它们进行了分离
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS LocalDate.class //日期 format: yyyy-MM-dd LocalTime.class //时间 format: HH:mm:ss
字符串转日期格式
Java 8 之前 转换都需要借助 SimpleDateFormat
类,而Java 8 之后只需要 LocalDate
、LocalTime
、LocalDateTime
的 of
或 parse
方法。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date1 = sdf.parse("2021-01-26"); LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22); LocalDateTime.parse("2021-01-26 12:12:22");
日期计算
//一周后的日期 SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd"); Calendar ca = Calendar.getInstance(); ca.add(Calendar.DATE, 7); Date d = ca.getTime(); String after = formatDate.format(d); //算两个日期间隔多少天,计算间隔多少年,多少月方法类似 int day = (int) ((date1.getTime() - date2.getTime()) / (1000 * 3600 * 24)); //一周后的日期 LocalDate localDate = LocalDate.now(); LocalDate after = localDate.plus(1, ChronoUnit.WEEKS); //算两个日期间隔多少天,计算间隔多少年,多少月 Period period = Period.between(date1, date2); System.out.println("date1 到 date2 相隔:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");
获取指定日期
除了日期计算繁琐,获取特定一个日期也很麻烦,比如获取本月最后一天,第一天。
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //获取当前月第一天: Calendar c = Calendar.getInstance(); c.set(Calendar.DAY_OF_MONTH, 1); String first = format.format(c.getTime()); System.out.println("first day:" + first); LocalDate today = LocalDate.now(); //获取当前月第一天: LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
JDBC 和 java8
现在 jdbc 时间类型和 java8 时间类型对应关系是
Date
--->LocalDate
Time
--->LocalTime
Timestamp
--->LocalDateTime
而之前统统对应 Date
,也只有 Date
。
时区
java.util.Date
本身并不支持国际化,需要借助 TimeZone
。java.time.ZonedDateTime
来表示带时区的时间。它可以看成是 LocalDateTime + ZoneId
。
//北京时间: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")); //当前时区时间 ZonedDateTime zonedDateTime = ZonedDateTime.now(); //LocalDateTime 转 ZonedDateTime ZonedDateTime localZoned = localDateTime.atZone(ZoneId.systemDefault());
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程使用 AI 从 0 到 1 写了个小工具
· 快收藏!一个技巧从此不再搞混缓存穿透和缓存击穿
· AI 插件第二弹,更强更好用
· Blazor Hybrid适配到HarmonyOS系统
· 支付宝 IoT 设备入门宝典(下)设备经营篇