前言
封装继承多态,就是这个多态感觉一直没有摸到边。在实际编程中,也是习惯性地忽略OOP,talk is cheap, show me the code
一.对象导论
1.1 OOP之父Alan kay总结了OOP的五个特性:
(1)万物结尾对象
(2)程序是对象的集合
(3)每个对象都有自己的 由其他对象所构成的存储
(4)每个对象都拥有其类型
(5)同类的所有对象能接受同样的信息
只有第三条我暂时无法体会
对象具有状态,行为,标识
状态:属性。数据。在程序中我们会对对象进行初始化
行为:定义的方法。功能
标识:对象名
比较常用的就是说对象有属性和方法
我认为很有必要区分引用,对象,实例
引用,实例在计算机中有物理对应,引用就是内存地址,实例是一块内存空间。我认为只有当实例存在的时候,对象才存在,此时的实例就是对象。我们在声明一个对象的时候,通常会让引用指向实例,但并不一定,还有匿名对象
通常情况下,当引用指向实例的时候,他们三者等价
“服务”是一个非常通用的概念,程序向用户提供服务,其方式是调用不同对象所能提供的服务。leecode就创建了一个solution类,其后台创建一个solution对象调用相关的服务输出一个结果
OOP的附带好处:高内聚,低耦合
进而可以引出这么一句话:OOP是在软件开发过程中依据实际需求自然而然产生的。这个需求就是代码管理
随着代码量几何倍数增长,其发展路径如下:
命令->函数->类->继承->多态
比如这样一个对象,要掌握所有的打印技术。但是这样一个类就太臃肿了。于是出现了继承,一个对象掌握基本打印技术,设计多个对象分别掌握一种具体的打印技术。如果打印技术太多怎么办呢?(太多?多态?)有时候我们需要用不同的打印技术干同一件事情,难道我们需要针对每一种打印技术单独做处理吗?能不能在这种情况下把不同的打印技术先看做最基本的打印技术呢?多态实现了这一点。由此也引出了向上转型,向基类转型
原来我认为封装很普通的概念,但其实无处不在。函数封装了部分命令,他们的影响通常只在函数内部,不会影响到外部。而在具体的业务场景下,随着系统功能的拓展,大部分情况下是不会对原有系统进行结构性调整的,重构。要保证即便新服务出现异常,原有服务仍然运转正常。这就是封装性
对象的状态也要封装,通常是由对象的行为来访问或者修改对象的状态
算法库就是一个可复用的类,pytorch,tensorflow,ROS都是
1.2 单根继承结构
除C++之外的所有OOP语言都是。保证了所有对象都从创建开始都有类型信息,不会因无法确定类型出现问题,尤其是系统级操作
1.3 容器
在docker那听过这个名词。顾名思义,就是装东西的一个空间。一般可以用数组实现。在做算法练习时,声明数组需要声明空间大小,通常只能按最坏情况估计,是不可能提前知道的,只能运行时获得
因此设计一种叫容器的类型来存储对象(引用,地址)。容器可以扩充,那python的列表是一种容器吗?按照定义,也可以包含不同类型的对象吗?
容器也分很多种,或者说是一些类统称为容器,容器只是一种标准。这些类通常就是数据结构中的顺序表,链表,栈,队列,树,图
1.4 参数化类型(参数化类)
前面提到,容器可以包含不同类型的对象吗?
JDK5之前,规定容器只能存储Object对象,又因为单根继承结构,所以容器可以存出任何对象。但是,装入的时候对象丢失了真实的类型,取出的时候无法恢复就会报错。如果采用向下转型(向扩展类转型),会带来额外的时空消耗
JAVA设计了一个类-参数化类型,通过这个机制,可以定制一个存储特定类型对象的容器。(python的列表就相当于一个通用容器)这就是 泛型。原来是这样啊:ArrayList<Shape> shapes = new ArrayList<Shape>()
参数化类型就是泛型,它是一种机制,设计了一个类来实现这个功能
在思考容器的时候,就想成是存储数据的数组
1.5 对象的创建(生命周期)
这个议题我没什么体会。就是讲了两种策略。一是提前分配精确的内存空间,二是提前分配一块空间,在程序运行过程中进行动态的申请和释放
区分堆,栈,堆栈
堆和栈都是一种数据结构
堆栈是物理结构,按照栈的特性来设计,可以存在于专用的寄存器,也可以是在主存中开辟一块区域作为堆栈。这在JAVA的内存结构一块有涉及
1.6 并发编程
我个人是很喜欢并发的,有一种说不出的密度之美。将程序切分成多个可独立运行的部分,也就是线程,来作为 为单一处理器分配执行时间的手段。注意,并发不是并行。
并发:一个处理器同时处理多个任务,并且只是逻辑上的同时处理(看起来是)
并行:多个处理器同时处理多个任务,这就是物理上的同时处理
1.7 JAVA与Internet
我之前从没想过写java web,最多就是搞点算法,对web完全不懂。但是要想了解JAVA,就必须了解web编程,之所以JAVA有今天的地位,是因为他解决了web上的程序设计问题
1.7.1 客户/服务器系统
这个在计网学过,服务器也是计算机,但是他有更明确的功能,要存储数据,并能分发信息。一种功能非常强大的专用计算机。又出现了服务这个词,还有一个叫伺服器
难点在于使用单一服务器同时为多个客户提供服务。可以想象一种极端情况,春节买票。
我之前都不知道浏览器是什么。从我最开始使用,浏览器就有了前缀,IE, 百度,chrome,等等,并且浏览器首页就是他们的官网,这就导致我认为浏览器分很多种,把他们的官网当作浏览器。
浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示HTML文档、PDF、图片、视频等多媒体内容。这些网络资源的位置由 URI 来指定
那是不是说登录他们的官网,其实就相当于已经向一个服务器发出了请求
web最初是设计为服务器-浏览器模式,但是浏览器只是观察者,不进行任何处理,这种设计安全,因为处理全部由服务器掌握,但缺点就是很慢
解决方案是扩展浏览器的功能,然后换一个名字叫客户端,将其实做一个功能受限的操作系统(很抽象)
客户端编程的问题和方法
1.7.1.1 插件
听说过,没写过。就是讲一段代码插入到浏览器的合适位置,以使其获得某种新功能。这个机制允许程序员不经过软件厂商的同意,扩展浏览器的功能,插件同时也是会添加到服务器中的
1.7.1.2 脚本语言
也是听说过,很牛逼的样子。
脚本语言先天容易理解(离底层更远,python),只是作为HTML页面一部分的简单文本,可以快速加载,但是会暴露给任何人。通常不会让脚本语言去做复杂的事情
脚本语言的解释需要浏览器插件的支持,javascript,python都是脚本语言,注意JS和JAVA没什么关系
1.7.1.3 JAVA
一个论断,脚本语言能解决客户端编程的8成问题的话,剩下的2成交给JAVA
07年之前,java通过applet和JAVA web start来进行客户端编程
这样一来,只要用户的浏览器内置了JAVA解释器,就可以了
1.7.1.4 备选方案
所谓的JAVA解释器,也就是JRE(java运行时环境)需要10MB的带宽极大地阻碍了applet的发展,因为这个带宽太奢侈了
说起JRE,我在想如果我想把基于python的深度学习算法直接移植到安卓,是不是也得安装一个python运行时环境
1.7.1.5 总结
那就是说,在作为观察者的浏览器升格为作为处理者的客户端的过程中,脚本语言和JAVA等具有WEB编程能力的语言都贡献了力量
1.7.2 服务器端编程
对服务器的请求涉及数据库事务,也就是增删改查
增删改要修改数据库,这就需要服务器端的程序来处理
那么对于基于JAVA的服务器,使用JAVA编写叫做servlet以及JSP的程序来实现服务器端编程
servlet就是server applet,服务器连接器
一些笔记
关键字的使用范围
static:属性,方法,代码块,内部类 public static void function()
final:类,属性,方法,public final class, private static final int, public static final funciton()
abstract:类,方法,public abstract class, public abstract void function()
关键字遵循的默认开发顺序
- Annotations
- public
- protected
- private
- abstract
- static
- final
- transient
- volatile
- synchronized
- native
- strictfp
内存解析
这个部分在对象的生存周期部分做过讨论,还涉及到什么栈,堆,堆栈的区分
面向对象下
1. static
为什么有static,我猜应该是某些属性和方法和具体的对象没有关系。将属性细分为了类属性和对象属性
1.1 修饰变量
只有实例化之后才会分配内存给对象。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份
即有些属性不归某个对象独有,而是同类型的所有对象共享
static修饰的变量为静态变量。相当于对对象的属性进一步做了划分,分为静态变量和非静态变量
于是乎,每个同类型的对象都独立的拥有一套类中的非静态属性,共享静态属性
静态变量又叫做类变量:
(1)类变量归类所有,从底层说,类变量随着类的加载而加载
(2)因此类变量的加载早于对象的创建
(3)所以可以直接用类名调用类属性。逻辑上也符合直觉
(4)因为类只加载一次,类变量也就只加载一次,在内存中只存在一份,存在方法区中的静态域
比如System.out,Math.PI
1.2 修饰方法
静态方法。修饰方法时,static放在权限修饰符后面,比如public static void test()
(1)如静态变量,随着类的加载而加载
(2)可以直接用类名调用类方法
1.3 注意点
在静态方法中不能使用this,super关键字 ,静态方法里面只能直接调用静态变量
从底层来说,类在加载类方法的时候,实例方法还不存在
比如public类中的main()
1.4 经验之谈
声明类属性:
(1)属性与具体的对象无关
(2)常量 final static
声明类方法:
(1)操作类属性
(2)工具类
2. main的语法
(1)作为程序的入口
(2)public static void main()也是一个普通的类方法
(3)main中的形参String[] args也可以作为和控制台交互的方式,还可以用Scanner类来交互
Scanner scan = new Scanner(System.in);
这种方式非常麻烦,首先得编译得到字节码文件,然后呢邮件在run里边找到run configuration(运行配置),然后定位到字节码文件,在右边找到program argument(程序参数)进行设置
3. 类的成员之四-代码块(初始化块)
就是一段用花括号包围起来的区域,形式上就是方法体的形式,只是没有权限修饰付,返回值,方法名
就想象成python的init(),但是python的init()综合了代码块和构造器的功能,本来两者的主要功能不就是初始化属性嘛。
对于java,更多的是在构造其中完成初始化的一系列操作
(1)用来初始化类,对象。python就比较彻底,初始化也必须是在函数中进行,并且规定了初始化的函数名,——__init__()
(2)只能使用static修饰,static{}
(3)静态代码块:一次性地
随着类的加载而执行,因为类只加载一次所以只执行一次。
初始化类属性
(4)非静态代码块:
随着对象的创建而执行,只要有对象创建就要执行一次。
可以在创建对象时对所有属性进行初始化。这在python中已经非常熟悉了
(5)应用场景
有些对象我们希望只有一份,唯一的,但是又不能将其作为属性。那就放到静态代码块去声明。肯定也不能放到构造器啊,构造器随着对象的创建而调用,每次创建都会调用一个
对属性可以赋值的位置:
1.默认初始化
2.显式初始化
3.构造器中初始化
4.有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值⑤在代码块中赋值
4. final
-
修饰类 public final class
此类不可再继承,比如String类,System类,StringBuffer类 -
修饰方法 public final void function()
此方法不能被子类重写,比如Object类的getClass() -
修饰变量 final int
将变量设置为常量,无法被重新赋值。
修饰属性:必须显示地初始化,在非静态代码块,构造器都可以,随着对象的创建而赋值
修饰局部变量:尤其是使用final修饰形参时,要特别注意
还有一个,就是final和static的组合,得到一个类常量,private final static int a。具体地来说,对final和static的顺序没有硬性规定,只是说习惯用法:[访问限定符] [static] [final] [类型名],类型名 紧跟在 final 的后面。整型常量,浮点型常量
有一个出错点,比如自定义一个类Other,现在实例化一个对象,final Other o = new Other()。这并不是将类设置为不能继承,o是一个变量,确切地说是一个指针,一个地址,只是说这个地址不能再变了
5. 抽象类和抽象方法abstract
背景:当父类抽象到一定程度,以至于用不到他的实例,也就不需要对其进行实例化。
(1)修饰类 abstract class
抽象类不可实例化
子类仍然要调用抽象类的构造器
(2)修饰方法 public abstract void function();注意这个最后的分号,别忘了
只有方法的声明,没有方法体。也不允许对象调用抽象方法,也就是说抽象方法只能声明在抽象类中
这样抽象父类只是声明这个方法,由子类去重写继承于抽象父类的抽象方法的方法体
(3)应用场景
父类只包含通用的属性,而方法需要子类提供的具体信息,这时候完善父类的方法非常困难,因此父类只声明方法,由子类根据具体情况去重写方法体
实际上,当抽象的足够好的时候,我们一般都是创建子类的对象,不会去实例化父类
(4)注意点
不能修饰
私有方法:子类都看不到这个方法,怎么重写呢
静态方法:假设抽象父类有抽象静态方法,直接用父类名去调用静态方法,结果她连方法体都没有
final方法以及final类:方法不能重写,类不能继承,但是abstract都要求可以、
总的来说,抽象并不是逻辑上必不可少的部分,我认为是对软件开发的一种优化,通常抽象类里边必然带有抽象方法,这是极致抽象带来的必然结果
6. 抽象类的匿名子类
抽象父类只是说不能实例化,但并没有说不能多态,继承关系还是正常的。比如有一个抽象父类person,有一个子类worker
那么,person p = new worker();是合法的
场景是假如我们不知道抽象父类的子类类名,那么就如上,在花括号中重写抽象方法,用抽象父类类名代替不知道的子类类名
好处就是省事,不需要专门去造一个子类
更省事的做法是造一个匿名子类的匿名对象
7. 接口interface
背景:有时候会有从多个类派生一个子类的需求,但是JAVA是单根继承结构,为了实现多重继承,引入了接口
接口是继承关系的一种补充,单根继承不就是说有些功能父类也没有,也不好将其抽象成类,那就抽象成现对简单的接口。比如有一个新功能,就可以将其封装成接口,再由类去实现
或者说接口是类的状态和行为的补充,但是对补充的状态和行为有限制
接口就是用来实现的,但是可以用接口名去访问接口的全局常量
(1)使用interface来定义, interface
(2)类和接口是并列的结构
(3)接口中的成员
jdk7:只能定义全局常量和抽象方法,所以说它是一种功能上的补充
全局常量:public static final int
抽象方法:public abstract
jdk8:除了以上,还有静态方法和默认方法
默认方法使用default关键字
静态方法(类方法):只能通过接口来调用。一方面接口不能实例化,另一方面有些方法又只能由接口来调用,这就有点像工具类了。前面将static关键字的时候,当它修饰类,有两个目的,一个是构建工具类,一个是使用类方法操作类属性。而且扩展接口的定义本身,就有朝着类发展的趋势
默认方法:可以说是有方法体的抽象方法,除了有方法体之外,其余操作和抽象方法一样
在实际开发中,默认方法通常是不进行重写的,因为默认方法的存在就是为了拓展接口的功能,使实现类不至于那么臃肿,拿来就可以直接用。静态方法也差不多,赋予接口更多的工具属性,拿来就用
(4)接口通过被类实现来使用
(5)java类可以实现多个接口,用于实现多重继承 class bullet implements Fly,Attack{}
更复杂的是将继承和实现一块写class bullet extends plane implements Fly,Attack{}
(6)接口之间是继承关系,并且允许多重继承 interface cc extends aa,bb{}
(7)接口的使用要体现多态性,抽象类和接口都不能实例化
所以说他们只能使用子类或者实现类的实例,这就是多态的应用了
(8)接口应该被看作是一种规范。前面说了接口就是对类的状态和行为的一种补充
驱动(但我不太熟这块,硬件涉及的多)比如说很多设备都有USB,将USB视作接口,不同设备视作类
但是呢,看起来usb完全可以当做一个类啊。所以说面向对象很多时候在逻辑上没必要,他就是一套用于管理的工具箱。如果一个类的状态和行为是完全固定的,那就考虑将其抽象成接口。对于USB接口,他的遵循当前的协议,协议在一段时期是不变的
在比如说,JDBC定义了操作不同数据库的大量接口,接口无法告诉你具体做什么,但可以规范可能要做什么
不需要直接面向数据库编程,而是面向JDBC的接口编程
接口在代理设计模式中的应用
比如说大文档的查看,先显示文字内容,等需要的时候,再使用代理显示比较大的图片
1 将操作封装到接口
2 在被代理类中重写操作
3 在代理类中声明接口,用以接收被代理类的实例
4 在代理类中重写操作,用接口的引用去调用被代理类中重写的操作
(9)接口的匿名实现类
和抽象类的匿名子类对象类似,假设有一个USB接口,那么usb u = new usb(){实现所有的抽象方法};
接口是不能实例化的
子类将继承自父类的方法的方法体修改叫重写
子类补全继承的抽象方法叫实现
类将实现的接口中的抽象方法补全也叫实现
同名排错
Q:如果继承和接口出现同名属性??
A:对于继承,那子类的属性和直接父类的属性相同;对于接口,接口的属性是全局常量,直接使用接口名调用这个属性
Q:如果继承和接口出现同名的默认方法??
A:在子类没有重写的前提下,默认调用父类的方法。说明还是继承的优先级更高
Q:如果实现的不同接口出现同名同参的默认方法??
A:报错。接口冲突。解决办法可以重写该默认方法
Q:如果继承,多个接口出现了同名同参的方法,且已经重写了该方法,如何分别调用子类,父类,接口中的该方法??
子类:this.method() 父类:super.method() 接口:接口名.super.method(),接口的写法是规定
8. 内部类
(1)分类
成员内部类:静态非静态,类成员内部类,实例成员内部类
局部内部类:方法、代码块,构造器内部的类
(2)成员内部类
作为外部类的成员:调用外部类的成员,要注意语法,外部类名.this.method(),说明是外部类对象进行的调用
被static修饰,表示该成员为类成员
被4权限修饰符修饰,一般类只能被public和缺省修饰
作为一个类:类内部可以定义属性方法构造器
被final修饰,表示此类不能继承
被absract修饰,表示此类不能实例化
(3)重点
如何实例化成员内部类
设person是外部类,dog是静态的成员内部类,实例化person.dog pd = new person.dog();
;设bird是非静态的成员内部类,则实例化person p = new person(); person.bird pb = p.new bird();
这里的语法还是比较反直觉的,首先从外部看成员内部类的类名应该是 外部类名.成员内部类名。
对于非静态的情况,先创建 外部类名.成员内部类名这个类的引用,然后由外部类对象去申请内存空间,再赋值给引用
如何在成员内部类中区分调用内外部的资源
要确定资源的调用者是谁!!!调用者调用自己拥有的资源。在成员内部类中,this.argument表示成员内部类对象调用自己的属性,外部类名.this.argument表示外部类对象调用自己的属性
开发中对内部类的设计
这里最后还用了多态
方式二一开始看比较反直觉,看到这种形式肯定直觉地认为,接口怎么能实例化呢??但仔细看,又不全是实例化 的语法。究其根本,是我将 不能实例化 和 不能创建该类型的引用划了等号。可以说,OOP学下来,从头到尾都没有说过不能不能创建该类型的引用。尤其是,涉及到抽象和接口的时候,总是认为不能实例化肯定就不能创建该类型的引用,这是错误的
(4)注意点
假设类的方法中声明了内部类,并且内部类的方法调用了声明内部类的类的方法中的局部变量时,要求次局部变量声明为常量
这是JAVA的一种规定,至于为什么?我们知道编译源文件的时候,每个类都会有一个.class字节码文件,哎,编不下去了,就这样吧,要求常量,杜绝在内部类中进行修改
局部内部类访问的局部变量如果就是一个普通变量,它是放到栈里面的,和他所属的方法在一个生命周期。但是内部类的生命周期就不一定了。作为局部变量,static又不能修饰,使用final,将其作为常量,放到方法区的常量池是合理的
异常处理
1. 异常概述以及体系结构
客户输入数据的格式,读取文件是否存庄,网络是否始终保持通畅等等这些问题很难靠代码逻辑来解决(无穷无尽的问题),总不能显示一堆乱码,最好给一些提示
程序执行中发生的不正常情况称为异常
分类:
Error:java虚拟机无法解决的严重问题。比如stackoverflow(栈溢出),OOM(堆溢出)一般不编写针对性的代码进行处理,只能改源码的逻辑
Exception:编程错误或者偶然地外在因素导致的一般性问题。空指访问,试图读取不存在的文件,网络连接中断,
数组角标越界等都是
红色为编译时异常,蓝色为运行时异常
Exception的分类
在编写源码时就考虑到异常的检测,提示,处理
编译时异常
运行时异常:空指针异常,数组越界异常,类型转换异常(多态再强转),数字格式异常(将abc转成数字),输入不匹配异常(使用Scanner类的时候),除0异常
2. 异常处理机制一:try-catch-finally
前面说了,为了处理异常如果不断加if,代码会十分臃肿,因此java采用将异常和正常代码隔离的方式
异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异吊代码处生成一个对应异常类的对象。
并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
过程二:“抓”:可以理解为异常的处理方式:(1)try-catch-finally (2)throws
try-catch-finally
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}finally{
//一定会执行的代码
}
try内部出现异常的话,就会生成一个异常类的对象传入catch
1.finally是可选的。
2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
3.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成就跳出当前的try-catch结构(在没有写finally的情况,I
4. catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
5.常用的异常对象处理的方式: String getMepsage();printStackTrace()
6. try结构中声明的变量,在外部是不能调用的。所以选择声明在try-catch外部,并初始化
7. 此结构可以在finally内部进行嵌套
感觉上finally可有可无的。
实际上catch中可能会出现异常,try或者catch中也可能有return语句,这种情况下,finally的语句仍然是要执行的
try-catch结构仍然需要保证程序能够返回定义该方法时设置的返回值类型,定义返回整型,那么程序的任何一个出口都要保证能返回整型。实际上try或者catch就是两个出口了,将return语句设置在这两个地方,finally不设置return语句
在try-catch有return语句的情况下,finally的执行顺序??
A:在执行return语句的前一步执行finally中的语句
什么时候将语句写到finally当中,尤其是开发过程中??
A: 比如说在讲到垃圾回收机制时,垃圾回收机制只回收JVM堆内存里的对象空间。对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力。因此需要手动进行资源释放。这样的释放就声明在finally中
快捷键:选中相关行,右键,选择surround with
编译时异常和运行时异常的不同处理:
try-catch结构只是将编译器显示的异常换了一种说法,程序实际上还是进行不下去的。
运行时异常能过编译,但是程序无法输出正确结果。比如说常见的.运算符,见到就要考虑前面是不是空指针。总体来说,运行时异常很常见,通常不编写try-catch结构,因为异常太多了,都写的话程序太臃肿
编译时异常连编译都过不了,那就一定要处理,至少得让程序能正常编译
3. 异常处理机制二:throws + 异常类型
throws最终是要和try-catch协同的
在方法名后面抛出异常
最终将异常抛到调用栈的底部使用try-catch
从method1抛到method2,再抛到main,main调用了method2,于是在main中使用try-catch
重写方法抛出异常的规则
假如子类重写了父类的方法,父类的方法又抛出了异常,那么子类抛出的异常不大于父类
很好理解,子类重写方法势必会让方法更具体,产生的异常也就更加具体
从具体的代码上讲,因为存在多态这种向上转型的情况,编写方法的时候考虑的是父类抛出的异常,但实际传入的却是子类的实例,那么这时候如果子类方法抛出的异常更大,那就出错了,如果更小,那就是异常这一块的多态;相等就不提了
开发中的选择
对于一个方法来说,要么选择throws,要么选择try-catch结构。感觉上组合起来还是很多的,只能具体问题具体分析
(1)对于重写的方法,如果父类没有throws,那么子类也不行,即子类必须用try-catch。是否是跟多态有关(体会不深)
(2)如果连续调用了多个具有递进关系的方法,那么建议使用throws处理。调用他们的主方法,使用try-catch结构(这一条体会不深)
4. 手动抛出异常
抓抛模型中的抛,前面讨论的都是系统自动生成的异常对象
为什么还要不怕麻烦,手动生成异常对象并抛出呢??
假设这样一个场景,当检测到输入非法之后,我们不希望执行后面的语句了,能怎么做呢??此时程序编译、运行都是正常的。用空间去换,设置哨兵检测是否非法,以哨兵的结果来判断是否还执行后面的语句
此时可以将检测非法输入这个分支视为异常进行手动抛出,这是一种人为结果。产生异常之后,方法就可以抛出异常了,然后在调用方法的地方使用try-catch结构处理
5. 用户自定义异常类
如何自定义异常类?
1.继承于现有的异常结构:RuntimeException .Exception
2.提供全局常量:serialVersionUID。这个序列号是为了唯一标识所属的类,所以类属性还可以这样用。内存中的对象可以通过网络传输,但是可能存在重名的问题,因此再加上序列号进行多重标识
3. 提供重载的构造器,一般一个空参的和一个带String message的就可以了
当手动抛的异常是自定义异常,那么方法抓(throws)的就是自定义异常,也可以抓他的父类,多态嘛