Java中的继承

Java通过继承和组合支持类重用。在这个两部分的小部件中,我们将关注继承,面向对象编程的基本概念之一。首先,您将学习如何使用extends关键字从父类派生子类,调用父类构造函数和方法以及覆盖方法。然后,在第2部分,我们将巡视java.lang.Object和它的方法。Object是Java的最终超类,每个其他类继承。


通过继承关系类

Java 101:Java中的继承,图1 Jeff Friesen

图1.一对继承层次结构植根于通用车辆类别

继承是一种编程构造,软件开发人员用它来建立is-a类之间的关系。继承使我们能够从更一般的类别中导出更具体的类别。更具体的类别一种更通用的类别。例如,支票帐户是一种可用于存款和取款的帐户。类似地,卡车是用于拖运大物品的一种车辆。

继承可以通过多个级别下降,导致更具体的类别。作为示例,图1示出了从车辆继承的汽车和卡车; 车从车继承; 和垃圾车从卡车继承。箭头从更具体的“子”类别(下部)指向较不具体的“父”类别(更高)。

Java 101:Java中的继承 Jeff Friesen

图2.气垫船多次继承陆地车辆和水车类别

此示例说明了单个继承,其中子类别从一个直接父类别继承状态和行为。相比之下,多重继承使子类别能够从两个或多个直接父类别继承状态和行为。图2中的层次结构说明了多重继承。

菱形图案在多重继承中

当查看继承层次结构时,可以通过存在菱形图案轻松检测多重继承。图2示出了在车辆,陆地车辆,水车和气垫船的情况下的这种模式。

类别由类描述。Java通过类扩展支持单继承,其中一个类通过扩展该类直接从另一个类继承可访问的字段和方法。Java不支持通过类扩展的多继承。

类扩展

Java通过extends关键字支持类扩展。当存在时,extends指定两个类之间的父子关系。下面我用extends建立类之间的关系VehicleCar之间,然后AccountSavingsAccount

清单1. extends关键字指定父子关系
class Vehicle
{
   // member declarations
}
class Car extends Vehicle
{
   // inherit accessible members from Vehicle
   // provide own member declarations
}
class Account
{
   // member declarations
}
class SavingsAccount extends Account
{
   // inherit accessible members from Account
   // provide own member declarations
}

extends关键字类名后先于另一个类名指定。extendsextends识别父类之前,类名称标识子类和类名称。以后不可能指定多个类名,extends因为Java不支持基于类的多重继承。

这些例子编纂的是一种关系:Car 是一种专门化的VehicleSavingsAccount 是一种专门化的AccountVehicle并且Account被称为基类父类超类CarSavingsAccount被称为子类子类,或子类

最后的类

你可以声明一个不应该扩展的类; 例如出于安全原因。在Java中,我们使用final关键字来防止一些类被扩展。只需前缀一个类标题与final,如final class Password。给定此声明,如果有人尝试扩展,编译器将报告错误Password

子类继承其父类和其他祖先的可访问字段和方法。然而,他们从不继承构造函数。相反,子类声明自己的构造函数。此外,他们可以宣布自己的领域和方法,以区别于他们的父母。考虑清单2。

清单2. Account父类
class Account
{
   private String name;

   private long amount;

   Account(String name, long amount)
   {
      this.name = name;
      setAmount(amount);
   }

   void deposit(long amount)
   {
      this.amount += amount;
   }

   String getName()
   {
      return name;
   }

   long getAmount()
   {
      return amount;
   }

   void setAmount(long amount)
   {
      this.amount = amount;
   }
}

清单2描述了一个具有名称和初始金额的通用银行帐户类,它们都在构造函数中设置。此外,它允许用户存款。(您可以通过存入负数金额提款,但我们将忽略此可能性。)请注意,帐户名称必须在创建帐户时设置。

表示货币值

对于清单2,我选择将货币值表示为long整数,其中值存储为一分钱。您可能更喜欢使用a double或a float来存储货币价值,但这样做可能会导致不准确。为了更好的解决方案,考虑BigDecimal,这是Java的标准类库的一部分。

清单3展示了一个SavingsAccount扩展Account其父类的子类。

清单3. SavingsAccount子类扩展它的Account父类
class SavingsAccount extends Account
{
   SavingsAccount(long amount)
   {
      super("savings", amount);
   }
}

SavingsAccount,因为它并不需要声明其他字段或方法的类实在是微不足道。但是,它会声明一个构造函数来初始化其Account超类中的字段。初始化发生在Account通过Java的super关键字调用构造函数时,后面跟着一个带括号的参数列表。

何时何地调用super()

正如this()必须是在同一个类中调用另一个构造函数的构造函数中super()的第一个元素,必须是在其超类中调用构造函数的构造函数中的第一个元素。如果违反此规则,编译器将报告错误。如果编译器检测到super()方法中的调用,它也将报告错误; 只有super()在构造函数中调用。

清单4进一步扩展Account了一个CheckingAccount类。

清单4.一个CheckingAccount子类扩展了它的Account父类
class CheckingAccount extends Account
{
   CheckingAccount(long amount)
   {
      super("checking", amount);
   }

   void withdraw(long amount)
   {
      setAmount(getAmount() - amount);
   }
}

CheckingAccount是一个比一个更重要的SavingsAccount因为它声明一个withdraw()方法。注意这个方法对setAmount()和的调用getAmount(),它CheckingAccount继承自Account。您不能直接访问该amount字段,Account因为此字段已声明private(参见清单2)。

