美文网首页Java-Python-Django社区
【JavaSE(四)】Java面向对象(中)

【JavaSE(四)】Java面向对象(中)

作者: 苍云横渡 | 来源:发表于2018-05-11 16:09 被阅读110次

原文地址:https://www.cloudcrossing.xyz/post/36/

1.继承

1.1 继承概述

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

通过extends关键字可以实现类与类的继承,格式:class 子类名 extends 父类名 {}.

单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。

有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。继承中类之间提现的是一种"is a"的关系

//使用继承前
/*
class Student {
    public void eat() {
        System.out.println("吃饭");
    }
    
    public void sleep() {
        System.out.println("睡觉");
    }
}

class Teacher {
    public void eat() {
        System.out.println("吃饭");
    }
    
    public void sleep() {
        System.out.println("睡觉");
    }
}
*/

//使用继承后
class Person {
    public void eat() {
        System.out.println("吃饭");
    }
    
    public void sleep() {
        System.out.println("睡觉");
    }
}

class Student extends Person {}

class Teacher extends Person {}

class ExtendsDemo {
    public static void main(String[] args) {
        Student s = new Student();
        s.eat();
        s.sleep();
        System.out.println("-------------");
        
        Teacher t = new Teacher();
        t.eat();
        t.sleep();
    }
}

继承的好处:

  • A:提高了代码的复用性,多个类相同的成员可以放到同一个类中
  • B:提高了代码的维护性,如果功能的代码需要修改,修改一处即可
  • C:让类与类产生了一个关系,是多态的前提

继承的弊端:

  • A:让类的耦合性增强。这样某个类的改变,就会影响其他和该类相关的类。
    • 原则:低耦合,高内聚。
    • 耦合:类与类的关系
    • 内聚:自己完成某件事情的能力
  • B:打破了封装性

继承的特点:

  • A:Java中类只支持单继承,即一个类只有一个父类
  • B:Java中可以多层继承(继承体系)
    • class A {}
    • class B extends A {}
    • class C extends B {}

继承的注意事项:

  • A:子类不能继承父类的私有成员
  • B:子类不能继承父类的构造方法,但可以通过super()去访问

1.2 继承中的成员关系

(1)成员变量:

  • A:子类的成员变量名称和父类中的成员变量名称不一样,直接访问
  • B:子类的成员变量名称和父类中的成员变量名称一样,
    • 子类的方法访问变量的查找顺序
      • 在子类的方法中的局部范围找,有就访问
      • 在子类的成员范围中找,有就访问
      • 在父类的成员范围中找,有就访问
      • 找不到就报错
class Father {
    public int num3 = 10;
    
    public void method() {
        int num = 50;
    }
}

class Son extends Father {
    public int num2 = 20;
    public int num = 30;
    
    public void show() {
        int num = 40;
        System.out.println(num);  //输出40
        System.out.println(num2);  //输出20
        System.out.println(num3);  //输出10
        System.out.println(this.num);  //输出30
    }
}

class ExtendsDemo4 {
    public static void main(String[] args) {
        //创建对象
        Son s = new Son();
        s.show();
    }
}

(2)构造方法

  • A:子类的构造方法默认会去访问父类的无参构造方法(隐式语句)
    • 是为了子类访问父类数据的初始化
    • 子类每一个构造方法的第一条语句默认都是:super();
  • B:父类中如果没有无参构造方法,则
    • 子类用super去明确调用父类的带参构造
    • 子类通过this调用本身的其他构造,但是一定会有一个去访问父类的构造,否则父类数据就没有初始化
    • 让父类提供构造方法

如果父类没有无参构造方法,那么子类的构造方法会出现什么现象呢?答案是报错。

class Father {
    /*
    public Father() {
        System.out.println("Father的无参构造方法");
    }
    */
    
    public Father(String name) {
        System.out.println("Father的带参构造方法");
    }
}

class Son extends Father {
    public Son() {
        //super();隐式语句  
        super("随便给");
        System.out.println("Son的无参构造方法");
        //super("随便给");
    }
    
    public Son(String name) {
        //super();隐式语句  
        //super("随便给");
        this();
        System.out.println("Son的带参构造方法");
    }
}

