0%

JavaSE中阶

  • 面向对象
  • 异常

面向过程与面向对象(上)

面向过程与面向对象区别
  • 面向过程:当事件比较简单的时候,利用面向过程,注重的是事件的具体的步骤/过程,注重的是过程中的具体的行为,以函数为最小单位,考虑怎么做
  • 面向对象:注重找“参与者”,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做

案例:人把大象装进冰箱

  • 面向过程
    • 函数1:打开冰箱(){人站在冰箱前,打开冰箱,冰箱开到30度角的时候,冰箱的灯打开了………}
    • 函数2:储存大象(){大象先迈左腿,再迈右退,考虑冰箱能不能装下……}
    • 函数3:关闭冰箱(){人站在冰箱前,关闭冰箱,冰箱开到30度角的时候,冰箱的灯关闭了……….}
  • 面向对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
人{
打开(冰箱){
冰箱.打开();
}
存储(大象){
大象.进入();
}
关闭(冰箱){
冰箱.关闭();
}
}

冰箱{
打开(){ 1.2.3.}
关闭(){}
}

// 需求:增加柜子;面向对象很轻松,面向过程复杂
柜子{
}

大象{
进入(冰箱){}
}

面向过程 —> 面向对象 , 其实就是由执行者 —> 指挥者的 一个过渡

  • 面向过程:编年体史记
  • 面向对象:纪传体史记

二者相辅相成,并不是对立的。解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系、方便我们分析整个系统;具体到微观操作,仍然使用面向过程方式来处理

类和对象的关系
  1. 万事万物皆对象

  2. 类和对象

    • 对象:具体的事物,具体的实体,具体的实例,模板下具体的产品

    • 类:对对象向上抽取出像的部分,公共的部分,形成类,类是抽象的,是一个模板

  3. 一般在写代码的时候先写类,然后在根据类创建对应的对象。

面向对象三个阶段
  1. 面向对象分析OOA – Object Oriented Analysis
    • 对象:张三,王五,朱六,你,我
    • 抽取出一个类—-》人类
    • 类里面有什么
      • 动词–》动态特性–》方法
      • 名词–》静态特性–》属性
  2. 面向对象设计OOD – Object Oriented Design
    • 先有类,再有对象
    • 类:人类(Person)
    • 对象:zhangsan ,lisi,zhuliu
  3. 面向对象编程OOP – Object Oriented Programming
创建类
1
2
3
4
5
6
7
8
9
10
11
12
13
//(1)属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。
属性定义格式:
[修饰符] 属性类型 属性名 = [默认值] ;

//(2)方法
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
方法定义格式:
[修饰符] 方法返回值类型 方法名(形参列表) {
// n条语句
}
void代表没有返回值;
方法的作用:重用代码,封装功能,便于修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.excepenxi;
public class Person {
//名词---》属性---》成员变量---》放在类中方法外(注意:我们只把有需要的内容写到代码里,不相关的东西不要放在代码中)
int age ;//年龄
String name;//姓名
double height;//身高
double weight;//体重
//动词---》方法
//吃饭
public void eat(){
int num = 10;//局部变量:放在方法中
System.out.println("我喜欢吃饭");
}
//睡觉:
public void sleep(String address){
System.out.println("我在"+address+"睡觉");
}
//自我介绍:
public String introduce(){
return "我的名字是:"+name+",我的年龄是:"+age+",我的身高是:"+height+",我的体重是:"+weight;
}
}
创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.excepenxi;
public class Test {//测试类
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个人类的具体的对象/实例:
//创建一个对象,对象的名字叫:zs
//Person 属于 引用数据类型
//第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化的值。
Person zs = new Person();
zs.name = "张三";
zs.age = 19;
zs.height = 180.4;
zs.weight = 170.4;
//再创建一个对象:
//再次创建类的时候,就不会进行类的加载了,类的加载只在第一次需要的时候加载一次
Person ls = new Person();
ls.name = "李四";
ls.age = 18;
ls.height = 170.6;
ls.weight = 160.5;
//对属性值进行读取:
System.out.println(zs.name);
System.out.println(ls.age);
//对方法进行操作:
//不同的对象,属性有自己的特有的值,但是方法都是调用类中通用的方法。
//属性:各个对象的属性是独立的,
//方法:各个对象的方法是共享的。
zs.eat();
ls.eat();
zs.sleep("教室");
/*String str = zs.introduce();
System.out.println(str);*/
System.out.println(zs.introduce());
}
}
局部变量与成员变量的区别
  • 区别1:代码中位置不同
    • 成员变量:类中方法外定义的变量
    • 局部变量:方法中定义的变量 代码块中定义的变量
  • 区别2:代码的作用范围
    • 成员变量:当前类的很多方法
    • 局部变量:当前一个方法(当前代码块)
  • 区别3:是否有默认值
    • 成员变量:有
    • 局部变量:没有
    • 引用数据类型: null

  • 区别4:是否要初始化
    • 成员变量:不需要,不建议初始化,后续使用的时候再赋值即可
    • 局部变量:一定需要,不然直接使用的时候报错
  • 区别5:内存中位置不同
    • 成员变量:堆内存
    • 局部变量:栈内存
  • 区别6:作用时间不同
    • 成员变量:当前对象从创建到销毁
    • 局部变量:当前方法从开始执行到执行完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.excepenxi;
public class Student {
byte e;
short s;
int c ;//成员变量:在类中方法外
long num2;
float f ;
double d;
char ch;
boolean bo;
String name;
public void study(){
int num = 10 ; //局部变量:在方法中
System.out.println(num);//10
//int num ;重复命名,出错了
{
int a;//局部变量:在代码块中
}
int a;
if(1==3){
int b;
}
System.out.println(c);
}
public void eat(){
System.out.println(c);
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.c);
System.out.println(s.bo);
System.out.println(s.ch);
System.out.println(s.d);
System.out.println(s.e);
System.out.println(s.f);
System.out.println(s.name);
System.out.println(s.num2);
System.out.println(s.s);
s.d = 10.4;
}
}
构造器
Person类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.excepenxi;
public class Person {
//构造器:没有任何参数的构造器我们叫做:空参构造器--》空构造器
public Person(){
/*age = 19;
name = "lili";
height = 169.5;*/
}
//属性:
String name;
int age;
double height;
//方法:
public void eat(){
System.out.println("我喜欢吃饭");
}
}
Test类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.excepenxi;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Person类的具体的对象/实例/实体:
/*
创建对象的过程:
1.第一次遇到Person的时候,进行类的加载(只加载一次)
2.创建对象,为这个对象在堆中开辟空间
3.为对象进行属性的初始化动作
new关键字实际上是在调用一个方法,这个方法叫构造方法(构造器)
调用构造器的时候,如果你的类中没有写构造器,那么系统会默认给你分配一个构造器,只是我们看不到罢了。
可以自己显式(反过来就是隐式)的将构造器编写出来
构造器的格式:
[修饰符] 构造器的名字(){
}
构造器和方法的区别:
1.没有方法的返回值类型
2.方法体内部不能有return语句
3.构造器的名字很特殊,必须跟类名一样
构造器的作用:不是为了创建对象,因为在调用构造器之前,这个对象就已经创建好了,并且属性有默认的初始化的值。
调用构造器的目的是给属性进行赋值操作的。
注意:我们一般不会在空构造器中进行初始化操作,因为那样的话每个对象的属性就一样了。
实际上,我们只要保证空构造器的存在就可以了,里面的东西不用写
*/
Person p = new Person();
System.out.println(p.age);
System.out.println(p.name);
System.out.println(p.height);
Person p2 = new Person();
System.out.println(p2.age);
System.out.println(p2.name);
System.out.println(p2.height);
}
}
构造器重载
Person类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.excepenxi;
public class Person {
//属性:
String name;
int age;
double height;
//空构造器
public Person(){
}
public Person(String name,int age,double height){
//当形参名字和属性名字重名的时候,会出现就近原则:
//在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
this.name = name;
this.age = age;
this.height = height;
}
public Person(String a,int b){
name = a;
age = b;
}
//方法:
public void eat(){
System.out.println("我喜欢吃饭");
}
}
Test类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.excepenxi;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
/*
1.一般保证空构造器的存在,空构造器中一般不会进行属性的赋值操作
2.一般我们会重载构造器,在重载的构造器中进行属性赋值操作
3.在重载构造器以后,假如空构造器忘写了,系统也不会给你分配默认的空构造器了,那么你要调用的话就会出错了。
4. 当形参名字和属性名字重名的时候,会出现就近原则:
在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
*/
Person p = new Person();
/*p.age = 19;
p.name = "lili";
p.height = 180.4;*/
Person p2 = new Person("lili",19,180.4);
System.out.println(p2.age);
System.out.println(p2.height);
System.out.println(p2.name);
}
}
内存分析
1
2
3
4
5
6
7
public class Person {
int id;
int age;
public static void main(String args[]){
Person p1 = new Person();
}
}

