Java面向对象编程——抽象类、接口、多态

继封装和继承之后,抽象类接口多态同样是面向对象编程的重点。

抽象类

  • 抽象类:不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  • 抽象方法:抽象类和接口中具有的特殊方法,与一般方法相比,抽象方法只需要声明而不需要实现,但必须在子类中实现。static方法不能声明为抽象方法

  • 使用方法:使用修饰符abstract声明一个抽象类,
    一般具有抽象方法:abstract <类型> <方法名>(参数);

  • 使用目的:用于继承之后定义子类,必然会用于定义子类

  • 特性
    1. 不能创建对象
    2. 具有某个功能(抽象方法),但必须在子类中实现。子类继承抽象类之后,必须实现抽象方法,不然仍然为抽象类

  • 关系(与子类):is-a ,和常规继承一样

NewsReader抽象类代码实例

public abstract class NewsReader {

    // 和普通类一样,可以有成员变量
    private Integer x;

    // 和普通类一样,可以有一般方法
    public Integer getX() {
        return x;
    }

    public void setX(Integer x) {
        this.x = x;
    }

    // 可以有构造方法
    public NewsReader(){
        System.out.println("NewsReader");
    }

    // 抽象方法
    // 具有这个功能,但具体的功能实现,在子类中确定
    public abstract void readNews();
}

继承抽象类的UrlNewsReader子类代码实例

public class UrlNewsReader extends NewsReader{

    public UrlNewsReader(){
        
        // 先调用抽象类父类构造方法
        super();
        System.out.println("UrlNewsReader");
    }

    // 必须实现的抽象方法
    public void readNews(){
        System.out.println("Url reading......");
    }
}

测试代码

public class Main {

    public static void main(String[] args) {
        
        // 尝试创建NewsReader对象
        // NewsReader newsReader = new NewsReader();
        // error:'NewsReader' is abstract; cannot be instantiated
        
        // 创建UrlNewsReader对象
        UrlNewsReader urlNewsReader = new UrlNewsReader();
        urlNewsReader.readNews();
    }
}

测试结果

NewsReader
UrlNewsReader
Url reading......
  • 总结:抽象类虽然不能创建对象,但可以有构造方法,并可以在子类中用super关键字调用

接口

  • 接口接口是一种抽象类型,是一系列抽象方法的集合,语义上表示某种功能(比如听说读写,但没有具体的实现)。一个类通过实现接口的方式,从而来实现接口的抽象方法。接口中的抽象方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

  • 意义:把接口实现真正分开

  • 使用方法:用关键字interface声明接口,实现的类通过关键字implements表达实现一个接口,从而声明这个类具有接口定义的属性和行为

  • 特性
    1. 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字
    2. 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字
    3. 接口中的方法都是公有的,声明不需要public关键字(隐式的public abstract,其它修饰符会报错)

  • 与类的区别
    1. 接口不能用于实例化对象
    2. 接口没有构造方法
    3. 接口中所有的方法必须是抽象方法(没有实现)
    4. 接口不能包含成员变量,除了隐式的public static final变量(其它修饰符都会报错),因为它不是类,不能实例化对象,所以不会有状态,只有属性
    5. 接口不是被类继承了,而是要被类实现
    6. 接口支持多实现,表示实现了多种功能

Displayable接口代码实例

public interface Displayable {
    
    // 隐式public static final变量
    int a = 1;

    // 隐式public abstract方法
    void display();
}

实现接口的News类代码

// News实现Displayable
public class News implements Displayable {
    protected String title;
    protected String content;

    public News(){}

    // 构造的自由和责任交给用户
    public News(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    //注解表示方法来自上一层,可以检查方法名和方法类型是否写错了
    @Override
    // 必须实现display(),且只能公有
    public void display() {
        System.out.println(title + "\n" + content);
    }
}

测试代码

public class Main {

    public static void main(String[] args) {
        News news = new News("abc","def");
        news.display();
    }
}

测试结果

abc
def
  • 问题:抽象类和接口的选择?
    回答:语义上抽象类表示一类事物的特性,而接口表示类实现的功能,除了对两者特性的区分外,对抽象类和接口的选择,得先从预估的子类抽象出来的事物来看待。如果抽象出来的共同特征能归为一个类别,用抽象类合适,如果抽象出来的特征能归为一种功能,用接口合适。因此,实现同样的功能,写法不一样,表达的意思是不一样的。
    例子:Content这样的抽象,更像是一种功能,而不是一类实物的抽象,用接口较好。

多态

  • 多态:同一个行为具有多个不同表现形式或形态的能力

  • 优点
    1. 消除类型间的耦合关系
    2. 可替换性
    3. 可扩充性
    4. 接口性
    5. 灵活性
    6. 简化性

  • 必要条件
    1. 继承
    2. 重写
    3. 父类引用指向子类对象

  • 实现方式
    1. 重写
    2. 接口(接口可以看作一种被继承才能实现的父类)
    3. 抽象类和抽象方法(抽象类必须作为父类来继承得以实现)

实例1

class Animal{
    public void move(){
        System.out.println("Animals can move");
    }
}

// 继承
class Dog extends Animal{
    
    // 重写
    public void move(){
        System.out.println("Dogs can walk and run");
    }
    