class ExtendsDemo7 {
    public static void main(String[] args) {
        Son s = new Son();
        System.out.println("----------------");
        Son ss = new Son("林青霞");
    }
}
//运行结果
Father的带参构造方法
Son的无参构造方法
----------------
Father的带参构造方法
Son的无参构造方法
Son的带参构造方法

注意:this(...)或者super(...)必须出现在第一条语句上。如果不是放在第一条语句上,就可能对父类的数据进行了多次初始化(因为子类的构造方法默认会去访问父类的无参构造方法),所以必须放在第一条语句上。并且super(...)和this(..)不能同时存在构造函数第一行。

(3)成员方法

  • A:子类的成员方法和父类中的成员方法名称不一样,直接调用
  • B:子类的成员方法和父类中的成员方法名称一样,则
    • 通过通过子类对象访问一个方法的查找顺序:
      • 在子类中找,有就调用
      • 在父类中找,有就调用
      • 找不到,就报错
class Father {
    public void show() {
        System.out.println("show Father");
    }
}

class Son extends Father {
    public void method() {
        System.out.println("method Son");
    }
    
    public void show() {
        System.out.println("show Son");
    }
}

class ExtendsDemo8 {
    public static void main(String[] args) {
        //创建对象
        Son s = new Son();
        s.show();  //输出:show Son
        s.method();  //输出:method Son
        //s.fucntion(); //找不到符号
    }
}

1.3 super关键字

(1)super关键字作用

  • A:主要存在于子类方法中,用于指向子类对象中父类对象。
  • B:访问父类的属性
  • C:访问父类的函数
  • D:访问父类的构造函数

(2)this和super的区别

  • A:this代表本类对应的引用
  • B:super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)

注意:this,super只能在有对象的前提下使用,不能在静态上下文使用。

(3) this和super的使用

  • A:调用成员变量
    • this.成员变量 调用本类的成员变量
    • super.成员变量 调用父类的成员变量
  • B:调用构造方法
    • this(...) 调用本类的构造方法
    • super(...) 调用父类的构造方法
  • C:调用成员方法
    • this.成员方法 调用本类的成员方法
    • super.成员方法 调用父类的成员方法
class Father {
    public int num = 10;
}

class Son extends Father {
    public int num = 20;
    
    public void show() {
        int num = 30;
        System.out.println(num); //输出30
        System.out.println(this.num); //输出20
        System.out.println(super.num); //输出10
    }
}

class ExtendsDemo5 {
    public static void main(String[] args) {
        Son s = new Son();
        s.show();
    }
}

思考:如果自定义了一个类,没有显示的进行类的继承,那么该类中成员函数是否可以使用super关键字?可以使用,这个类继承了Object类,Object类是所有类的父类

1.4 方法重写

子类中出现了和父类中方法声明一模一样的方法,叫做方法重写(Override)

方法重载(Overload)本类中出现的方法名一样,参数列表不同的方法(与返回值无关)。

子类对象调用方法的时候,先找子类本身,再找父类。

方法重写的应用:

  • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。这样,即沿袭了父类的功能,又定义了子类特有的内容。
class Phone {
    public void call(String name) {
        System.out.println("给"+name+"打电话");
    }
}

class NewPhone extends Phone {
    public void call(String name) {
        //System.out.println("给"+name+"打电话");
        super.call(name);
        System.out.println("可以听天气预报了");
    }
}

class ExtendsDemo9 {
    public static void main(String[] args) {
        NewPhone np = new NewPhone();
        np.call("林青霞");
    }
}
//运行结果:
给林青霞打电话
可以听天气预报了

方法重写的注意事项:

  • A:父类中私有方法不能被重写
    • 因为父类的私有方法子类根本无法继承
  • B:子类重写父类方法时,访问权限不能更低,最好一致
  • C:父类静态方法,子类也必须是通过静态方法进行重写
  • D:子类重写父类方法的时候,最好声明一模一样

1.5 继承相关的几个练习题

(1)看程序写结果

class Fu {
    static {
        System.out.println("静态代码块Fu");
    }

    {
        System.out.println("构造代码块Fu");
    }

    public Fu() {
        System.out.println("构造方法Fu");
    }
}

class Zi extends Fu {
    static {
        System.out.println("静态代码块Zi");
    }

