面向对象和异常
面向对象
多态
多态:指一个引用(类型)在不同情况下的多种状态;换句话说,多态是指通过指向父类的指针,来调用在不同子类中实现的方法。
实现多态
- 继承:多个子类对同一方法的重写;
- 接口:实现接口并覆盖接口中的同一方法;
方法重载(overload)实现的时编译时的多态性(前绑定),而方法重写(override)实现的是运行时的多态性(后绑定);
一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。运行时的多态是面向对象最精髓的东西,要实现多态,需要做两件事:
- 方法重写:子类继承父类并重写父类中已有的抽象的方法;
- 对象造型:用父类型引用子类型对象,这种同样的引用调用同样的方法就会根据子类对象的不同而表现不同的行为;
什么是多态机制?
所谓多态,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行间才能决定。
因为在程序运行时才能确定具体的类,这样不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态分为编译时多态和运行时多态
- 编译时多态是静态的,只要是指方法的重载,根据参数列表的不同来区分不同的函数,通过编译之后会成为两个不同的行数,在运行时谈不上多态;
- 运行时多态是动态的,通过动态绑定来实现,也就是我们所说的多态性;
多态的实现
三个必要条件:
- 继承:在多态种必须存在由继承关系的子类和父类;
- 重写:子类对父类种某些方法进行重新定义,在调用这些方法时就会调用子类的方法;
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这也,该引用才能具备技能调用父类的方法和子类的方法;
抽象类
为什么会有抽象类?
- 父类方法的不确定性,子类基本都重写父类的某个方法(父类对象调用多个子类实现的方法是即为多态),此时父类的此方法的代码块有点没必要,将其直接抽象,直接让子类来实现它。
- 当父类的一些方法不能确定时,可以用
abstract
关键字来修饰方法【抽象方法】,用abstract
来修饰该类【抽象类】
注意
- 用
abstract
关键字来修饰的一个类时,这个类就叫抽象类; - 用
abstract
关键字来修饰一个方法时,这个方法就是抽象方法; - 抽象类不能被实例化;
- 抽象类不一定有抽象方法,但又抽象方法的一定是抽象类;
- 抽象方法不能有主体,即
{}
; - 抽象方法中可以有具体方法;
- 抽象类不能使用
final
修饰,定义抽象类就是为了让其他类继承的,如果定义为final
,该类就不能被继承,相互矛盾。
接口
接口就是给出一些没有内容的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。语法:
class <类名> implements <接口>{
变量;
方法;
}
可以把接口看作是更加抽象的抽象类,抽象类中的方法可以有方法体,接口中的所有方法都没有方法体,接口体现了程序设计的多态和高内聚低耦合的设计思想。
注意
- 接口不能被实例化;
- 接口中的所有方法都不能有主体;
- 一个类可以实现多个接口;
- 接口中可以有变量,但变量不能用
private
和protected
修饰;- 接口中的变量,本质上都是
static
,无论你加不加static
修饰; - 在Java开发中,我们经常把常用的变量定义在接口中,作为全局变量使用,访问形式:接口名.变量名;
- 接口中的变量,本质上都是
- 一个接口不能继承其他的类,但是可以继承其他接口;
- 单继承多实现,接口弥补了单继承的缺点;(c++是多继承)
final
用于修饰类、属性和方法
- 被
final
修饰的类不可以被继承; - 被
final
修饰的方法不可以被重写(Override); - 被
final
修饰的变量不可以被改变,主要被final
修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的;
final、finally和finalize的区别
final
可以修饰类、变量、方法,修饰类表示该类不能被继承,修饰方法表示该方法不能被重写,修饰变量表示该变量是一个常量,不能被重新赋值;finally
一般作用在try-catch
代码块中,该代码块都会执行,一般用来存放一些关闭资源的代码;finalize
是一个方法,始于Object
类的一个方法,而Object
类是所有类的父类。该方法一般由垃圾回收器来调用,当我们调用System.gc()
方法的时候,由垃圾回收器调用finalize()
,回收垃圾。
异常
Throwable
是Java语言中所有错误或异常的超类。
异常处理
当出现程序无法控制的外部环境问题时,Java就会用异常对象来描述。
- 问题
- 用户提供的文件不存在;
- 文件内容损坏;
- 网络不可用等;
异常分类
- 检查性异常(编译时):
java.lang.Exception
- 程序正确,但因为外在的环境条件不满足引发异常。
- 用户错误及I/O问题——程序试图打开一个并不存在的远程Socket端口,或者打开不存在的文件时,这不是程序本身的逻辑错误。
- 运行期异常(运行时):
java.lang.RunTimeException
- 意味着程序存在bug,如数组越界、除0等
- 错误:
java.lang.Error
- 一般少见,也很难通过程序解决。可能源于bug,但一般更可能源于环境问题
- 内存耗尽、爆栈等
顶层是java.lang.Throwable
类,检查性异常、运行期异常、错误都是这个类的子孙类。java.lang.Exception
和java.lang.Error
继承自java.lang.Throwable
,而java.lang.RunTimeException
继承自java.lang.Exception
。
处理异常的常用方法
- 在发生异常的地方直接处理;
- 将异常抛给调用者,让调用者处理;
1. try...catch...
程序运行产生异常时,将从异常发生点中断程序并向外抛出异常信息。
try{
...
code; // 发生异常,中断,进入catch代码块
...
}catch(Exception e){ // Exception是父类,可以捕获所有的异常
// 把异常的信息输出,找到bug,错误发生点
e.printStackTrace();
}
- 也可以使用子类具体异常具体捕获,但
Exception e
方便点;
2. finally
如果把finally
块放在try...catch...
语句后,finally
块一般都会得到执行,它相当与一个万能的保险,即使前面的try
块发生异常,而又没有对应的catch
块,finally
块将马上执行;
try{
...
FileReader fr = new FileReader(filePath); // 假设打开
Socket sc = new Socket(ip, socketNum); // 发生异常,中断
...
}catch(Exception e){
e.printStackTrace(); // 抛出异常,文件处于打开状态,需要必要的操作
}finally{
// 不管有没有异常,都会执行
// 一般,把需要关闭的资源:文件、连接、内存等进行相关操作
try{
if (fr != null){ // 如果打开了,就关闭
fr.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
- try...finally可以使用
- finally不可以使用
finally将不会被执行
finally
块中发生了异常;- 程序所在线程死亡;
- 在前面的代码中用来
System.exit()
; - 关闭CPU;
3. throws(不建议)
将异常抛给使用者,让调用者处理异常
public class test{
public static void main(String[] args){
Father father = new Father();
father.test1();
}
}
class Father{
private Son son = null;
public Father(){
son = new Son();
}
public void test1(){
System.out.println("1");
try{
son.test2(); // 函数抛出异常,你需要处理,也可以继续抛出
}catch(Exception e){
e.printStackTrace();
}
}
}
class Son{
public void test2() throws Exception{// 抛出异常,谁调用谁管
FileReader fr = null;
fr = new FileReader(filePath); // 文件不存在,检查性异常,try catch
// 可以不处理,将异常抛出去
}
}