    public void bark(){
        System.out.println("Dogs can bark");
    }
}

public class Test{
    public static void main(String args[]){
        Animal a = new Animal();    // 父类引用指向父类对象
        Animal b = new Dog();       // 父类引用指向子类对象
        a.move();
        b.move();
        // Test.java:30: cannot find symbol symbol : method bark() location: class Animal b.bark();^
        // Animal类没有bark()方法
        // b.bark();
    }
}

// 结果
Animals can move
Dogs can walk and run
  • 总结:b是Animal类型,却运行了Dog类的方法,因为编译时会检查引用类型。在运行时,JVM 会判定对象类型到底属于哪一个对象。因此,在上面的例子中,虽然 Animal有move()方法,程序会正常编译。在运行时,会运行特定对象的方法。(按Animal编译,按Dog运行)

实例2

public class Employee {
    private String name;
    private String address;
    private int number;
    public Employee(String name, String address, int number) {
        System.out.println("Employee 构造函数");
        this.name = name;
        this.address = address;
        this.number = number;
    }
    public void mailCheck() {
        System.out.println("邮寄支票给: " + this.name + " " + this.address);
    }
    public String getName() {
      return name;
    }
    ...
}

public class Salary extends Employee {
    private double salary; // 全年工资
    public Salary(String name, String address, int number, double salary) {
       super(name, address, number);
       setSalary(salary);
    }
    public void mailCheck() {
       System.out.println("Salary 类的 mailCheck 方法 ");
       System.out.println("邮寄支票给:" + getName() + " ,工资为:" + salary);
    }
    ...
}

public class VirtualDemo {
    public static void main(String [] args) {
      Salary s = new Salary("员工 A", "北京", 3, 3600.00);      // 子类引用指向子类对象
      Employee e = new Salary("员工 B", "上海", 2, 2400.00);    // 父类引用指向子类对象
      System.out.println("使用 Salary 的引用调用 mailCheck -- ");
      s.mailCheck();
      System.out.println("\n使用 Employee 的引用调用 mailCheck--");
      e.mailCheck();
    }
}

//结果
Employee 构造函数
Employee 构造函数
使用 Salary 的引用调用 mailCheck -- 
Salary 类的 mailCheck 方法 
邮寄支票给:员工 A ,工资为:3600.0

使用 Employee 的引用调用 mailCheck--
Salary 类的 mailCheck 方法 
邮寄支票给:员工 B ,工资为:2400.0
  • :子类引用不可以指向父类对象

实例3

News的子类UrlNews代码

为简洁,Displayable接口及实现接口的News类代码已在上面提及,不再重复展示

public class UrlNews extends News{
    private String url;

    // 缺省构造函数,默认调用super()
    public UrlNews() {}

    // 含参构造函数
    public UrlNews(String title, String content, String url) {
        super(title,content);
        this.url = url;
    }

    public String getUrl() {
        return url;
    }

    // 重写
    @Override
    public void display() {
        System.out.println("News from Url:" + url);
        super.display();
    }
}

测试代码

NewsReader抽象类及其UrlNewsReader子类代码已在上面提及,不再重复展示

public class Main {

    public static void main(String[] args) throws IOException {
        
        // 父类引用指向父类对象
        News news = new News("abc","父类");
        // 父类引用指向子类对象
        News urlnews = new UrlNews("ABC","子类", "class.com");
        news.display();
        urlnews.display();
        
        // 实现接口的子类可以放入接口参数
        viewNews(news);
        viewNews(urlnews);

        // 接口引用指向子类对象
        Displayable displayable = new UrlNews("abc","def","ghi");
        displayable.display();

        // 抽象类引用指向子类对象
        NewsReader newsReader = new UrlNewsReader();
        newsReader.readNews();
    }

    private static void viewNews(Displayable item) {
        item.display();
        System.out.println("播放完毕");
    }
}

测试结果

abc                             // news.display();
父类                            
News from Url:class.com         // urlnews.display();
ABC
子类
abc                             // viewNews(news);
父类
播放完毕
News from Url:class.com         // viewNews(urlnews);
ABC
子类
播放完毕
News from Url:ghi               // displayable.display();
abc
def
NewsReader                      // NewsReader newsReader = new UrlNewsReader();
UrlNewsReader
Url reading......               // newsReader.readNews();
  • 多态的好处:利用了动态传参,可以避免很多重复代码
    例如:在Main中定义read()方法,根据多态的原则,可以按如下方法定义:
    private static void read(News news) {
        news.display();
    }

如果没有多态,则需要将News类的子类的read()方法全部定义一遍:

    private static void read(UrlNews urlNews) {
        urlNews.display();
    }

    // FileNews是News的子类时且display()方法有重写时
    private static void read(FileNews fileNews) {
        fileNews.display();
    }

当News的子类很多时,在Main中就要写很多重复代码,浪费资源,且不方便维护。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 今日任务: 1,能够独立使用抽象类 2,能够独立使用多态 3,能够独立使用接口 4,能够理解适配器设计模式 1. ...
    Villain丶Cc阅读 1,333评论 0 17
  • java继承 继承的概念 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。 继承就是子类继...
    863cda997e42阅读 649评论 0 1
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 柏拉图是希腊著名的哲学家之一,苏格拉底的学生,亚里士多德的老师。 人们经常说“柏拉图式的爱情”什么的。但到底什么是...
    沉小屋阅读 309评论 0 1
  • 我不喜欢我妈妈。 一直以来我都不喜欢她。 小的时候, 爸爸妈妈总吵架。 有时候吵急了, 妈妈就离家出走了。 那时候...
    大伟传说阅读 657评论 4 3