    {
        System.out.println("构造代码块Zi");
    }

    public Zi() {
        System.out.println("构造方法Zi");
    }
}

class ExtendsTest2 {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}
//运行结果:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
  • A:一个类的静态代码块,构造代码块,构造方法的执行流程
    • 静态代码块 > 构造代码块 > 构造方法
  • B:静态的内容是随着类的加载而加载
    • 静态代码块的内容会优先执行
  • C:子类初始化之前先会进行父类的初始化

(2)看程序写结果

class X {
    Y b = new Y();  //1
    X() {
        System.out.print("X");  //2
    }
}

class Y {
    Y() {
        System.out.print("Y");
    }
}

public class Z extends X {
    Y y = new Y();  //3
    Z() {
        //super
        System.out.print("Z");  //4
    }
    public static void main(String[] args) {
        new Z(); 
    }
}
//运行结果:
YXYZ

一个类的初始化过程:

  • 成员变量的初始化
    • 默认初始化
    • 显示初始化
    • 构造方法初始化

子父类的初始化(分层初始化):先进行父类初始化,然后进行子类初始化。

虽然子类中构造方法默认有一个super(),但是初始化的时候,不是按照那个顺序进行的。而是按照分层初始化进行的。它仅仅表示要先初始化父类数据,再初始化子类数据。(不是YYXZ)

(3)方法重写和方法重载的区别?方法重载能改变返回值类型吗?

  • 方法重写:在子类中,出现和父类中一模一样的方法声明的现象
  • 方法重载:同一个类中,出现的方法名相同,参数列表不同的现象
  • 方法重载能改变返回值类型,因为它和返回值类型无关。

2 final关键字

2.1 final关键字概述

final关键字是最终的意思,可以修饰类、方法、变量。

特点:

  • A:final关键字修饰的类不能被继承
  • B:final关键字修饰的方法不能被重写
  • C:final关键字修饰的变量是一个常量(即自定义常量),只能被赋值一次
//final class Fu //无法从最终Fu进行继承

class Fu {
    public int num = 10;
    public final int num2 = 20;

    /*
    public final void show() {
    
    }
    */
}

class Zi extends Fu {
    // Zi中的show()无法覆盖Fu中的show()
    public void show() {
        num = 100;
        System.out.println(num);
        
        //无法为最终变量num2分配值
        //num2 = 200;
        System.out.println(num2);
    }
}

class FinalDemo {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

2.1 final修饰局部变量的问题

  • 基本类型:基本类型的值不能改变
  • 引用类型:引用类型的地址值不能发生改变,该对象的堆内存的值可以发生改变
class Student {
    int age = 10;
}

class FinalTest {
    public static void main(String[] args) {
        //局部变量是基本数据类型
        int x = 10;
        x = 100;
        System.out.println(x);
        final int y = 10;
        //无法为最终变量y分配值
        //y = 100;
        System.out.println(y);
        System.out.println("--------------");
        
        //局部变量是引用数据类型
        Student s = new Student();
        System.out.println(s.age);
        s.age = 100;
        System.out.println(s.age);
        System.out.println("--------------");
        
        final Student ss = new Student();
        System.out.println(ss.age);
        ss.age = 100;
        System.out.println(ss.age);
        
        //重新分配内存空间
        //无法为最终变量ss分配值
        ss = new Student();
    }
}

2.1 final修饰变量的初始化时机

  • 被final修饰的变量只能被赋值一次
  • 在构造方法完毕前(针对非静态常量)即可
    • 定义成员变量的时候赋值(推荐)
    • 构造代码块内赋值
    • 构造方法内赋值
class Demo {
    //int num = 10;
    //final int num2 = 20;
    
    int num;
    final int num2;
    
    {
        //num2 = 10;
    }
    
    public Demo() {
        num = 100;
        //无法为最终变量num2分配值
        num2 = 200;
    }
}

class FinalTest2 {
    public static void main(String[] args) {
        Demo d = new Demo();
        System.out.println(d.num);
        System.out.println(d.num2);
    }
}

3 多态

3.1 多态概述

多态指的是某一个事物在不同时刻表现出来的不同状态。

举个例子:

  • 猫可以使猫的类型。Cat m = new Cat();
  • 同时猫也是动物的一种,也可以把猫称为动物,Animal m = new Cat();
  • 或者水的三种状态,液体、气体、固体

多态的前提:

  • A:有继承或者实现关系
  • B:有方法重写
    • 动物 d = new 猫(); d.show(); 动物 d = new 狗(); d.show();
  • C:有父类或者父接口引用指向子类对象
    • 父 f = new 子();

多态的分类:

  • A:具体类多态
    • class Fu {}
    • class Zi extends Fu {}
    • Fu f = new Zi();
  • B:抽象类多态
    • abstract class Fu {}
    • class Zi extends Fu {}
    • Fu f = new Zi();
  • C:接口多态
    • interface Fu {}
    • class Zi implements Fu {}
    • Fu f = new Zi();

3.2 多态中的成员访问特点

  • A:成员变量:编译看左边,运行看左边
  • B:构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
  • C:成员方法:编译看左边,运行看右边(因为成员方法存在方法重写,所以它运行看右边
  • D:静态方法:编译看左边,运行看左边(静态和类相关,算不上重写,所以,访问还是左边的)
class Fu {
    public int num = 100;

    public void show() {
        System.out.println("show Fu");
    }
    
    public static void function() {
        System.out.println("function Fu");
    }
}

class Zi extends Fu {
    public int num = 1000;
    public int num2 = 200;

    public void show() {
        System.out.println("show Zi");
    }
    
    public void method() {
        System.out.println("method zi");
    }
    
    public static void function() {
        System.out.println("function Zi");
    }
}

class DuoTaiDemo {
    public static void main(String[] args) {
        //要有父类引用指向子类对象。
        Fu f = new Zi();
        System.out.println(f.num);  //输出100
        //找不到符号
        //System.out.println(f.num2);
        
        f.show();  //输出show Zi
        //找不到符号
        //f.method();
        f.function();  //输出function Fu
    }
}

3.3 多态的优缺点

多态的好处:

  • 提高代码的维护性(继承体现)
  • 提高代码的扩展性(多态体现)
class Animal {
    public void eat(){
        System.out.println("eat");
    }
    public void sleep(){
        System.out.println("sleep");
    }
}

class Dog extends Animal {
    public void eat(){
        System.out.println("狗吃肉");
    }
    public void sleep(){
        System.out.println("狗站着睡觉");
    }
}
class Cat extends Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void sleep() {
        System.out.println("猫趴着睡觉");
    }
}
class Pig extends Animal {
    public void eat() {
        System.out.println("猪吃白菜");
    }
    public void sleep() {
        System.out.println("猪侧着睡");
    }
}

//针对动物操作的工具类
class AnimalTool {
    private AnimalTool(){}

    /*
    //调用猫的功能
    public static void useCat(Cat c) {
        c.eat();
        c.sleep();
    }
    
    //调用狗的功能
    public static void useDog(Dog d) {
        d.eat();
        d.sleep();
    }
    
    //调用猪的功能
    public static void usePig(Pig p) {
        p.eat();
        p.sleep();
    }
    */

    public static void useAnimal(Animal a) {
        a.eat();
        a.sleep();
    }
    
}

class DuoTaiDemo2 {
    public static void main(String[] args) {
        //我喜欢猫,就养了一只
        Cat c = new Cat();
        c.eat();
        c.sleep();
        
        //我很喜欢猫,所以,又养了一只
        Cat c2 = new Cat();
        c2.eat();
        c2.sleep();
        
        //我特别喜欢猫,又养了一只
        Cat c3 = new Cat();
        c3.eat();
        c3.sleep();
        //...
        System.out.println("--------------");
        //问题来了,我养了很多只猫,每次创建对象是可以接受的
        //但是呢?调用方法,你不觉得很相似吗?仅仅是对象名不一样。
        //我们准备用方法改进
        //调用方式改进版本1
        //useCat(c);
        //useCat(c2);
        //useCat(c3);
        
        //AnimalTool.useCat(c);
        //AnimalTool.useCat(c2);
        //AnimalTool.useCat(c3);

        //上面的调用还是太反锁了,每次都要调用相应动物的工具类方法
        //我们可以利用多态的特性改进
        //调用方式改进版本2     
        AnimalTool.useAnimal(c);
        AnimalTool.useAnimal(c2);
        AnimalTool.useAnimal(c3);
        System.out.println("--------------");
    }