内存分析

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {
int id;
int age;
String school;
public Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public static void main(String args[]){
Person p = new Person(1,20, "海淀");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
int id;
int age;
String school;
Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public void setAge(int a){
age=a;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {
Test t = new Test();
int age=40;
Person tom = new Person(1,20,"海淀");
Person jack = new Person(2,30,"朝阳");
t.change1(age);
t.change2(tom);
t.change3(jack);
System.out.println(age); //40
System.out.println("id:"+jack.id+",age:"+jack.age+",school:"+jack.school);
//id:2,age:66,school:"朝阳"
}
public void change1(int i){
i=3366;
}
public void change2(Person p){
p=new Person(3,22,"西城");
}
public void change3(Person p){
p.setAge(66);
}
}

this的使用
  • 创建对象的过程:
    1. 在第一次遇到一个类的时候,对这个类要进行加载,只加载一次
    2. 创建对象,在堆中开辟空间
    3. 对对象进行初始化操作,属性赋值都是默认的初始值
    4. new关键字调用构造器,执行构造方法,在构造器中对属性重新进行赋值

this

从上面的效果能够看到:this指代的就是当前对象

内存分析

this关键字 用法

  1. this可以修饰属性

总结:当属性名字和形参发生重名的时候,或者 属性名字 和局部变量重名的时候,都会发生就近原则,所以如果我要是直接使用变量名字的话就指的是离的近的那个形参或者局部变量,这时候如果我想要表示属性的话,在前面要加上:this.修饰

如果不发生重名问题的话,实际上你要是访问属性也可以省略this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.excepenxi;
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this.age = age;
this.name = name;
this.height = height;
}
//方法:
public void eat(){
int age = 10;
System.out.println(age);//就近原则,age指的是离它近的age--》局部变量的age 10
System.out.println(this.age);//这里指代的就是属性的age 19
System.out.println("我喜欢吃饭");
}
}
1
2
3
4
5
6
7
package com.excepenxi;
public class Test {
public static void main(String[] args) {
Person p = new Person(19,"lili",160.6);
p.eat();
}
}
  1. this修饰方法

总结:在同一个类中,方法可以互相调用,this.可以省略不写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.excepenxi;
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this.age = age;
this.name = name;
this.height = height;
}
//方法:
public void play(){
/*this.*/eat();
System.out.println("上网");
System.out.println("洗澡");
}
public void eat(){
System.out.println(/*this.*/age);
System.out.println("吃饭");
}
}
  1. this可以修饰构造器

总结:同一个类中的构造器可以相互用this调用

注意:this修饰构造器必须放在第一行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.excepenxi;
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this(age,name);
this.height = height;
}
public Person(int age,String name){
this(age);
this.name = name;
}
public Person(int age){
this.age = age;
}
//方法:
public void play(){
/*this.*/eat();
System.out.println("上网");
System.out.println("洗澡");
}
public void eat(){
System.out.println(/*this.*/age);
System.out.println("吃饭");
}
}
static的使用
  • static可以修饰:属性,方法,代码块,内部类

static修饰属性

static修饰属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.excepenxi;
public class Test {
//属性:
int id;
static int sid;
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Test类的具体的对象
Test t1 = new Test();
t1.id = 10;
t1.sid = 10;
Test t2 = new Test();
t2.id = 20;
t2.sid = 20;
Test t3 = new Test();
t3.id = 30;
t3.sid = 30;
//读取属性的值:
System.out.println(t1.id); //10
System.out.println(t2.id); //20
System.out.println(t3.id); //30
System.out.println(t1.sid); //30
System.out.println(t2.sid); //30
System.out.println(t3.sid); //30
}
}

  • static修饰属性总结
    1. 在类加载的时候一起加载入方法区中的静态域中
    2. 先于对象存在
    3. 访问方式: 对象名.属性名 类名.属性名(推荐)

static修饰属性的应用场景:某些特定的数据想要在内存中共享,只有一块 –》这个情况下,就可以用static修饰的属性

static修饰属性的应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.excepenxi;
public class Student {
//属性:
String name;
int age;
static String school;
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Student.school = "excepenxi";
//创建学生对象:
Student s1 = new Student();
s1.name = "张三";
s1.age = 19;
//s1.school = "excepenxi";
Student s2 = new Student();
s2.name = "李四";
s2.age = 21;
//s2.school = "excepenxi";
System.out.println(s2.school);
}
}
  • 属性
    • 静态属性 (类变量)
    • 非静态属性(实例变量)

static修饰方法

