继承、super、this、抽象类

主要内容

  • 三大特性 ——继承
  • 方法重写
  • super 关键字
  • this 关键字
  • 抽象类

第一章 继承

1.1 概述

由来

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:

 

 

其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是: is -a 的关系。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

 定义

  • 继承 :就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

好处

  1. 提高代码的复用性。
  2. 类与类之间产生了关系,是多态的前提。

1.2  继承的格式

通过  extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

1
2
3
4
5
6
class 父类 {
...   
}
class 子类 extends 父类 {
...   
}

继承演示,代码如下:

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
/*
 * 定义员工类Employee,做为父类
*/
class Employee {
String name; // 定义name属性    
// 定义员工的工作方法    
public void work() {    
System.out.println("尽心尽力地工作");        
}    
}
/*
 * 定义讲师类Teacher 继承 员工类Employee
 */
class Teacher extends Employee {
// 定义一个打印name的方法    
public void printName() {    
System.out.println("name=" + name);        
}    
}
/*
 * 定义测试类
 */
public class ExtendDemo01 {
public static void main(String[] args) {    
        // 创建一个讲师类对象
Teacher t = new Teacher();        
      
        // 为该员工类的name属性进行赋值
t.name = "小明";         
      
       // 调用该员工的printName()方法  
t.printName(); // name = 小明        
        
       // 调用Teacher类继承来的work()方法  
       t.work();  // 尽心尽力地工作  
}    
}

案例

 

1
2
3
4
5
6
7
8
9
10
package cn.itcast.day09.demo01;
 
// 定义一个父类:员工
public class Employee {
 
    public void method() {
        System.out.println("方法执行!");
    }
 
}

 

  

1
2
3
4
5
6
package cn.itcast.day09.demo01;
 
// 定义了一个员工的子类:讲师
public class Teacher extends Employee {
 
}

  

1
2
3
4
5
package cn.itcast.day09.demo01;
 
// 定义了员工的另一个子类:助教
public class Assistant extends Employee {
}

  

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
package cn.itcast.day09.demo01;
 
/*
在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a。
 
定义父类的格式:(一个普通的类定义)
public class 父类名称 {
    // ...
}
 
定义子类的格式:
public class 子类名称 extends 父类名称 {
    // ...
}
 */
public class Demo01Extends {
 
    public static void main(String[] args) {
        // 创建了一个子类对象
        Teacher teacher = new Teacher();
        // Teacher类当中虽然什么都没写,但是会继承来自父类的method方法。
        teacher.method();
 
        // 创建另一个子类助教的对象
        Assistant assistant = new Assistant();
        assistant.method();
    }
 
}

  

 

1.3  继承后的特点——成员变量

当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?

成员变量不重名

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

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
class Fu {
// Fu中的成员变量。    
int num = 5;    
}
class Zi extends Fu {
// Zi中的成员变量    
int num2 = 6;    
// Zi中的成员方法    
public void show() {    
// 访问父类中的num,        
System.out.println("Fu num="+num); // 继承而来,所以直接访问。        
// 访问子类中的num2        
System.out.println("Zi num2="+num2);        
}    
}
class ExtendDemo02 {
public static void main(String[] args) {    
        // 创建子类对象
Zi z = new Zi();         
       // 调用子类中的show方法  
z.show();          
}    
}
演示结果:
Fu num = 5
Zi num2 = 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
class Fu {
// Fu中的成员变量。    
int num = 5;    
}
class Zi extends Fu {
// Zi中的成员变量    
int num = 6;    
public void show() {    
// 访问父类中的num        
System.out.println("Fu num=" + num);        
// 访问子类中的num        
System.out.println("Zi num=" + num);        
}    
}
class ExtendsDemo03 {
public static void main(String[] args) {    
       // 创建子类对象  
Zi z = new Zi();         
       // 调用子类中的show方法  
z.show();         
}    
}
演示结果:
Fu num = 6
Zi num = 6

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于之前学过的 this 。

使用格式:

1
super.父类成员变量名

子类方法需要修改,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Zi extends Fu {
// Zi中的成员变量    
int num = 6;    
public void show() {    
//访问父类中的num        
System.out.println("Fu num=" super.num);        
//访问子类中的num        
System.out.println("Zi num=" this.num);        
}    
}
演示结果:
Fu num = 5
Zi num = 6

小贴士:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。

案例

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.itcast.day09.demo02;
 
public class Fu {
 
