1 可变参数
当方法的参数列表数据类型已经确定,但是参数的个数不确定时,可以使用可变参数
- 格式为
修饰符 返回值类型 方法名(数据类型...变量名){}
- 原理:底层为一个数组,根据传递参数个数不同,会创建不同长度的数组来存储参数,传入参数可以是0个
- 终极写法
public static void func(Object...obj)
```java package Pro;
public class VarArgs { public static void main(String[] args) { System.out.println(sum()); System.out.println(sum(1)); System.out.println(sum(1,2)); System.out.println(sum(1,2,3)); } public static int sum(int…args){ int sum = 0; for (int n : args) { sum += n; } return sum; } }
<a name="gcFr3"></a>
# 2 异常
- **异常** :程序在执行过程中,出现的非正常情况,最终会导致JVM的非正常停止
<a name="cPvMl"></a>
## 2.1 异常的分类
- `java.lang.Throwable` 是所有异常的超类,有两个直接子类 `java.lang.Error` 和 `java.lang.Exception`
- **Error** 非常严重的错误,无法通过处理的错误,只能事先避免
- **Exception** 编译期异常,由于使用不当,可以避免的错误
- **RuntimeError** 运行期异常,java程序运行过程中出现的问题
<a name="jmlmN"></a>
## 2.2 throw/throws
<a name="nEPiW"></a>
### throw
- 可以使用 `throw` 关键字在指定的方法中抛出指定的异常
- 注意事项
- `throw` 必须写在方法的内部
- `throw` 后 `new` 的对象必须是 `Exception` 或其子类对象
- `throw` 抛出指定的异常对象,就必须处理这个异常对象
- `throw` 后面创建的是 `RuntimeException` 或其子类,可以不处理,默认交给JVM处理
- `throw` 后面创建的是编译异常,就必须处理此异常,要么 `throws` 要么 `try...catch`
```java
package Pro.Exception;
public class DemoThrow {
public static void main(String[] args) {
int[] arr1 = null;
int[] arr2 = {};
// int e1 = getElement(arr1, 0);
// System.out.println(e1);
int e2 = getElement(arr2, 0);
System.out.println(e2);
}
public static int getElement(int[] arr, int index){
if(arr==null) {
throw new NullPointerException("所传递的数组为空!");
}
if(index<0 || index>=arr.length){
throw new ArrayIndexOutOfBoundsException("数组索引越界!");
}
return arr[index];
}
}
throws
- 可以使用
throws
关键字将异常对象抛出给方法的调用者处理,最终交给JVM处理 - 注意
throws
必须写在方法声明处throws
声明的异常必须是Exception
或其子类throws
必须抛出方法内部的所有异常对象,若存在继承关系,只需抛出其父类- 调用了一个声明抛出异常的方法,就必须处理此异常,若不处理
- 要么继续抛出该异常
- 要么
try...catch
```java package Pro.Exception;
import java.io.FileNotFoundException; import java.io.IOException;
public class DemoThrows { public static void main(String[] args) throws IOException { String fileName = “a.txt”; readFile(fileName); }
public static void readFile(String fileName) throws IOException {
if (!fileName.equals("a.txt")) {
throw new FileNotFoundException("传递的文件不是a.txt");
}
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名不对!");
}
System.out.println("路径正确,读取成功!");
}
}
<a name="JnlRW"></a>
## 2.3 try...catch...finally
- try中可能会抛出多个异常对象,就可以使用多个catch来处理异常
- 如果try中产生了异常,就会执行catch中的异常处理逻辑
- 如果try中未捕获到异常,不会中执行catch中的代码块
- 无论是否捕获到异常,try...catch之后的代码都可以正常执行
```java
package Pro.Exception;
import java.io.FileNotFoundException;
import java.io.IOException;
public class DemoTryCatch {
public static void main(String[] args) {
String fileName = "b.txt";
try{
readFile(fileName);
}catch (IOException e){
System.out.println("文件路径不对!");
}
System.out.println("后续代码继续执行!");
}
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名不对!");
}
if(!fileName.equals("a.txt")){
throw new FileNotFoundException("找不到文件" + fileName);
}
System.out.println("成功打开" + fileName);
}
}
finally
是无论是否出现异常都会执行的代码块注意事项
public String getMessage()
返回此throwable对象的简述public String toString()
返回此throwable对象的详细信息public void printStackTrace()
JVM打印异常默认调用的方法2.5 多异常处理
多异常处理有三种方式
多个异常分别处理
- 一次捕获,多次处理(一个try,多个catch)
- catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则会报错
- 一次捕获,同时处理
- try中的所有异常可以归类为一个超类异常时方可使用 ```java package Pro.Exception;
import com.sun.tools.javac.util.List;
public class DemoMultiException {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
List
// I.一次捕获,多次处理
// try{ // System.out.println(arr[2]); // System.out.println(list.get(3)); // }catch(ArrayIndexOutOfBoundsException e){ // System.out.println(e); // }catch(IndexOutOfBoundsException e){ // System.out.println(e); // }
// III.一次捕获,同时处理
try{
System.out.println(arr[3]);
System.out.println(list.get(3));
}catch(IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
<a name="jfWlK"></a>
## 2.6 子父类异常
- 如果父类抛出了多个异常,子类重写父类方法时,有三种选择方式
- 抛出和父类相同的异常
- 抛出父类异常的子类
- 不抛出异常
- 如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,若子类产生异常,只能捕获处理
<a name="IH8hu"></a>
## 2.7 自定义异常类
```java
public class CustomException extends Exception/RuntimeException{
public CustomException(){
super();
}
public CustomException(String exceptionMessage){
super(exceptionMessage);
}
}
3 多线程
4.1 并发与并行
- 并发 指两个或多个事件在 同一个时间段内 发生
-
4.2 线程与进程
概念
进程 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个将进程从创建、运行到消亡的过程
线程 是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,也可以有多个线程
线程调度
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
- 抢占式调度
优先让优先级高的线程使用CPU,如果优先级相同,则随机选择一个,Java使用的为抢占式调度
4.3 创建线程类
- 主线程 执行主(main)方法的线程
创建新线程有两种方法
- 将类声明为
java.lang.Thread
的子类,该子类重写Thread类的run方法,然后通过其实例启动一个线程 - 声明实现
Runnable
接口的类,该类实现run方法,该类实现一个对象作为Thread
类的构造参数,然后可以通过start
启动一个线程
注意 : 多次启动同一个线程是非法的,特别是当一个线程已结束执行 ,不能再重新启动
创建线程的第一种方式
package multithreading;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run " + i);
}
}
}
package multithreading;
public class Demo01Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 10; i++) {
System.out.println("main " + i);
}
}
}
创建线程的第二种方式
package multithreading;
public class SecondClock implements Runnable{
@Override
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println(i + 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package multithreading;
public class Demo02Thread {
public static void main(String[] args) {
SecondClock sc = new SecondClock();
new Thread(sc).start();
}
}
Thread的方法
Thread的构造方法
public Thread()
分配一个新的线程对象public Thread(String name)
分配一个指定名字的新的线程对象public Thread(Runnable target)
分配一个带有指定目标的新的线程对象public Thread(Runnable target, String name)
分配一个带有指定目标的线程对象并起名
Thread的常用方法
String getName()
返回该线程的名称void setName(String name)
设置线程的名称static Thread currentThread()
返回对当前正在执行的线程对象的引用public static void sleep(long millis)
睡眠millis毫秒public void start()
使此线程启动-
两种方式的区别
实现Runnable接口比继承Thread类所具有的优势
避免了单继承的局限性(一个类只能有唯一一个直接父类)
增强了程序的扩展性,降低了程序的耦合性(解耦)
继承
Thread
方式 ```java package multithreading;
public class DemoAnonymous { public static void main(String[] args) { new Thread(){ @Override public void run(){ System.out.println(“这是一个匿名内部类实现的线程”); } }.start(); } }
- 实现 `Runnable` 接口方式
```java
package multithreading;
public class DemoAnonymous02 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名接口对象实现多线程:" + Thread.currentThread().getName());
}
}).start();
}
}
4.4 线程安全
多个线程访问了共享数据 ,就会产生线程安全问题。 线程安全问题是应当避免的,可以让一个线程在访问共享数据是时,其他线程只能等待,而无论此线程是否抢占到CPU执行权。
线程同步
1.同步代码块 synchronized(锁对象){访问统一资源的代码块}
可以使用在某一代码块中,表示对此代码块执行线程访问的互斥操作
- 同步代码块中的锁对象可以使用任意的对象
- 必须保证多个线程使用的锁对象是同一个
- 锁对象作用:只让一个线程在同步代码块中执行
该方法的缺陷 程序频繁地判断锁、获取锁、释放锁,使程序的效率降低
package multithreading;
public class SailTicket implements Runnable {
private int ticketCount = 100;
private Object obj = new Object();
@Override
public void run() {
while (ticketCount > 0) {
System.out.println("-------------------开始抢锁--------------------");
synchronized (obj) {
if (ticketCount > 0) {
System.out.print(Thread.currentThread().getName() + "抢到了卖票权,");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在出售第" + ticketCount + "张票");
ticketCount--;
}
}
}
}
}
package multithreading;
public class DemoSynchronized {
public static void main(String[] args) {
SailTicket run = new SailTicket();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
运行结果图
同步技术的原理 使用了一个锁对象,多个线程同时访问同步代码块时,谁抢到了锁对象谁就优先执行,其他线程则只能被阻塞,等待当前线程执行玩同步代码块后再重新开始抢夺使用权。
2.同步方法
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){访问共享数据的代码}
同步技术的原理 实际上也是利用锁对象,同步方法锁使用的锁对象是this,也就是调用线程的 Runnable
对象本身
package multithreading;
public class SailTicket02 implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while (ticketCount > 0) {
sailTicket();
}
}
private synchronized void sailTicket() {
if(ticketCount > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了售票权,正在出售第" + ticketCount + "张票");
ticketCount--;
}
}
}
package multithreading;
public class DemoSynchronized02 {
public static void main(String[] args) {
SailTicket02 run = new SailTicket02();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
3.静态同步方法
使用步骤
- 使用
Lock
接口的实现类ReentrantLock
创建一个对象 - 在可能会出现安全问题的代码前调用
lock()
方法 - 在可能会出现安全问题的代码后调用
unlock()
方法 ```java package multithreading;
import java.util.concurrent.locks.ReentrantLock;
public class SailTicket03 implements Runnable { private int ticketCount = 100; ReentrantLock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (ticketCount > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "抢到了售票权,正在出售第" + ticketCount + "张票");
ticketCount--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
}
```java
package multithreading;
public class DemoLock {
public static void main(String[] args) {
SailTicket03 run = new SailTicket03();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
4.5 线程状态
线程的六种状态及转化
线程状态 | 导致状态发生的条件 |
---|---|
NEW | 至今尚未启动的线程处于这种状态 |
RUNNABLE | 正在Java虚拟机中执行的线程 |
BLOCKED | 受阻塞并等待某个监视器锁的线程 |
WAITING | 无限期地等待另一个线程来执行某一特定操作的线程 |
TIMED_WATING | 等待另一个线程来执行取决于指定等待时间的操作的线程 |
TERMINATED | 已退出的线程 |
线程通信/等待唤醒
多个线程在处理同一个资源,但是处理的动作(线程的任务并不相同),线程之间就存在线程通信问题。
Object类中的方法
void wait()
在其他线程调用此对象的notify()
方法或notifyAll()
方法之前,导致当前线程等待void notify()
唤醒在此对象监视器上等待的单个线程,会继续执行wait方法之后的代码
无限期等待
package multithreading;
/*卖包子的案例*/
public class WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
//创建一个顾客线程
new Thread() {
@Override
public void run() {
// 保证等待与唤醒只能有一个在执行,需要使用同步技术
while (true) {
synchronized (obj) {
System.out.println("老板,我要买包子。");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行wait之后的代码
System.out.println("老板,你这包子真不错,我还要。");
System.out.println("==============================");
}
}
}
}.start();
//创建一个老板线程
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("小伙子,你的包子做好了。");
obj.notify();
}
}
}
}.start();
}
}
程序执行的结果
计时等待
void sleep(long m)
方法void wait(long m)
方法,若在m时间内未被notify唤醒,则线程自动醒来
唤醒方法
void notify()
如果有很多人等你做包子,你只做好了一个,你随便选一个给他们吃就行void notifyAll()
比如说有很多个人在等你做包子,你给他们同时做好了,就可以叫他们一起来付钱(虽然包子是够的,但人性贪婪,顾客会争着抢着先付钱)4.6 线程池
频繁地创建与销毁线程需要时间,如果一个线程执行很短的时间就结束了,就会大大降低系统的效率,因此引入 线程池 的概念
- 当程序第一次启动的时候,就会创建多个线程,保存到一个集合中
- 当想要使用线程时,就可以从集合中取出线程使用
- 当线程使用完毕后,需要把线程归还给线程池
java.util.concurrent.Executors
JDK1.5之后的线程池工厂类
Executors
类中的静态方法
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
ExecutorService
接口中的方法
Future submit(Runnable task)
,提交一个task用于执行,并返回一个表示该任务的Future-
5 Lambda表达式
Lambda的使用前提
必须具有接口,且要求接口中有且仅有一个抽象方法
- 使用Lambda必须具有 上下文推断
注:有且仅有一个抽象方法的接口称之为 函数式接口
先来一个lambda表达式开开眼界
package LAMBDA;
public class Demo01 {
public static void main(String[] args) {
//使用匿名内部类创建多线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " created.");
}
}).start();
//使用lambda表达式创建多线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + " created.")).start();
}
}
5.1 标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成
- 一些参数
- 一个箭头
- 一段代码
其标准格式为(参数类型 参数名称) -> {代码语句}
格式说明
- ():接口中抽象方法的参数列表,无参数就留空,多个参数之间用逗号分隔
- -> : 把参数传递给方法体{}
-
5.2 无参数无返回值
给定一个厨子
Cook
接口,内含唯一的抽象方法cookFood()
public interface Cook {
void makeFood();
}
使用lambda标准格式调用
invokeCook
方法 ```java public class DemoInvokeCook { public static void main(String[] args) {invokeCook(() -> {
System.out.println("店里来客了,快去给我做饭啦!");
});
}
private static void invokeCook(Cook cook) {
cook.makeFood();
} }
<a name="BBiDU"></a>
## 5.3 有参数有返回值
需求描述
- 使用数组存储多个Person对象
- 调用 `Arrays.sort` 方法对数组中的人年龄进行升序排序
```java
import java.util.Arrays;
public class DemoPerson {
public static void main(String[] args) {
Person[] arr = {
new Person("瓜兮兮", 18),
new Person("哈搓搓", 28),
new Person("憨包包", 20)
};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Arrays.sort(arr, (Person o1, Person o2) ->{
return o1.getAge() - o2.getAge();
});
System.out.println("=======================");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
给定一个计算器 Calculator
接口
public interface Calculator {
int calc(int a, int b);
}
使用lambda标准格式计算两数之和
public class DemoInvokeCalc {
public static void main(String[] args) {
invokeCalc(120, 130, (int a, int b) -> {
return a + b;
});
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
5.4 省略格式
Lambda表达式的内涵:凡是可以根据上下文推导出来的内容,都可以省略书写(可推导,可省略)
可以省略的内容有