3.1 类与对象

第2章介绍了基本数据类型的使用,这些类型可以处理整数、浮点数、字符和布尔类型的数据。而类(class)则是一种更加复杂的数据类型,它主要包括两种成员,即字段(field)和方法(method)。其中,字段用来存储数据,方法则定义数据的一系列操作。

在Java中,定义类需要使用class关键字。下面通过项目资源管理器中的“源包”右键菜单“新建”→“Java类”项添加一个新的类,如图3-1所示。

本例中,将新建的类命名为CAuto。然后,修改CAuto.java文件的内容,如下所示。

图3-1 添加Java类

代码中,CAuto类定义了两个字段和一个方法,分别是:

□ model字段,表示车的型号,定义为String类型,默认为空字符串。

□ doors字段,表示车门数量,定义为int类型,默认为4。

□ moveTo()方法,用于显示车的移动信息。

String是什么?它也是一个类,这里暂时当作简单的字符串类型使用就可以了。字符串又是什么?可以把它视为文本内容,还要使用一对双引号定义。

代码中使用了String.format()方法,它的功能是将各种类型的数据组合为字符串形式,先照样子敲代码就可以了,第6章会详细讨论字符串的应用。

回到CAuto类,应该如何使用它呢?首先,CAuto是一个类型,可以定义此类型的“变量”,也就是CAuto类的实例(instance)。下面的代码定义了CAuto类型的变量auto。

    CAuto auto;

这里,auto称为CAuto类的一个实例,或者CAuto类型的对象,可以简称为auto对象。不过,auto对象暂时还不能使用,因为它还没有实例化,其值默认为null。

实例化一个对象时,需要使用new关键字,如下面的代码所示。

    CAuto auto = new CAuto();

接下来,就可以使用auto对象了,如下面的代码所示。

    public static void main(String[] args) {
    CAuto auto = new CAuto();
    auto.model = "X9";
    auto.moveTo(10, 99);
    }

代码中,通过圆点运算符(.)调用对象的字段和方法,首先将model字段的值设置为X9,然后调用moveTo()方法。执行代码,可以看到如图3-2所示的结果。

此外,注意,main()是程序的入口方法,它定义在应用的主类中。

图3-2 使用CAuto类的实例

3.1.1 构造函数与对象释放

再看一下auto对象的实例化代码。

    CAuto auto = new CAuto();

代码中的CAuto()是方法吗?好像是,不过,这可不是一般的方法,而是在调用CAuto类的构造函数,但并没有定义这个构造函数。

实际上,如果没有在类中没有定义构造函数,则会包含一个空的构造函数。当然,也可以自己创建构造函数。下面的代码在CAuto类中添加一个构造函数。

构造函数虽然看上去和方法差不多,但它的名称与类名相同,而且不需要定义返回值类型,如moveTo()方法中关于void关键字的部分。关于返回值的更多内容,3.2节会详细讨论。

实际开发中,一个类还可以有多个构造函数,只要它们的参数设置能够有效区分就可以。下面的代码在CAuto类中创建三个构造函数。

细心的读者可能会发现一些小问题,例如,这三个构造函数之间并没有什么联系,而且在两个构造函数中出现了重复的代码。这也许不是什么大问题,但还有机会改进代码。下面的代码就是CAuto.java文件修改后的全部代码。

第一个构造函数中使用了两个参数,分别指定model和doors字段的值。重点在接下来的两个构造函数中,首先看下面的版本。

    public CAuto(String m){
    this(m, 4);
    }

当看到this关键字时,应该想到当前实例(对象),而这里就是在调用CAuto(String m, int doors)构造函数,其中将车门数设置为4。最后构造函数就比较好理解了,它调用CAuto(String m)版本的构造函数。

实际上,这三个构造函数组成一个构造函数链,通过这种方法可以减少重复代码,提高代码维护效率。

下面的代码分别使用这三个构造函数创建对象。

    public static void main(String[] args) {
        CAuto auto1 = new CAuto();
        CAuto auto2 = new CAuto("X9");
        CAuto auto3 = new CAuto("XX", 2);
        System.out.println(auto3.doors);
    }

图3-3 调用不同的构造函数

代码执行结果如图3-3所示。