static修饰方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.excepenxi;
public class Demo {
int id;
static int sid;
public void a(){
System.out.println(id);
System.out.println(sid);
System.out.println("------a");
}
// 1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行
static public void b(){
//System.out.println(this.id); // 4.在静态方法中不能使用this关键字
//a(); // 3.在静态方法中不能访问非静态的方法
//System.out.println(id); // 2.在静态方法中不能访问非静态的属性
System.out.println(sid);
System.out.println("------b");
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
// 5.非静态的方法可以用对象名.方法名去调用
Demo d = new Demo();
d.a();
// 6.静态的方法可以用"对象名.方法名"去调用,也可以用"类名.方法名"(推荐)
Demo.b();
d.b();
}
}
代码块
  • 类的组成:属性,方法,构造器,代码块,内部类
  • 代码块分类:普通块,构造块,静态块,同步块(多线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.excepenxi;
public class Test {
//属性
int a;
static int sa;
//方法
public void a(){
System.out.println("-----a");
{
//普通块限制了局部变量的作用范围
System.out.println("这是普通块");
System.out.println("----000000");
int num = 10;
System.out.println(num);
}
//System.out.println(num); //no
//if(){}
//while(){}
}
public static void b(){
System.out.println("------b");
}
//构造块
{
System.out.println("------这是构造块");
}
//静态块
static{
System.out.println("-----这是静态块");
//在静态块中只能访问:静态属性,静态方法
System.out.println(sa);
b();
}
//构造器
public Test(){
System.out.println("这是空构造器");
}
public Test(int a){
this.a = a;
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Test t = new Test();
t.a();
Test t2 = new Test();
t2.a();
}
}
  • 总结:代码块执行顺序
    • 最先执行静态块,只在类加载的时候执行一次
      • 一般以后实战写项目:创建工厂,数据库的初始化信息都放入静态块
      • 一般用于执行一些全局性的初始化操作
    • 再执行构造块(不常用)
    • 再执行构造器
    • 再执行方法中的普通块
包、import

创建包

  • 包名定义
    1. 名字全部小写
    2. 中间用.隔开
    3. 一般都是公司域名倒着写 : com.jd com.excepenxi
    4. 加上模块名字:com.jd.login com.jd.register
    5. 不能使用系统中的关键字:nul,con,com1—com9 …..
    6. 包声明的位置,一般都在非注释性代码的第一行:package com.excepenxi;
导包问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//声明包:
package com.excepenxi;
import com.excepenxi.Person; //导包:就是为了进行定位
import java.util.Date;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
new Person();
new Date();
//在导包以后,还想用其他包下同名的类,就必须要手动自己写所在的包
new java.sql.Date(1000L);
// Test类和Demo类同级别,不需要导包
new Demo();
}
}
  • 总结

    1. 使用不同包下的类要需要导包:import **.*.*; 例如:import java.util.Date;
    2. 在导包以后,还想用其他包下同名的类,就必须要手动自己写所在的包
    3. 同一个包下的类想使用不需要导包,可以直接使用
    4. 在java.lang包下的类,可以直接使用无需导包:System.out.println(Math.random());
    5. IDEA中导包快捷键:alt+enter (可以自己设置自动导包)
    6. 可以直接导入*:import java.util.*; //代表所有
  • 在Java中的导包没有包含和被包含的关系:设置目录平级的格式(不是包含和被包含的显示)

1
2
import com.excepenxi.*; // 没有包含和被被包含概念
import com.excepenxi.sub.D; // 需导包

点击小齿轮勾选

静态导入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.excepenxi;
//静态导入:导入java.lang下的Math类中的所有静态的内容
import static java.lang.Math.*;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
System.out.println(round(5.6));
}
//在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法
public static int round(double a){
return 1000;
}
}

面向对象(中)

封装(Encapsulation)
  • 生活案例:ATM 、 电线

  • Java中封装的理解:将某些东西进行隐藏,然后提供相应的方式进行获取

    • 我们程序设计追求“高内聚,低耦合”
    • ➢高内聚:类的内部数据操作细节自己完成,不允许外部干涉
    • ➢低耦合:仅对外暴露少量的方法用于使用
    • 隐藏对象内部的复杂性,只对外公开简单的接口;便于外界调用,从而提
      高系统的可扩展性、可维护性
    • 通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来,这就是封装性的设计思想
  • 封装的好处:提高代码的安全性

通过一个属性感受封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.excepenxi.test01;
public class Girl {//女孩类
//属性:
private int age;
//读取年龄:
public int duquAge(){
return age;
}
//设置年龄:
public void shezhiAge(int age){
if(age >= 30 ){
this.age = 18;
}else{
this.age = age;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.excepenxi.test01;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Girl类的对象:
Girl g = new Girl();
/*g.age = 33;
System.out.println(g.age);*/
//设置年龄:
g.shezhiAge(33);
//读取年龄:
System.out.println(g.duquAge());
}
}

上面的代码,对于属性age来说,我加了修饰符private,这样外界对它的访问就受到了限制,现在我还想加上其他的限制条件,但是在属性本身上没有办法再加了,所以我们通过定义方法来进行限制条件的添加

  • 以属性为案例:进行封装

    1. 将属性私有化,被private修饰–》加入权限修饰符
      • 一旦加入了权限修饰符,其他人就不可以随意的获取这个属性
    2. 提供public修饰的方法让别人来访问/使用
    3. 即使外界可以通过方法来访问属性了,但是也不能随意访问,因为咱们在方法中可以加入限制条件
  • 实际开发中,方法一般会写成 setter,getter方法

    • 可以利用IDEA快捷键生成:alt+insert –>getter and setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Girl {//女孩
//属性:
private int age;
//读取年龄:
public int getAge(){
return age;
}
//设置年龄:
public void setAge(int age){
if(age >= 30 ){
this.age = 18;
}else{
this.age = age;
}
}
}

加深练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.excepenxi.test2;
public class Student {
//属性:
private int age;
private String name;
private String sex;
//加入对应的setter和getter方法:
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
if("男".equals(sex) || "女".equals(sex) ){//sex是男 或者 是 女
this.sex = sex;
}else{
this.sex = "男";
}
}
//加入构造器:
public Student(){
}
public Student(int age,String name,String sex){
this.age = age;
this.name = name;
//this.sex = sex;
this.setSex(sex); //复用代码
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.excepenxi.test2;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Student对象:
Student s1 = new Student();
s1.setName("nana");
s1.setAge(19);
s1.setSex("女"); //输入其他信息,会输出 男
System.out.println(s1.getName()+"---"+s1.getAge()+"----"+s1.getSex());
Student s2 = new Student(18,"菲菲","asdfasdfsadf"); // 18 菲菲 男
System.out.println(s2.getName()+"---"+s2.getAge()+"----"+s2.getSex());
}
}
继承(Inheritance)
  • 类是对对象的抽象
    • 荣耀20 ,小米 红米3,华为 p40 pro —> 类:手机类
  • 继承是对类的抽象:继承就是 is - a 的关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//学生类:Student:
属性:姓名,年龄,身高,学生编号
方法:吃饭,睡觉,喊叫,学习

//教师类:Teacher:
属性:姓名,年龄,身高,教师编号
方法:吃饭,睡觉,喊叫,教学

//员工类:Emploee:
属性:姓名,年龄,身高,员工编号
方法:吃饭,睡觉,喊叫,工作

//共同的东西:
人类:
属性:姓名,年龄,身高
方法:吃饭,睡觉,喊叫

//学生类/教师类/员工类 继承自人类

//以后定义代码,先定义人类:
人类: ---》父类,基类,超类
属性:姓名,年龄,身高
方法:吃饭,睡觉,喊叫

//再定义 : ---》子类,派生类
学生类:Student:
属性:学生编号
方法:学习

教师类:Teacher:
属性:教师编号
方法:教学

员工类:Emploee:
属性:员工编号
方法:工作

//子类 继承自父类

狗类:
属性:姓名,年龄,身高
方法:吃饭,睡觉,喊叫

//我们的继承关系,是在合理的范围中进行的抽取 ,抽取出子类父类的关系:
上面的案例中:
学生类/教师类/员工类 继承 自 人类 ---》合理
学生类/教师类/员工类 继承 自 狗类 ---》不合理

