集册 Java 精简入门教程 构建 Java 应用程序

构建 Java 应用程序

欢马劈雪     最近更新时间:2020-08-25 03:35:49

572

在本节中,我们继续将 Person 构建为 Java 应用程序。在此过程中,您可更好地了解一个对象或对象集合如何演变成应用程序。

Java 应用程序入口点

所有 Java 应用程序都需要一个入口点,这样 Java 运行时才能知道从这里开始执行代码。这个入口点就是 main() 方法。 域对象 —— 也就是说,作为应用程序 业务域 一部分的对象(例如 PersonEmployee)—— 通常没有 main()方法,但每个应用程序至少有一个类拥有该方法。

如您所知Person 及其 Employee 子类在概念上是人力资源应用程序的一部分。现在,您将向应用程序添加一个新类,以给它一个入口点。

创建一个 Driver 类

顾名思义, Driver 类 的用途是”驱动”一个应用程序。注意,人力资源应用程序的这个简单 Driver 包含一个 main() 方法:

package com.nowjava.intro;
public class HumanResourcesApplication {
  public static void main(String[] args) {
  }
}

在 Eclipse 中创建 Driver 类的过程与创建 PersonEmployee 的过程相同。将该类命名为 HumanResourcesApplication,确保选择了相关的选项,向该类添加一个 main() 方法。Eclipse 将为您生成该类。

然后,将一些代码添加到新的 main() 方法中,使其类似于下面这段代码:

package com.nowjava.intro;
import java.util.logging.Logger;

public class HumanResourcesApplication {
  private static final Logger log = Logger.getLogger(HumanResourcesApplication.class.getName());
  public static void main(String[] args) {
    Employee e = new Employee();
    e.setName("J Smith");
    e.setEmployeeNumber("0001");
    e.setTaxpayerIdentificationNumber("123-45-6789");
    e.setSalary(BigDecimal.valueOf(45000.0));
    e.printAudit(log);
  }
}

现在,启动 HumanResourcesApplication 类并观察它的运行情况。您应看到此输出(反斜杠表示一个连续行):

Sep 19, 2015 7:59:37 PM com.nowjava.intro.Person printAudit
INFO:Name=J Smith,Age=0,Height=0,Weight=0,EyeColor=null,Gender=null\
TaxpayerIdentificationNumber=123-45-6789,EmployeeNumber=0001,Salary=45000.00

这就是创建一个简单 Java 应用程序的全部过程。在下一部分中,将介绍一些可帮助您开发更复杂应用程序的语法和库。

继承

您在本x系列中已遇到过多个继承示例。本节将更详细地解释继承的工作原理 — 包括继承分层结构、构造函数和继承,以及继承抽象。

继承的工作原理

Java 代码中的各个类位于分层结构中。分层结构中一个给定类上方的类是该类的 超类。这个特定的类是该分层结构更高层中每个类的 子类。子类继承其超类的属性和行为。java.lang.Object 类位于类分层结构的顶部,意味着每个 Java 类都是 Object 的子类并继承其属性和行为。

例如,假设有一个与清单 14 的内容类似的 Person 类。

清单 14. 公共 Person
public class Person {

  public static final String STATE_DELIMITER = "~";

  public Person() {
    // Default constructor
  }

  public enum Gender {
    MALE,
    FEMALE,
    UNKNOWN
  }

  public Person(String name, int age, int height, int weight, String eyeColor, Gender gender) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
    this.eyeColor = eyeColor;
    this.gender = gender;
  }

  private String name;
  private int age;
  private int height;
  private int weight;
  private String eyeColor;
  private Gender gender;

清单 14 中的 Person 类隐式地继承了 Object。因为我们会假定每个类都继承 Object,所以不需要为您定义的每个类键入 extends Object。但说一个类继承它的超类是什么意思?它表示 Person 能够访问其超类中已公开的变量和方法。在本例中,Person 可查看和使用 Object 的公共方法和变量,以及 Object 的受保护方法和变量。

定义一个类分层结构

现在假设有一个继承 PersonEmployee 类。Employee 的类定义(或 继承图 )类似于这样:

public class Employee extends Person {

  private String taxpayerIdentificationNumber;
  private String employeeNumber;
  private BigDecimal salary;
  // ...
}

Employee 继承图暗示,Employee 能够访问 Person 中的所有公共和受保护变量和方法(因为 Employee 直接扩展 Person ),以及 Object 中的公共和受保护变量和方法(因为 Employee 实际上也扩展了 Object ,尽管是间接扩展)。但是,因为 EmployeePerson 都位于同一个包中,所以 Employee 也能访问 Person 中的 package-private (有时称为 友好 )变量。

要想深入了解类分层结构,可创建第三个扩展 Employee 的类:

public class Manager extends Employee {
  // ...
}

在 Java 语言中,任何类可拥有最多 1 个超类,但一个类可拥有任意个子类。这是 Java 语言继承分层结构方面要记住的最重要一点。

单继承与多继承

像 C++ 这样的语言支持 多继承 的概念:在层次结构中的任何一点,一个类可以直接从一个或多个类继承。Java 语言只支持 单继承 — 这意味着只能使用单个类的 extends 关键字。因此,任何 Java 类的类层次结构始终包含一条直线,一直到 java.lang.Object。但是,正如您将在下一个主要部分 —接口中学习的那样,Java 语言支持在一个类中实现多个接口,提供您一种实现单一继承的的处理方法。

构造函数和继承

构造函数不是完整的面向对象成员,所以它们不是继承的;您必须在子类中明确地实现它们。介绍该主题之前,我要回顾一下有关如何定义和调用构造函数的一些基本规则。

构造函数基础知识