    /*
    //调用猫的功能
    public static void useCat(Cat c) {
        c.eat();
        c.sleep();
    }
    
    //调用狗的功能
    public static void useDog(Dog d) {
        d.eat();
        d.sleep();
    }
    */
}

首先定义对应的类(猫,狗,猪),继承自动物,提供对应的方法重写,并在工具类添加方法调用。但是随着不同动物的增加(比如蛇,鸟等),工具类的方法是否需要每次修改添加呢?

不用这么麻烦!我们可以利用多态的特性。观察以上代码

多态的弊端:父不能使用子的特有功能。现象:子可以当作父使用,父不能当作子使用。

class Fu {
    public void show() {
        System.out.println("show fu");
    }
}

class Zi extends Fu {
    public void show() {
        System.out.println("show zi");
    }
    
    public void method() {
        System.out.println("method zi");
    }
}

class DuoTaiDemo3 {
    public static void main(String[] args) {
        //测试
        Fu f = new Zi();
        f.show();  //输出:show zi
        //f.method();  //报错
    }
}

3.4 多态的转型以及理解

由于多态不能使用子类的特有功能。那么我们如何使用子类的特有功能呢?

  • 创建子类对象调用方法即可(可以,但是很多时候不合理。而且,太占内存了)
  • 把父类的引用强制转换为子类的引用(向下转型)

对象间的转型问题:

  • 向上转型:从子到父,Fu f = new Zi();
  • 向下转型:从父到子,Zi z = (Zi)f;,要求该f必须是能够转换为Zi的
class Fu {
    public void show() {
        System.out.println("show fu");
    }
}

class Zi extends Fu {
    public void show() {
        System.out.println("show zi");
    }
    
    public void method() {
        System.out.println("method zi");
    }

}

class DuoTaiDemo4 {
    public static void main(String[] args) {
        //测试
        Fu f = new Zi();
        f.show();
        //f.method();
        
        //创建子类对象调用方法
        //Zi z = new Zi();
        //z.show();
        //z.method();
        
        //把父类的引用强制转换为子类的引用(向下转型)
        Zi z = (Zi)f;
        z.show();
        z.method();
    }
}

再举一个例子来理解多态转型的现象。

    class 孔子爹 {
        public int age = 40;
        
        public void teach() {
            System.out.println("讲解JavaSE");
        }
    }
    
    class 孔子 extends 孔子爹 {
        public int age = 20;
        
        public void teach() {
            System.out.println("讲解论语");
        }
        
        public void playGame() {
            System.out.println("英雄联盟");
        }
    }
    
    //Java培训特别火,很多人来请孔子爹去讲课,这一天孔子爹被请走了
    //但是还有人来请,就剩孔子在家,价格还挺高。孔子一想,我是不是可以考虑去呢?
    //然后就穿上爹的衣服,带上爹的眼睛,粘上爹的胡子。就开始装爹
    //向上转型
    孔子爹 k爹 = new 孔子();
    //到人家那里去了
    System.out.println(k爹.age); //40
    k爹.teach(); //讲解论语
    //k爹.playGame(); //这是儿子才能做的
    
    
    //讲完了,下班回家了
    //脱下爹的装备,换上自己的装备
    //向下转型
    孔子 k = (孔子) k爹; 
    System.out.println(k.age); //20
    k.teach(); //讲解论语
    k.playGame(); //英雄联盟

4 抽象类

4.1 抽象类的概述

java把多个共性的东西提取到一个类中,叫做继承。但是在有些时候,方法声明一样,每个具体的对象在具体实现的时候内容不一样。所以,我们在定义这些共性的方法的时候,就不能给出具体的方法体。

而一个没有具体的方法体的方法是抽象的方法。在一个类中如果有抽象方法,该类必须定义为抽象类

4.2 抽象类的特点