//区分:
学生是一个人
教师是一个人
员工是一个人 ---》合理
学生是一个狗 ---》不合理
  • 代码层面的解释:先写父类,再写子类
    • 父类:人类 Person
    • 子类:学生类 Student
Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.excepenxi.test03;
public class Person {
//属性:
private int age;
private String name;
private double height;
//提供setter getter方法:
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
//方法:
public void eat(){
System.out.println("可以吃饭。。。");
}
public void sleep(){
System.out.println("可以睡觉。。。");
}
}
Student类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.excepenxi.test03;
public class Student extends Person {//子类Student 继承 父类Person
//属性:
private int sno;//学号
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
//方法:
public void study(){
System.out.println("学生可以学习");
}
}
Test类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.excepenxi.test03;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建子类Student的对象
Student s = new Student();
s.setSno(1001);
s.setAge(18);
s.setName("菲菲");
s.setHeight(180.4);
System.out.println("学生名字为:"+s.getName()+",学生的年纪:"+s.getAge());
//访问方法:
s.study();
s.eat();
s.sleep();
}
}
  • 继承的好处:提高代码的复用性

父类定义的内容,子类可以直接拿过来用就可以了,不用代码上反复重复定义了

  • 需要注意的点:

父类private修饰的内容,子类实际上也继承,只是因为封装的特性阻碍了直接调用,但是提供了间接调用的方式,可以间接调用

总结

  1. 继承关系

    • 父类/基类/超类
    • 子类/派生类
    • 子类继承父类一定在合理的范围进行继承的 子类 extends 父类
  2. 继承的好处

    • 提高了代码的复用性,父类定义的内容,子类可以直接拿过来用就可以了,不用代码上反复重复定义
    • 便于代码的扩展
    • 为了以后多态的使用,是多态的前提
  3. 父类private修饰的内容,子类也继承过来了

  4. 一个父类可以有多个子类

  5. 一个子类只能有一个直接父类,但是可以间接的继承自其它类

  6. 继承具有传递性

    • Student –》继承自 Person —》继承自Object
    • Object类是所有类的根基父类
    • 所有的类都直接或者间接的继承自Object

内存分析

继承之权限修饰符

  • private:在当前类中可以访问
  • default:缺省修饰符:到同一个包下的其他类都可以访问
  • protected:最大到不同包下的子类
  • public:在整个项目中都可以访问

private

default

protected

总结:
属性,方法的修饰符有四种:private,缺省,protected,public
类的修饰符有两种:缺省,public

以后写代码,一般属性用private修饰 ;方法用public修饰

继承之方法的重写
  1. 重写:发生在子类和父类中,当子类对父类提供的方法不满意的时候,要对父类的方法进行重写
  2. 重写有严格的格式要求:子类的方法名字和父类必须一致,参数列表(个数,类型,顺序)也要和父类一致
1
2
3
4
5
6
7
8
public class Person {
public void eat(){
System.out.println("吃食物");
}
public void sleep(){
System.out.println("睡觉");
}
}
1
2
3
4
5
6
7
8
9
public class Student extends Person {
public void study(){
System.out.println("学习");
}
@override
public void eat(){
System.out.println("我喜欢吃小龙虾喝啤酒。。"); //重写
}
}
1
2
3
4
5
6
7
8
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Student类的对象
Student s = new Student();
s.eat(); //输出我喜欢吃小龙虾喝啤酒。。 而不是吃食物
}
}

  • 重载和重写的区别
    • 重载:在同一个类中,当方法名相同,形参列表不同的时候 多个方法构成了重载
    • 重写:在不同的类中,子类对父类提供的方法不满意的时候,要对父类的方法进行重写

super修饰属性和方法
  • super:指的是:父类的
  • super可以修饰属性,可以修饰方法;

在子类的方法中,可以通过 super.属性 或 super.方法 的方式,显示的去调用父类提供的属性,方法。在通常情况下,super.可以省略不写

在特殊情况下,当子类和父类的属性重名时,你要想使用父类的属性,必须加上修饰符super.,只能通过super.属性来调用
在特殊情况下,当子类和父类的方法重名时,你要想使用父类的方法,必须加上修饰符super.,只能通过super.方法来调用
在这种情况下,super.就不可以省略不写

super修饰构造器

其实我们平时写的构造器的第一行都有:super() –>作用:调用父类的空构造器,只是我们一般都省略不写
(所有构造器的第一行默认情况下都有super(),但是一旦你的构造器中显示的使用super调用了父类构造器,那么这个super()就不会给你默认分配了。如果构造器中没有显示的调用父类构造器的话,那么第一行都有super(),可以省略不写)

如果构造器中已经显示的调用super父类构造器,那么它的第一行就没有默认分配的super();了

在构造器中,super调用父类构造器和this调用子类构造器只能存在一个,两者不能共存:
因为super修饰构造器要放在第一行,this修饰构造器也要放在第一行

改正二选一即可

  • 以后写代码构造器的生成可以直接使用IDEA提供的快捷键:alt+insert

继承条件下构造方法的执行过程

1
2
3
4
5
6
7
8
9
10
11
12
package com.excepenxi.test10;
public class Person {
int age;
String name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public Person() {
}
}
1
2
3
4
5
6
7
8
9
public class Student extends Person {
double height ;
public Student() {
}
public Student(int age, String name, double height) {
super(age, name);
this.height = height;
}
}
1
2
3
4
5
6
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Student s = new Student(19,"feifei",160.8);
}
}
Object类的toString()方法

所有类都直接或间接的继承自Object类,Object类是所有Java类的根基类
也就意味着所有的Java对象都拥有Object类的属性和方法
如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类

  • Object类的toString()的作用

方法的原理

现在,使用toString方法的时候,打印出来的东西 “不好看”,对于其他人来说不友好,可读性不好
我们现在是想知道对象的信息,名字,年龄,身高。。。。。。

出现的问题:子类Student对父类Object提供的toString方法不满意,不满意–》对toString方法进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.excepenxi.test01;
public class Student /*extends Object*/{
private String name;
private int age;
private double height;
//set get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
//构造器
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String toString() {
return "这是一个Student对象,这个对象的名字:"+name+",年龄:"+age+",身高:"+height;
}
}

测试类

总结:toString的作用就是对对象进行“自我介绍”,一般子类对父类提供的toString都不满意,都要进行重写

IDEA提供了快捷键

1
2
3
4
5
6
7
8
9
10
11
/*public String toString() {
return "这是一个Student对象,这个对象的名字:"+name+",年龄:"+age+",身高:"+height;
}*/
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
Object类的equals方法

equals作用:这个方法提供了对对象的内容是否相等 的一个比较方式,对象的内容指的就是属性