    int numFu = 10;
 
    int num = 100;
 
    public void methodFu() {
        // 使用的是本类当中的,不会向下找子类的
        System.out.println(num);
    }
 
}

 

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.itcast.day09.demo02;
 
public class Zi extends Fu {
 
    int numZi = 20;
 
    int num = 200;
 
    public void methodZi() {
        // 因为本类当中有num,所以这里用的是本类的num
        System.out.println(num);
    }
 
}

  

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 cn.itcast.day09.demo02;
 
/*
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
 
直接通过子类对象访问成员变量:
    等号左边是谁,就优先用谁,没有则向上找。
间接通过成员方法访问成员变量:
    该方法属于谁,就优先用谁,没有则向上找。
 */
public class Demo01ExtendsField {
 
    public static void main(String[] args) {
        Fu fu = new Fu(); // 创建父类对象
        System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容
        System.out.println("===========");
 
        Zi zi = new Zi();
 
        System.out.println(zi.numFu); // 10
        System.out.println(zi.numZi); // 20
        System.out.println("===========");
 
        // 等号左边是谁,就优先用谁
        System.out.println(zi.num); // 优先子类,200
//        System.out.println(zi.abc); // 到处都没有,编译报错!
        System.out.println("===========");
 
        // 这个方法是子类的,优先用子类的,没有再向上找
        zi.methodZi(); // 200
        // 这个方法是在父类当中定义的,
        zi.methodFu(); // 100
    }
 
}

  

区分子方法中重名的三种变量

1
2
3
4
5
6
7
package cn.itcast.day09.demo03;
 
public class Fu {
 
    int num = 10;
 
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.itcast.day09.demo03;
 
public class Zi extends Fu {
 
    int num = 20;
 
    public void method() {
        int num = 30;
        System.out.println(num); // 30,局部变量
        System.out.println(this.num); // 20,本类的成员变量
        System.out.println(super.num); // 10,父类的成员变量
    }
 
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.itcast.day09.demo03;
 
/*
局部变量:         直接写成员变量名
本类的成员变量:    this.成员变量名
父类的成员变量:    super.成员变量名
 */
public class Demo01ExtendsField {
 
    public static void main(String[] args) {
        Zi zi = new Zi();
 
        zi.method();
    }
 
}

  

 

1.4  继承后的特点——成员方法

当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?

成员方法不重名

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Fu{
public void show(){    
System.out.println("Fu类中的show方法执行");        
}    
}
class Zi extends Fu{
public void show2(){    
System.out.println("Zi类中的show2方法执行");        
}    
}
public  class ExtendsDemo04{
public static void main(String[] args) {    
Zi z = new Zi();        
      //子类中没有show方法,但是可以找到父类方法去执行
z.show();         
z.show2();        
}    
}

成员方法重名 ——重写(Override)

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

  • 方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Fu {
public void show() {    
System.out.println("Fu show");        
}    
}
class Zi extends Fu {
//子类重写了父类的show方法    
public void show() {    
System.out.println("Zi show");        
}    
}
public class ExtendsDemo05{
public static void main(String[] args) {    
Zi z = new Zi();        
      // 子类中有show方法,只执行重写后的show方法   
z.show();  // Zi show        
}    
}

重写的应用

子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:

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
class Phone {
public void sendMessage(){    
System.out.println("发短信");        
}    
public void call(){    
System.out.println("打电话");        
}    
public void showNum(){    
System.out.println("来电显示号码");        
}    
}
//智能手机类
class NewPhone extends Phone {
    
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能    
public void showNum(){    
//调用父类已经存在的功能使用super        
super.showNum();        
//增加自己特有显示姓名和图片功能        
System.out.println("显示来电姓名");        
System.out.println("显示头像");        
}    
}
public class ExtendsDemo06 {
public static void main(String[] args) {    
       // 创建子类对象  
       NewPhone np = new NewPhone();  
        
        // 调用父类继承而来的方法
        np.call();
      
       // 调用子类重写的方法  
       np.showNum();  
}    
}

小贴士:这里重写时,用到 super.父类成员方法,表示调用父类的成员方法。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.itcast.day09.demo04;
 
public class Fu {
 
    public void methodFu() {
        System.out.println("父类方法执行!");
    }
 
    public void method() {
        System.out.println("父类重名方法执行!");
    }
 
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.itcast.day09.demo04;
 
public class Zi extends Fu {
 
    public void methodZi() {
        System.out.println("子类方法执行!");
    }
 