通过构造函数,可以进行对象的初始化操作,那么,当对象不再使用时应该怎么做呢?实际上,Java运行环境可以自动回收不再使用的对象,大多情况下并不需要开发者编写代码进行处理。不过,当对象中使用了一些外部资源时,就应该保证这些资源能够正确地关闭,例如,打开文件并进行读写操作后,就应该及时关闭文件。

3.1.2 getter()和setter()方法

这里并不是要创建名为getter()和setter()的方法,而是通过这两种方法控制字段数据的读取和设置操作。

还以CAuto类为例,看一下doors字段的使用,如下面的代码所示。

    CAuto auto = new CAuto("X9");
    auto.doors = -2;
    System.out.println(auto.doors);

本例中的车门数量设置为-2,难道是在平行宇宙中?还是回到现实世界中,要控制车门数量的设置操作。

对于这样的问题,可以使用getter()和setter()方法来解决。首先,将字段设置为私有的(private),这样就不能在类的外部访问它。然后,使用setXXX()方法设置字段数据,使用getXXX()方法返回字段数据。

下面的代码展示在CAuto类中修改doors字段的方式。

代码中所做的修改包括以下几个。

□ 将doors字段的public修饰符更改为private,稍后会讨论这两个修饰符的区别。

□ 添加setDoors()方法来设置车门数量,其中,当指定的数据在2到5之间时,就修改doors字段的值,否则使用默认的4门。

□ 添加getDoors()方法来获取车门数量,其中使用return语句返回doors字段的值即可。

□ 构造函数中,对于车门数量的设置,改用setDoors()方法来实现。

下面的代码测试与车门数量相关的操作。

    public static void main(String[] args) {
        CAuto auto = new CAuto("X9", 6);
        System.out.println(auto.getDoors());
        auto.setDoors(2);
        System.out.println(auto.getDoors());
    }

图3-4 使用setter和getter方法

代码执行结果如图3-4所示。

示例中,首先使用构造函数设置车门数量为6,可以看到,实际上doors设置为4。当使用setDoors()方法设置车门数量为2时,doors字段的值才会正确设置。

实际应用中,可以通过setter()方法控制数据的正确性,设置的数据有问题时,可以使用一个默认值,如前面的示例中那样。当然,如果数据无效,也可以抛出一个异常(Exception),让对象的使用者来处理,第5章会讨论异常处理的相关内容。

此外,如果一些数据不需要在类的外部设置,只允许读取,可以只定义getter()方法。

3.1.3 静态成员与静态初始化

前面,在CAuto类中创建的字段和方法,都必须使用CAuto类的实例(对象)来访问,它们称为类的实例成员。开发中,使用static关键字,还可以将成员定义为静态成员,静态成员可以使用类的名称直接访问。

下面的代码(CAutoFactory.java文件)创建了一个汽车工厂类。

在CAutoFactory类中,定义了三个静态成员,分别如下所示。

□ counter字段,生产计数器,表示工厂生产了多少汽车,它定义为私有的,只能在类的内部自动处理。

□ getCounter()方法,以只读方式返回生产计数器的值。

□ createSuv()方法,用于创建SUV车型。

下面的代码测试CAutoFactory类的使用。

    public static void main(String[] args) {
        CAuto suv1 = CAutoFactory.createSuv();
        CAuto suv2 = CAutoFactory.createSuv();
        System.out.println("汽车生产数量为" + CAutoFactory.getCounter());
    }

图3-5 使用静态成员

代码执行结果如图3-5所示。

示例中,使用CAutoFactory类直接调用createSuv()方法,每一次执行后,counter的值都会加1。所以,当创建两辆SUV汽车对象后,CAutoFactory.getCounter()方法返回的数据就是2。

如果需要对静态成员进行初始化,还可以在类中使用static语句定义一个结构来完成,结构中的代码会在第一次调用静态成员时执行一次。下面的代码在CAutoFactory类中添加一个静态初始化结构。

图3-6 调用静态初始化结构

再次执行代码,可以看到如图3-6所示的结果。

本例中,虽然代码中多次调用了CAutoFactory类中的静态成员,但静态初始化代码只会执行一次。

此外,使用汽车工厂生产汽车的方法是否使代码更加直观呢?实际上,这里使用了一种比较常用的代码结构,它的名称正是“工厂方法”。