父类Object提供的equals就是在比较==地址,没有实际的意义,我们一般不会直接使用父类提供的方法,
而是在子类中对这个方法进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.excepenxi.test02;
public class Phone {//手机类:
//属性:
private String brand;//品牌型号
private double price;//价格
private int year ;//出产年份
//方法:
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
//toString
@Override
public String toString() {
return "Phone{" +
"brand='" + brand + '\'' +
", price=" + price +
", year=" + year +
'}';
}
//构造器:
public Phone() {
}
public Phone(String brand, double price, int year) {
this.brand = brand;
this.price = price;
this.year = year;
}
//对equals方法进行重写:
public boolean equals(Object obj) {//Object obj = new Phone();
//将obj转为Phone类型:
Phone other = (Phone)obj;//向下转型,为了获取子类中特有的内容
if(this.getBrand()==other.getBrand()&&this.getPrice()==other.getPrice()&&this.getYear()==other.getYear()){
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.excepenxi.test02;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建Phone类的对象:
Phone p1 = new Phone("华为P40",2035.98,2020);
Phone p2 = new Phone("华为P40",2035.98,2020);
//比较两个对象:p1和p2对象:
//==的作用:比较左右两侧的值是否想的,要么相等,返回true,要么不相等,返回false
System.out.println(p1==p2);//-->>>对于引用数据类型来说,比较的是地址值。--->一定返回的是false
//Object类提供了一个方法 equals方法 :作用:比较对象具体内容是否相等。
boolean flag = p1.equals(p2);//点进源码发现:底层依旧比较的是==,比较的还是地址值。
System.out.println(flag);
}
}

用instanceof关键字判断前面对象是否是后面的实例

利用集成开发工具生成equals方法

利用eclipse

利用idea

类和类之间的关系

小女孩找小男孩

  • 面向对象的思维:找参与者,找女孩类,找男孩类、
  • 体会了什么叫方法的形参,什么叫方法的实参

形参

具体传入的内容 实参

  • 类和类可以产生关系
    • 将一个类作为另一个类中的方法的形参
    • 将一个类作为另一个类的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Girl {
//属性:
String name;
double weight;
Mom m /*= new Mom()*/;
//方法:
/*public void add(int a){//参数是基本数据类型
System.out.println(a);
System.out.println(a+100);
}*/
//谈恋爱的方法:
public void love(Boy b){//参数是引用数据类型Boy
System.out.println("我男朋友的名字是:"+b.name+",我男朋友的年龄是:"+b.age);
b.buy();
}
//女孩跟妈妈聊天:
public void wechat(){
m.say();
}
//构造器:
public Girl(String name, double weight) {
this.name = name;
this.weight = weight;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Boy {
//属性:
int age;
String name;
//方法:
public void buy(){
System.out.println("跟我谈恋爱,我给你买买买。。。");
}
//构造器:
public Boy(int age, String name) {
this.age = age;
this.name = name;
}
}
1
2
3
4
5
6
public class Mom {
//方法:
public void say(){
System.out.println("妈妈唠唠叨叨 都是爱,听妈妈的话。。");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Boy类的具体的对象:
Boy boy = new Boy(30,"鹿晗");
//创建一个Girl类的具体的对象:
Girl girl = new Girl("关晓彤",100);
//谈恋爱:
//girl.love(boy);
Boy boy2 = new Boy(35,"陈伟霆");
girl.love(boy2);
//还可以跟妈妈微信聊天:
girl.m = new Mom(); //引用数据类型赋值
girl.wechat();
}
}

总结

多态(Polymorphism)
  • 多态跟属性无关,多态指的是方法的多态,而不是属性的多态
  • 案例:小女孩养动物
1
2
3
4
5
public class Animal {//父类:动物:
public void shout(){
System.out.println("我是小动物,我可以叫。。。");
}
}
1
2
3
4
5
6
7
8
9
public class Cat extends Animal{
//喊叫方法:
public void shout(){
System.out.println("我是小猫,可以喵喵叫");
}
public void scratch(){
System.out.println("我是小猫,我可以挠人");
}
}
1
2
3
4
5
6
7
8
9
public class Dog extends Animal{
//喊叫:
public void shout(){
System.out.println("我是小狗,我可以汪汪叫");
}
public void guard(){
System.out.println("我是小狗,我可以看家护院,保护我的小主人。。。");
}
}
1
2
3
4
5
6
7
8
public class Pig extends Animal{
public void shout(){
System.out.println("我是小猪,我嗯嗯嗯的叫");
}
public void eat(){
System.out.println("我是小猪,我爱吃东西。。");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Girl {
//跟猫玩耍:
/*public void play(Cat cat){
cat.shout();
}*/
//跟狗玩耍:
/*public void play(Dog dog){
dog.shout();
}*/
//跟小动物玩耍:
public void play(Animal an){
an.shout();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//具体的猫:--》猫的对象
//Cat c = new Cat();
//具体的小女孩:--》女孩的对象
Girl g = new Girl();
//小女孩跟猫玩:
//g.play(c);
//具体的狗---》狗的对象:
//Dog d = new Dog();
//小女孩跟狗玩:
//g.play(d);
//具体的动物:--》动物的对象:
//Cat c = new Cat();
//Dog d = new Dog();
Pig p = new Pig();
Animal an = p;
g.play(an);
}
}
  • 先有父类,再有子类:–》继承;先有子类,再抽取父类 —-》泛化
  • 什么是多态
    • 多态就是多种状态:同一个行为,不同的子类表现出来不同的形态
    • 多态指的就是同一个方法调用,然后由于对象不同会产生不同的行为
  • 多态的好处
    • 为了提高代码的扩展性,符合面向对象的设计原则:开闭原则
    • 开闭原则:指的就是扩展是 开放的,修改是关闭的
    • 注意:多态可以提高扩展性,但是扩展性没有达到最好,以后我们会学习 反射
  • 多态的要素
    • 继承:Cat extends Animal ,Pig extends Animal, Dog extends Animal
    • 重写:子类对父类的方法shout()重写
    • 父类引用指向子类对象
1
2
3
Pig p = new Pig();
Animal an = p;
g.play(an);

将上面的代码合为一句话:

  • Animal an = new Pig();
  • =左侧:编译期的类型
  • =右侧:运行期的类型
1
2
3
public void play(Animal an){//Animal an = an = new Pig();
an.shout();
}

上面的代码,也是多态的一种非常常见的应用场合:父类当方法的形参,然后传入的是具体的子类的对象,然后调用同一个方法,根据传入的子类的不同展现出来的效果也不同,构成了多态

内存分析

向下转型、向上转型

  • 想访问到eat()方法和weight属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Pig p = new Pig();
Animal an = p;//转型:向上转型
an.shout();
//加入转型的代码:
//将Animal转为Pig类型:
Pig pig = (Pig)an ;//转型:向下转型
pig.eat();
pig.age = 10;
pig.weight = 60.8;
}
}

内存分析

思考之前的equals方法

简单工厂设计模式

不仅可以使用父类做方法的形参,还可以使用父类做方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象。

简单工厂模式的实现,它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。

简单工厂模式的基本要求是:

  • 定义一个static方法,通过类名直接调用
  • 返回值类型是父类类型,返回的可以是其任意子类类型
  • 传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Girl g = new Girl();
//Cat c = new Cat();
//Dog d = new Dog();
//Pig p = new Pig();
Animal an = PetStore.getAnimal("狗");
g.play(an);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PetStore {//宠物店 ---》工厂类
//方法:提供动物
public static Animal getAnimal(String petName){//多态的应用场合(二)
Animal an = null;
if("猫".equals(petName)){//petName.equals("猫") --》这样写容易发生空指针异常
an = new Cat();
}
if("狗".equals(petName)){
an = new Dog();
}
if("猪".equals(petName)){
an = new Pig();
}
return an;
}
}

面向对象(下)

final修饰符
  1. 修饰变量
  2. 修饰方法:final修饰方法,那么这个方法不可以被该类的子类重写
  3. 修饰类:final修饰类,代表没有子类,该类不可以被继承
    • 一旦一个类被final修饰,那么里面的方法也没有必要用final修饰了(final可以省略不写)
修饰变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//第1种情况:
//final修饰一个变量,变量的值不可以改变,这个变量也变成了一个字符常量,约定俗称的规定:名字大写
final int A = 10;//final修饰基本数据类型
//A = 20; 报错:不可以修改值

//第2种情况:
final Dog d = new Dog();//final修饰引用数据类型,那么地址值就不可以改变
//d = new Dog(); -->地址值不可以更改
//d对象的属性依然可以改变:
d.age = 10;
d.weight = 13.7;

//第3种情况:
final Dog d2 = new Dog();
a(d2);

//第4种情况:
b(d2);
}
public static void a(Dog d){
d = new Dog();
}
public static void b(final Dog d){//d被final修饰 ,指向不可以改变
//d = new Dog();
}
}

final修饰方法,那么这个方法不可以被该类的子类重写

修饰类

案例:JDK提供的Math类,看源码发现

抽象类、抽象方法
  • 抽象类和抽象方法的关系:抽象类中可以定义0-n个抽象方法。
  • 抽象类作用:在抽象类中定义抽象方法,目的是为了为子类提供一个通用的模板,子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容。抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制,使子类更加的通用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.excepenxi;
//4.一个类中如果有方法是抽象方法,那么这个类也要变成一个抽象类。
//5.一个抽象类中可以有0-n个抽象方法
public abstract class Person {
//1.在一个类中,会有一类方法,子类对这个方法非常满意,无需重写,直接使用
public void eat(){
System.out.println("一顿不吃饿得慌");
}
//2.在一个类中,会有一类方法,子类对这个方法永远不满意,会对这个方法进行重写。
//3.一个方法的方法体去掉,然后被abstract修饰,那么这个方法就变成了一个抽象方法
public abstract void say();
public abstract void sleep();
}
//6.抽象类可以被其他类继承:
//7.一个类继承一个抽象类,那么这个类可以变成抽象类
//8.一般子类不会加abstract修饰,一般会让子类重写父类中的抽象方法
//9.子类继承抽象类,就必须重写全部的抽象方法
//10.子类如果没有重写父类全部的抽象方法,那么子类也可以变成一个抽象类。
class Student extends Person{
@Override
public void say() {
System.out.println("我是东北人,我喜欢说东北话。。");
}
@Override
public void sleep() {
System.out.println("东北人喜欢睡炕。。");
}
}
class Demo{
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//11.创建抽象类的对象:-->抽象类不可以创建对象
//Person p = new Person();
//12.创建子类对象:
Student s = new Student();
s.sleep();
s.say();

//13.多态的写法:父类引用指向子类对象:
Person p = new Student();
p.say();
p.sleep();
}
}