    public void method() {
        System.out.println("子类重名方法执行!");
    }
 
}

  

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
package cn.itcast.day09.demo04;
 
/*
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
    创建的对象是谁,就优先用谁,如果没有则向上找。
 
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
 
重写(Override)
概念:在继承关系当中,方法的名称一样,参数列表也一样。
 
重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。
重载(Overload):方法的名称一样,参数列表【不一样】。
 
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
 */
public class Demo01ExtendsMethod {
 
    public static void main(String[] args) {
        Zi zi = new Zi();
 
        zi.methodFu();
        zi.methodZi();
 
        // 创建的是new了子类对象,所以优先用子类方法
        zi.method();
    }
 
}

  

注意事项

1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
2. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

方法覆盖重写的注意事项:

1. 必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。

2. 子类方法的返回值必须【小于等于】父类方法的返回值范围。
小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。

3. 子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。

方法覆盖重写的场景应用:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.itcast.day09.demo06;
 
// 本来的老款手机
public class Phone {
 
    public void call() {
        System.out.println("打电话");
    }
 
    public void send() {
        System.out.println("发短信");
    }
 
    public void show() {
        System.out.println("显示号码");
    }
 
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.itcast.day09.demo06;
 
// 定义一个新手机,使用老手机作为父类
public class NewPhone extends Phone {
 
    @Override
    public void show() {
        super.show(); // 把父类的show方法拿过来重复利用
        // 自己子类再来添加更多内容
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.itcast.day09.demo06;
 
public class Demo01Phone {
 
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.call();
        phone.send();
        phone.show();
        System.out.println("==========");
 
        NewPhone newPhone = new NewPhone();
        newPhone.call();
        newPhone.send();
        newPhone.show();
    }
 
}

1.5  继承后的特点——构造方法

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。

2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Fu {
  private int n;
  Fu(){
    System.out.println("Fu()");
  }
}
class Zi extends Fu {
  Zi(){
    // super(),调用父类构造方法
    super();
    System.out.println("Zi()");
  
}
public class ExtendsDemo07{
  public static void main (String args[]){
    Zi zi = new Zi();
  }
}
输出结果:
Fu()
Zi()

案例

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
package cn.itcast.day09.demo07;
 
public class Fu {
 
    public Fu() {
        System.out.println("父类无参构造");
    }
 
    public Fu(int num) {
        System.out.println("父类有参构造!");
    }
 
}
 
 
package cn.itcast.day09.demo07;
 
public class Zi extends Fu {
 
    public Zi() {
        super(); // 在调用父类无参构造方法
//        super(20); // 在调用父类重载的构造方法
        System.out.println("子类构造方法!");
    }
 
    public void method() {
//        super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
    }
 
}
 
 
package cn.itcast.day09.demo07;
 
/*
继承关系中,父子类构造方法的访问特点:
 
1. 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
2. 子类构造可以通过super关键字来调用父类重载构造。
3. super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。
总结:
子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
 */
public class Demo01Constructor {
 
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
 
}

  super的三用法

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 cn.itcast.day09.demo08;
 
public class Fu {
 
    int num = 10;
 
    public void method() {
        System.out.println("父类方法");
    }
 
}
 
 
package cn.itcast.day09.demo08;
 
/*
super关键字的用法有三种:
1. 在子类的成员方法中,访问父类的成员变量。
2. 在子类的成员方法中,访问父类的成员方法。
3. 在子类的构造方法中,访问父类的构造方法。
 */
public class Zi extends Fu {
 
    int num = 20;
 
    public Zi() {
        super();
    }
 
    public void methodZi() {
        System.out.println(super.num); // 父类中的num
    }
 
    public void method() {
        super.method(); // 访问父类中的method
        System.out.println("子类方法");
    }
 
}

  

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package cn.itcast.day09.demo09;
 
public class Fu {
 
    int num = 30;
 
}
 
 
package cn.itcast.day09.demo09;
 
/*
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
 
1. 在本类的成员方法中,访问本类的成员变量。
2. 在本类的成员方法中,访问本类的另一个成员方法。
3. 在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法当中要注意:
A. this(...)调用也必须是构造方法的第一个语句,唯一一个。
B. super和this两种构造调用,不能同时使用。
 */
public class Zi extends Fu {
 
    int num = 20;
 
    public Zi() {
//        super(); // 这一行不再赠送
        this(123); // 本类的无参构造,调用本类的有参构造
//        this(1, 2); // 错误写法!
    }
 