  • A:抽象类和抽象方法必须用关键字abstract修饰,格式:abstract class 类名 {};public abstract void eat();
  • B:抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类
  • C:抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。且抽象方法只包含一个方法名,没有方法体
  • D:抽象类不能实例化,因为它不是具体的。如果需要实例化,则由具体的子类实例化,即抽象类多态。格式:Animal a = new Cat();
  • E:抽象类的子类
    • a:是一个抽象类
    • b:是一个具体类。这个类必须重写抽象类中的所有抽象方法
//父类是抽象类
abstract class Animal {
    //抽象方法
    //public abstract void eat(){} //存在空方法体,会报错。因为抽象方法不能有主体
    public abstract void eat();
    
    public Animal(){}
}

//子类是抽象类
abstract class Dog extends Animal {}

//子类是具体类,重写抽象方法
class Cat extends Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

class AbstractDemo {
    public static void main(String[] args) {
        //创建对象
        //Animal是抽象的; 无法实例化
        //Animal a = new Animal();

        //通过多态的方式
        Animal a = new Cat();
        a.eat();
    }
}

4.3 抽象类的成员特点

  • 成员变量:有变量,有常量
  • 构造方法:有构造方法,但是不能实例化(具体类的构造方法不能用abstract修饰,否则无法实例化)
    • 抽象类的构造方法的作用:用于子类访问父类数据的初始化
  • 成员方法:
    • 有抽象方法:限定子类必须完成某些动作
    • 有非抽象方法:子类继承的事情,提高代码服用性
abstract class Animal {
    public int num = 10;//变量
    public final int num2 = 20;//常量

    public Animal() {}//无参构造方法
    
    public Animal(String name,int age){}//带参构造方法
    
    public abstract void show();//不能有主体的抽象方法
    
    public void method() {//非抽象方法
        System.out.println("method");
    }
}

class Dog extends Animal {
    public void show() {
        System.out.println("show Dog");
    }
}

class AbstractDemo2 {
    public static void main(String[] args) {
        //创建对象
        Animal a = new Dog();
        a.num = 100;
        System.out.println(a.num);
        //a.num2 = 200;
        System.out.println(a.num2);
        System.out.println("--------------");
        a.show();
        a.method();
    }
}

4.4 抽象类的几个小问题

(1)一个类如果没有抽象方法,却定义为了抽象类,有什么用?

  • 答案是为了不让创建对象

(2)abstract不能和哪些关键字共存?

  • A:final-->冲突,因为抽象类必须有子类,而final定义的类不能有子类
  • B:private-->冲突
  • C:static-->无意义
abstract class Fu {
    public abstract void show();

    //非法的修饰符组合: abstract和private
    //private abstract void show();
    
    //非法的修饰符组合:非法的修饰符组合: abstract和final
    //Zi中的show()无法覆盖Fu中的show()
    //final abstract void show();   
    
    //非法的修饰符组合:abstract和static
    //static abstract void show();
    
    public static void method() {
        System.out.println("method");
    }
}

class Zi extends Fu {
    public void show() {}
}

class AbstractDemo3 {
    public static void main(String[] args) {
        Fu.method();
    }
}

4.5 抽象类的猫狗案例

  • 具体事物:猫,狗
  • 共性:姓名,年龄,吃饭

分析:从具体到抽象

  • 猫:
    • 成员变量:姓名,年龄
    • 构造方法:无参,带参
    • 成员方法:吃饭(猫吃鱼)
  • 狗:
    • 成员变量:姓名,年龄
    • 构造方法:无参,带参
    • 成员方法:吃饭(狗吃肉)

因为有共性的内容,所以就提取了一个父类:动物。但是又由于吃饭的内容不一样,所以吃饭的方法是抽象的,而方法是抽象的类,类就必须定义为抽象类。

抽象动物类:

  • 成员变量:姓名,年龄
  • 构造方法:无参,带参
  • 成员方法:吃饭();

实现:从抽象到具体

  • 动物类:
    • 成员变量:姓名,年龄
    • 构造方法:无参,带参
    • 成员方法:吃饭();
  • 狗类:
    • 继承自动物类
    • 重写吃饭();
  • 猫类:
    • 继承自动物类
    • 重写吃饭();
//定义抽象的动物类
abstract class Animal {
    //姓名
    private String name;
    //年龄
    private int age;
    
    public Animal() {}
    
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
    
    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 abstract void eat();
}

//定义具体的狗类
class Dog extends Animal {
    public Dog() {}
    
    public Dog(String name,int age) {
        super(name,age);
    }
    