面试题

(1)抽象类不能创建对象,那么抽象类中是否有构造器?
抽象类中一定有构造器。构造器的作用是给子类初始化对象的时候要先super调用父类的构造器。

(2)抽象类是否可以被final修饰?
不能被final修饰,因为抽象类设计的初衷就是给子类继承用的。要是被final修饰了这个抽象类了,就不存在继承了,就没有子类。

接口
接口声明格式
1
2
3
4
[访问修饰符]  interface 接口名   [extends  父接口1,父接口2…]  {
常量定义;
方法定义;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.excepenxi.test04;
/**
* 1.类是类,接口是接口,它们是同一层次的概念。
* 2.接口中没有构造器
* 3.接口如何声明:interface
* 4.在JDK1.8之前,接口中只有两部分内容:
* (1)常量:固定修饰符:public static final
* (2)抽象方法:固定修饰符:public abstract
* 注意:修饰符可以省略不写,IDE会帮你自动补全,但是初学者建议写上,防止遗忘。
*/
public interface TestInterface01 {
//常量:
/*public static final*/ int NUM = 10;
//抽象方法:
/*public abstract*/ void a();
/*public abstract*/ void b(int num);
/*public abstract*/ int c(String name);
}
interface TestInterface02{
void e();
void f();
}
/*
5.类和接口的关系是什么? 实现关系 类实现接口:
6.一旦实现一个接口,那么实现类要重写接口中的全部的抽象方法:
7.如果没有全部重写抽象方法,那么这个类可以变成一个抽象类。
8.java只有单继承,java还有多实现
一个类继承其他类,只能直接继承一个父类
但是实现类实现接口的话,可以实现多个接口
9.写法:先继承 再实现:extends Person implements TestInterface01,TestInterface02
*/
class Student extends Person implements TestInterface01,TestInterface02 {
@Override
public void a() {
System.out.println("---1");
}
@Override
public void b(int num) {
System.out.println("---2");
}
@Override
public int c(String name) {
return 100;
}
@Override
public void e() {
System.out.println("---3");
}
@Override
public void f() {
System.out.println("---4");
}
}
class Test{
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//10.接口不能创建对象:
//TestInterface02 t = new TestInterface02();
TestInterface02 t = new Student();//接口指向实现类 ---》多态
//11.接口中常量如何访问:
System.out.println(TestInterface01.NUM);
System.out.println(Student.NUM);
Student s = new Student();
System.out.println(s.NUM);
TestInterface01 t2 = new Student();
System.out.println(t2.NUM);
}
}

  • 在JDK1.8之前,接口中只有两部分内容
    1. 常量:固定修饰符:public static final
    2. 抽象方法:固定修饰符:public abstract
  • 在JDK1.8之后,新增非抽象方法
    1. 被public default修饰的非抽象方法
      • 注意1:default修饰符必须要加上,否则出错
      • 注意2:实现类中要是想重写接口中的非抽象方法,那么default修饰符必须不能加,否则出错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface TestInterface {
//常量:
public static final int NUM= 10;
//抽象方法:
public abstract void a();
//public default修饰的非抽象方法:
public default void b(){
System.out.println("-------TestInterface---b()-----");
}
}
class Test implements TestInterface{
public void c(){
//用一下接口中的b方法:
b();//可以
//super.b();不可以
TestInterface.super.b();//可以
}
@Override
public void a() {
System.out.println("重写了a方法");
}
@Override
public void b() {
}
}
  1. 静态方法
    • 注意1:static不可以省略不写
    • 注意2:静态方法不能重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface TestInterface2 {
//常量:
public static final int NUM = 10;
//抽象方法:
public abstract void a();
//public default非抽象方法;
public default void b(){
System.out.println("-----TestInterface2---b");
}
//静态方法:
public static void c(){
System.out.println("TestInterface2中的静态方法");
}
}
class Demo implements TestInterface2{
@Override
public void a() {
System.out.println("重写了a方法");
}
public static void c(){
System.out.println("Demo中的静态方法");
}
}
class A {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Demo d = new Demo();
d.c();
Demo.c();
TestInterface2.c();
}
}

疑问:为什么要在接口中加入非抽象方法???

