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(true 或 false);和 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 使类的构造函数对调用者不可见。当我们有另一个类负责创建和管理消息池(可能在另一个进程甚至另一个系统上执行)时,我们可能希望这样做。

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

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 中的时间(以毫秒为单位)以 long(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 多重继承主题……更重要的是,这种语言特性真正解决了以前不方便或不可能解决的问题,那就太好了。

谢谢!

Creative Commons License本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.