Java继承意味着一个类可以从另一个类继承其属性和方法
关于本投稿
我正在参考《Java入门第2版》这本书,非常清晰易懂。
1. 继承的基础
1.1 传承
在进行程序开发时,经常需要创建与之前创建的类相似的类。然而,直接复制黏贴以前创建的类是低效的,因此可以通过继承以前创建的类来编写高效的代码。
public class Hero{
private String name = "hoge";
private int Hp = 100;
public void attack(Monster m){
m.hp -= 5;
}
public void recover(){
this.Hp += 5;
}
}
public class SuperHero{
private String name = "hoge";
private int Hp = 100;
private boolean flying;
public void attack(Monster m){
m.hp -= 5;
}
public void recover(){
this.Hp += 20;
}
public void fly(){
this.flying = true;
System.out.println("飛行状態");
}
public void land(){
this.flying = false;
System.out.println("着地状態");
}
}
在这段代码中,当在Hero类中添加新方法时,需要在SuperHero类中进行相同的修改。由于SuperHero类和Hero类的大部分代码相似,整体的可读性较差,维护工作也比较麻烦。因此,使用继承可以消除代码的重复。
public class SuperHero extends Hero{
private boolean flying;
public void attack(Monster m){
m.hp -= 10;
}
public void recover(){
this.Hp += 20;
}
public void fly(){
this.flying = true;
System.out.println("飛行状態");
}
public void land(){
this.flying = false;
System.out.println("着地状態");
}
}
通过重写上述内容,可以定义一个继承了 Hero 类的字段和方法的 SuperHero 类。在这种情况下,Hero 类被称为父类(超类),SuperHero 类被称为子类(子类)。
1.2 繼承的變化範疇
在Java中,不允许多重继承。具体而言,如果有一个Hero类和一个Monster类,那么不能继承这两个类来定义一个名为HeroMonster的类。换句话说,不能定义一个子类,有多个父类。
可以在子类中重新定义父类中定义的方法(称为方法重写)。在Java类中,也存在不能被继承的类。String类可以作为一个例子。还可以声明一个不允许自身继承的类。在类声明时,加上final关键字。
public final class Main{
public static void main(Strign args[]){
//main関数の内容
}
}
同样地,为了防止方法被重写,可以在方法声明时添加final关键字。
1.3 实例的行为
使用继承来考虑生成实例的行为。使用上述的Hero类和SuperHero类进行说明。SuperHero实例内部包含了一个Hero实例,形成了双重结构。当从实例的外部调用方法执行命令时,多重结构的实例会尽可能地调用外部子实例的方法。具体来说,从SuperHero实例调用recover方法时,将调用重写的方法,并且不会调用Hero实例的recover方法。
解释了对父实例的访问。作为附加规定,如果在飞行状态下进行恢复,将回复25点Hp。在调用父实例的方法和字段时,
超级.方法名称(参数)
超级.字段名称
那就这样吧。
public class SuperHero extends Hero{
private boolean flying;
public void attack(Monster m){
m.hp -= 10;
}
public void recover(){
this.Hp += 20;
if(this.flying){
super.recover();
}
}
public void fly(){
this.flying = true;
System.out.println("飛行状態");
}
public void land(){
this.flying = false;
System.out.println("着地状態");
}
}
在这种情况下,如果使用recover()来调用super.recover(),它会被识别为this.recover(),导致陷入无限循环,请注意。
1.4 继承与构造函数
之前已经解释了继承的实例是如何形成多重结构的。接下来,我们来考虑实例是如何构建的。请参考上面的程序,并执行以下程序。
public class Hero{
private String name = "hoge";
private int Hp = 100;
public void attack(Monster m){
m.hp -= 5;
}
public void recover(){
this.Hp += 5;
}
Hero(){
System.out.println("Heroコンストラクタ");
}
}
public class SuperHero extends Hero{
private boolean flying;
public void attack(Monster m){
m.hp -= 10;
}
public void recover(){
this.Hp += 20;
}
public void fly(){
this.flying = true;
System.out.println("飛行状態");
}
public void land(){
this.flying = false;
System.out.println("着地状態");
}
SuperHero(){
System.out.println("SuperHeroコンストラクタ");
}
}
public class Main{
public static void main(Strign args[]){
SuperHero sh = new SuperHero();
}
}
首先,父类实例部分被创建,接着子类实例部分被创建在外部。最后,JVM会自动调用SuperHero类的构造函数。执行结果如下所示。
Heroコンストラクタ
SuperHeroコンストラクタ
注意到内层实例的Hero实例的构造函数也被调用了。这是因为在Java规则中,”所有构造函数都必须在开头调用内部实例的构造函数。”
public class SuperHero extends Hero{
.
.
.
SuperHero(){
super();
System.out.println("SuperHeroコンストラクタ");
}
}
编译器会自动插入super()。
无法创建亲属实例的情况。
有时候,子实例部分的构造函数可能会调用未在父实例部分中定义的构造函数,导致错误发生。我们以Human类和Soldier类为例进行具体说明。
public class Human{
private String name;
private int Hp;
private int Mp;
Human(String name){
this.name = name;
this.Hp = 100;
this.Mp = 100;
}
Human(String name, int hp, int mp){
this.name = name;
this.Hp = hp;
this.Mp = mp;
}
}
public class Soldier{
.
.
.
}
public class Main{
public static void main(Strign args[]){
Soldier soldier = new Soldier();
}
}
在这段代码中,由于Soldier类没有构造函数,因此会调用父类Human类的构造函数。然而,由于Human类没有无参构造函数,所以这段代码会出现错误。为了防止这种情况发生,可以在Soldier类的构造函数中强制调用带有参数的构造函数来解决问题。
public class Soldier{
.
.
.
Soldier(){
super("hoge");
}
}
1.6 正確的繼承和錯誤的繼承
合理的的继承是指遵循“是一个”的原则的继承方式。
子类是一个父类的一种(子类属于父类的某个部分)。
如果用上述的代码来进行解释,SuperHero类是Hero类的一部分。Hero类是SuperHero类的一部分是错误的。继承是一个很方便的功能,但如果继承错误,可能会在程序中引发矛盾,也可能无法利用多样性。
2. 使用抽象类进行继承。
2.1 抽象类的存在意义
抽象类是指具有抽象方法和字段的类。继承抽象类的具体类必须重写抽象类中定义的方法。另外,通过将多个抽象类定义为接口,可以实现继承多个抽象类(详见2.3)。
抽象类和抽象方法的存在意义在于,当开发人员增加时,它们是开发高效且安全程序所必需的材料。举个具体例子,如果在没有抽象类的情况下进行开发,某个方法的内容可能在目前阶段尚未确定,故可能将方法的内容留空。若其他开发人员看到这个方法时,可能无法确定是否需要更改内容。但是,如果将其定义为抽象方法,就需要重写该方法,从而可以确定需要更改内容。因此,通过使用抽象方法,可以放心进行开发。
2.2 抽象类和抽象方法
public class Character{
.
.
public abstract void atttack(Monster m);
}
public class Character{
.
.
public abstract void atttack(Monster m);
}
攻击(attack)方法应在继承Character类的类中进行具体化。
public abstract class Character{
.
.
public abstract void atttack(Monster m);
}
为了禁止创建未完成的类的实例,可以将该类定义为抽象类。
2.3 接口
在Java中,可以将具有高抽象度的抽象类特别作为接口来处理。作为接口处理的条件是,
-
すべてのメソッドが抽象メソッド
基本的にフィールドを1つも持たない(フィールドを定義した場合は、定数扱いとなる)
有一个。
public interface Creature{
public abstract void run(); //public abstractは省略可能
}
在接口中定义变量时,会自动添加public static final修饰符,并将其作为常量处理。实现Creature类时,应按照以下方式进行操作。
public class Pig implements Creature{
public void run(){
System.out.println("Pigが逃げ出した");
}
}
通过定义接口,
-
インタフェースをimplementsする複数の子クラスに、共通のメソッド群を実装するように強制でき、
あるクラスがインタフェースを実装していれば、少なくともそのインタフェースが定めたメソッドを持つことが保障される
有这样的好处。