如果接口中只能定义抽象方法的话,那么我要是修改接口中的内容,那么对实现类的影响太大了,所有实现类都会受到影响。现在在接口中加入非抽象方法,对实现类没有影响,想调用就去调用即可。

内部类
成员内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.excepenxi.test07;
/**
* 1.类的组成:属性,方法,构造器,代码块(普通块,静态块,构造块,同步块),内部类
* 2.一个类TestOuter的内部的类SubTest叫内部类, 内部类 :SubTest 外部类:TestOuter
* 3.内部类:成员内部类 (静态的,非静态的) 和 局部内部类(位置:方法内,块内,构造器内)
* 4.成员内部类:
* 里面属性,方法,构造器等
* 修饰符:private,default,protect,public,final,abstract
*/
public class TestOuter {
//非静态的成员内部类:
public class D{
int age = 20;
String name;
public void method(){
//5.内部类可以访问外部类的内容
/*System.out.println(age);
a();*/
int age = 30;
//8.内部类和外部类属性重名的时候,如何进行调用:
System.out.println(age);//30
System.out.println(this.age);//20
System.out.println(TestOuter.this.age);//10
}
}
//静态成员内部类:
static class E{
public void method(){
//6.静态内部类中只能访问外部类中被static修饰的内容
/*System.out.println(age);
a();*/
}
}
//属性:
int age = 10;
//方法:
public void a(){
System.out.println("这是a方法");
{
System.out.println("这是一个普通块");
class B{
}
}
class A{
}
//7.外部类想要访问内部类的东西,需要创建内部类的对象然后进行调用
D d = new D();
System.out.println(d.name);
d.method();
}
static{
System.out.println("这是静态块");
}
{
System.out.println("这是构造块");
}
//构造器:
public TestOuter(){
class C{
}
}
public TestOuter(int age) {
this.age = age;
}
}
class Demo{
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建外部类的对象:
TestOuter to = new TestOuter();
to.a();
//9.创建内部类的对象:
//静态的成员内部类创建对象:
TestOuter.E e = new TestOuter.E();
//非静态的成员内部类创建对象:
//错误:TestOuter.D d = new TestOuter.D();
TestOuter t = new TestOuter();
TestOuter.D d = t.new D();
}
}
局部内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.excepenxi.test08;
public class TestOuter {
//1.在局部内部类中访问到的变量必须是被final修饰的
public void method(){
final int num = 10;
class A{
public void a(){
//num = 20;
System.out.println(num);
}
}
}
//2.如果类B在整个项目中只使用一次,那么就没有必要单独创建一个B类,使用内部类就可以了
public Comparable method2(){
// Comparable等集合说
class B implements Comparable{
@Override
public int compareTo(Object o) {
return 100;
}
}
return new B();
}
public Comparable method3(){
//3.匿名内部类
return new Comparable(){
@Override
public int compareTo(Object o) {
return 200;
}
};
}
public void teat(){
Comparable com = new Comparable(){
@Override
public int compareTo(Object o) {
return 200;
}
};
System.out.println(com.compareTo("abc"));
}
}
面向对象项目

项目需求

项目结构分析

匹萨父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.excepenxi.test01;
/**
* 父类:匹萨类
*/
public class Pizza {
//属性
private String name;//名称
private int size;//大小
private int price;//价格
//方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
//展示匹萨信息:
public String showPizza(){
return "匹萨的名字是:"+name+"\n匹萨的大小是:"+size+"寸\n匹萨的价格:"+price+"元";
}
//构造器
public Pizza() {
}
public Pizza(String name, int size, int price) {
this.name = name;
this.size = size;
this.price = price;
}
}
培根匹萨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.excepenxi.test01;
public class BaconPizza extends Pizza {
//属性:
private int weight;
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
//构造器:
public BaconPizza() {
}
public BaconPizza(String name, int size, int price, int weight) {
super(name, size, price);
this.weight = weight;
}
//重写父类showPizza方法:
@Override
public String showPizza() {
return super.showPizza()+"\n培根的克数是:"+weight+"克";
}
}
水果匹萨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.excepenxi.test01;
public class FruitsPizza extends Pizza{
//属性:
private String burdening;
public String getBurdening() {
return burdening;
}
public void setBurdening(String burdening) {
this.burdening = burdening;
}
//构造器:
public FruitsPizza() {
}
public FruitsPizza(String name, int size, int price, String burdening) {
super(name, size, price);
this.burdening = burdening;
}
//重写父类showPizza方法:
@Override
public String showPizza() {
return super.showPizza()+"\n你要加入的水果:"+burdening;
}
}
测试类
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//选择购买匹萨:
Scanner sc = new Scanner(System.in);
System.out.println("请选择你想要购买的匹萨(1.培根匹萨 2.水果匹萨):");
int choice = sc.nextInt();//选择
//通过工厂获取匹萨:
Pizza pizza = PizzaStore.getPizza(choice);
System.out.println(pizza.showPizza());
}
}
工厂类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.excepenxi.test01;
import java.util.Scanner;
public class PizzaStore {
public static Pizza getPizza(int choice){
Scanner sc = new Scanner(System.in);
Pizza p = null;
switch (choice){
case 1:
{
System.out.println("请录入培根的克数:");
int weight = sc.nextInt();
System.out.println("请录入匹萨的大小:");
int size = sc.nextInt();
System.out.println("请录入匹萨的价格:");
int price = sc.nextInt();
//将录入的信息封装为培根匹萨的对象:
BaconPizza bp = new BaconPizza("培根匹萨",size,price,weight);
p = bp;
}
break;
case 2:
{
System.out.println("请录入你想要加入的水果:");
String burdening = sc.next();
System.out.println("请录入匹萨的大小:");
int size = sc.nextInt();
System.out.println("请录入匹萨的价格:");
int price = sc.nextInt();
//将录入的信息封装为水果匹萨的对象:
FruitsPizza fp = new FruitsPizza("水果匹萨",size,price,burdening);
p = fp;
}
break;
}
return p;
}
}

异常(Exception)

在程序的运行过程中,发生了不正常的现象,阻止了程序的运行,我们称之为发生异常

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商:
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}
}

通过if-else解决异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.excepenxi.test01;
import java.util.Scanner;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商:
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
if(sc.hasNextInt()){
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
if(sc.hasNextInt()){
int num2 = sc.nextInt();
if(num2 == 0){
System.out.println("对不起,除数不能为0");
}else{
System.out.println("商:"+num1/num2);
}
}else{
System.out.println("对不起,你录入的不是int类型的数据!");
}
}else{
System.out.println("对不起,你录入的不是int类型的数据!");
}
}
}

用if-else堵漏洞的缺点

  • 代码臃肿,业务代码和处理异常的代码混在一起
  • 可读性差
  • 程序员需要花费大量的经历来维护这个漏洞
  • 程序员很难堵住所有的漏洞
try-catch捕获异常

基于if-else处理异常缺点太多,所以java中专门出了一个异常处理机制:”异常三连” try-catch-finally

异常查看

  • 捕获异常: try-catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test2 {
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商:
try{
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch(Exception ex){
System.out.println("对不起,程序出现异常!");
}
System.out.println("----谢谢你使用计算器111");
System.out.println("----谢谢你使用计算器222");
System.out.println("----谢谢你使用计算器333");
}
}