    public Zi(int n) {
        this(1, 2);
    }
 
    public Zi(int n, int m) {
    }
 
    public void showNum() {
        int num = 10;
        System.out.println(num); // 局部变量
        System.out.println(this.num); // 本类中的成员变量
        System.out.println(super.num); // 父类中的成员变量
    }
 
    public void methodA() {
        System.out.println("AAA");
    }
 
    public void methodB() {
        this.methodA();
        System.out.println("BBB");
    }
 
}

  

1.6 super 和this

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。理解图解如下:

 super和this的含义

  • super :代表父类的存储空间标识(可以理解为父亲的引用)。
  • this :代表当前对象的引用(谁调用就代表谁)。

super和this的用法

1. 访问成员

1
2
3
4
this.成员变量     ‐‐    本类的   
super.成员变量     ‐‐    父类的  
this.成员方法名()   ‐‐    本类的      
super.成员方法名()   ‐‐    父类的

用法演示,代码如下:

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
class Animal {
    public void eat() {
        System.out.println("animal : eat");
    }
}
class Cat extends Animal {
    public void eat() {
        System.out.println("cat : eat");
    }
    public void eatTest() {
        this.eat();   // this  调用本类的方法
        super.eat();  // super 调用父类的方法
    }
}
public class ExtendsDemo08 {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        Cat c = new Cat();
        c.eatTest();
    }
}
输出结果为:
animal : eat
cat : eat
animal : eat

2. 访问构造方法

1
2
this(...)     ‐‐    本类的构造方法   
super(...)    ‐‐    父类的构造方法

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

1.7  继承的特点

1. Java只支持单继承,不支持多继承。

1
2
3
//一个类只能有一个父类,不可以有多个父类。
class extends A{}  //ok    
class extends A,B... //error

2. Java支持多层继承(继承体系)。

1
2
3
class A{}
class extends A{}
class extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。

3. 子类和父类是一种相对的概念。

 

 

第二章 抽象类

2.1  概述

 

 

 

 

 

由来

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。

定义

  • 抽象方法 : 没有方法体的方法。
  • 抽象类 :包含抽象方法的类。

2.2 abstract 使用格式

抽象方法

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:

1
修饰符 abstract 返回值类型 方法名 (参数列表);

代码举例:

1
public abstract void run();

抽象类

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

1
2
3
abstract class 类名字 {
  
}

代码举例:

1
2
3
public abstract class Animal {
    public abstract void run();
}

抽象的使用

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父
类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

代码举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Cat extends Animal {
    public void run (){
       System.out.println("小猫在墙头走~~~");         
    }
}
public class CatTest {
   public static void main(String[] args) {   
        // 创建子类对象
        Cat c = new Cat();
       
        // 调用run方法
        c.run();
   }  
}
输出结果:
小猫在墙头走~~~

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。

示例:

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
package cn.itcast.day09.demo11;
 
/*
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
 
如何使用抽象类和抽象方法:
1. 不能直接创建new抽象类对象。
2. 必须用一个子类来继承抽象父类。
3. 子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
4. 创建子类对象进行使用。
 */
public abstract class Animal {
 
    // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
    public abstract void eat();
 
    // 这是普通的成员方法
//    public void normalMethod() {
//
//    }
 
}
 
 
package cn.itcast.day09.demo11;
 
public class Cat extends Animal {
 
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
 
}
 
 
package cn.itcast.day09.demo11;
 
public class DemoMain {
 
    public static void main(String[] args) {
//        Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
 
        Cat cat = new Cat();
        cat.eat();
    }
 
}

  

2.3  注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

1 . 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

  • 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

2 . 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

  • 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

  • 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

4 . 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

  • 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有

意义。

案例一:

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 cn.itcast.day09.demo12;
public abstract class Fu {
 
    public Fu() {
        System.out.println("抽象父类构造方法执行!");
    }
 
    public abstract void eat();
 
}
 
 
package cn.itcast.day09.demo12;
/*
一个抽象类不一定含有抽象方法,
只要保证抽象方法所在的类是抽象类,即可。
 
这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途。
 */
public abstract class MyAbstract {
}
 
 
package cn.itcast.day09.demo12;
public class Zi extends Fu {
 
    public Zi() {
        // super();
        System.out.println("子类构造方法执行");
    }
 