记住,一个构造函数的名称始终与它用于构造的类相同,而且它没有返回类型。例如:

public class Person {
  public Person() {
  }
}

每个类至少拥有一个构造函数,而且如果没有明确地为类定义构造函数,那么编译器会生成一个(称为 默认构造函数 )。前面的类定义和这个类定义具有相同的功能:

public class Person {
}

调用一个超类构造函数

要调用一个超类构造函数,而不是默认的构造函数,也必须明确地这么做。例如,假设 Person 拥有一个构造函数,该函数仅接受所创建的 Person 对象的名称。通过 Employee 的默认构造函数,您可调用 Person 构造函数,如清单 15 所示:

清单 15. 初始化一个新的 Employee
public class Person {
  private String name;
  public Person() {
  }
  public Person(String name) {
    this.name = name;
  }
}

// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
    super("Elmer J Fudd");
  }
}

但是,您可能绝对不想以这种方式初始化一个新的 Employee 对象。更熟悉面向对象的概念和一般性的 Java 语法之前,如果认为需要超类构造函数,一个不错的想法是在子类中实现它们。清单 16 在 Employee 中定义了一个与 Person 中的构造函数相似的构造函数,以便它们匹配。从维护的角度讲,这种方法要清楚易懂得多。

清单 16. 调用一个超类
public class Person {
  private String name;
  public Person(String name) {
    this.name = name;
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee(String name) {
    super(name);
  }
}

声明一个构造函数

构造函数所做的第一件事就是调用其直接超类的默认构造函数,除非您 — 在该构造函数的第一行代码中 — 调用了一个不同的构造函数。例如,这两种声明具有相同的功能:

public class Person {
  public Person() {
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
  }
}

或者:

public class Person {
  public Person() {
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
  super();
  }
}

无参数构造函数

如果您提供了一个替代的构造函数,就必须明确地提供默认构造函数;否则后者将不可用。例如,以下代码会出现编译错误:

public class Person {
  private String name;
  public Person(String name) {
    this.name = name;
  }
}
// Meanwhile, in Employee.java
public class Employee extends Person {
  public Employee() {
  }
}

此示例中的 Person 类没有默认构造函数,因为它提供了一个替代性的构造函数,但没有明确地包含默认构造函数。

构造函数如何调用构造函数

一个构造函数可通过 this 关键字和一个参数列表来调用另一个类中的构造函数。像 super() 一样,this() 调用必须是构造函数中的第一行代码。例如:

public class Person {
  private String name;
  public Person() {
    this("Some reasonable default?");
  }
  public Person(String name) {
    this.name = name;
  }
}

您会经常看到此用法,其中一个构造函数委托给另一个构造函数,如果调用后者,则传入一个默认构造函数。向一个类添加一个新构造函数,同时最大限度减少对已使用旧构造函数的代码的影响,这也是一种不错的方式。

构造函数访问级别

构造函数可拥有您想要的任何访问级别,并且会应用一些可见性规则。表 1 总结了构造函数访问规则。

表 1. 构造函数访问规则
构造函数访问修饰符描述
public任何类都可调用构造函数。
protected只能由同一个包或任何子类中的类调用构造函数。
无修饰符( package-private同一个包中的任何类都可调用构造函数。
private只能由定义构造函数的类调用该构造函数。

您可能想到了将构造函数声明为 protected 甚至是 package-private 的用例,不过 private 构造函数有何用处?比如说在实现 工厂模式 时,我不允许通过 new 关键字直接创建对象,此时就使用了私有构造函数。在这种情况下,我会使用一个静态方法来创建类的实例,而且该方法 — 包含在该类中 — 将允许调用这个私有构造函数。

继承和抽象

如果一个子类覆盖了超类中的一个方法,该方法在本质上是隐藏的,因为通过对子类的引用来调用它时,会调用该方法的子类版本,而不是超类版本。这并不是说,无法再访问超类方法。子类可在方法名称前添加 super 关键字来调用超类方法(与构造函数规则不同,这可从子类方法的任何行中执行,甚至可在一个不同的方法内执行)。默认情况下,如果通过对子类的引用来调用子类方法,那么 Java 程序会调用它。

这同样适用于变量,前提是调用方能够访问该变量(也就是变量对尝试访问它的代码是可见的)。随着您逐渐精通 Java 编程,此细节可能会让您非常伤心。但是,Eclipse 提供了大量的警告来提示您正在隐藏来自超类的变量,或者方法调用没有调用您认为它应调用的实体。

在 OOP 上下文中, 抽象 是指将数据和行为一般化为某种类型,这种类型在继承分层结构中比当前类具有更高的层级。将变量或方法从一个子类移动到一个超类时,就可以说是在 抽象化 这些成员。这么做的主要目的是,通过将通用的代码推送到分层结构中尽可能高的层级,从而重用这些代码。将通用的代码放在一个位置也更容易维护。

抽象类和方法

有时,您希望创建一些仅用作抽象,而不是必需实例化的类。这些类称为 抽象类。出于同样的原因,有时需要以不同的方式为每个实现超类的子类实现某些方法。这些方法是 抽象方法。以下是抽象类和抽象方法的一些基本规则:

  • 可将任何类声明为 abstract
  • 无法实例化抽象类。
  • 抽象方法不能包含一个方法主体。
  • 任何包含抽象方法的类都必须声明为 abstract

使用抽象

假设您不允许直接实例化 Employee 类。使用 abstract 关键字声明它即可:

public abstract class Employee extends Person {
  // etc.
}

如果尝试运行此代码,会出现一个编译错误:

public void someMethodSomwhere() {
  Employee p = new Employee();// compile error!!
}

编译器抱怨 Employee 是抽象的,因此无法实例化。

抽象的力量

展开阅读全文