继承应该是我们接触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支持单继承,不支持多继承。一个类只能有一个父类。
2.但是java支持多重继承
class A()
class B() extends class A()
class C() extends class 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实现(大多数都是线程不安全的,不要随便用)。
3.子类拥有父类非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;//访问不到
}
4.子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
当然这个也很好理解,如果子类只能有父类的属性和方法,那要子类还有什么用??
5.子类可以用自己的方式实现父类的方法。
这个叫做函数重写(覆盖),我们一会会重点分析。
构造器:
父类中除了用private修饰的方法和属性外,父类的构造器也不能被子类继承。
但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过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),就会报错
这是我们初学JAVA时都会有所了解的,但是为什么要这么做呢却没有深入的了解,我们在
下面会详细说明。
重写与重载
重写:
重写又叫覆盖,它是把从父类那里继承下来的方法做了修改。但是不能改变参数列表,也不能缩小方法的访问权限,比如父类如果是protect,那你就不能写成private但是可以写成public.这里非常要注意就是父类的方法如果是private那么你根本就继承不到这个方法,也就不存在重写的概念,只能叫做自己新加给子类的方法。同时,子类所抛出的异常不能比父类的异常大,也不能抛出新的异常。
我们Person类中有一个方法:
public void eat(){
System.out.println("i am eating");
}
有一个子类修道成仙了,不吃饭,于是他可以在他自己的类里这样改:
@Override //这个是注解,表明这个方法是重写了父类的方法,最好写上
public void eat(){
System.out.println("i don't 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 testCons(){
Man man = new Man();
man.setName("Jack");
Woman man = new Woman();
woman.setName("lucy");
man.marry(woman);
Man m = new Man();
m.setName("tom");
man.setMoney(man,1000000);
System.out.print(man.getName()+""+man.getMoney());
man.setMoney(woman,1000000);
System.out.print(man.getName()+""+man.getMoney());
man.setMoney(m,990000);
System.out.print(man.getName()+""+man.getMoney());
}
总结:
继承还有很多知识点,比如向上转型和向下转型(上面解决上一篇问题的代码就用到了这个知识点),
在继承中,对象是怎么初始化的,静态代码块的使用,final关键字的使用等等。
但是我打算先放一放再讲,等写完组合,聚合和多态再来讨论这些知识会更好一点。