JDK新特性
1. jshell脚本
jdk9以及以上版本,方便我们编写简单的逻辑代码。
2. 接口上的改变
Java7接口中可以包含的内容有:
a 常量
概念:接口中可以定义的“成员变量”,但是必须使用public static final 修饰,效果上看,这就是接口的“常量”,并且必须赋值。
b 抽象方法
Java8还可以额外含有:
c 默认方法
格式:(public default 返回值类型 方法名称(参数列表){ 方法体})。
解决的问题:解决了接口升级的问题,比如新添加一个接口中的方法,不需要再对每个方法在其对应的实现类重写,默认方法会被实现类继承下来。
说明:接口中的默认方法,可以使用实现类直接调用,也可以被覆盖重写。
d 静态方法 (public static 返回值类型 方法名称(参数列表){ 方法体})
调用方式:不能使用实现类调用,因为一个类可能实现多个接口,多个接口中的静态方法可能会产生冲突,使用接口名.的方式。
Java9额外包含有:
e 私有方法
问题:我们需要抽取一个公共的方法用来解决默认方法之间的代码重复问题,但是方法不能被实现类使用,因此java9允许我们定义私有方法。
格式:普通方法的私有private 返回值类型 方法名称(参数列表){ 方法体 };
静态方法的私有private static 返回值类型 方法名称(参数列表){ 方法体 }
3. 对于集合的添加优化of方法
- jdk9以及以上版本,List,Set和Map接口中增加了一个静态的方法of,可以给集合一次性添加多个元素====== static List of (E…elements)
- 使用前提:当集合中存储的元素个数已经确定,不再改变的时候可以用。
- 注意:
1 of方法只适用于接口,不适用于接口的实现类,是接口中的静态方法。
2 of方法返回的是一个不可以改变的集合,集合不能进行增删等操作。
3 Set和Map接口在调用of方法的时候不能有重复的元素,否则抛异常。
public class MyTest {
public static void main(String[] args) {
List<String> list = List.of("a","b","c","d");
Set<String> set = Set.of("a","a","b","c");
Map<String,String> map = Map.of("key1","value1","key2","value2",...);
/*
list.add("e");抛异常UnsupportedOperationExceptions
Set.of("a","a","b","c");抛异常IllegalArgumentExceptions
Map.of("key1","value1","key1","value2");抛异常IllegalArgumentExceptions
*/
}
}
4. Lambda表达式(1.8开始)
4.1 编程思想的转换
- 面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情. - 函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
4.2 Lambda标准格式
(一) Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
(一) Lambda表达式的标准格式为:(参数类型 参数名称) ‐> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
- -> 是新引入的语法格式,代表指向动作。
- 大括号内的语法与传统方法体要求基本一致。
4.3 Lambda的使用前提
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一
时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。 - 备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
4.4 Lambda的省略格式
-
可推导即可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可
以使用Lambda的省略写法。 -
在Lambda标准格式的基础上,使用省略写法的规则为:
1 小括号内参数的类型可以省略;
2.如果小括号内有且仅有一个参,则小括号可以省略;
3.如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
4.5 Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以
作为解决方案,提升性能。
/**
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方
法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
*/
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
优化:
public class Demo03LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(2, () ‐> {
System.out.println("Lambda执行!");
return msgA + msgB + msgC;
});
}
}
/**
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。
*/
5 函数式接口
5.1 概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
/**
*一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不
*使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
*/
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
5.2 常用的函数接口
5.2.1 Supplier接口
解读:被称为生产型接口,接口的泛型是什么,就会返回什么类型的对象。
import java.util.function.Supplier;
public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}
5.2.2 Consumer接口
解读:消费型接口,void accept(T t),泛型指定什么类型,就可以消费什么类型的数据。
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
它有一个默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t);};
}
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组 合的情况:
import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()));
}
}
5.2.3 Predicate接口
逻辑比较型接口:有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。
- 抽象方法:test
import java.util.function.Predicate;
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5);
}
}
- 默认方法:and,既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实 现“并且”的效果时,可以使用default方法and
import java.util.function.Predicate;
/**
*如果要判断一个字符串既要包含大写“H”,又要包含大写“W”
*/
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
- 默认方法:or–与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。
import java.util.function.Predicate;
/**
* 如果希望实现逻辑“字符串包含大写H或者包含大写W”
*/
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
- 默认方法:negate–“与”、“或”已经了解了,剩下的“非”(取反)也会简单。从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前 调用 negate 方法,正如 and 和 or 方法一样
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5);
}
}
5.2.4 Function接口
- 最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。
import java.util.function.Function;
/**
* 使用的场景例如:将 String 类型转换为 Integer 类型。
*/
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s));
}
}
- 默认方法:andThen–该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多。
import java.util.function.Function;
public class Demo12FunctionAndThen {
/**
* 第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一 起。
*/
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(str‐>Integer.parseInt(str)+10, i ‐> i *= 10);
}
}
6.Stream流(JDK1.8加入)
6.1 流思想的概念
- 这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。
- 这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
- 备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
6.2 Stream流说明
6.2.1 Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等。
6.2.2 和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭。
代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
6.2.3 使用步骤
- 获取数据源–>数据转换→执行实际操作(此处才会真正的执行,延迟性)
6.3 获取Stream流
6.3.1 根据Collection获取流
/**
* Collection接口中有的一个默认的方法
* default Stream<E> stream() {
* return StreamSupport.stream(spliterator(), false);
* }
*
*/
public class Demo{
public static void main(String[] args) {
Set.stream();
List.stream();
...
//Collection的所有实现类均可以使用,返回的是一个Stream对象。
}
}
6.3.2 Map获取流
public class Demo{
public static void main(String[] args) {
Map<String,String> map = new HashMap();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
...
}
}
6.3.3 数组获取流
public class Demo{
public static void main(String[] args) {
String[] arr = {"a","b","c","d","e"};
//使用Stream中的静态方法of(T..t),传递的是可变参数,可变参数实际就是数组
Stream stream = Stream.of(arr);
}
}
6.3 Stream流常用的方法
6.3.1 方法分类
- 延迟方法:自身的方法,支持链式调用。
- 终结方法:自身之外的方法,除了终结方法其余的都是延迟方法。
6.3.2 forEach方法(终结)
import java.util.stream.Stream;
/**
* void forEach(Consumer<? super T> action);
* 区别于普通循环中的forEach
*/
public class DemoForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name‐> System.out.println(name));
}
}
6.3.3 filter方法
import java.util.stream.Stream;
/**
* Stream<T> filter(Predicate<? super T> predicate);
* 该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法
* 将会留用元素;如果结果为false,那么 filter 方法将会舍弃元素。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
original.filter(o->o.startsWith("张")).filter(o->o.endsWith("忌")).forEach(
o-> System.out.println(o)
);
}
}
6.3.3 map方法
import java.util.stream.Stream;
/**
* <R> Stream<R> map(Function<? super T, ? extends R> mapper);
* 如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
* 该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
}
}
6.3.4 count方法(终结方法)
import java.util.stream.Stream;
/**
* long count();
* 该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
6.3.5 取用前几个:limit
import java.util.stream.Stream;
/**
* limit 方法可以对流进行截取,只取用前n个。
* Stream<T> limit(long maxSize);
* 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张三", "李四", "王五");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
6.3.6 跳过前几个:skip
import java.util.stream.Stream;
/**
* 如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流
* Stream<T> skip(long n);
* 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张三", "李四", "王五");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
6.3.7 组合:concat
import java.util.stream.Stream;
/**
* 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
* static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);
* 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
*/
public class Demo {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
6.3.8 收集流的方法
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List<String> cList = list.stream().collect(Collectors.toList());
Set<String> set = list.stream().collect(Collectors.toSet());
String[] strArr = {"a,15","b,16","c,17"};
Map<String, String> map = Stream.of(strArr).collect(Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));
}
}
6.4 方法的引用
6.4.1 概念
通过类名或对象名引用已经存在的方法来简化lambda表达式,其中方法引用的操作符是双冒号"::"。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。
函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。
注:Lambda 中 传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常。
6.4.2 引用的方式
- 通过对象名引用成员方法
/**
* Printable 是一个函数式接口
* @FunctionalInterface
* public interface Printable {
* void print(String str);
* }
*/
public class Demo {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
- 通过类名称引用静态方法
/**
* Calcable是一个函数式接口
* @FunctionalInterface
* public interface Calcable {
* int calc(int num);
* }
*/
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
-
通过super引用成员方法
Lambda表达式: () -> super.sayHello()
方法引用: super::sayHello -
通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。
Lambda表达式: () -> this.buyHouse()
方法引用: this::buyHouse -
类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。
Lambda表达式: name -> new Person(name)
方法引用: Person::new -
数组的构造器引用
数组的构造器引用一定要指定int或者Integer类型的数值作为数组的初始长度。
Lambda表达式: length -> new int[length]
方法引用: int[]::new -
个人理解:lambda表达式和方法引用其实都是对函数式接口的实现,其实只需要关注参数以及结果就可以,这也正符合了“拿什么参数做什么”的原则,lambda表达式强调我自己做,而方法引用更侧重于用别人帮我做。
7.异常处理
7.1 JDK7对异常处理
*
JDK7的新特性
在try的后边可以增加一个(),在括号中可以定义流对象
那么这个流对象的作用域就在try中有效
try中的代码执行完毕,会自动把流对象释放,不用写finally
格式:
try(定义流对象;定义流对象....){
可能会产出异常的代码
}catch(异常类变量 变量名){
异常的处理逻辑
}
*/
public class Demo02JDK7 {
public static void main(String[] args) {
try(//1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");){
//可能会产出异常的代码
//一次读取一个字节写入一个字节的方式
//3.使用字节输入流对象中的方法read读取文件
int len = 0;
while((len = fis.read())!=-1){
//4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
fos.write(len);
}
}catch (IOException e){
//异常的处理逻辑
System.out.println(e);
}
}
}
7.2 JDK9对异常处理
/*
JDK9新特性
try的前边可以定义流对象
在try后边的()中可以直接引入流对象的名称(变量名)
在try代码执行完毕之后,流对象也可以释放掉,不用写finally
格式:
A a = new A();
B b = new B();
try(a,b){
可能会产出异常的代码
}catch(异常类变量 变量名){
异常的处理逻辑
}
*/
public class Demo03JDK9 {
public static void main(String[] args) throws IOException {
//1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
try(fis;fos){
//一次读取一个字节写入一个字节的方式
//3.使用字节输入流对象中的方法read读取文件
int len = 0;
while((len = fis.read())!=-1){
//4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
fos.write(len);
}
}catch (IOException e){
System.out.println(e);
}
//fos.write(1);//Stream Closed
}
}
8 模块化(jdk9推出)
- 在项目中创建两个模块。一个是myOne,一个是myTwo
- 在myOne模块中src目录下,创建module-info.java,并写入以下内容
module myOne{
//导出包供其他模块使用 exports 包名;
exports com.my.test;
//提供服务 provides 接口名称 with 实现类名称
provides MyService with MyServiceImpl;
}
- 在myTwo模块中src目录下,创建module-info.java,并写入以下内容
module myTwo{
//引入其他模块requires 模块名;
requires myOne;
//使用其他模块服务
uses MyService ;
}