    @Override
    public void eat() {
        System.out.println("吃饭饭");
    }
}
 
 
 
package cn.itcast.day09.demo12;
public class DemoMain {
 
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.eat();
    }
 
}

  

案例二:

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
package cn.itcast.day09.demo13;
// 最高的抽象父类
public abstract class Animal {
 
    public abstract void eat();
 
    public abstract void sleep();
 
}
 
 
package cn.itcast.day09.demo13;
// 子类也是一个抽象类
public abstract class Dog extends Animal {
 
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
 
    // public abstract void sleep();
}
 
 
package cn.itcast.day09.demo13;
public class DogGolden extends Dog {
    @Override
    public void sleep() {
        System.out.println("呼呼呼……");
    }
}
 
 
package cn.itcast.day09.demo13;
public class Dog2Ha extends Dog {
    @Override
    public void sleep() {
        System.out.println("嘿嘿嘿……");
    }
}
 
 
package cn.itcast.day09.demo13;
public class DemoMain {
 
    public static void main(String[] args) {
//        Animal animal = new Animal(); // 错误!
 
//        Dog dog = new Dog(); // 错误,这也是抽象类
 
        Dog2Ha ha = new Dog2Ha(); // 这是普通类,可以直接new对象。
        ha.eat();
        ha.sleep();
        System.out.println("==========");
 
        DogGolden golden = new DogGolden();
        golden.eat();
        golden.sleep();
    }
 
 
}

  

第三章 继承的综合案例

3.1  综合案例:群主发普通红包

群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:

1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
2. 成员领取红包后,保存到成员余额中。

请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

3.2  案例分析

根据描述分析,得出如下继承体系:

 

 3.3  案例实现

定义用户类:

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 User {
  
   // 成员变量  
    private String username; // 用户名    
    private double leftMoney; // 余额   
   // 构造方法
public User() { }
    public User(String username, double leftMoney) {
        this.username = username;
        this.leftMoney = leftMoney;
    }
// get/set方法    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public double getLeftMoney() {
        return leftMoney;
    }
    public void setLeftMoney(double leftMoney) {
        this.leftMoney = leftMoney;
    }
// 展示信息的方法    
    public void show() {
        System.out.println("用户名:"+ username +" , 余额为:" + leftMoney + "元");
    }
}

定义群主类:

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
public class QunZhu extends User {
// 添加构造方法    
    public QunZhu() {
    }
    public QunZhu(String username, double leftMoney) {
       // 通过super 调用父类构造方法  
        super(username, leftMoney);
    }
/*    
群主发红包,就是把一个整数的金额,分层若干等份。        
1.获取群主余额,是否够发红包.        
不能则返回null,并提示.            
能则继续.            
2.修改群主余额.        
3.拆分红包.        
3.1.如果能整除,那么就平均分。            
3.2.如果不能整除,那么就把余数分给最后一份。            
*/    
    public ArrayList<Double> send(int money, int count) {
       // 获取群主余额  
       double leftMoney = getLeftMoney();  
        if(money > leftMoney) {
           return null;
        }
       // 修改群主余额的  
        setLeftMoney(leftMoney ‐ money);
      
       // 创建一个集合,保存等份金额  
        ArrayList<Double> list = new ArrayList<>();
      
       // 扩大100倍,相当于折算成'分'为单位,避免小数运算损失精度的问题  
        money = money * 100;
      
        // 每份的金额
        int m = money / count;
        // 不能整除的余数
        int l = money % count;
      
       // 无论是否整除,n‐1份,都是每份的等额金额  
        for (int i = 0; i < count ‐ 1; i++) {
           // 缩小100倍,折算成 '元'  
            list.add(m / 100.0);
        }
      
       // 判断是否整除  
        if (l == 0) {
           // 能整除, 最后一份金额,与之前每份金额一致  
            list.add(m / 100.0);
        else {
           // 不能整除, 最后一份的金额,是之前每份金额+余数金额  
            list.add((m + l) / 100.00);
        }
      
       // 返回集合  
        return list;
    }
}

定义成员类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Member extends  User {
    public Member() {
    }
    public Member(String username, double leftMoney) {
        super(username, leftMoney);
    }
    // 打开红包,就是从集合中,随机取出一份,保存到自己的余额中
    public void openHongbao(ArrayList<Double> list) {
       // 创建Random对象  
       Random r = new Random();  
       // 随机生成一个角标  
       int index = r.nextInt(list.size());  
       // 移除一个金额
        Double money = list.remove(index);
       // 直接调用父类方法,设置到余额  
        setLeftMoney( money );
    }
}   

定义测试类:

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
public class Test {
    public static void main(String[] args) {
        // 创建一个群主对象
        QunZhu qz = new QunZhu("群主" 200);
        
       // 创建一个键盘录入  
       Scanner sc = new Scanner();  
        System.out.println("请输入金额:");
        int money = sc.nextInt();
        System.out.println("请输入个数:");
        int count = sc.nextInt();
        
      // 发送红包   
        ArrayList<Double> sendList = s.send(money,count);
        
        // 判断,如果余额不足
        if(sendList == null){
            System.out.println(" 余额不足...");
            return;
        }
        // 创建三个成员
        Member m = new Member();
        Member m2 = new Member();
        Member m3 = new Member();
        
       // 打开红包  
        m.openHongbao(sendList);
        m2.openHongbao(sendList);
        m3.openHongbao(sendList);
// 展示信息        
        qz.show();
        m.show();
        m2.show();
        m3.show();
    }
}

 

案例:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package cn.itcast.day09.demo14;
public class User {
 