    public void eat() {
        System.out.println("狗吃肉");
    }
}

//定义具体的猫类
class Cat extends Animal {
    public Cat() {}
    
    public Cat(String name,int age) {
        super(name,age);
    }
    
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

//测试类
class AbstractTest {
    public static void main(String[] args) {
        //测试狗类
        //具体类用法
        //方式1:
        Dog d = new Dog();
        d.setName("旺财");
        d.setAge(3);
        System.out.println(d.getName()+"---"+d.getAge());
        d.eat();
        //方式2:
        Dog d2 = new Dog("旺财",3);
        System.out.println(d2.getName()+"---"+d2.getAge());
        d2.eat();
        System.out.println("---------------------------");

        //抽象多态类用法
        //方式1:
        Animal a = new Dog();
        a.setName("旺财");
        a.setAge(3);
        System.out.println(a.getName()+"---"+a.getAge());
        a.eat();
        //方式2:
        Animal a2 = new Dog("旺财",3);
        System.out.println(a2.getName()+"---"+a2.getAge());
        a2.eat();
    }
}

5 接口

5.1 接口概述

回顾猫狗案例,它们仅仅提供一些基本功能。如果我们想要训练他们获取新技能,比如:猫钻火圈,狗跳高等,不是动物本身就具备的技能,这应该属于经过特殊的培训训练出来的。

所以,这些额外的动作定义到动物类中就不合适,也不适合直接定义到猫或者狗中,因为只有部分猫狗具备这些技能

为了体现事物功能的扩展性,Java中就提供了接口来定义这些额外功能,并不给出具体实现,将来哪些猫狗需要被培训,只需要这部分猫狗把这些额外功能实现即可。

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

5.2 接口的特点