super()和无参构造函数

如果super()没有在子类构造函数中指定,并且超类没有声明一个无参数构造函数,那么编译器将报告一个错误。这是因为子类构造函数在super()不存在时必须调用无参数超类构造函数。

演示帐户类层次结构

我创建了一个应用AccountDemo程序类,让您尝试Account类层次结构。首先看看AccountDemo源代码。

清单5. AccountDemo演示帐户类层次结构
class AccountDemo
{
   public static void main(String[] args)
   {
      SavingsAccount sa = new SavingsAccount(10000);
      System.out.println("account name: " + sa.getName());
      System.out.println("initial amount: " + sa.getAmount());
      sa.deposit(5000);
      System.out.println("new amount after deposit: " + sa.getAmount());

      CheckingAccount ca = new CheckingAccount(20000);
      System.out.println("account name: " + ca.getName());
      System.out.println("initial amount: " + ca.getAmount());
      ca.deposit(6000);
      System.out.println("new amount after deposit: " + ca.getAmount());
      ca.withdraw(3000);
      System.out.println("new amount after withdrawal: " + ca.getAmount());
   }
}

main()清单5中方法首先表明SavingsAccount,然后CheckingAccount。假设Account.javaSavingsAccount.javaCheckingAccount.javaAccountDemo.java源文件在同一目录中,既可以执行以下命令来编译所有这些源文件:

javac AccountDemo.java
javac *.java

执行以下命令运行应用程序:

java AccountDemo

您应该观察以下输出:

account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000

方法覆盖

子类可以覆盖(替换)继承的方法,以便调用子类的方法版本。覆盖方法必须指定与要覆盖的方法相同的名称,参数列表和返回类型。为了演示,我已经print()Vehicle下面的类中声明了一个方法。

清单6.声明要覆盖的print()方法
class Vehicle
{
   private String make;
   private String model;
   private int year;

   Vehicle(String make, String model, int year)
   {
      this.make = make;
      this.model = model;
      this.year = year;
   }

   String getMake()
   {
      return make;
   }

   String getModel()
   {
      return model;
   }

   int getYear()
   {
      return year;
   }

   void print()
   {
      System.out.println("Make: " + make + ", Model: " + model + ", Year: " +
                         year);
   }
}

接下来,我print()Truck类中重写。

清单7. print()Truck子类中重写
class Truck extends Vehicle
{
   private double tonnage;

   Truck(String make, String model, int year, double tonnage)
   {
      super(make, model, year);
      this.tonnage = tonnage;
   }

   double getTonnage()
   {
      return tonnage;
   }

   void print()
   {
      super.print();
      System.out.println("Tonnage: " + tonnage);
   }
}

Truckprint()方法具有相同的名称,返回类型和参数列表Vehicleprint()方法。也请注意,这Truckprint()方法首先调用Vehicleprint()通过前缀的方法super.方法名。通常最好先执行超类逻辑,然后执行子类逻辑。

从子类方法调用超类方法

为了从覆盖子类方法调用超类方法,在方法名称前加上保留字super和成员访问运算符。否则,你将最终递归调用子类的覆盖方法。在某些情况下,子类将private通过声明相同命名的字段来掩盖非超类字段。您可以使用super和成员访问运算符来访问非private超类字段。

为了完成这个例子,我已经摘录了一个VehicleDemo类的main()方法:

Truck truck = new Truck("Ford", "F150", 2008, 0.5);
System.out.println("Make = " + truck.getMake());
System.out.println("Model = " + truck.getModel());
System.out.println("Year = " + truck.getYear());
System.out.println("Tonnage = " + truck.getTonnage());
truck.print();

最后一行,truck.print();,调用truckprint()方法。此方法首先调用Vehicleprint()输出卡车的品牌,型号和年份; 然后输出卡车的吨位。这部分输出如下所示:

Make: Ford, Model: F150, Year: 2008
Tonnage: 0.5

使用final来阻止方法覆盖

有时,您可能需要声明一个方法,不应为了安全或其他原因而重写。您可以final为此使用关键字。为了防止覆盖,只需在方法标题前加上final,如final String getMake()。如果任何人试图在子类中覆盖此方法,编译器将报告错误。

方法重载而不是覆盖

假设您将print()清单7中的方法替换为下面的方法:

void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();
}

修改的Truck类现在有两个print()方法:前面的显式声明的方法和继承的方法Vehicle。该void print(String owner)方法不覆盖Vehicleprint()方法。相反,它重载它。

您可以检测重载尝试,而不是在编译时覆盖方法,方法是在子类的方法头中添加@Override注释:

@Override
void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();

}


指定@Override告诉编译器给定的方法覆盖另一个方法。如果有人试图重载该方法,编译器将报告错误。没有这个注释,编译器不会报告错误,因为方法重载是合法的。

何时使用@覆盖

开发前缀覆盖方法的习惯@Override。这个习惯将帮助你更快地检测超载错误。

方法覆盖和保护方法

Java提供了protected在方法覆盖上下文中使用的关键字。您还可以protected用于字段。此关键字通常用于标识设计为要覆盖的方法,因为并非所有可访问方法都应重写。

当您声明方法或字段protected时,方法或字段可以访问在同一个包中声明的任何类中的所有代码。它也可以访问子类,而不管它们的包。(我将在以后的文章中讨论软件包。)




                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值