重新认识java(三) ---- 面向对象之继承!

学习一个新知识的第一步,就是要知道它是什么,然后要知道为什么要用它,最后要知道如何使用它。这篇文章,我们重新认识一下java中的继承。

继承是个什么东西

我们先来看一下上一篇文章中的代码:

你会发现,这两个类中都有name属性,都有marry方法。一个人,不可能只有名字吧。他还有年龄,地址,手机号码,身份证号码,身高,体重巴拉巴拉的。除了男人和女人,还有小孩,老人,教师。。。

如果我们每个类里都写一遍name,age。。。也许你还没写完程序,自己就先累死了。不用我说,大家也应该知道了,没错,我们需要继承的帮助。

我们把相同的属性抽取出来,定义一个新的类Person,然后让男人,女人都去继承它,从而获得Person的属性,这样,就大大简化了我们的工作。

我们来尝试一下。

//父类
public class Person {
    protected String name;
    protected int age;

    public void eat(){
        System.out.println("i am eating");
    }
}
//Man类 继承Person
public class Man extends Person {
    private boolean hasBeard;

    public void showMan(){
        System.out.println("i am a man");
    }

    public boolean isHasBeard() {
        return hasBeard;
    }
}
//woman类 继承Person
public class Woman extends Person{
    private boolean hasLongHair;

    public void shouWoman(){
        System.out.println("i am a woman");
    }

    public boolean isHasLongHair() {
        return hasLongHair;
    }
}

简单的继承我想大家都懂,我就不多说占用篇幅了。

继承的特点

我们已经知道了什么是继承,那么继承有没有什么限制呢?

1.java中只支持单继承

也就是说,一个类只能够有一个父类。但是java支持“多重继承”。

单继承:

class A(){}
class B extends A (){} 

多重继承:

class A{}
class B extends A {} 
class C extends B {} 

为什么java不支持多继承呢?因为容易造成不必要的混乱。比如说:

  • 结构复杂化:如果是单一继承,一个类的父类是什么,父类的父类是什么,都很明确,因为只有单一的继承关系,然而如果是多重继承的话,一个类有多个父类,这些父类又有自己的父类,那么类之间的关系就很复杂了。
  • 优先顺序模糊:假如我有A,C类同时继承了基类,B类继承了A类,然后D类又同时继承了B和C类,所以D类继承父类的方法的顺序应该是D、B、A、C还是D、B、C、A,或者是其他的顺序,很不明确。
  • 功能冲突:因为多重继承有多个父类,所以当不同的父类中有相同的方法是就会产生冲突。如果B类和C类同时又有相同的方法时,D继承的是哪个方法就不明确了,因为存在两种可能性。

当然,多继承的这些问题很多语言已经解决了,比如c++,python等,但并不是所有的语言都有必要去解决这个问题。java的类虽然不能实现多继承,但是java的接口支持多实现,这个我们讲到接口的时候再说。

对多继承感兴趣的可以google一下mixin(混入),还可以去看一下基于java8的mixin实现(大多数都是线程不安全的,不要随便用)。

2.子类拥有父类非private的属性,方法

也就是说,父类的属性或者方法如果是peivate的,那么子类是不能继承它的。讲到这里,就必须得提一下四个修饰符了:

---- 本类 同包(无关类或子类) 不同包(子类) 不同包(无关类)
private
default
protected
public

在java中,protected关键字大展身手的地方就是在继承中。《thinking in java》中是这样介绍protected的:

在理想世界中,仅靠关键字private已经足够了。但在实际项目中,经常会想要将某些事物尽可能堆这个世界隐藏起来,但仍然允许导出的类的成员访问他们。关键字protected就是起这个作用的。它指明”就类用户而言,这是privated,但是对于任何一个继承于此类的导出类或其他任何一个位于同一个包内的类来说,他却是可以访问的”

怎么理解呢?写个代码你就明白了


package cn.pkgA
class A {
    protected String name;
}
class B extends A{}
class C {
    B b = new B();
    b.name;//可以访问到
}
package cn.pkgB

class C {
    B b = new B();
    b.name;//访问不到
}


3.子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

如果子类只能有父类的属性和方法,那要子类还有什么用??

4.子类可以用自己的方式实现父类的方法。

这个叫做函数重写(覆盖),我们一会会重点分析。

构造器

除了被peivate修饰的方法和变量之外,父类的构造器也不能被子类继承。

但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的当属列表。

如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。

我们给Person类添加一个构造器:

public class Person {
    protected String name;
    protected int age;