    private String name; // 姓名
    private int money; // 余额,也就是当前用户拥有的钱数
 
    public User() {
    }
 
    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
 
    // 展示一下当前用户有多少钱
    public void show() {
        System.out.println("我叫:" + name + ",我有多少钱:" + money);
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getMoney() {
        return money;
    }
 
    public void setMoney(int money) {
        this.money = money;
    }
}
 
 
package cn.itcast.day09.demo14;
 
import java.util.ArrayList;
 
// 群主的类
public class Manager extends User {
    public Manager() {
    }
 
    public Manager(String name, int money) {
        super(name, money);
    }
 
    public ArrayList<Integer> send(int totalMoney, int count) {
        // 首先需要一个集合,用来存储若干个红包的金额
        ArrayList<Integer> redList = new ArrayList<>();
 
        // 首先看一下群主自己有多少钱
        int leftMoney = super.getMoney(); // 群主当前余额
        if (totalMoney > leftMoney) {
            System.out.println("余额不足");
            return redList; // 返回空集合
        }
 
        // 扣钱,其实就是重新设置余额
        super.setMoney(leftMoney - totalMoney);
 
        // 发红包需要平均拆分成为count份
        int avg = totalMoney / count;
        int mod = totalMoney % count; // 余数,也就是甩下的零头
 
        // 除不开的零头,包在最后一个红包当中
        // 下面把红包一个一个放到集合当中
        for (int i = 0; i < count - 1; i++) {
            redList.add(avg);
        }
 
        // 最后一个红包
        int last = avg + mod;
        redList.add(last);
 
        return redList;
    }
}
 
 
package cn.itcast.day09.demo14;
import java.util.ArrayList;
import java.util.Random;
 
// 普通成员
public class Member extends User {
 
    public Member() {
    }
 
    public Member(String name, int money) {
        super(name, money);
    }
 
    public void receive(ArrayList<Integer> list) {
        // 从多个红包当中随便抽取一个,给我自己。
        // 随机获取一个集合当中的索引编号
        int index = new Random().nextInt(list.size());
        // 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
        int delta = list.remove(index);
        // 当前成员自己本来有多少钱:
        int money = super.getMoney();
        // 加法,并且重新设置回去
        super.setMoney(money + delta);
    }
}
 
 
package cn.itcast.day09.demo14;
 
import java.util.ArrayList;
 
public class MainRedPacket {
 
    public static void main(String[] args) {
        Manager manager = new Manager("群主", 100);
 
        Member one = new Member("成员A", 0);
        Member two = new Member("成员B", 0);
        Member three = new Member("成员C", 0);
 
        manager.show(); // 100
        one.show(); // 0
        two.show(); // 0
        three.show(); // 0
        System.out.println("===============");
 
        // 群主总共发20块钱,分成3个红包
        ArrayList<Integer> redList = manager.send(20, 3);
        // 三个普通成员收红包
        one.receive(redList);
        two.receive(redList);
        three.receive(redList);
 
        manager.show(); // 100-20=80
        // 6、6、8,随机分给三个人
        one.show();
        two.show();
        three.show();
    }
 
}

  

自己思考并完成扩展需求。

案例扩展:

1. 如果成员的余额不为0呢,将如何处理?

2 . 如果群主想输入带小数的金额呢,将如何处理?

posted @   极地阳光-ing  阅读(148)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示