  • A:接口用关键字interface修饰,格式:interface 接口名 {}
  • B:类实现接口用implements修饰,格式:class 类名 implements 接口名 {}
  • C:接口不能实例化(如果需要实例化,则由具体的子类实例化,即接口多态
  • D:接口的实现类(子类)
    • a:是一个抽象类
    • b:是一个具体类,这个类必须重写接口中的所有抽象方法

PS:由此可见具体类多态几乎没有,抽象类多态常用,接口多态最常用。

//定义动物培训接口
interface AnimalTrain {
    public abstract void jump();
}

//抽象类实现接口
abstract class Dog implements AnimalTrain {
}

//具体类实现接口
class Cat implements AnimalTrain {
    public void jump() {
        System.out.println("猫可以跳高了");
    }
}

class InterfaceDemo {
    public static void main(String[] args) {
        //AnimalTrain是抽象的; 无法实例化
        //AnimalTrain at = new AnimalTrain();
        //at.jump();
        
        AnimalTrain at = new Cat();
        at.jump();
    }
}

5.3 接口的成员特点

  • A:成员变量
    • 只能是常量,默认修饰符为 public static final,建议:自己手动给出
  • B:构造方法
    • 没有构造方法,因为接口主要是扩展功能的,没有具体的存在
  • C:成员方法
    • 只能是抽象的,默认修饰符为 public abstract,建议:自己手动给出
interface Inter {
    public int num = 10;
    public final int num2 = 20;
    public static final int num3 = 30;
    
    //错误: 需要<标识符>,接口没有构造方法
    //public Inter() {}
    
    //接口方法不能带有主体
    //public void show() {}

    //abstract void show(); //默认public
    public void show(); //默认abstract
}

//接口名+Impl这种格式是接口的实现类命名格式
/* 没有重写抽象方法
class InterImpl implements Inter {
    public InterImpl() {
        super();
    }
}
*/
class InterImpl implements Inter {
    public InterImpl() {
        super();
    }
    
    public void show() {}
}

//测试类
class InterfaceDemo2 {
    public static void main(String[] args) {
        //创建对象
        Inter i = new InterImpl();
        System.out.println(i.num);//输出10
        System.out.println(i.num2);//输出20
        //i.num = 100;//无法为最终变量num分配值
        //i.num2 = 200;//无法为最终变量num2分配值
        System.out.println(Inter.num);//输出10
        System.out.println(Inter.num2);//输出20
        System.out.println("--------------");
    }
}

5.4 类与接口之间的关系

  • A:类与类:继承关系,只能单继承,可以多层继承
  • B:类与接口:实现关系,可以单实现,也可以多实现,并且还可以在继承一个类的同时实现多个接口
  • C:接口与接口:继承关系,可以单继承,也可以多继承
interface Father {
    public abstract void show();
}

interface Mother {
    public abstract void show2();
}

interface Sister extends Father,Mother {

}

//多实现
class Son implements Father,Mother {
    public void show() {
        System.out.println("show son");
    }
    
    public void show2() {
        System.out.println("show2 son");
    }
}

class InterfaceDemo3 {
    public static void main(String[] args) {
        //创建对象
        Father f = new Son();
        f.show();
        //f.show2(); //报错
    
        Mother m = new Son();
        //m.show(); //报错
        m.show2();
    }
}

5.5 抽象类与接口的区别

  • A:成员区别
    • 抽象类
      • 成员变量:可以变量,也可以常量
      • 构造方法:有
      • 成员方法:可以抽象,也可以非抽象
    • 接口
      • 成员变量:只可以常量
      • 构造方法:无
      • 成员方法:只可以抽象
  • B:关系区别
    • 类与类:继承关系,只可以单继承
    • 类与接口:实现关系,可以单实现,也可以多实现
    • 接口与接口:继承,单继承,多继承
  • C:设计理念区别
    • 抽象类:被继承提现的是”is a”的关系。抽象类中定义的是该继承体系的共性功能
    • 接口:被实现体现的是”like a”的关系。接口中定义的是该继承体系的扩展功能

5.6 接口的猫狗案例

接抽象类的猫狗案例,加入跳高的额外功能。

分析:从具体到抽象

  • 猫:
    • 姓名,年龄
    • 吃饭,睡觉
  • 狗:
    • 姓名,年龄
    • 吃饭,睡觉

由于有共性功能,所以,我们抽取出一个父类:动物。

  • 动物:
    • 姓名,年龄
    • 吃饭();
    • 睡觉(){}
  • 猫:继承自动物
  • 狗:继承自动物

跳高的额外功能是一个新的扩展功能,所以我们要定义一个接口。

  • 接口:跳高
  • 部分猫:实现跳高
  • 部分狗:实现跳高

实现:从抽象到具体。使用:使用具体类。

//定义跳高接口
interface Jumpping {
    //跳高功能
    public abstract void jump();
}

//定义抽象类
abstract class Animal {
    //姓名
    private String name;
    //年龄
    private int age;
    
    public Animal() {}
    
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
    
    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 abstract void eat();
    
    //睡觉(){}
    public void sleep() {
        System.out.println("睡觉觉了");
    }
}

//具体猫类
class Cat extends Animal {
    public Cat(){}
    
    public Cat(String name,int age) {
        super(name,age);
    }
    
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

//具体狗类
class Dog extends Animal {
    public Dog(){}
    
    public Dog(String name,int age) {
        super(name,age);
    }
    
    public void eat() {
        System.out.println("狗吃肉");
    }
}

//有跳高功能的猫
class JumpCat extends Cat implements Jumpping {
    public JumpCat() {}
    
    public JumpCat(String name,int age) {
        super(name,age);
    }

    public void jump() {
        System.out.println("跳高猫");
    }
}

//有跳高功能的狗
class JumpDog extends Dog implements Jumpping {
    public JumpDog() {}
    
    public JumpDog(String name,int age) {
        super(name,age);
    }

    public void jump() {
        System.out.println("跳高狗");
    }
}

class InterfaceTest {
    public static void main(String[] args) {
        //定义跳高猫并测试
        JumpCat jc = new JumpCat();
        jc.setName("哆啦A梦");
        jc.setAge(3);
        System.out.println(jc.getName()+"---"+jc.getAge());
        jc.eat();
        jc.sleep();
        jc.jump();
        System.out.println("-----------------");
        
        JumpCat jc2 = new JumpCat("加菲猫",2);
        System.out.println(jc2.getName()+"---"+jc2.getAge());
        jc2.eat();
        jc2.sleep();
        jc2.jump();
    }
}

相关文章

网友评论

本文标题:【JavaSE(四)】Java面向对象(中)

本文链接:https://www.haomeiwen.com/subject/dljzrftx.html