    public void eat(){
        System.out.println("i am eating");
    }
    //带参数的构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

这个时候,如果你不给子类添加构造器并在第一行写入super(name,age),则会报错:

在子类添加如下方法后错误消失:

public Man(String name, int age/*, boolean hasBeard*/) {
    super(name, age);
    //this.hasBeard = hasBeard;
}

//注释掉的地方可有可无

为什么会有这个要求呢?你一会就知道了,先卖个关子。

重写与重载

重写

重写又叫覆盖,发生在继承关系下的子类中。我们上面说过,子类可以用自己的方式实现父类的方法,重写不能改变参数列表,也不能缩小方法的访问权限,如果父类方法抛出异常,子类抛出的异常不能比父类的异常“大”,也不能抛出新的异常。

我们Person类中有一个方法:

    public void eat(){
        System.out.println("i am eating");
    }

有一个子类修道成仙了,不吃饭,于是他可以在他自己的类里这样改

    @Override  //这个是注解,表明这个方法是重写了父类的方法,最好写上
    public void eat(){
        System.out.println("i don't eat");
    }

这里提一下,子类重写父类方法不能缩小父类方法的访问权限但扩大是可以的。比如说父类有一个protected方法,子类重写它的时候不能改为private,但是可以改成public。

这里还有一个不大不小的坑。如果你的父类方法是peivate的,比如:

    private void eat(){
        System.out.println("i am eating");
    }

你可以在子类中这样写:

    public void eat(){
        System.out.println("i don't eat");
    }

但是,这不是重写!!!!因为父类方法是私有的,所以子类根本没有得到eat()这个方法,子类的eat()方法是你重新定义的一个和父类没有半毛钱的函数。

重载

把重载放到这里讲只是因为它和重写有的然傻傻分不清楚,重载和继承没有任何关系(当然,继承之间也存在重载,也就是说,继承可以重载,但是重载不一定继承),它发生在类本身。重载方法的特点是方法名相同而参数列表不同。

比如这样:

public void count(int a , int b){
    System.out.println("a+b=" + (a+b));
}

public void count(int a , int b,int c){
    System.out.println("a+b=" + (a+b+c));
}

public void count(int a , int b ,double c){
    System.out.println("a+b=" + (a+b+c));
}

函数重载的特点:

  • 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常(区别于重写);
  • 方法能够在同一个类中或者在一个子类中被重载。

注意:参数列表必须不同!

继承的缺点

  • 继承是一种强耦合关系,父类变,子类就必须变。
  • 继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。

提醒!慎用继承!

如果你知道高手写代码都想着怎么解耦你就知道这个缺点室友多么讨厌了。

你可能会问,我不用继承用什么?别急,接下来的几篇文章会告诉你。

昨天的遗留问题

看了上一篇文章的人可能还记得那个遗留问题。我们现在来解决一下:

//父类
public class Person {
    protected String name;
    
    public void marry(Person p){
        System.out.println("marry");
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
//man类
public class Man extends Person {
    private Woman wife;
    private double money;

    @Override
    public void marry(Person p) {
        this.wife = (Woman)p;
        p.marry(this);
    }
    //只有自己和妻子可以用钱
    public void setMoney(Person p,double money) {
        if (p == this || p == this.wife)
            this.money = money;
        else
            System.out.println(p.getName()+"抢钱!");
    }

    public double getMoney() {
        return money;
    }
}
//woman类
public class Woman extends Person{
    private boolean hasLongHair;
    private Man husband;

    @Override
    public void marry(Person p) {
        this.husband = (Man)p;
    }
}

我们来看一下效果:

看起来还不错,不是么。

当然,我更喜欢这么做

public void setMoney(Person p,double money) {
      if (p == this || p == this.wife)
          this.money = money;
      else if(money > this.money)
          this.money = money;
      else  
          System.out.println(p.getName()+"抢钱!");
}

总结

继承还有很多知识点,比如向上转型和向下转型(上面解决上一篇问题的代码就用到了这个知识点),在继承中,对象是怎么初始化的,静态代码块的使用,final关键字的使用等等。

但是我打算先放一放再讲,等写完组合,聚合和多态再来讨论这些知识会更好一点。

下一篇《重新认识java(四) --- 组合、聚合与继承的爱恨情仇》敬请期待。


有错误或者我没讲到的地方或者更好的思路请及时与我联系!

转载请注明出处!!!!!
本文原创自csdn和简书!!
csdn地址:http://blog.csdn.net/qq_31655965/article/details/53381737
简书地址:http://www.jianshu.com/p/4258962bebb6

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 1 面向对象No6 面向对象 OO Object Oriented 编程时以对象为单元,封装数据和逻辑,以此提...
    征程_Journey阅读 1,099评论 0 2
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,028评论 0 62
  • 当母亲拿着工资预支的1000元给我询问我何时动身离家时,我心里是非常的沉重的,假装轻松的样子告诉她离家的日子和火车...
    木子流悦阅读 5,425评论 0 1
  • 端午节前一周,闲暇之余,也不知自己是哪根神经搭错了,忽然鬼使神差地点开淘宝,搜索起故乡的土特产来。不得不承认,淘宝...
    衣莲生阅读 750评论 5 4