Java基础杂烩_继承(Inheritance)
Posted Makise_C
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础杂烩_继承(Inheritance)相关的知识,希望对你有一定的参考价值。
〇、什么是继承?
1. 继承是面向对象编程的重要特性,是面向对象设计的一项核心技术,继承的出现提升了各种类的复用频率并将多态这一特性发挥至了极限。合理地利用继承将大大提高代码的可读性和编程的逻辑性。
2. 以上都是我编的
3. 声明继承的语法:在子类后使用extends来指定该类所继承的超类。
class SubClass extends SuperClass{
...
}
一、变量
1. 子类会继承超类的所有变量,但能否访问依然取决于修饰符 (private/public)。
2. 子类可以视这所有来自超类的变量为自己的(this.),不过其并没有对private类型变量的访问权限。
3. 如果子类内部创建了一个同名的变量,那么我们在调用这个变量名的时候默认访问的是子类自己创建的变量。
4. 在类的内部通过super.依然可以访问超类持有的同名变量,或者在外部执行时使用类型强制转换(ParentClass)childClass.variable 来指明你需要调用超类的同名变量。
用一个我刚接触继承(上个月)时使用的并不恰当的例子:
class Dad{
int age; //父亲的年龄
void say() {
System.out.println("I am Dad!");}
public Dad() {
age=30;}
public Dad(int i) {
age=i;}
}
class Son extends Dad{
int age; /
void say() {
System.out.println("I am Son!\\nMy dad is "+super.age+" years old\\nI am "+this.age+" years old"); //打印信息
}
public Son() {
age=20; //儿子的年龄
super.age=50; //修改父亲的年龄
}
}
public class testInheritance {
public static void main (String[] args) {
var son = new Son();
son.say();}
}
可以看到,Son为自己定义了一个与超类同名的实体变量age,然而超类的变量没有被覆盖,而是保留了下来,与此同时,子类依然拥有访问父类age的权限。
不过大部分情况下,实例变量很多为private,同时这样也是合理的,否则无法贯彻封装的理念。
二、方法
1. 方法的覆盖(Overriding)
子类可以以同签名(方法名+接收的参数列表)且同返回值(其实是协变的)的形式覆盖 父类自有的方法,子类对象一旦覆写父类的方法,就无法再在外部调用自己父类的方法了。
class Animal {
String name;
private int numLegs;
public String getName() {
return this.name;
}
public boolean isSleeping(int hour, int minute) {
if (hour > 24 || hour < 0 || minute > 60 || minute < 0) {
throw new IllegalArgumentException("invalid time specified");
}
return (hour >= 22 || hour <= 5);
}
}
class Cat extends Animal {
private boolean isShortHaired;
public boolean isSleeping(int hour, int minute) {
return true;
}
}
public class testInheritance {
public static void main (String[] args) {
var cat = new Cat();
if(cat.isSleeping(12,50)) {
System.out.println("She is sleeping");
}
if(((Animal)cat).isSleeping(12,50)) {
System.out.println("She is sleeping");
}
}}
我们尝试使用强制转换将cat变为Animal类型,并调用他的超类方法,但是我们失败了。
为什么不行呢...?
2. 方法的重载(Overloading)
当两个方法有相同的名字,但参数列表不同时,编译器会自动地通过你调用方法时输入的参数来调用对应的方法,这个过程被称为重载,而不是覆盖。
class Animal {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name=name;
}
}
class Cat extends Animal {
String nickname;
public void setName() {
nickname="Kitty";
}
}
public class testInheritance {
public static void main (String[] args) {
var cat = new Cat();
cat.setName(); //来自Cat的setName
cat.setName("Marx II"); //来自Animal的setName
System.out.println(cat.getName());
System.out.println(cat.nickname);
}
}
可以看到,两个同名的方法都被保留了下来,这是因为在编译器看来,接收参数不同的两个同名方法是完全可以区分的。因此,只要两个方法的签名不同,就不满足覆盖的要求。
3. 覆盖方法的可见性
子类总是无私的,他不允许你将覆盖过来的方法越向比原方法可见性更低的层级(public ->private)。
class Animal {
public boolean isSleeping(int hour, int minute) {
if (hour > 24 || hour < 0 || minute > 60 || minute < 0) {
throw new IllegalArgumentException("invalid time specified");
}
return (hour >= 22 || hour <= 5);
}
}
class Cat extends Animal {
private boolean isSleeping(int hour, int minute) {
return true;
}
}
三、构造器
子类会自动继承来自父类的无参构造器,不会继承有参数的构造器,也就是说:父类有无参构造器时,子类可以不定义构造器
当父类只定义了一个有参数的构造器时,子类必须定义一个自己的构造器,且必须在构造器内部的第一行来调用父类的构造器。
也就是说,初始化子类之前,必须先初始化父类。这个过程可以由jvm自发地调用无参构造器来完成,有时候必须由开发者自己来完成。
规则:
- 父类的构造器不能以父类的类名调用,必须使用super
- super必须在子类构造器的开头(第一行)被调用
- 不使用super时,自动调用父类的无参构造器。
- super只能使用子类构造器传入的参数或给定的数值/对象来进行构造,无法引入任何外部的实体变量
class Animal {
private String name;
private int numLegs;
public Animal(String name, int numLegs) {
this.name = name;
this.numLegs = numLegs;
}
}
class Cat extends Animal {
private boolean isShortHaired;
public Cat(String name,boolean isShortHaired) {
super(name,4);
this.isShortHaired=isShortHaired;
}
}
四、抽象类/抽象方法
定义抽象类的语法:
abstra class ClassName{
......
}
对于一个拥有抽方法的抽象类,他的子类只有两个选择:
1) 覆写掉这个抽象方法
2) 如果不想覆写掉这个抽象方法,必须将自己也变为抽象类。
抽象类的使用规则:
- 抽象类可以没有抽象方法,有抽象方法类的必须是抽象类
- 抽象类不能被实例化
- 变量可以被声明为抽象类,但是他只能用来引用他的实体子类。
- 在调用方法时,超类中包含的具体方法是可以被调用的,同时也会自动的调用该子类覆写的抽象方法。
- 如果是子类中独有的方法,则不能直接调用,必须通过类型转换实现。
- 尽可能的在抽象超类中准备好需要覆写的抽象类和通用的方法,提高代码结构的逻辑性。
abstract class Animal {
public abstract boolean isExtroverted(); //一个抽象方法,不需要代码块,不需要返回值
}
class Cat extends Animal {
public boolean isExtroverted() {
return false;
}
}
public class testInheritance {
public static void main (String[] args) {
Animal cat = new Cat("Jessica",false);
if(cat.isExtroverted()) {
System.out.println("She is a quiet girl");
}
}
}
五、多态
“一个对象变量可以指示多种实际类型的现象成为多态(polymorphism)。在运行时能够自动地选择适当的方法,成为动态绑定(dynamic binding)。”
——《Java 核心技术 卷Ⅰ》
1. 超类变量可以引用子类对象
Animal a=new Cat();
以上,创建了一个animal类的变量a,引用了一个子类的对象Cat,它可以正确的调用引用的对象中的方法,但无法访问Animal所没有的字段和方法,即使他们存在于子类Cat中。
2. 子类变量无法引用超类对象
如有需要,必须进行强制转换
Cat c=a; //编译不通过
Cat c=(Cat)a; //可以接受
尽管如此,如果你的变量a本身引用的并不是Cat对象,Java会在运行时抛出ClassCastException异常。
3. 协变
有时候方法会返回一个对象,在覆盖此类方法时,子类可以替换掉原有的返回类型,使其变为原返回类型的子类,如此一来我们可以称这两个方法拥有可协变的返回类型。
如我们创造了一个方法,让一只猫生下了小猫,这个方法肯定会返回一个Cat,而且我们绝对不希望它返回别的动物(嗯...)。
class Animal {
public Animal birth() {
return new Animal(); //生下一直动物
}
}
class Cat extends Animal {
public Cat birth() {
return new Cat("Kitty",false); //生下一只猫
}
}
六、解惑(自己的)
至此我已经知道为什么覆盖后子类不能调用父类的原方法了。
Cat cat=new Cat();
我们在这里创建了一个Cat类,他使用的是Cat的构造方法,如果我们在Cat内覆盖了一个isSleeping方法,那么如果我们想调用Animal中的sleep,我们会怎么做呢?
(Animal)cat.isSleeping();
我们将c转换成了Animal类型,然后调用了sleep,看起来合乎情理
但实际上真正的问题是:cat只是一个Cat变量,他引用的是一个Cat对象,这个对象在被声明构造方法的时候就已经注定了他的身份,我们做的是让一个符合他所属类的变量去引用他。
如果我们将c进行强制转换,那么所达到的结果就是:我们把c变成了一个Animal变量,他引用了一个Cat对象。这是不是有些眼熟?
Animal c=new Cat();
合乎情理。但可惜,cat终究是cat,他不能使用不属于他的方法。
对象变量只是一个箱子,上面贴着一个标签,当我们进行强制转换的时候,我们把Cat这个签子撕下来,贴一个Animal上去,让箱子里的动物做出些行为。
可我们必须知道里面放着的永远是一只全天都在睡大觉的长毛美短。
由此我们也引出了使用继承的核心机制——"Is a":当我们不知道是否应该使用继承时,我们需要判断的唯一标准就是子类是否也是超类的一种。
cat是animal,但animal不是cat,cat也不能代表所有animal。
cat继承自animal。
以上是关于Java基础杂烩_继承(Inheritance)的主要内容,如果未能解决你的问题,请参考以下文章