Java 中的对象是什么?

Java 面向对象编程的方法几乎是该语言一切的基础。 这是你需要了解的。
163 位读者喜欢这篇文章。
What is your favorite open source Java IDE?

Pixabay. CC0.

Java 是一种面向对象的编程语言,它将世界视为具有属性行为的对象的集合。 Java 的面向对象版本非常简单,几乎是该语言一切的基础。 因为它对 Java 来说非常重要,所以我会解释一些底层原理,以帮助任何不熟悉该语言的人。

继承

一般来说,所有 笛卡尔几何对象,如圆形、正方形、三角形、直线和点,都具有基本属性,例如位置和范围。 零范围的对象,如点,通常只有这些。 像直线这样的对象有更多的属性,例如线段的起点和终点,或者直线上的两点(如果是“真直线”)。 像正方形或三角形这样的对象还有更多属性,例如角点,而圆可能有一个中心和半径。

我们可以看到这里有一个简单的层次结构:一般的几何对象可以扩展为特定的几何对象,例如点、线、正方形等。每个特定的几何对象继承了位置和范围的基本几何属性,并添加了自己的属性。

这是一个单继承的例子。 Java 最初的面向对象模型只允许单继承,即对象不能属于多个继承层次结构。 这种设计决策来自于程序员在 复杂的多个继承场景中发现的各种歧义,通常是在“有趣的设计决策”导致函数 foo() 在层次结构中被定义(和重新定义)多次的情况下。

自 Java 8 以来,已经存在一个有限的多重继承结构,它需要程序员采取特定操作来确保没有歧义。

强类型和静态类型

Java 是一种类型和静态类型语言。 这意味着什么?

静态类型语言是指变量的类型在编译时已知的语言。 在这种情况下,除非存在将 B 类型的值转换为 A 类型的值的转换机制,否则无法将 B 类型的值分配给声明类型为 A 的变量。这种类型转换的一个例子是将整数值(如 1、2 或 42)转换为浮点值(如 1.0、2.0 或 42.0)。

类型语言是指很少(或可能没有)类型转换会自动应用的语言。 例如,虽然强类型语言可能允许自动将整数转换为实数,但它永远不允许自动将实数转换为整数,因为在一般情况下,这种转换需要舍入或截断。

原始类型、类和对象

Java 提供了许多原始类型:byte(一个 8 位有符号整数);short(一个 16 位有符号整数);int(一个 32 位有符号整数);long(一个 64 位有符号整数);float(一个单精度 32 位 IEEE 浮点数);double(一个双精度 64 位 IEEE 浮点数);boolean(真或假);以及 char(一个 16 位 Unicode 字符)。

除了这些原始类型之外,Java 允许程序员使用类声明创建新类型。 类声明用于定义对象模板,包括它们的属性和行为。 一旦声明了一个类,通常可以使用 new 关键字创建该类的实例。 这些实例直接对应于我们一直在讨论的“对象”。 Java 附带了一个有用的类定义库,包括一些简单的基本类,例如 String,它用于保存字符序列,例如“Hello, world”。

让我们定义一个简单的消息类,其中包含发送者的姓名以及消息文本

class Message {
	String sender;
	String text;
	public Message(String sender, String text) {
		this.sender = sender;
		this.text = text;
	}
}

在此类声明中需要注意几个重要事项

  1. 该类(按照惯例)总是以大写字母开头声明。
  2. Message 类包含两个属性(或字段)

    – 一个名为 sender 的 String 字段

    – 一个名为 text 的 String 字段

    属性或字段(按照惯例)总是以小写字母开头声明。
  3. 有一些东西以 public Message 开头。

    – 它是一个方法(方法定义了对象的行为)。

    – 它用于构造 Message 类的实例。

    – 构造方法总是采用与类相同的名称,并且被理解为在构造完成后返回该类的一个实例。

    – 其他方法(按照惯例)总是以小写字母开头声明。

    – 此构造函数是“public”,这意味着任何调用者都可以访问它。
  4. 作为构造过程的一部分,一些行以 this 开头。

    this 指的是该类的当前实例。

    – 因此 this.sender 指的是对象的 sender 属性。

    – 而普通的 sender 指的是 Message 构造方法的参数。

    – 因此,这两行是将调用构造函数时提供的值复制到对象本身的字段中。

所以我们有了 Method 类定义。 我们如何使用它? 以下代码摘录显示了一种可能的方式

Message message = new Message("system", "I/O error");

在这里我们看到

  1. 类型为 Message 的变量 message 的声明
  2. 创建 Message 类的一个新实例,其中 sender 设置为“system”,text 设置为“I/O error”
  3. Message 的这个新实例赋值给变量 message
  4. 如果在代码的后面,变量 message 被分配了不同的值(Message 的另一个实例),并且没有创建其他变量来引用 Message 的这个实例,那么这个实例不再被任何东西使用,可以进行垃圾回收。

这里发生的关键事情是,我们正在创建一个 Message 类型的对象,并将对该对象的引用保存在变量 message 中。

我们现在可以使用该消息; 例如,我们可以打印 sendertext 属性中的值,如下所示

System.out.println("message sender = " + message.sender);
System.out.println("message text = " + message.text);

这是一个非常简单和不复杂的类定义。 我们可以通过多种方式修改此类定义

  1. 我们可以通过在声明前面使用关键字 private 使属性的实现细节对调用者不可见,从而允许我们在不影响调用者的情况下更改实现。
  2. 如果我们选择隐藏类中的属性,那么我们通常会定义获取设置这些属性的过程; 按照 Java 的惯例,这些过程将被定义为

    public String getSender()

    public String getText()

    public void setSender(String sender)

    public void setText(String text)
  3. 在某些情况下,我们可能希望拥有“只读”属性; 在这些情况下,我们不会为此类属性定义 setter。
  4. 我们可以使用 private 关键字而不是 public 使类的构造函数对调用者不可见。 当我们有另一个类负责创建和管理消息池(可能在另一个进程中甚至在另一个系统上执行)时,我们可能希望这样做。

现在,假设我们想要一种记录消息生成时间的类型的消息。 我们可以这样声明它

class TimedMessage extends Message {
	long creationTime;
	public TimedMessage(String sender, String text) {
		super(sender, text);
		this.creationTime = System.currentTimeMillis();
	}
}

在这里我们看到一些新的东西

  1. TimedMessage 正在扩展 Message 类——也就是说,TimedMessage 正在从 Message 继承属性和行为。
  2. 构造函数使用传入的 sendertext 的值调用其父类或超类中的构造函数,即 super(sender, text),以确保其继承的属性被正确初始化。
  3. TimedMessage 添加了一个新属性 creationTime,构造函数将其设置为当前的系统时间(以毫秒为单位)。
  4. Java 中的时间(以毫秒为单位)以长整型(64 位)值保存(0 是 1970 年 1 月 1 日 00:00:00 UTC)。
  5. 稍微跑题一下,名称 creationTime 表明它应该是一个只读属性,这也表明其他属性也应该是只读的; 也就是说,TimedMessage 实例可能不应该被重用,也不应该更改它们的属性。

Object 类

“Object 类”听起来像是一种自相矛盾的说法,不是吗?但请注意,我们定义的第一个类 Message 看起来 似乎没有继承任何东西——但它实际上继承了。所有没有明确继承其他类的类都以 Object 类作为其直接且唯一的父类;因此,所有类都以 Object 类作为其最终的超类。

你可以在 Java 的文档中了解更多关于 Object 类的知识。 让我们(简要地)回顾一些有趣的细节

  1. Object 有一个构造函数 Object(),也就是说,没有参数。
  2. Object 为其所有子类提供了一些有用的方法,包括:

    clone(),它创建并返回当前实例的副本

    equals(Object anotherObject),它确定 anotherObject 是否等于当前 Object 的实例

    finalize(),用于在不再使用当前实例时对其进行垃圾回收(见上文)

    getClass(),它返回用于声明当前实例的类

    — 此方法返回的值是 Class的一个实例,它允许在运行时了解声明类——这个过程被称为内省
  3. hashCode() 是一个整数值,为当前实例提供一个几乎唯一的值。

    – 如果两个不同实例的哈希码相等,则它们可能相等;需要对属性(以及可能的方法)进行详细比较才能确定完全相等性;

    – 如果哈希码不相等,则实例也不相等。

    – 因此,哈希码可以加速相等性测试。

    – 哈希码也可用于创建 HashMap(映射是一种关联数组或字典,它使用哈希码来加速查找)和一个 HashSet(集合是对象的集合;程序员可以测试一个实例是否在集合中;哈希码用于加速测试)。
  4. notify()notifyAll()wait()wait(long timeout)wait(long timeout, int nanos) 在单独线程上执行的协作实例之间进行通信。
  5. toString() 生成实例的可打印版本。

总结

我们已经涉及了面向对象编程的一些重要方面,采用 Java 风格。 有六个重要的相关主题将在以后的文章中介绍

  • 命名空间和包
  • 重写子类中的方法——例如,String 类有它自己特定的 hashCode() 方法,可以识别其作为字符数组的含义;这是通过重写从 Object 继承的 hashCode() 方法实现的
  • 接口,它允许描述必须由实现该接口的类提供的行为;当唯一感兴趣的是特定行为时,可以通过该接口引用实现给定接口的类的实例
  • 基本类型或类的数组以及类的集合(例如列表、映射和集合)
  • 方法的重载——其中几个具有相同名称和相似行为的方法具有不同的参数
  • 使用 Java 发行版未附带的库

您想接下来阅读什么? 在评论中告诉我们,敬请关注!

接下来阅读什么
标签
Chris Hermansen portrait Temuco Chile
自从 1978 年从不列颠哥伦比亚大学毕业以来,我几乎一直使用某种计算机,自 2005 年以来一直是全职 Linux 用户,1986 年到 2005 年一直是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。

2 条评论

我已经有一段时间没有进行认真的 java 开发了。 听说 java 8 中继承模型发生了变化,我有点失望。 过去在 C++ 多重继承中遇到过麻烦,我一直喜欢 java 单继承模型和接口等的使用。

如果您可以扩展一下 java 多重继承主题,那就太好了......更重要的是,该语言特性真正解决了以前不方便或不可能解决的问题。

谢谢!

感谢您的评论和建议,James,非常感谢。

我计划推出更多 Java 文章,并且“Java Objects Redux”当然可以添加到其中。

回复 作者: JamesF

Creative Commons License 本作品已获得 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.