异常
Java中的错误情况:
语法错误:编译肯定不通过,必须照着笔记查看是违反什么语法,修正之后才能编译通过。
逻辑错误:编译可以通过,但是运行得不到正确的结果。
异常:有的编译可以通过。但是程序运行期间,发生异常,导致程序中断/崩溃;有的是编译不通过,会提醒我们可能会发生xx异常,我们必须做出处理,如果不处理,编译不通过;如果处理了,编译通过,如果处理得当,程序发生异常后,还是可以正常运行;否则的话程序发生异常后,还会崩溃。
1.什么是异常
程序执行过程中,发生的非正常情况。Java的语法错误和逻辑错误不在异常讨论范围内。
2.Java中如何表现异常和处理异常?
(1)Java中一切皆对象,在这里也有体现。
Java会给每一种“异常”情况,用一种类型来描述,即一种异常对应一个类。
例如:空指针异常==》NullPointerException
数组下标越界==》ArrayIndexOutOfBoundsException
类型转换异常==》ClassCastException
算数异常==》ArithmeticException
...
(2)当某句代码发生异常时,JVM会根据当前的情况,判定是属于哪种异常,并且会给我们new一个异常的对象,“抛”出来。
(3)如果在发生异常的代码的外围有try..catch这中结构,那么JVM会继续判断try..catch结构是否可以捕获,如果可以捕获,程序就不会挂掉,否则程序依然会挂掉。
如果在发生异常的代码外,没有try..catch,那么程序肯定会挂掉。
异常体系
1.如果仅从类的角度来说,根父类仍然是Object,如果从异常的角度来说,根异常类型是java.lang.Throwable类。
(1)只有作为此类(或其一个子类)的实例的对象由Java虚拟机抛出,或者可以由Java throw语句抛出。
(2)只有此类或其子类之一才可以是catch子句中的参数类型
2.两个子类的实例,Error和Exception,通常用于指示发生了异常情况。即Java的异常可以分为两大类:
(1)java.lang.Error:用于表示合理的应用程序不应该试图捕获的严重问题。
例如:Virtual MachineError(虚拟机错误),其中一个子类StackOverflowError(栈内存溢出):在使用递归时可能会发生。另一个子类OutOfMemoryError(简称OOM,堆内存溢出),因为GC回收对象的内存基本上都是在堆中。
(2)Exception:指出了合理的程序应用程序想要捕获的条件。
3.Exception又可以划分为:
(1)编译时异常(又称为受检异常):除了运行时异常,剩下都是编译时异常。
在编译期间,编译器会提示程序员某段代码可能发生异常,但是不表示一定会发生,需要程序员必须提前写好处理异常的代码,否则编译不通过。
(2)运行时异常(又称为非受检异常):RuntimeException及其子类。
空指针异常==》Null Pointer Exception
数组下标越界==》ArrayIndexOutOfBoundsException
类型转换异常==》ClassCastException
算数异常==》ArithmeticException
这些异常都是RuntimeException异常的子类。在编译期间,完全感受不到,在程序运行时可能发生。运行时异常,大多数都需要靠程序员“自觉”通过代码去避免,
例如:类型转换之前,通过instanceof判断后再向下转型;在两个整数相除之前,要考虑除数是不是为0.
注意:
无论是编译时异常还是运行时异常,一旦发生,只要没有合理处理,都会导致程序中断/崩溃。
异常处理
1.try...catch
(1)语句结构
try{
可能发生异常的代码;
}catch(异常类型1 e){
处理异常的代码;
}catch(异常类型2 e){
处理异常的代码;
}...
2.说明
(1)catch中的{}中处理异常的代码:
①仅仅是打印,做简单程序时是在控制台,做项目时,记录到日志。
②必须编写对应的逻辑处理。
(2)catch可能有多个
①如果有多个,判断的顺序是从上往下
②如果上面的某个catch()中的类型与try中发生异常的类型“匹配”,那么下面的catch就不看了,即多个catch只会执行其中一个。
③如果有多个异常,多个异常的类型之间如果有包含关系,必须子类在上,父类在下。
④虽然编写了多个的catch,但是仍然存在一个catch都匹配不上的情况。
(3)当某些代码需要加try..catch进行异常处理时,可以选中这些代码,然后按快捷键Ctrl+Alt+T,然后选择try..catch。
3.异常信息打印
(1)标准的异常打印语句(JSE期间推荐)
e.printStackTrace():打印异常对象的堆栈跟踪记录,就是这个异常对象经过哪些调用。
(2)普通的打印语句
System.out.println(xx);当成普通对象打印
(3)普通的错误打印
System.error.println(xx);当成错误对象打印,内容跟(2)相同,颜色不同。
finally块
1.finally块它必须和try或try..catch一起使用,不能单独使用
2.语法格式
(1)格式一
try{
}finally{
}
//说明:try中发生的话,它不处理异常,把异常抛给使用者
(2)格式二
try{
可能发生异常的代码;
}catch(异常类型1 e){
处理异常的代码;
}catch(异常类型2 e){
处理异常的代码;
}...finally{
}
3.finally块有什么用?
(1)无论try中是否发生异常
(2)无论catch是否存在,或者说,存在的话,是否可以捕获异常
(3)无论try...catch中是否有return语句,finally块都要执行。
4.放什么进去?
通常是资源代码释放,例如:xxx.close()。
5.在什么情况下finally不执行?
在try或catch中执行了System.exit(0),直接退出虚拟机,就不会执行finally中的代码块了。
6.finally和return
要点:
(1)无论try或catch中是否有return,finally都要执行。(finally的return会让try和catch中的return失效,开发时千万不要出现这种情况)
throws声明异常
1. throws用在哪里?
在使用方法声明时
//非抽象方法
【修饰符】 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
//抽象方法或native方法
【修饰符】 返回值类型 方法名(【形参列表】)【throws 异常列表】;
2.为什么使用?
通常情况下,我们会在可能发生异常的代码“外围”用try...catch进行异常的检测和处理。但是有的时候,在该代码位置不适合try...catch处理异常,而是应该由上级(调用者)来处理,那么这个时候,就需要吧可能发生的异常类型告诉调用者这个方法可能发生xx异常需要注意并处理。如果调用者也不处理,那么就会导致程序崩溃。
3.throws后面可以写几个异常?
(1)throws后面可以写多个异常,并且多个异常的顺序无所谓
(2)如果throws后面又多个异常,并且多个异常之间有包含关系的话,可以并列存在,也没有顺序要求,通常这种情况我们保留父类就可以了。
4.throws异常列表在重写时,有什么要求?
方法重写的要求:
(1)方法名:必须相同
(2)形参列表:必须相同
(3)返回值类型
基本数据类型和void都必须相同
引用数据类型:<=被重写的方法
(4)权限修饰符:>=被重写的方法的权限修饰符
注意:被重写的方法不能private,跨包重写不能时缺省
(5)其他修饰符:被重写方法不能使static和final
(6)以下:
①如果被重写方法没有throws编译时异常,那么重写方法时,就不能throws编译时异常
②如果被重写方法throws编译时异常,那么重写方法时,只能throws该异常类型或该异常类型的子类。<=
③如果被重写方法throws运行时异常,子类重写时,可以一样,可以不写。(无关)
④如果被重写方法没有throws运行时异常,子类重写时,可以一样,可以throws自己的运行时异常。(无关)
结论:重写时throws后只看编译时类型。
两同两小一大四不能(补充笔记(十))
(1)方法的重写(override)
当子类继承了父类的成员方法后,如果父类的方法体实现不适合子类,那么子类可以选择重写。
(2)方法的重写有要求
①方法名必须相同
②形参列表必须相同
③返回值类型:
Ⅰ:父类方法的返回值类型是void和基本数据类型,那么子类方法必须与父类相同。
Ⅱ:父类方法的返回值类型是引用数据类型,那么子类方法的返回值类型可以是这个类,或者这个类的子类。
④权限修饰符:
Ⅰ:如果父类的方法的权限修饰符是private,那么子类是无法进行重写的,
如果父类是其他包的,并且方法的修饰符是缺省,那么子类也是无法进行重写的。
Ⅱ:其他情况下,重写方法的权限修饰符 可以大于等于被重写方法的权限修饰符
本包中,父类的方法的修饰符是缺省的,子类的重写方法的修饰符可以是缺省的,protected,public 其他包中,父类的方法的修饰符是protected,子类的重写方法的修饰符可以是protected,public 其他包中,父类的方法的修饰符是public,子类的重写方法的修饰符可以是public
⑤其他修饰符
父类的static,final方法是不能重写的
(3)抛出异常时的要求
①如果被重写方法没有throws编译时异常,那么重写方法时,就不能throws编译时异常
②如果被重写方法throws编译时异常,那么重写方法时,只能throws该异常类型或该异常类型的子类。<=
③如果被重写方法throws运行时异常,子类重写时,可以一样,可以不写。(无关)
④如果被重写方法没有throws运行时异常,子类重写时,可以一样,可以throws自己的运行时异常。(无关)
//throws声明异常
public class TestThrows {
public static void main(String[] args) {
try {
copy("d:/1.txt","e:/1.txt");
//调用者在调用copy方法时,就发现copy方法可能发生FileNotFoundException,需要处理
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("其他代码");
System.out.println("-----------------------------");
Father f = new Son();//多态引用
f.method(); //编译时看Father,运行时看Son
try {
f.fun();//编译时看Father,运行时看Son
} catch (IOException e) { //父类的类型是可以匹配子类的异常对象
e.printStackTrace();
}
System.out.println("-----------------------------");
Father father = new Son();
Object value = f.getValue();//编译时看Father,运行时看Son
}
/**
* 希望编写一个方法,实现复制一个文件的功能
* @param srcFileName String 源文件的路径和文件名
* @param destFileName String 目标文件的路径和文件名
*/
public static void copy(String srcFileName, String destFileName)throws FileNotFoundException {
// FileInputStream 这个类学过,从名字中猜测一下,用于读文件
//new FileInputStream(srcFileName);可能抛出一个异常,这个异常是“编译时异常”FileNotFoundException
//编译器告诉我们必须编写处理代码
// 通常的处理代码是try...catch,但是,这里用try...catch不合适
//因为当前方法只负责复制文件,关于源文件不存在这种情况,当前方法是处理不了的,必须交给调用者处理。
//如果不管它呢,编译不通过,所以我必须告诉调用者,你要处理FileNotFoundException异常
//我们只能通过在方法签名后面加throws 语法告诉调用者xx异常
FileInputStream fis = new FileInputStream(srcFileName);
// fis.read();
}
}
class Father{
public void method(){//父类被重写方法,并没有throws 编译时异常,例如:FileNotFoundException
//..
}
public void fun()throws IOException { //父类被重写方法,throws 编译时异常,例如:IOException
//...
}
public void test()throws NullPointerException{
}
protected Object getValue(){
return new Object();
}
}
class Son extends Father{
/* @Override
public void method()throws FileNotFoundException {
//父类的这个方法没有抛出编译时异常,那么重写方法时,就不能throws编译时异常
super.method();
}*/
@Override
public void fun() throws FileNotFoundException {//重写方法时,可以抛出比被重写方法 “小”的异常
//...
}
@Override
public void test() {
super.test();
}
public String getValue(){
return "";
}
}
class Son2 extends Father{
@Override
public void fun() throws IOException {//重写方法时,可以抛出和被重写方法 “一样”的异常
super.fun();
}
}
异常throw
和异常有关的关键字:try,catch,finally,throws,throw。
1.throw:在Java中是抛异常对象
2.异常的对象在Java中有两个角色可以抛:
(1)JVM
通常都是抛出系统预定义的异常,JVM会根据当前代码的情况,判断是那种异常情况,找到对应的类型,创建对象并抛出
(2)throw语句
①可以抛出系统预定义异常,需要我们自己new对象,自己抛。
②自定义异常类型,自己new对象,自己抛。
3.注意
(1)无论是JVM抛的,还是我们自己通过throw语句抛的,最终都要通过try...catch处理,否则都会导致程序崩溃。
(2)当然在哪里try...catch,根据实际情况来决定,可以在当前方法中直接try..catch,也可以先throws,然后由调用者try...catch
4.如何throw?
//throw格式一
异常类型 对象 = new 异常类型(【实参列表】);
throw 对象;
//格式二
throw new 异常类型(【实参列表】)
5.throw语句也是跳转语句
当方法执行了throw语句,且在throw外面没有加try..catch。它后面的语句就无法执行了,会导致当前方法结束。
当方法执行了throw语句,但在throw外面加了try..catch。try后面的就无法执行了,但是try...catch下面的代码可以继续执行。
通常,可以使用throw语句代替return语句,用于异常情况下结束当前方法。
自定义异常
1.为什么要自定义异常?
系统已经预定义了很多的异常类型,但是有时候还是难以描述我们当前的代码问题,比如:在编写一个某个银行的管理系统,我们想要描述客户取钱的时候,金额输入太多,余额不足的情况,如果想要通过抛异常的方式来表达的话,在现有的系统预定类型中找不到。
2.如何自定义异常?
要求:
(1)必须继承Throwable类或者它的子类
通常我们是机场Exception或者RuntimeException。
如果是继承RuntimeException或者它的子类的话,那么这个自定义的异常也是运行时异常,否则是编译时异常。
(2)建议自定义异常,保留无参构造
(3)建议自定义异常,定义一个这样的构造器
【修饰符】 构造器名称(String message){
//这个message是关于异常对象的描述信息。
}
(4)建议自定义异常实现一个接口java.io.Serializable接口(后面IO流章节学习,它是关于对象序列化的)。
Serializable和Cloneable这两个接口中都没有抽象方法,这种接口属于标识性接口,只是为了区别类型用的,例如:我们把类分为支持克隆和不支持克隆。实现了Cloneable几口就支持,不实现这个接口就不支持,Cloneable接口的抽象方法在Object类中,clone()方法。因为它希望无论对象呈现为什么类型(即编译时类型是任何类型)都可以调用clone()方法。如果任意类型的对象都想要拥有的方法,只能放在Object类中。
Serializable接口也是用于区分两种对象,一种支持序列化,一种不支持序列化,序列化的工作,实际上是由虚拟机帮我们完成的。
3.语法格式
【修饰符】 class 自定义异常类 extends 父异常类{ }
4.自定义异常的抛出,必须由程序员自己创建new,并且自己throw抛出。
多线程
1.并行与并发
并行(parallel):无论从宏观还是微观的角度,都是多个任务同时运行,需要多个处理器(cpu)的支持。
并发(concurrency):指的是两个或者多个事件在同一个时间段内发生。指在同一时刻只能有一条指令执行,但多个进程(线程)的指令被快速轮换执行,使得在宏观上具有多个进程(线程)同时执行的效果。
2.进程与线程
程序:为了完成某个任务和功能,选择一种编程语言编写的一组指令的集合。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
==》进程是操作系统调度和分配资源的最小单位。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
Java程序本身就是多线程的。
除了main线程,后台还有GC线程,异常监测线程....
操作系统支持多线程系统(多任务操作系统),然后操作系统支持多线程。早期时,计算机都是只有一个CPU,并且单核。同一时刻只能运行一个线程==》单任务单进程,顺序进行。
中期时,计算机都是只有一个CPU,但是我们希望充分利用CPU,可以让CPU在多个任务之间快速切换,让用户感觉是多个任务同时进行==》并发。
后来开始支持多核CPU,可以在同一时刻运行多个任务==》并行。
当然,在任务数量大于CPU个数时,还是会有并行和并发同时存在的。
另行创建和启动线程
1.在Java中如何支持多线程?
(1)java.lang包:Thread类,Runnable接口。(SE阶段)
(2)java.util.concurrent:callable接口等(高级部分),俗称JUC。
2.如何开启main线程以外的线程
JavaSE阶段有两种方式:
(1)继承Thread类
(2)实现Runnable接口
Thread类
1.步骤
(1)声明一个线程类继承Thread类
(2)必须重写父类的一个方法:run();
这个方法的方法体,就是这个线程的任务代码,如果不进行重写,那么这个线程毫无意义。
例如:在这个线程中,打印1-10之间的偶数。
(3)创建线程对象
(4)必须启动线程,调用start()方法。
示例:
public class TestThread { public static void main(String[] args) { //(3)创建线程对象 MyThread my = new MyThread();// my.run();//语法上允许这么调,但是,这么写和多线程一点关系都没有。 my.start();//这个start()它是从Thread类继承的 //在main线程中,我打印1-10之间的奇数 for(int i=1; i<=1000; i+=2){ System.out.println("main,i = " + i); } }}class MyThread extends Thread{ //重写:ctrl + o @Override public void run() { for(int i=2; i<=1000; i+=2){ System.out.println("自定义线程,i = " + i); } }}
start方法:
他是从Thread继承来的方法
run方法:
不是由我们直接调用的,笼统的说,是JVM中某个程序帮我们调用的,线程调度程序。
Runnable接口
1.步骤
(1)声明一个线程类实现Runnable接口。
(2)重写Runnable接口的抽象方法:run()
(3)创建自定义线程对象
(4)创建Thread类对象(因为实现类,父接口,父类中都没有start方法),将线程对象传入Thread构造器,调用有参构造创建对象。
(5)启动线程,调用start方法。
示例:
public class TestRunnable { public static void main(String[] args) { MyRunnable my = new MyRunnable();// my.start(); //错误的,因为MyRunnable这个类中没有,父类和父接口也没有 //创建Thread类对象 Thread thread = new Thread(my);//my给Thread类的target赋值 thread.start(); //在main线程中,我打印1-10之间的奇数 for(int i=1; i<=1000; i+=2){ System.out.println("main,i = " + i); } }}class MyRunnable implements Runnable{ @Override public void run() { for(int i=2; i<=1000; i+=2){ System.out.println("自定义线程,i = " + i); } }}
如何通过Thread类的对象,间接调用run方法的?
看源码:java.lang.Thread类的public void run()
public void run(){ if(target!=null){ target.run(); }}
快捷键:查看某个类的源码 Ctrl+N
查看某个类的成员列表Ctrl+F12,有的可能要加上Fn
其实JVM的线程调度器是调用了Thread类对象的run方法。
使用匿名内部类对象来实现线程的创建和启动
方法一:继承Thread类
new Thread("新的线程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}.start();
方法二:实现Runnable
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}).start();
线程的API
-
public void run() :此线程要执行的任务在此处定义代码。
-
public void setName(String name):设置线程名称。
-
public String getName() :获取当前线程名称。每一个线程都有名字,main线程的名称就是main,其他线程默认是thread-编号,编号从零开始。
-
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
-
public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
-
public final int getPriority() :返回线程优先级
-
public final void setPriority(int newPriority) :改变线程的优先级,如果优先级不在MIN _PRIORITY 和MAX_PRIORITY之间(常量),会抛出非法参数异常。
每个线程都有一定的优先级,优先级高的线程抢到CPU的概率越高,但是不代表优先级低的一点机会都没有。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
MAX_PRIORITY(10):最高优先级
MIN _PRIORITY (1):最低优先级
NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
-
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
-
public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
-
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。