2.7 介绍类和对象

我们已经介绍了运算符、表达式、语句和方法,终于可以介绍类了。类是一段代码的名称,其中包含很多保存数据值的字段和操作这些值的方法。是 Java 支持的五种引用类型之一,而且是最重要的一种。我们会在单独的一章(第 3 章)全面介绍类。这里之所以要介绍,是因为类是继方法之后的另一种高级句法,而且本章剩下的内容需要对类的概念有基本的认识,要知道定义类、实例化类和使用所得对象的基本句法。

关于类最重要的事情是,它们定义了一种新数据类型。例如,可以定义一个名为 Point 的类,表示笛卡尔二维坐标系中的数据点。这个类可能会定义两个字段,保存点的 xy 坐标,还可能会定义处理和操作点的方法。Point 类就是一个新数据类型。

谈论数据类型时,要把数据类型和数据类型表示的值区分开,这一点很重要。char 是一种数据类型,用于表示 Unicode 字符。但是一个 char 类型的值表示某个具体的字符。类是一种数据类型,而类表示的值是对象。我们使用“类”这个名称的原因是,每个类定义一种对象。Point 类是一种数据类型,用于表示 (x, y ) 点,而 Point 对象表示某个具体的 (x, y ) 点。正如你想得那样,类和类的对象联系紧密。在接下来的几节中,会介绍这两个概念。

2.7.1 定义类

前面讨论的 Point 类可以使用下面的方式定义:

  1. /** 表示笛卡尔坐标系中的(x,y)点 */
  2. public class Point {
  3. // 点的坐标
  4. public double x, y;
  5. public Point(double x, double y) { // 初始化字段的构造方法
  6. this.x = x; this.y = y;
  7. }
  8. public double distanceFromOrigin() { // 操作x和y字段的方法
  9. return Math.sqrt(x*x + y*y);
  10. }
  11. }

这个类的定义保存在一个名为 Point.java 的文件中,然后编译成一个名为 Point.class 的文件,供 Java 程序和其他类使用。现在定义这个类只是为了完整性,并提供上下文,不要奢望能完全理解所有细节。第 3 章的大部分内容会专门讲解如何定义类。

记住,你不需要定义想在 Java 程序中使用的每个类。Java 平台包含上千个预先定义好的类,在每台运行 Java 的电脑中都能使用。

2.7.2 创建对象

我们已经定义了 Point 类,现在 Point 是一种新数据类型,我们可以使用下面的代码声明一个变量,存储一个 Point 对象:

  1. Point p;

不过,声明一个存储 Point 对象的变量并不会创建这个对象。要想创建对象,必须使用 new 运算符。这个关键字后面跟着对象所属的类(即对象的类型)和括号中可选的参数列表。这些参数会传入类的构造方法,初始化新对象的内部字段:

  1. // 创建一个Point对象,表示(2,-3.5)
  2. // 声明一个变量p,存储这个新Point对象的引用
  3. Point p = new Point(2.0, -3.5);
  4. // 创建一些其他类型的对象
  5. // 一个Date对象,表示当前时间
  6. Date d = new Date();
  7. // 一个HashSet对象,保存一些对象
  8. Set words = new HashSet();

new 关键字是目前为止在 Java 中创建对象最常用的方式。还有一些其他方式也有必要提一下。首先,有些符合特定条件的类很重要,Java 为这些类定义了专用的字面量句法,用于创建这些类型的对象(本节后面会介绍)。其次,Java 支持动态加载机制,允许程序动态

加载类和创建类的实例,详情参见第 11 章。最后,对象还可以通过反序列化创建。对象的状态可以保存或序列化到一个文件中,然后可以使用 java.io.ObjectInputStream 类重新创建这个对象。

2.7.3 使用对象

我们已经知道如何定义类,如何通过创建对象实例化类,现在要介绍使用对象的 Java 句法。前面说过,类定义了一些字段和方法。每个对象都有自己的字段副本,而且可以访问类中的方法。我们使用点号(.)访问对象的具名字段和方法。例如:

  1. Point p = new Point(2, 3); // 创建一个对象
  2. double x = p.x; // 读取这个对象的一个字段
  3. p.y = p.x * p.x; // 设定一个字段的值
  4. double d = p.distanceFromOrigin(); // 访问这个对象的一个方法

这种句法在面向对象语言中很常见,Java 也不例外,因此会经常见到。特别注意一下 p.distanceFromOrigin()。这个表达式告诉 Java 编译器,查找一个名为 distanceFromOrigin() 的方法(在 Point 类中定义),然后使用这个方法对 p 对象的字段进行计算。第 3 章会详细介绍这种操作。

2.7.4 对象字面量

介绍基本类型时我们看到,每种基本类型都有字面量句法,可以直接在程序的代码中插入各种类型的值。Java 还为一些特殊的引用类型定义了字面量句法,介绍如下。

1. 字符串字面量

String 类使用一串字符表示文本。因为程序经常需要通过文字和用户沟通,所以在任何编程语言中处理文本字符串的能力都十分重要。在 Java 中,字符串是对象,表示文本的数据类型是 String 类。现代 Java 程序使用的字符串数据通常比其他程序都多。

因为字符串是如此基本的数据类型,所以 Java 允许在程序中插入文本字面量,方法是把字符放在双引号(")中。例如:

  1. String name = "David";
  2. System.out.println("Hello, " + name);

别把字符串字面量两侧的双引号和字符字面量两侧的单引号搞混了。字符串字面量可以包含字符字面量中能使用的任何一个转义序列(参见表 2-2)。在双引号包围的字符串字面量中嵌入双引号时,转义序列特别有用。例如:

  1. String story = "\t\"How can you stand it?\" he asked sarcastically.\n";

字符串字面量中不能包含注释,而且只能有一行。Java 不支持把两行当成一行的任何接续字符。如果需要表示一串长文本,一行写不下,可以把这个文本拆成多个单独的字符串字面量,再使用 + 运算符把它们连接起来。例如:

  1. // 这么写不合法,字符串字面量不能断行
  2. String x = "This is a test of the
  3. emergency broadcast system";
  4. String s = "This is a test of the " + // 要这么写
  5. "emergency broadcast system";

这种字面量连接在编译程序时,而不是运行时完成,所以无需担心性能会降低。

2. 类型字面量

第二种支持专用对象字面量句法的类型是 Class 类。Class 类的实例表示一种 Java 数据类型,而且包含所表示类型的元数据。若想在 Java 程序中使用 Class 对象字面量,要在数据类型的名称后面加上 .class。例如:

  1. Class<?> typeInt = int.class;
  2. Class<?> typeIntArray = int[].class;
  3. Class<?> typePoint = Point.class;

3. null 引用

null 关键字是一种特殊的字面量,引用不存在的值,或者不引用任何值。null 这个值是独一无二的,因为它是任何一种引用类型的成员。null 可以赋值给属于任何引用类型的变量。例如:

  1. String s = null;
  2. Point p = null;

2.7.5 lambda表达式

Java 8 引入了一个重要的新功能——lambda 表达式。这是十分常见的编程语言结构,在函数式编程语言(Functional Programming Language,例如 Lisp、Haskell 和 OCaml)中使用范围极广。lambda 表达式的功能和灵活性远非局限于函数式语言,在几乎所有的现代编程语言中都能看到它的身影。

定义 lambda 表达式

lambda 表达式其实就是没有名称的函数,在 Java 中可以把它当成一个值。Java 不允许脱离类的概念运行方法,所以 lambda 表达式是在某个类中定义的匿名方法(开发者可能不知道具体是哪个类)。

lambda 表达式的句法如下:

  1. ( paramlist ) -> { statements }

下面是一个十分传统的简单示例:

  1. Runnable r = () -> System.out.println("Hello World");

lambda 表达式当成值使用时,会根据要存储的变量类型,自动转换为相应的对象。自动转换和类型推导是 Java 实现 lambda 表达式的基础。但是,这要求正确地理解 Java 的整个类型系统。4.5 节会详细说明 lambda 表达式,现在只需知道句法。

下面是个稍微复杂的示例:

  1. ActionListener listener = (e) -> {
  2. System.out.println("Event fired at: "+ e.getWhen());
  3. System.out.println("Event command: "+ e.getActionCommand());
  4. };