一、多线程
1. 基本概念
程序、进程、线程
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一
段静态的代码,静态对象。 - 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。 ——生命周期
- 程序是静态的,进程是动态的,进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域
- 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开
销小 - 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
并行与并发
- 并行: 多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发: 一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
线程声明周期
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imRXkee6-1597908459776)(Java%E5%9F%BA%E7%A1%80.assets/image-20200725111745530.png)]
2. 线程的创建与使用
2.1 传统方式创建线程
JDK1.5之前创建新执行线程有两种方法:
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
Thread类常用方法:
- start():启动当前线程,调用当前线程的run()
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- static currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- static yield():当前线程放弃CPU执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- static sleep():当前线程进入倒计时阻塞状态
- isAlive():当前线程是否存活
线程优先级:
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY: 5–>默认优先级
- 获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setpriority(int p):设置线程的优先级 (注意:要在线程start之前设置) - 注意:提高优先级只是提高这个线程抢占CPU的概率
示例:
public class MyThreadTest {
public static class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"::"+i);
}
}
}
@Test
public void threadTest() {
MyThread myThread = new MyThread();
myThread.setName("继承Thread类的线程");
myThread.start();
// 实现runnbale接口
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i);
if(i == 22){
myThread.join();
}
}
}
},"Runnable线程").start();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"::"+i);
if(i == 22){
Thread.currentThread().yield;
}
}
boolean flag = myThread.isAlive();
}
}
- 实现接口VS继承:实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
- 实现接口的方式更方便,多个线程操作(Runnable实现类)共享数据
2.2Java中的线程分类
- 守护线程:依赖于用户线程,如果用户线程结束,守护线程自动结束
- 守护线程是用来服务用户线程的,通过在start()方法前调用,th.setDaemon(true)可以把一个用户线程变成一个守护线程
- 用户线程: 显示使用的线程,如:main线程
2.3 JDK5.0新增的创建线程方式
实现Callable接口
// 实现Callable 注意泛型
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
CallableTest callable = new CallableTest();
// 把callable交给FutureTask用于取返回值
FutureTask<Integer> task = new FutureTask<>(callable);
// 把task交给线程,开启多线程
Thread thread = new Thread(task);
thread.start();
try {
// get会进入阻塞,直到线程结束,返回结果
System.out.println(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
线程池
3. 线程同步
-
多线程同时操作同一个共享数据时,会出现安全问题,如:经典的买票问题。
-
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
-
解决:在Java中使用线程同步来解决
3.1 Synchronized
- 只有操作共享数据的代码,才需要被同步
同步代码块
- 使用要求:必须确保使用同一个资源的多个线程共用一把锁, 这个非常重要, 否则就无法保证共享资源的安全
synchronized (锁对象){ // 任何一个类对象,都可以被当为锁
// 需要被同步的代码
}
同步方法
- 如果需要同步的代码,完全在方法里,可以使用此方式。
public synchronized void method(){
·····
}
synchronized的锁:
- 任意对象都可以作为同步锁。 所有对象都自动含有单一的锁(监视器)
- 同步方法的锁:静态方法(类名.class) 、 非静态方法(this)
- 同步代码块:自己指定, 很多时候也是指定为this或类名.class
同步的范围
- 如何找问题, 即代码是否存在线程安全? (非常重要)
- 明确哪些代码是多线程运行的代码
- 明确多个线程是否有共享数据
- 明确多线程运行代码中是否有多条语句操作共享数据
- 所有操作共享数据的这些语句都要放在同步范围中。
- 注意:
- 范围太小:没锁住所有有安全问题的代码
- 范围太大:没发挥多线程的功能,或者导致多线程语义上的功能不正确。
3.2 锁的释放
不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、==Thread.yield()==方法暂停当前线程的执行
释放锁的操作
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、 return终止了该代码块、
该方法的继续执行 - 当前线程在同步代码块、同步方法中出现未处理的Error或Exception, 导
致异常结束。 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。
3.3 线程死锁问题
-
说明:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
何时出现:嵌套同步时可以会出现死锁
3.4 Lock类
- jdk5.0提供的新特性(包路径:java.util.concurrent.locks.*),用于线程同步显示的加锁
public class LockTest {
public static class TestLock implements Runnable {
private final ReentrantLock lock = new ReentrantLock();
private int num = 100;
@Override
public void run() {
try {
// 显示加锁
lock.lock();
while (true) {
if (num <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"::"+num);
num--;
}
} finally {
// 显示解锁
lock.unlock();
}
}
}
public static void main(String[] args) {
TestLock tl = new TestLock();
Thread thread1 = new Thread(tl);
Thread thread2 = new Thread(tl);
Thread thread3 = new Thread(tl);
thread1.start();
thread2.start();
thread3.start();
}
}
- 注意:单元测试对多线程的支持不友好
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁, synchronized有代码块锁和方法锁
- 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:Lock -> 同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
4. 线程通信
传统的Synchronized中,线程通信使用到的方法
- wait():使当前线程进入阻塞(挂起)状态,并释放锁(同步监视器)
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
jdk5.0后,线程通信使用的方法
- 使用ReentrantLock 进行等待唤醒,需要使用Condition对象
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
-
Condition对象的中方法:
-
await():与Object的wait()功能相同
-
signal():与Object的notify()功能相同
-
-
一个Lock可以创建多个Condition,用于交替唤醒
注意与说明:
-
注意:这三个方法只有在synchronized方法或synchronized代码块中,并且调用这三个方法的对象必须和synchronized的锁对象一致,否则会报java.lang.IllegalMonitorStateException异常。
-
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
示例:
// 产品对象
class Product {
private volatile int productCount = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 生产方法
public void produce() {
try {
lock.lock();
if (productCount < 20) {
productCount++;
String name = Thread.currentThread().getName();
System.out.println(name + "::" + "生产第" + productCount);
condition.signal();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
// 消费方法
public void consume() {
try {
lock.lock();
if (productCount > 0) {
String name = Thread.currentThread().getName();
System.out.println(name + "::" + "消费第" + productCount);
productCount--;
condition.signal();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
public class ProducerAndConsumer {
private static final Product p = new Product();
// 生产线程
public static class Produce implements Runnable {
@Override
public void run() {
while (true) {
p.produce();
}
}
}
// 消费线程
public static class Consume implements Runnable {
@Override
public void run() {
while (true) {
p.consume();
}
}
}
public static void main(String[] args) {
Produce produce = new Produce();
Consume consume = new Consume();
Thread thread1 = new Thread(produce);
Thread thread2 = new Thread(consume);
thread1.setName("生产线程");
thread2.setName("消费线程");
thread1.start();
thread2.start();
}
}
5. 线程池
- 实际开发中,一般不会手动创建线程,都是使用池技术进行复用。
- 使用池技术的好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- ·····
JDK 5.0起提供了线程池相关API:
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
-
ExecutorService:线程池接口,常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,一般用来执行Runnable
- Future submit(Callable task):执行任务,一般又来执行Callable
- void shutdown() :当所有线程都结束后,关闭连接池
- shutdownNow():相当于每个线程调动interrupt()
示例:
public class ThreadPool {
public static void main(String[] args) {
// 创建线程池
// ExecutorService pool = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
// 管理线程池
pool.setCorePoolSize(15);
// 下面使用两个线程
pool.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+" ::"+i);
}
}
}
});
pool.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 1) {
System.out.println(Thread.currentThread().getName()+" ::"+i);
}
}
}
});
// 关闭连接池
pool.shutdown();
}
}
二、Java8 特性
1. Lambda
- Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)
- Lambda 是函数式接口的实现
1.1 函数式接口
- 只包含一个抽象方法的接口,称为函数式接口
- 接口上使用
@FunctionalInterface
注解标识和检查
Java内置四大核心函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为 void accept(T t) T的对象应用操作,包含方法: |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法: T get() |
Function<T, R> 函数型接口 | T | R | 对类型为 果是R类型的对象。包含 T的对象应用操作,并返回结果。结方法: R apply(T t) |
Predicate<T> 断定型接口 | T | bool | 确定类型为 boolean 值。包含 T的对象是否满足某约束,并返回 方法: boolean test(T t) |
- 其他接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | R apply(T t, U u); |
UnaryOperator<T> (Function子接口) | T | T | T apply(T t) |
BinaryOperator<T> (BiFunction 子接口) | T, T | T | T apply(T,t1, T t2); |
BiConsumer<T, U> | T, U | void | void accept(T t, U u) |
BiPredicate<T,U> | T,U | boolean | boolean test(T t,U u) |
ToIntFunction<T>ToLongFunction<T> ToDoubleFunction<T> | T | int、long、double | 分别计算int、 long、 double值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int、long、double | R | 参数分别为int、 long、 double 类型的函数 |
Lambda语法示例:
@Test
public void test01() {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(String.valueOf(i));
}
// 普通方式
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
// 表达式
/*
(String o1, String o2) -> {return o2.length() - o1.length()}
其中:
- 参数的类型可以省略,编译器会自动推断
- 单条语句时花括号和return关键字可以省略
- 如果是一个参数,参数的括号可以省略
*/
list.sort((o1, o2) -> o2.length() - o1.length());
list.forEach(System.out::println);
}
1.2 方法引用
-
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
-
使用要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同,如:
// Consumer 中的 void accept(T t) // 对应 // Printstream 中的 void println(T t) // lambda Consumer<String> c1 =(x) -> System.out.println(x); // 方法引用 Consumer<String> c1 = System.out::println; // 调用 c1.accept("hello");
-
-
其本质依然是Lambda表达式
-
方法引用语法:
对象::普通方法
类::静态方法名
类::普通方法
1.3 构造器引用
- 语法:
类名::new
![image-20200818153421520](Java%E5%9F%BA%E7%A1%80.assets/image-20200818153421520.png)
![image-20200818153435070](Java%E5%9F%BA%E7%A1%80.assets/image-20200818153435070.png)
1.4 数组引用
- 格式:
type[] :: new
Function<Integer, Integer[]> fun = (len) -> new Integer[len];
Function<Integer, Integer[]> fun = Integer[]::new;
2. Stream API
-
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
-
Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。 前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
-
关于Stream的说明:
- Stream 自己不会存储元素。
- Stream 不改变源对象的数据。他会返回一个持有结果的新Stream
- Stream 操作是延迟执行的。Stream只有执行终止操作时,Stream链才会执行
2.1 创建Stream方式
方式一:Java8 中的 Collection 接口被扩展,提供了两个获取流的方法
default Stream<E> stream()
:返回一个顺序流default Stream<E> parallelStream()
:返回一个并行流
方式二:通过数组,Java8 中的 Arrays 的静态方法 stream() 可以获取数组流
static <T> Stream<T> stream(T[] array)
:返回一个流
方式三:可以调用Stream类静态方法 of(),通过显示值创建一个流。它可以接收任意数量的参
public static<T> Stream<T> of(T... values)
:返回一个流
方式四:创建无限流,可以使用静态方法 Stream.iterate() 和 Stream.generate()创建无限流。
- 迭代:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- 生成
public static<T> Stream<T> generate(Supplier<T> s)
// 用于造数据
@Test
public void test4() {
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// 从0开始,获取10个偶数
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}
2.2 Stream中间操作
- 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为“惰性求值” 。
筛选与切片:
filter(Predicate p)
:接收 Lambda , 从流中排除某些元素distinct()
:去重,根据元素的hashCode() 和 equals()limit(long maxSize)
:截断流,使其元素不超过给定数量skip(long n)
:返回一个扔掉了前n 个元素的流。若流中元素不足 n 个,则返回一个空流
映射:
-
map(Function f)
:根据函数的逻辑,映射成一个新的元素。 -
mapToDouble(ToDoubleFunction f)
:映射成一个元素都为double集合。 -
mapToInt(ToIntFunction f)
:映射成一个元素都为int集合 -
mapToLong(ToLongFunction f)
:映射成一个元素都为Long集合 -
flatMap(Function f)
:将流中的每个值都换成另一个流,然后把所有流连接成一个流-
// 将员工的名字按字符拆开形成新的流 public Stream<Character> toCharStream(Employee employee) { char[] chars = employee.getName().toCharArray(); ArrayList<Character> list = new ArrayList<>(); for (char aChar : chars) { list.add(aChar); } return list.stream(); } @Test public void test3() { // 用flatMap把多个流合并成一个流,并输出 emps.stream() .flatMap((Function<Employee, Stream<?>>) this::toCharStream) .forEach(System.out::println); }
-
排序:
sorted()
:根据元素comparable排序sorted(Comparator com)
:根据Comparator排序
2.3 Stream终止操作
- 一旦调用了如下的某一个终止操作,Stream流就结束了
匹配与查找:
allMatch(Predicate p)
:查看所有元素是否满都足一个(组)条件,返回一个bool值anyMatch(Predicate p)
:只要有一个条件满足,就返回truenoneMatch(Predicate p)
:没有一个满足条件的,返回truefindFirst()
:返回第一个元素,返回一个Optional类型findAny()
:返回当前流中的任意元素,返回一个Optional类型count()
:返回流中元素总数,返回long类型max(Comparator c)
:返回流中最大的元素,Optional类型min(Comparator c)
:返回流中最小的元素,Optional类型forEach(Consumer c)
:内部迭代
归约
reduce(T iden, BinaryOperator b)
:可以将流中元素反复结合起来,得到一个值。返回 T,iden为初始值reduce(BinaryOperator b)
:可以将流中元素反复结合起来,得到一个值。返回 Optional <T>
收集
collect(Collector c)
:接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,Collector接口的实现有如下:
方法 | 返回值类型 | 作用 |
---|---|---|
toList | List<T> | 把流中元素收集到List |
List<Employee> emps =list.stream().collect(Collectors.toList()); | ||
toSet | Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); | ||
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
Collection<Employee> emps = list.stream().collect(Collectors.toCollection(ArrayList::new)); | ||
counting | Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting()); | ||
summingInt | Integer | 对流中元素的整数属性求和 |
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | ||
averagingInt | Double | 计算流中元素Integer属性的平均值 |
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | ||
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值,如:平均值 |
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); | ||
joining | String | 连接流中每个字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); | ||
maxBy | Optional<T> | 根据比较器选择最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); | ||
minBy | Optional<T> | 根据比较器选择最小值 |
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); | ||
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); | ||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 |
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List<T>> | 根据某属性值对流分组,属性为K,结果为V |
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | ||
partitioningBy | Map<Boolean, List<T>> | 根据true或false进行分区 |
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
- 此类主要解决的是空指针异常问题
常用方法:
- 创建Optional类对象的方法:
Optional.of(T t)
: 创建一个 Optional 实例, t必须非null;Optional.empty()
: 创建一个空的 Optional 实例Optional.ofNullable(T t)
: t可以为null
- 判断Optional容器中是否包含对象:
boolean isPresent()
: 判断是否包含对象void ifPresent(Consumer<? super T> consumer)
: 如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
- 获取Optional容器的对象:
T get()
: 如果调用对象包含值,返回该值,否则抛异常T orElse(T other)
: 如果有值则将其返回,否则返回指定的other对象。T orElseGet(Supplier<? extends T> other)
: 如果有值则将其返回,否则返回由Supplier接口实现提供的对象。T orElseThrow(Supplier<? extends X> exceptionSupplier)
: 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
三、Java9 特性
1. 模块化系统
模块化系统推出原因:
- Java 运行环境的膨胀和臃肿。 每次JVM启动的时候,至少会有30~60MB的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被classloader加载,第一步整个jar都会被JVM加载到内存当中去,而模块化的出现可以根据模块的需要加载程序运行需要的class
- 解决不同版本的类库交叉依赖问题
- 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。 每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API。
示例:
- 建立连个模块,并在连个模块的src目录下,新建module-info.java文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6hqqpcTN-1597908459779)(Java%E5%9F%BA%E7%A1%80.assets/image-20200819074101092.png)]
- 有一个需求:要想在java9demo模块中调用java9test模块下包中的结构, 需要在java9test的module-info.java中声明
// module 名为模块名
module java9test {
// exports:控制着哪些包可以被其它模块访问到。 所有不被导出的包默认都被封装在模块里面。
exports com.java.bean;
}
- 对应在java9demo配置module-info.java文件:
module java9demo {
// requires:指明对其它模块的依赖。
requires java9test;
}
2. jShell
- 他是一个java的交互式命令行环境,类似于Python的idle,可在命令行执行java代码
使用:
-
在windows或linux终端:
jshell
进入java的shell环境, -
tab支持自动补全
-
jShell支持文件中加载语句或者将语句保存到文件中
3. 语法改进
-
在java8中,接口可以定义static方法和default方法
- static方法和default方法有方法体
- static方法必须由接口调用,实现(子)类不可以调用接口中的static方法
-
接口新增可以定义private方法
-
匿名内部类的钻石操作符(<>)可以进行类型推断,java8之前,匿名内部类需要指定泛型类型
-
资源的释放语法优化:
- Java 8 中, 可以实现资源的自动关闭, 但是要求执行后必须关闭的所有资源必须在try子句中初始化, 否则编译不通过。 如:
try(InputStreamReader reader = new InputStreamReader(System.in)){ ······ }catch (IOException e){ e.printStackTrace(); }
- Java 9 中, 用资源语句编写try将更容易, 我们可以在try子句中使用已经初始化过的资源, 此时的资源是final的:
InputStreamReader reader = new InputStreamReader(System.in); OutputStreamWriter writer = new OutputStreamWriter(System.out); try (reader; writer) { //reader是final的,不可再被赋值 ····· } catch (IOException e) { e.printStackTrace(); }
-
java9之后,String类型及其衍生类的底层存储重char[]改用byte[]数组,用于节省内存空间。
- byte数组和一个字符集表示变量配合使用,如果是英文使用iso-8859字符集,每个字占一个字节,如果是汉字或其他文字,则使用其他占用字节较多的字符集
4. 只读集合
-
List<T> List.of(T ....)
:Jdk 里面为集合(List / Set / Map) 都添加了 of -
List<T> List.copyOf(T...)
:jdk10新增方法, 它们两个都用来创建不可变的集合,区别:-
var list1 = List.of("Java", "Python", "C"); // 由于list1是不可变的,所以只复制引用 var copy1 = List.copyOf(list1); System.out.println(list1 == copy1); // true var list2 = new ArrayList<String>(); // list2是可变的,所以在堆空间复制了一个新的不可变的ArrayList var copy2 = List.copyOf(list2); System.out.println(list2 == copy2); // false
-
-
Collication Collections.unmodifiableMap(Collection c)
5. InputStream加强
- InputStream新增一个方法:
transferTo(OutputStream o)
,可以用来将数据直接传输到 OutputStream,如:
ClassLoader cl = this.getClass().getClassLoader();
try (InputStream is = cl.getResourceAsStream("hello.txt");
OutputStream os = new FileOutputStream("src\\hello1.txt")) {
is.transferTo(os); // 把输入流中的所有数据直接自动地复制到输出流中
} catch (IOException e) {
e.printStackTrace();
}
6. Stream 新增API
-
takeWhile()
:takeWhile 返回从开头开始满足条件的尽量多的元素List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 21, 73, 67, 88); // 选择满足小于50的元素,直到不满足立刻结束 list.stream().takeWhile(x -> x < 50).forEach(System.out::println); // 结果:45,33
-
dropWhile()
:与takeWhile相反,从开始满足条件的都忽略,不满足条件的尽量多的元素List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 21, 73, 67, 88); list.stream().dropWhile(x -> x < 50).forEach(System.out::println); // 结果:76, 87, 42, 77, 21, 73, 67, 88
-
ofNullable()
:Java8中Stream不能完全为null,ofNullable 方法允许我们创建一个单元素 Stream// 报异常,不允许通过 Stream<Object> stream1 = Stream.of(null); // 允许通过 Stream<String> stringStream = Stream.of("AA", "BB", null); System.out.println(stringStream.count()); // 3 // 允许通过 List<String> list = new ArrayList<>(); list.add("AA"); list.add(null); System.out.println(list.stream().count()); // 2 // ofNullable():允许值为null Stream<Object> stream1 = Stream.ofNullable(null); System.out.println(stream1.count()); // 0 Stream<String> stream = Stream.ofNullable("hello world"); System.out.println(stream.count()); // 1
-
iterate()
: 无限流 iterate 方法的新重载方法, 可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代// 原来的控制终止方式: Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out::println); // 现在的终止方式: Stream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);
7. Optional增强
-
Optional类中新增方法
stream()
List<String> list = List.of("aa","bb","cc") Optional<List<String>> optional = Optional.ofNullable(list); Stream<List<String>> stream = optional.stream(); stream.flatMap(x -> x.stream()).forEach(System.out::println);
四、Java10 特性
1. 局部变量类型推断
- 局部变量的显示类型声明,认为是冗余的,给变量一个准确的名字,可以很清楚的表达出下面应该怎样继续
- 在编译时处理var, 编译器根据右边变量值的类型进行推断,然后将该类型写入字节码当中。
- var不是一个关键字,除了不能用它作为类名, 其他的都可以。
语法:
//1.局部变量的初始化
var list = new ArrayList<>();
//2.增强for循环中的索引
for(var v : list) {
System.out.println(v);
}
不能进行var推断场景:
-
初始值为null:
var s = null;
-
方法引用:
var r = System.out::println;
-
Lambda表达式:
var r = ()->Math.random;
-
为数组静态初始化:
var arr = {1,2,3};
-
没有初始化的变量声明:
var num;
-
方法的返回类型:
public var method(){····}
-
方法的参数类型:
public int method(var a){}
-
构造器的参数类型:
public TestClass(var name)
-
属性(全局变量)
-
catch块:
catch(var e){···}
五、Java11 特性
1. String 新增API
-
" \t \n".isBlank() //true
:判断是否为空白符(空格,tab、换行都是空白符) -
" \t abc \n ".strip(); //abc
:与trim方法相同,去除首尾空白符 -
" \t abc ".stripLeading();
:去除首部空白符 -
" \t abc ".stripTrailing();
:去除尾部空白符 -
"Java".repeat(3);// "JavaJavaJava"
:复制字符串 -
"A\nB\nC".lines().count(); // 3
:行数统计
2. Optional 增强
新增方法 | 描述 | 新增的版本 |
---|---|---|
boolean isEmpty() | 判断value是否为null | JDK 11 |
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) | value非空,执行参数action功能;如果value为空,执行参数emptyAction功能 | JDK 9 |
Optional or(Supplier<? extends Optional<? extends T>> supplier) | value非空,返回对应的Optional; value为空,返回形参封装的Optional | JDK 9 |
Stream stream() | value非空,返回仅包含此value的 Stream;否则,返回一个空的Stream | JDK 9 |
T orElseThrow() | value非空,返回value;否则抛异常NoSuchElementException | JDK 10 |
3. var升级
- 在var上添加注解的语法格式
//错误: 必须要有类型, 可以加上var
Consumer<String> con1 = (@Deprecated t) ->System.out.println(t.toUpperCase());
//正确:使用var的好处是在使用lambda表达式时给参数加上注解。
Consumer<String> con2 = (@Deprecated var t) ->System.out.println(t.toUpperCase());
4. 简化编译运行
java xxxx.java
:不用编译,直接运行java文件,但是有要求- 执行源文件中的第一个类, 第一个类必须包含主方法。
- 不能用其它源文件中的自定义类,本文件中的自定义类是可以使用的。