原理:
把可能出现异常的代码放入try代码块中,然后将异常封装为对象,被catch后面的()中的那个异常对象接收,接收以后:执行catch后面的{}里面的代码,然后try-catch后面的代码,该怎么执行就怎么执行。

详细说一下:
(1)try中没有异常,catch中代码不执行
(2)try中有异常,catch进行捕获:
如果catch中异常类型和你出的异常类型匹配的话:走catch中的代码–》进行捕获
如果catch中异常类型和你出的异常类型不匹配的话:不走catch中的代码–》没有捕获成功,程序相当于遇到异常了,中断了,后续代码不执行

注意:

(1)try中如果出现异常,然后用catch捕获成功的话,那么try中后续的代码是不会执行的
(2)如果catch捕获异常成功,那么try-catch后面的代码该执行还是执行没有影响

catch中如何处理异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.excepenxi.test01;
import java.util.Scanner;
public class Test3 {
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商:
try{
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch(Exception ex){
//第一种处理:什么都不写,什么都不做
//第二种处理:输出自定义异常信息
//System.out.println("对不起,你的代码有问题!");
//第三种处理:打印异常信息:
/*(1)调用toString方法,显示异常的类名(全限定路径)*/
/*System.out.println(ex);
System.out.println(ex.toString());*/
/*(2)显示异常描述信息对应的字符串,如果没有就显示null
System.out.println(ex.getMessage());*/
/*(3)显示异常的堆栈信息:将异常信息捕获以后,
在控制台将异常的效果给我们展示出来,方便我们查看异常*/
ex.printStackTrace();
//第四种处理:抛出异常:
//throw ex;
}
System.out.println("----谢谢你使用计算器111");
}
}
try-catch-finally
  1. 在什么情况下,try-catch后面的代码不执行?
    • throw抛出异常的情况
    • catch中没有正常的进行异常捕获
    • 在try中遇到return
  2. 怎么样才可以将 try-catch后面的代码 必须执行?
    • 只要将必须执行的代码放入finally中,那么这个代码无论如何一定执行
  3. return和finally执行顺序?
    • 先执行finally最后执行return
  4. 什么代码会放在finally中呢?
    • 关闭数据库资源,关闭IO流资源,关闭socket资源
  5. 有一句话代码很厉害,它可以让finally中代码不执行!
    • System.exit(0);//终止当前的虚拟机执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.excepenxi.test01;
import java.util.Scanner;
public class Test3 {
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商:
try{
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
System.exit(0);//终止当前的虚拟机执行
return;
}catch(ArithmeticException ex){
//throw ex;
}finally {
System.out.println("----谢谢你使用计算器111");
}
}
}
多重catch
  • try中出现异常以后,将异常类型跟catch后面的类型依次比较,按照代码的顺序进行比对,执行第一个与异常类型匹配的catch语句
  • 一旦执行其中一条catch语句之后,后面的catch语句就会被忽略了!
  • 在安排catch语句的顺序的时候,一般会将特殊异常放在前面(并列),一般化的异常放在后面
  • 先写子类异常,再写父类异常
  • 在JDK1.7以后,异常新处理方式:可以并列用|符号连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.excepenxi.test01;
import java.util.InputMismatchException;
import java.util.Scanner;
public class Test4 {
public static void main(String[] args) {
Integer
//实现一个功能:键盘录入两个数,求商:
try{
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch(ArithmeticException ex){
System.out.println("对不起,除数不可以为0");
}catch(InputMismatchException ex){
System.out.println("对不起,你录入的数据不是int类型的数据");
}catch(Exception ex){
System.out.println("对不起,你的程序出现异常");
}finally {
System.out.println("----谢谢你使用计算器111");
}
}
}
异常的分类

层次结构

注意:程序中语法错误,逻辑错误 都不属于上面的Error,Exception

运行时异常
1
2
3
4
5
6
7
8
9
10
11
public class Test5 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//运行时异常:
int[] arr = {1,2,3};
System.out.println(arr.length);
/*int[] arr2 = null;
System.out.println(arr2.length);*/
System.out.println(arr[10]);
}
}
try-catch嵌套try-catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//检查异常:
try {
try {
Class.forName("com.excepenxi.test01.Test").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
多重catch
1
2
3
4
5
6
7
8
9
10
11
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//检查异常:
try {
Class.forName("com.excepenxi.test01.Test").newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
throws
1
2
3
4
5
6
7
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//检查异常:
Class.forName("com.excepenxi.test01.Test").newInstance();
}
}
throw和throws的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.excepenxi.test01;
import java.util.Scanner;
public class Test7 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws Exception {
//实现一个功能:两个数相除,当除数为0的时候,程序出现异常
/*try {
devide();
} catch (Exception e) {
e.printStackTrace();
}*/
devide();
}
public static void devide() throws Exception {
Scanner sc = new Scanner(System.in);
System.out.print("请录入第一个数:");
int num1 = sc.nextInt();
System.out.print("请录入第二个数:");
int num2 = sc.nextInt();
if(num2 == 0 ){//除数为0 ,制造异常
//制造运行时异常:
/*throw new RuntimeException();*/
//制造检查异常:
/*try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}*/
throw new Exception();
}else{
System.out.println("商:"+num1/num2);
}
}
}

(1)位置不同:
throw:方法内部
throws: 方法的签名处,方法的声明处

(2)内容不同:
throw+异常对象(检查异常,运行时异常)
throws+异常的类型(可以多个类型,用逗号,拼接)

(3)作用不同:
throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理:要么自己处理要么再继续向外抛出异常

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.excepenxi.test02;
public class Student {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) throws Exception {
if(sex.equals("男")||sex.equals("女")){
this.sex = sex;
}else{//非男非女
//解决办法1:默认为男
/*this.sex = "男";*/
//解决办法2:给个友好型提示,但是打印结果为默认的null效果
/*System.out.println("对不起,你的性别错误了");*/
//解决办法3:
//制造运行时异常:
/*throw new RuntimeException("性别不对!");*/
//制造检查异常
/*try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}*/
throw new Exception();
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Student() {
}
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
//this.sex = sex;
try {
this.setSex(sex);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.excepenxi.test02;
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个Student的对象:
/*Student s = new Student();
s.setName("菲菲");
s.setAge(19);
try {
s.setSex("asdfasdfasdf");
} catch (Exception e) {
e.printStackTrace();
}
// 这里s就是调用toString方法
System.out.println(s);*/
Student s2 = new Student("娜娜",21,"asdfasdfasdf");
System.out.println(s2);
}
}
重载和重写的异常

重载
1
2
3
4
5
6
7
public class Demo {
// 怎么写都行
public void a() throws Exception{
}
public void a(int age) throws ArithmeticException{
}
}

重写报错案例

自定义异常

自定义的异常可以继承:运行时异常

1
2
3
4
5
6
7
8
public class MyException extends RuntimeException {
static final long serialVersionUID = -70348971907L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}

也可以继承检查异常

1
2
3
4
5
6
7
8
public class MyException extends Exception {
static final long serialVersionUID = -70348971907L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}

如果继承的是运行时异常,那么在使用的时候无需额外处理
如果继承的是检查异常,那么使用的时候需要try-catch捕获或者throws向上抛



----------- 本文结束 -----------




Buy me a coffee.