写在最前面
在企业中更多的都是使用 Java8 ,随着 Java8 的普及度越来越高,很多人都提到面试中关于Java 8 也是非常常问的知识点。本文根据 Github 上的文章进行学习,GitHub Java8地址: https://github.com/winterbe/java8-tutorial。
Java8 新特性 主要内容
Lambda表达式:Lambda表达式是一种函数式编程的方式,可以使代码更简洁。它允许在方法中传递代码块,从而实现更加灵活的编程方式。
Stream API:Stream API提供了一种更简单的方法来处理集合数据。通过使用Stream API,可以轻松地进行筛选、排序、映射等操作。
方法引用:方法引用是Lambda表达式的一种简化写法,它允许直接引用已经存在的方法,从而简化代码。
接口默认方法:Java 8允许在接口中定义默认方法,这些方法在实现类中可以被继承和覆盖,从而提供了更多的灵活性。
新的日期和时间API:Java 8引入了一个新的日期和时间API,这个API提供了更多的功能,包括处理时区、时间间隔、日期时间格式化等。
Optional类:Optional类是一个容器对象,它可以包含一个值或者为空。它可以避免空指针异常的出现,并且可以提供更加清晰的代码。
Parallel Stream 并行流:Java 8引入了并行流,它可以在多个线程上并行地处理集合数据,从而提高程序的性能。
以下几种没有上面的那么常见
可重复注解:Java 8允许在同一个地方多次使用同一个注解,从而提供更多的灵活性和可读性。
类型注解:Java 8允许在任何地方使用注解来标记类型,这可以帮助编写更加安全和可靠的代码。
Nashorn JavaScript引擎:Java 8引入了Nashorn JavaScript引擎,它可以在Java虚拟机中执行JavaScript代码,从而提供更多的灵活性。
Base64编码:Java 8提供了一种更加简单的方法来处理Base64编码,从而使编码和解码更加容易。
PermGen空间的移除:Java 8移除了PermGen空间,取而代之的是Metaspace,这使得内存管理更加简单和高效。
下面我们来看看最重要的7种 Java8 新特性
1、Lanbda表达式
Lambda允许把函数作为一个方法的参数,允许在方法中传递代码块,从而实现更加灵活的编程方式。Lambda表达式可以简化代码,减少样板代码的出现,并且使代码更加易读和易于维护。
1.1 lambda表达式语法
(parameters) -> expression (parameters) -> { statements; }
其中,parameters是方法的参数列表,->表示将参数列表与方法体分开,expression是单个表达式,statements是多条语句。
Lambda表达式在Java语言中引入了一个操作符->,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:
左侧:指定了Lambda表达式需要的所有参数
右侧:制定了Lambda体,即Lambda表达式要执行的功能。
1.2 Lambda代码示例
- 对比旧版的Java比较器
/** * @author Shier 2023/2/19 21:00 * Lambda表达式 */ public class LambdaTest { public static void main(String[] args) { // 1. 创建一个 List 集合,用于存储要排序的怨怒是 List<String> stringList = Arrays.asList("shier", "xiaoyi", "xiaosan", "zhangsan"); // 2 使用匿名的比较器对象 Collections.sort(stringList, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); } }); System.out.println(stringList.toString()); // [zhangsan, xiaoyi, xiaosan, shier] } }
- 使用Java8的新特性,lambda表达式可以将以上比较器简化成以下:
首先是可以去掉了传统的匿名对象的方法:
// 使用lambda表达式:第一次简化 Collections.sort(stringList, (String a, String b) -> { return b.compareTo(a); });
与旧版的书写明显简短了一些,不过这还不是最简化的
- 再一次简化成:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
- 但是这样看着还不是足以简化的,对于函数体只有一行的代码,你可以去掉大括号
{}
以及return
语句,所以下面这个才是比较简化的写法
stringList.sort((a, b) -> b.compareTo(a));
由于上面是 stringList
是 List 类型,List 本身就有一个 sort
方法,而且不要指定数据类型。并且Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。
其他的列子
//语法格式一 无参 无返回值 lambda 只需一条语句 Runnable runnable = () -> System.out.println("Runnable 运行了!!"); runnable.run(); //run是Runnable的方法 //语法格式二 一个参数 无返回值 Consumer<String> consumer =(x) -> System.out.println(x); consumer.accept("运行了????");*/ //consumer 是的 accept //语法格式三 有一个参数 参数括号可以省略 Consumer<String> consumer = x -> System.out.println(x); consumer.accept("运行了????"); //语法格式四 有两个参数 并且里面有多条语句 Comparator<Integer> com = (x , y) ->{ System.out.println("函数式接口!"); return Integer.compare(x,y); }; System.out.println(com.compare(45,1)); // x<y 输出-1 x = y 输出 0 x > y 输出 1 //语法格式五 有两个参数以上有返回值 lambada 只有一个语句 return 和大括号都可以省略不写 Comparator<Integer> com = (x, y) -> Integer.compare(x, y); System.out.println(com.compare(12,45));//输出-1 //语法格式六 lambda参数列表可以省略不写 JVM会进行”类型推断“ Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x, y); System.out.println(com.compare(12,45)); //-1 // 左右遇一括号省,左侧推断类型省,能省则省
省略规则:
参数类型可以省略不写。 就是上面的格式一
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
Lambda表达式可以用于各种不同的场景,比如对列表进行过滤、映射和聚合等操作,它可以让代码更加简洁和易于理解。
1.3 lambda 表达式作用域(lambda Scopes)
Lambda表达式可以访问其外部作用域中的变量,包括类成员变量、静态变量、方法的参数和局部变量。这些变量被称为自由变量,因为它们不是在Lambda表达式内部定义的。Lambda表达式可以读取自由变量的值,但是不能修改它们的值。如果我们试图在Lambda表达式内部修改一个自由变量的值,那么编译器会报错。
Lambda表达式可以访问三种类型的变量:
Lambda表达式外部的变量,也称自由变量。
Lambda表达式内部的变量,也称隐式参数。
Lambda表达式内部定义的局部变量,也称显示参数。
1.3.1 访问外部变量(自由变量)
public class LambdaScopeExample { private int x = 1; public void doSomething() { int y = 2; Consumer<Integer> lambda = (z) -> System.out.println(x + y + z); // 6 lambda.accept(3); } }
在这个例子中,Lambda表达式访问了外部的变量x和y。变量x是类成员变量,变量y是方法的局部变量。Lambda表达式可以读取这些变量的值,但是不能修改它们的值。
1.3.2 访问内部变量
Lambda表达式还可以访问自己的参数,也称为隐式参数。隐式参数可以在Lambda表达式中使用,但是不能在外部使用。下面是一个Lambda表达式使用隐式参数的例子:
Function<Integer, Integer> lambda = x -> x * 2;
在这个例子中,Lambda表达式的参数x是一个隐式参数,它只能在Lambda表达式内部使用。
1.3.3 自定义局部变量
Lambda表达式还可以定义自己的局部变量,也称为显式参数。显式参数只能在Lambda表达式内部使用,不能在外部使用。下面是一个Lambda表达式定义显式参数的例子:
IntStream.range(1, 11) .map(x -> { int y = x * 2; return y; }) .forEach(System.out::println); //
在这个例子中,Lambda表达式定义了一个局部变量y,它只能在Lambda表达式内部使用。
2、接口默认方法 (Default Methods for Interfaces)
2.1 概念
Java 8允许在接口中定义默认方法,这些方法在实现类中可以被继承和覆盖,从而提供了更多的灵活性。默认方法允许我们在接口中添加新的方法,而不会破坏已经实现该接口的现有类。
2.2 默认方法语法格式
default returnType methodName(parameterList) { // method body }
其中
- default关键字表示这是一个默认方法,
- returnType表示方法的返回类型,
- methodName表示方法名,
- parameterList表示方法的参数列表,
- method body表示方法的实现。
2.3 代码示例说明
Java 8使我们能够通过使用 default
关键字向接口添加非抽象方法实现。
- 首先定义一个接口
Formula
、里面包括一个默认接口方法、和接口抽象方法 - 通过匿名内部类进行访问接口方法
- 如果是实现了接口,那么该实现类就会默认继承该接口的默认方法
/**/** * @author Shier 2023/2/19 21:32 * 接口默认方法 */ public class DefaultMethods { public static void main(String[] args) { // 通过匿名内部类进行访问接口 Formula formula = new Formula() { @Override public int add(int a, int b) { return a + b; } }; System.out.println("接口公共方法:" + formula.add(10, 20)); //接口公共方法:30 System.out.println("接口默认方法:" + formula.defaultMethods("string1", "string2")); //接口默认方法:string1string2 } } // 定义一个接口 interface Formula { // 接口公共方法,必须实现的 int add(int a, int b); // 接口默认方法 default String defaultMethods(String a, String b) { return a + b; } }
不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。
3、函数式接口(Functional Interfaces)
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。 像这样的接口,可以被隐式转换为lambda表达式。
3.1 概念
Java 8引入了函数式接口,它是只包含一个抽象方法的接口,这个抽象方法定义了一个函数签名,也就是参数列表和返回值类型。函数式接口可以被用来创建Lambda表达式,从而提供更加灵活的编程方式。
3.2 示例代码
通过过滤元素进行演示,使用 Predicate 函数式接口,
/** * @author Shier 2023/2/19 22:02 * Predicate 函数式接口 */ public class FunctionInterfaceTest { public static void main(String[] args) { List<String> strings = Arrays.asList("shiera", "yupi", "dsadsan", "zhangsan"); // 使用过滤,将长度小于 5的元素 收集成一个列表,并返回 List<String> collect = strings.stream().filter(name -> name.length() < 5).collect(Collectors.toList()); System.out.println(collect); //[yupi] } }
使用函数式接口注解
@FunctionalInterface注解 检测它是不是一个函数式接口
//一个函数式接口 @FunctionalInterface interface Run{ void run();//只有一个抽象方法 } public static void Sport(Run r){ System.out.println("开始"); r.run();//参数构造 System.out.println("结束"); } Run run = ()->{ System.out.println("运动员跑");}; System.out.println("-----------------"); Sport(()->{System.out.println("运动员准备跑");});
自定义函数式接口
//自定义一个函数式接口 @FunctionalInterface public interface MyFuncInterf<T>{ public T getValue(String origin); } //定义一个方法 讲函数式接口作为方法参数 public String toLowerString(MyFuncInterf<String> mf,String origin){ return mf.getValue(origin); } //将Lambda表达式实现的接口作为参数传递 public void test01(){ String value = toLowerString((str) ->{ return str.toLowerCase(); },"abc"); System.out.println(value); }
将数字字符串转换为整数类型
@FunctionalInterface public interface Converter<F, T> { T convert(F from); }
Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted.getClass()); //class java.lang.Integer
内置四大函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer消费类型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accpt() |
Suppiler函数式接口 | 无 | R | 放回类型为T的对象 ,包含方法 T:get() |
Function<T,R> | T | R | 对类型为T的对象应用操作并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate断定型接口 | T | boolean | 确定类型为T 的对象是否满足某约束条件,并返回boolean值包含方法:boolean test (T t) |
Java 8提供了许多预定义的函数式接口,比如Function、Consumer、Predicate等。这些接口中的方法可以用Lambda表达式来实现,从而实现更加简洁的代码。
Predicate
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):
源码解析
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Predicate<T> { // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断. boolean test(T t); //and方法与关系型运算符"&&"相似,两边都成立才返回true default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } // 与关系运算符"!"相似,对判断进行取反 default Predicate<T> negate() { return (t) -> !test(t); } //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal). static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); }
示例
Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // 取反了,所以是false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate();