Skip to content

3. [TD]:类

关键词:类、接口、继承、异常、多态

推荐阅读

  • [ref1]:类与接口一书第2章的第2.1、2.2、2.4和2.7节
  • 第3.3节(String类)、第3.5节(ArrayList类)、第3.6节(Arrays类)

在练习 ELECTIONS 的第 1 部分中,未使用任何类。我们构建了一个解决方案,就像在 C 语言中构建的那样。现在,我们将引入 Java 类的概念。

3.1. Support

 

文件夹 [support / chap-03] 包含本章的 Eclipse 项目。

接下来我们将使用 JDK 1.8 版本,因为后续的一些项目需要该版本。若需了解当前使用的 JDK 版本,请按以下步骤操作:

  • 在 [4] 中,所使用的 JRE(Java 运行时环境)。 此处的 JRE 实际上是 JDK(Java 开发工具包),此处为 [jdk1.8.0_60]。 如果不是 JDK,或者您的版本低于 1.8,请按以下步骤操作:[5-21];
  • 为 [8],即 Eclipse 当前默认使用的 JRE;
  • 将 [11] 替换为 Eclipse 当前已知的 JDK 和 JRE;
  • 在 [15] 中,请选择 JDK 而不是 JRE。本文档使用的 Maven 项目需要 JDK;
  • 在 [21] 中,有一个版本 >=1.8 的 JDK;
  • 在 [22-23] 中,访问该项目的切面(同一 Eclipse 项目的不同视图);
  • 在 [24] 中,请确认您使用的 Java 版本 >=1.8;

3.2. 类 [ListeElectorale]

在C语言中,我们可能会使用一个结构体来表示参与选举的名单。它可以采用以下形式:

struct t_liste
   {
     char nom[15];
     long voix;
     int  elimine;
     int sieges;
   };

Java语言中并不存在“结构”这一概念。必须用“类”来代替它。因此,我们决定创建一个类来存储候选列表的信息。该类的框架如下:


package istia.st.elections;

public class ListeElectorale {

    /**
     * identité de la liste
     */
    private int id;

    /**
     * nom de la liste
     */
    private String nom;
    /**
     * nombre de voix de la liste
     */
    private int voix;
    /**
     * nombre de sièges de la liste
     */
    private int sieges;
    /**
     * indique si la liste est éliminée ou non
     */
    private boolean elimine;

    /**
     * constructeur par défaut
     */
    public ListeElectorale() {
    }

    /**
     *
     * @param nom String : le nom de la liste
     * @param voix int : son nombre de voix
     * @param sieges int : son nombre de sieges
     * @param elimine boolean : son état éliminé ou non
     */
    public ListeElectorale(int id,String nom, int voix, int sieges, boolean elimine) {
...
    }

    /**
     *
     * @return int : l'identifiant de la liste
     */
    public int getId() {
...
    }

    /**
     * initialise l'identifiant de liste
     * @param id int : identifiant de la liste
     * @throws ElectionsException si id<1
     */
    public void setId(int id) {
...
    }

    /**
     *
     * @return String : le nom de la liste
     */
    public String getNom() {
...
    }

    /**
     * initialise le nom de la liste
     * @param nom String : nom de la liste
     *  @throws ElectionsException si le nom est vide ou blanc
     */
    public void setNom(String nom) {
...
    }

    /**
     *
     * @return int : le nombre de voix de la liste
     */
    public int getVoix() {
 ...
    }

    /**
     * initialise le nombre de voix de la liste
     * @param voix int : le nombre de voix de la liste
     */
    public void setVoix(int voix) {
 ...
    }

    /**
     *
     * @return int : le nombre de sièges de la liste
     */
    public int getSieges() {
 ...
    }

    /**
     * fixe le nombre de sièges de la liste
     * @param sieges int : le nombre de sièges de la liste
     */
    public void setSieges(int sieges) {
...
    }

    /**
     *
     * @return boolean : valeur du champ elimine
     */
    public boolean isElimine() {
  ...
    }

    /**
     *
     * @param sieges int
     */
    public void setElimine(boolean elimine) {
 ...
    }

    /**
     *
     * @return String : identité de la liste électorale
     */
    public String toString() {
   ...
    }
}
  • 第 8 行:唯一标识列表的编号。此处并非必需,但为将来使用预留。
  • 第13行:候选名单的名称。
  • 第17行:该名单的得票数
  • 第21行:该名单获得的席位数
  • 第25行:布尔值,表示该名单是否被淘汰(得票率低于选举门槛)。

每个名为 [xyz] 的私有字段均可通过名为 [setXyz] 的方法进行初始化。而 [getXyz] 方法则用于获取私有字段 [xyz] 的值。 在特殊情况下,若 [xyz] 是一个布尔型字段,则方法 [getXyz] 可以被方法 [isXyz] 替代。 这些方法的特定命名遵循一项名为 JavaBean 的编码标准。因此,我们定义了以下公共方法:

  • getId(第 48 行)、setId(第 57 行)
  • getNom(第65行)、setNom(第74行)
  • getVoix(第82行),setVoix(第90行)
  • getSieges(第 98 行)、setSieges(第 106 行)
  • isElimine(第 114 行),setElimine(第 122 行)
  • 第 30-31 行:定义了一个无参数的构造函数。该构造函数允许创建一个 [ListeElectorale] 对象而不对其进行初始化。随后可通过 set 方法对其进行初始化。
  • 第 40-42 行:定义了一个构造函数,用于创建 [ListeElectorale] 对象,同时初始化其五个私有字段。
  • 第130-132行:定义了[toString]方法,该方法返回一个字符串,其中包含对象五个字段的值。

ListeElectorale类的测试程序可能如下:


package istia.st.elections.tests;

import istia.st.elections.ListeElectorale;

public class MainTest1ListeElectorale {
    public static void main(String[] args) {
        // 创建选民名册
        ListeElectorale listeElectorale1 = new ListeElectorale(1, "A", 32000,
                0, false);
        // 显示名单信息
        System.out.println("listeElectorale1=" + listeElectorale1);
        // 修改席位数量
        listeElectorale1.setSieges(2);
        // 显示名单1的身份信息
        System.out.println("listeElectorale1=" + listeElectorale1);
        // 新建选民名册
        ListeElectorale listeElectorale2 = listeElectorale1;
        // 显示名单2信息
        System.out.println("listeElectorale2=" + listeElectorale2);
        // 席位数量变更
        listeElectorale2.setSieges(3);
        // 显示两个名单的身份信息
        System.out.println("listeElectorale2=" + listeElectorale2);
        System.out.println("listeElectorale1=" + listeElectorale1);
    }
}

该测试的 Eclipse 环境可能如下所示:

  • [1]:该项目名为 [elections-02A]
  • [2]:应用程序将被放置在一个包中,此处为 [istia.st.elections]
  • [3]:[ListeElectorale.java]是类[ListeElectorale]的源代码
  • [4]:测试类将被放置在一个包中,此处为 [istia.st.elections.tests]
  • [5]:测试类 [MainTest1ListeElectorale]

执行上述程序后,屏幕显示如下:

Image


待完成任务:参考上述内容,补全类 ListeElectorale 的代码。


3.3. 创建异常类 [ElectionsException]

在Java语言的各种异常类中,有一个名为[RuntimeException]的类。该类继承自[Exception]类,后者是所有异常类的根类。 [RuntimeException] 实例或其派生实例的特殊之处在于,无需对其进行声明或处理。这类异常被称为“非受控异常”

让我们来看一个简单的示例。类 [BufferedReader] 的实例可用于从数据流中读取文本行。该类拥有一个方法 [readLine],其签名如下:

public String readLine()throws IOException

可以看出,该方法可能抛出类型为 [IOException] 的异常。该类的结构树如下:

1
2
3
4
java.lang.Object
  java.lang.Throwable
      java.lang.Exception
          java.io.IOException

类 [IOException] 继承自类 [Exception](第 3 行)。 编译器强制我们处理并声明 [java.lang.Exception] 类型或其派生类型的异常([RuntimeException] 分支除外,我们稍后将介绍该分支)。因此,要读取键盘输入的一行文本,我们必须编写类似以下代码:

1
2
3
4
5
6
7
8
BufferedReader clavier=....;
String ligne=null;
try{
    ligne=clavier.readLine();
}catch (IOException ex){
     // 处理异常
    ....
}

再举一个例子。要将字符串转换为整数,可以使用静态方法 [Integer.parseInt],其签名如下:

public static int parseInt(String s) throws NumberFormatException

参数 [s] 即是要转换为整数的字符串。可以看到该方法可能抛出类型为 [NumberFormatException] 的异常。该类的继承关系如下:

1
2
3
4
5
6
java.lang.Object
  java.lang.Throwable
      java.lang.Exception
          java.lang.RuntimeException
              java.lang.IllegalArgumentException
                  java.lang.NumberFormatException

类 [NumberFormatException] 继承自类 [RuntimeException](第 4 行)。编译器并不强制我们处理和声明类型为 [java.lang.RuntimeException] 或其派生类的异常。因此,我们可以编写如下代码:

1
2
3
4
5
6
7
8
9
BufferedReader clavier=....;
String ligne=null;
try{
    ligne=clavier.readLine();
}catch (IOException ex){
     // 处理异常
    ....
}
int age=Integer.parseInt(ligne);

我们无需添加 [try - catch] 子类来处理 [Integer.parseInt](第 9 行)可能抛出的异常。

创建和使用从 [RuntimeException] 派生的异常类各有优缺点:

  • 优点方面:代码更简洁
  • 缺点方面:这可能会让人回想起 C 语言的编程方式,即每个函数都返回一个错误代码,而很少有人使用这种方式,正是为了使代码更简洁。当发生此类未处理的错误时,程序会崩溃,通常以一种不太优雅的方式。

我们决定创建一个特殊类,将应用程序 ELECTIONS 中可能发生的所有异常归入其中。该类将命名为 [ElectionsException],并继承自 [RuntimeException] 类。其代码如下:


package istia.st.elections;

public class ElectionsException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    public ElectionsException() {
        super();
    }

    public ElectionsException(String message) {
        super(message);
    }

    public ElectionsException(Throwable cause) {
        super(cause);
    }

    public ElectionsException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 第1行:我们将该类放置在[istia.st.elections]包中;
  • 第3行:该类继承自[RuntimeException]。因此,它属于未受控类
  • 第 4 行:一个序列化标识符,目前可以忽略;
  • 在我们的应用程序中,我们将使用两种类型的构造函数:
    • 如下所示,第15-17行中的经典构造函数:
throw new ElectionsException("Le nombre de sièges doit être >0")

在这种情况下,调用会抛出此类异常的方法可按以下方式进行处理:


        // 异常测试
        try {
            listeElectorale2.setSieges(-3);
        } catch (ElectionsException ex) {
            System.err.println("L'exception suivante s'est produite : ["
                    + ex.toString() + "]");
        }
  • (续)
    • 或者第14-20行用于上报已发生的异常,将其封装为类型为[ElectionsException]的异常:

    try {
        ...;
        } catch (SQLException ex) {
            // 封装异常
            throw new ElectionsException("erreur de fermeture de la connexion à la BD",ex);
        }

这种第二种方法的优势在于保留了第一个异常可能包含的信息。在这种情况下,调用会抛出此类异常的方法可以按以下方式处理它:


        try {
            ...;
        } catch (ElectionsException ex) {
            System.out.println(ex.getMessage() + ", Cause : "+ ex.getCause().getMessage());
            System.exit(1);
        }

待完成的工作:修改类 ListeElectorale 的代码,使得当请求的初始化不正确时(例如将名称初始化为空字符串),set 方法会抛出类型为 [ElectionsException] 的异常。


此新版本的测试Eclipse项目可能如下所示:

  • [1]:该项目名为 [elections-02B]
  • [2]:应用程序位于一个包中,此处为 [istia.st.elections]
  • [3]:类 [ListeElectorale] 和 [ElectionsException]
  • [4]:测试类被放置在一个包中,此处为 [istia.st.elections.tests]
  • [5]:测试类 [MainTest1ListeElectorale]

已研究过的测试类 [MainTest1ListeElectorale] 经过微调,用于测试异常情况:


package istia.st.elections.tests;

import istia.st.elections.ElectionsException;
import istia.st.elections.ListeElectorale;

public class MainTest1ListeElectorale {
    public static void main(String[] args) {
        // 创建选民名册
        ListeElectorale listeElectorale1 = new ListeElectorale(1, "A", 32000,
                0, false);
        // 显示名单信息
        System.out.println("listeElectorale1=" + listeElectorale1);
        // 修改席位数量
        listeElectorale1.setSieges(2);
        // 显示名单1的身份信息
        System.out.println("listeElectorale1=" + listeElectorale1);
        // 新建选民名册
        ListeElectorale listeElectorale2 = listeElectorale1;
        // 显示名单2信息
        System.out.println("listeElectorale2=" + listeElectorale2);
        // 席位数量变更
        listeElectorale2.setSieges(3);
        // 显示两个名单的身份信息
        System.out.println("listeElectorale2=" + listeElectorale2);
        System.out.println("listeElectorale1=" + listeElectorale1);
        // 异常测试
        try {
            listeElectorale2.setSieges(-3);
        } catch (ElectionsException ex) {
            System.err.println("L'exception suivante s'est produite : ["
                    + ex.toString() + "]");
        }

    }
}
  • 第28行:尝试用一个被禁止的值初始化座位数
  • 第30行:若发生异常,则显示异常信息

测试执行结果如下:

Image

可以发现,当尝试使用无效值初始化座位数时(代码第28行),类[ListeElectorale]确实抛出了异常。

3.4. 单元测试类

前面的测试类型依赖于目视检查。我们通过查看屏幕上显示的内容是否符合预期来进行验证。这种方法在专业环境中并不推荐。测试应尽可能实现自动化,并力求无需人工干预。毕竟,人会感到疲劳,其验证测试的能力会随着一天的推移而逐渐减弱。

应用程序会随着时间推移而演进。每次更新后,都必须验证应用程序是否“退化”,c.a.d,即它是否仍能通过最初编写时所做的功能测试。这类测试被称为“回归测试”。稍具规模的应用程序可能需要数百个测试。 实际上,我们会测试应用程序中每个类的每个方法。这被称为单元测试。如果这些测试未实现自动化,可能会占用大量开发人员的时间。

为此开发了多种测试自动化工具。其中一款名为 [JUnit],这是一个用于管理测试的类库。我们将使用该工具对 [ListeElectorale] 类进行测试。

一个名为 JUnit(版本号 4.x)的测试程序结构如下:


package istia.st.elections.tests;

import org.junit.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JUnitEssai {

    @Before
    public void avant() throws Exception {
        System.out.println("tearUp");
    }

    @After
    public void après() throws Exception {
        System.out.println("tearDown");
    }

    @Test
    public void t1() {
        System.out.println("test1");
        Assert.assertEquals(1, 1);
    }

    @Test
    public void t2() {
        System.out.println("test2");
        Assert.assertEquals(1, 2);
    }

}
  • 第 1 行:该类位于 [istia.st.elections.tests] 包中;
  • 第 11 行:标注为 [@Before] 的方法将在每次单元测试之前执行;
  • 第 16 行:标注为 [@After] 的方法将在每次单元测试后执行;
  • 第 21 行:带有注解 [@Test] 的方法是单元测试所测试的方法。带有注解 [@Test] 的方法将依次执行,除非测试人员另有指定,测试人员可以自行选择要测试的方法。 每次执行 [@Test] 方法之前,都会先执行 [@Before] 方法。每次执行 [@Test] 方法之后,都会先执行 [@After] 方法;
  • 第 22-25 行:定义了一个名为 [t1] 的测试方法;
  • 第 18 行:[Assert.assert*] 方法之一,用于验证断言。该类包含以下 [assert] 方法:
    • assertEquals(表达式1, 表达式2):验证两个表达式的值是否相等。支持多种表达式类型(int、String、float、double、boolean、char、short)。如果两个表达式不相等,则抛出类型为 [AssertionFailedError ] 的异常,
    • assertEquals(实数1, 实数2, delta):验证两个实数是否在误差delta范围内相等,即c.a.d abs(实数1-实数2)<=delta。 例如,可以编写 assertEquals(实数1, 实数2, 1E-6) 来验证两个值是否在 10⁻⁶ 误差范围内相等,
    • assertEquals(message, expression1, expression2) assertEquals(message, 实数1, 实数2, delta) 是用于指定错误消息的变体,该消息将与 [AssertionFailedError] 类型的异常相关联,当 [assertEquals] 方法失败时会抛出该异常,
    • assertNotNull(Object) assertNotNull(message, Object):验证 Object 不为 null,
    • assertNull(Object) assertNull(message, Object):验证 Object 是否等于 null,
    • assertSame(Object1, Object2) assertSame(message, Object1, Object2):验证引用 Object1 Object2 是否指向同一个对象,
    • assertNotSame(Object1, Object2) assertNotSame(message, Object1, Object2):验证引用 Object1 Object2 是否指向不同的对象;
  • 第 24 行:此断言应通过;
  • 第 30 行:此断言应失败;

在 Eclipse 环境中,可以按以下方式创建测试类 JUnit:

  • [1]:右键单击要添加测试类的包,然后选择 [JUnit / New / JUnit Test Case]
  • [1]:选择一个版本 JUnit;
  • [2]:选择要创建测试类的文件夹;
  • [3]:选择要创建测试类的包;
  • [4]:测试类的名称;
  • [5]:选择要包含在将要生成的类中的方法;
  • [6]:已生成类 JUnitEssai

前一个向导生成的类几乎为空:


package istia.st.elections.tests;

import org.junit.Assert;
import org.junit.After;
import org.junit.Before;

public class JUnitEssai {

    @Before
    public void setUp() throws Exception {        
    }

    @After
    public void tearDown() throws Exception {
    }
}

让我们按以下方式补充并修改上述代码:


package istia.st.elections.tests;

import org.junit.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JUnitEssai2 {

    @Before
    public void avant() throws Exception {
        System.out.println("tearUp");
    }

    @After
    public void après() throws Exception {
        System.out.println("tearDown");
    }

    @Test
    public void t1() {
        System.out.println("test1");
        Assert.assertEquals(1, 1);
    }

    @Test
    public void t2() {
        System.out.println("test2");
        Assert.assertEquals(1, 2);
    }

}

在 Eclipse 中,右键单击测试类,然后选择 [Run as / JUnit test] 选项,即可执行该类:

Image

执行此测试后,结果如下:

Image

上图显示方法 [test2] 已失败。 每次测试失败时,都会关联一条错误消息。对于 [test2],即上文显示的那条。该消息指明了发生错误的行号(第 30 行)。第 30 行中,失败的调用是:


    Assert.assertEquals(1, 2);

第一个参数称为预期值,第二个参数称为实际值。上文提到的 [test2] 错误信息表明,预期值为 2,但实际值为 3。

最后,各种测试方法在控制台上输出的消息如下:

Image

这些消息表明,在每次测试方法执行之前和之后,[@Before] 和 [@After] 方法确实分别被调用。

测试类并不一定由开发人员亲自编写,也可以由编写应用程序规格说明的人员负责编写。 某些被称为 TDD(测试驱动开发)的开发方法主张,在编写待测试类之前先编写测试类。这有时有助于澄清那些否则可能被多义解释的规格说明。

现在,我们为类 [ListeElectorale] 创建一个名为 [JUnitTest1ListeElectorale] 的测试类。在 Eclipse 中,操作步骤如前所述:

我们按以下方式补充向导生成的代码:


package istia.st.elections.tests;

import org.junit.Assert;
import istia.st.elections.ElectionsException;
import istia.st.elections.ListeElectorale;

import org.junit.Test;

public class JUnitTest1ListeElectorale {

    @Test
    public void t1() {
        // 创建选民名册
        ListeElectorale liste = new ListeElectorale(1, "a", 32000, 0, false);
        // 验证
        Assert.assertEquals("a", liste.getNom());
        Assert.assertEquals(32000, liste.getVoix());
        Assert.assertEquals(false, liste.isElimine());
        Assert.assertEquals(0, liste.getSieges());
        // ID有效性验证
        boolean erreur = false;
        try {
            liste.setId(-4);
        } catch (ElectionsException e) {
            erreur = true;
        }
        Assert.assertEquals(true, erreur);
        // 姓名有效性验证
        erreur = false;
        try {
            liste.setNom("");
        } catch (ElectionsException e) {
            erreur = true;
        }
        Assert.assertEquals(true, erreur);
        // 选票有效性验证
        erreur = false;
        try {
            liste.setVoix(-4);
        } catch (ElectionsException e) {
            erreur = true;
        }
        Assert.assertEquals(true, erreur);
        // 席位有效性验证
        erreur = false;
        try {
            liste.setSieges(-4);
        } catch (ElectionsException e) {
            erreur = true;
        }
        Assert.assertEquals(true, erreur);
    }

}

运行测试后得到以下结果:

Image

测试成功。现在我们可以认为[ListeElectorale]类已可正常运行。

3.5. MainElections:版本 2

推荐阅读

  • [1]:类与接口一书第2章的第2.1、2.2、2.4和2.7节
  • 第 3.3 节(String 类)、第 3.5 节(ArrayList 类)、第 3.6 节(Arrays 类)

我们希望重写应用程序 [Elections],并添加以下新约束:

  • 将使用类 [ListeElectorale] 来表示候选列表
  • 应用程序将通过键盘请求以下信息:
  • 待填补的席位数量
  • 列表的名称和声音。事先并不知道有多少个列表。最后一个列表将通过名称为字符串“*”来标识。
  • 由于事先无法确定名单数量,这些名单将首先存储在一个类型为 [ArrayList] 的对象中。随后,当所有名单均已录入后,它们将被转移到一个名单数组中。
  • 结果将按获得席位数从高到低的顺序显示。

要对数组 T 进行排序,可以使用 [Arrays] 类的各种静态方法:

  • Arrays.sort(T):若数组 T 具有自然排序顺序(如数字按升序、日期按升序、字符串按字母顺序等),则按该顺序对其进行排序
  • Arrays.sort(T,comparateur):用于对没有自然排序顺序的数组 T 进行排序。本例中,待排序的列表数组需根据列表的特定字段(即获得的席位数)进行排序。

在方法 Arrays.sort(T,比较器) 中,参数“比较器”是一个实现了以下 Comparator 接口的对象:

Image

  • compare 方法用于比较数组 T 中的两个元素
  • equals 方法用于判断两个对象是否相等

这两个方法比较的是 Object 类型 obj1obj2。 究竟是 obj1<obj2obj1=obj2 还是 obj1>obj2 更大,取决于希望在两个对象之间建立的排序关系。具体如何判断:

  • obj1 小于 obj2
  • obj1 大于 obj2
  • obj1 等于 obj2

所有 Java 类都继承自 Object 类,该类已自带 [equals] 方法。若要对类型为 O 的对象数组 T 进行排序,O 类的 [equals] 方法并无用处。因此,可以保留 Object 类提供的默认实现。 此时只需实现 [compare] 方法。该方法会被 [Arrays.sort] 方法反复调用。后者每次都会将待排序数组 T 中的两个元素 obj1obj2 作为参数传递给 compare 方法。 在本例中,这些元素的类型为 [ListeElectorale]。此处需注意多态性的应用。方法 [compare] 被定义为接收 [Object] 类型的参数。 这意味着它可以接收类型为 [Object] 或其派生类型的参数(多态性)。由于 [Object] 是所有 Java 类的父类,因此实际参数可以是类型 [ListeElectorale]。

对于升序排序,方法 [compare] 应返回:

  • -1,若 obj1 小于 obj2
  • +1,若 obj1 大于 obj2
  • obj1 等于 obj2,则返回 0

若按降序排序,则 +1 和 -1 的值互换。 术语“小于”、“大于”、“等于”表达了一种排序关系。对于 [ListeElectorale] 类型的对象,若列表1的票数少于列表2列表1“小于”列表2

在与类 [MainElections] 相同的源文件中,可以添加第二个类:

// 选民名册比较类别
class CompareListesElectorales implements Comparator {

     // 根据得票数比较两份选民名册
    public int compare(Object obj1, Object obj2) {
         // 获取选民名册
        ListeElectorale listeElectorale1 = (ListeElectorale) obj1;
        ListeElectorale listeElectorale2 = (ListeElectorale) obj2;
         // 比较这两份选民名册的得票数
....        
    }
}
  • 第 2 行:该类未声明为 public。在 Java 源文件中,可以包含多个类,但只有一个类可以具有 public 属性,即与源文件同名的类。

在之前的 compare 方法中, 参数类型为 Object,,这导致第 7 行和第 8 行必须将方法参数从类型 Object 转换为类型 ListeElectorale。 方法 compare 的签名由接口 Comparator 强制规定,该接口是为比较任意对象而编写的。 自 JDK 1.5 版本起,存在一个通用的 Comparator 接口:Comparator<T>,其中 T 是任意 Java 类型。 接口 Comparator<T> 中的方法 compare 用于比较 T 类型的对象,而非 Object 类型的对象,从而避免了之前的类型转换。 用于比较 ListeElectorale 类型对象的类可能如下所示:


// 选民名单比较类
class CompareListesElectorales implements Comparator<ListeElectorale> {

    // 根据席位数量比较两份选民名单
    public int compare(ListeElectorale listeElectorale1,
            ListeElectorale listeElectorale2) {
...
    }
}
  • 第 2 行:该类实现了 Comparator<ListeElectorale> 接口
  • 第 5-6 行:方法 compare 的参数类型为 ListeElectorale。现在不再需要类型转换。

JDK 1.5 引入了泛型类/接口的概念,适用于 JDK 1.4 中的多种类/接口,这些类/接口最初仅处理 Object 类型的对象。列表、字典等便是如此。

我们在前文提到,由于无法预知列表的数量,因此无法将其存储在数组中。这些列表可以存储在一个实现“对象列表”概念的 ArrayList 对象中。 该类存储 Object 类型的对象。自 JDK 1.5 版本起,已支持类型化对象列表。 因此,在将列表传输到数组之前,将使用 ArrayList<ListeElectorale> 对象来存储这些列表。如果该数组名为 tListes,则可通过以下语句对其进行排序:


// 候选人名单排序
Arrays.sort(tListes, new CompareListesElectorales());

其中 CompareListesElectorales 是实现 Comparator<ListeElectorale> 接口的类。


待完成任务:根据这些新规范重写 [Elections] 应用程序。


Eclipse 项目可能如下所示:

[1] 的运行示例如下:

Image