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;
}
}
在此类声明中需要注意几个重要事项
- 该类(按照惯例)总是以大写字母开头声明。
- Message 类包含两个属性(或字段)
– 一个名为 sender 的 String 字段
– 一个名为 text 的 String 字段
属性或字段(按照惯例)总是以小写字母开头声明。 - 有一些东西以 public Message 开头。
– 它是一个方法(方法定义了对象的行为)。
– 它用于构造 Message 类的实例。
– 构造方法总是采用与类相同的名称,并且被理解为在构造完成后返回该类的一个实例。
– 其他方法(按照惯例)总是以小写字母开头声明。
– 此构造函数是“public”,这意味着任何调用者都可以访问它。 - 作为构造过程的一部分,一些行以 this 开头。
– this 指的是该类的当前实例。
– 因此 this.sender 指的是对象的 sender 属性。
– 而普通的 sender 指的是 Message 构造方法的参数。
– 因此,这两行是将调用构造函数时提供的值复制到对象本身的字段中。
所以我们有了 Method 类定义。 我们如何使用它? 以下代码摘录显示了一种可能的方式
Message message = new Message("system", "I/O error");
在这里我们看到
- 类型为 Message 的变量 message 的声明
- 创建 Message 类的一个新实例,其中 sender 设置为“system”,text 设置为“I/O error”
- 将 Message 的这个新实例赋值给变量 message
- 如果在代码的后面,变量 message 被分配了不同的值(Message 的另一个实例),并且没有创建其他变量来引用 Message 的这个实例,那么这个实例不再被任何东西使用,可以进行垃圾回收。
这里发生的关键事情是,我们正在创建一个 Message 类型的对象,并将对该对象的引用保存在变量 message 中。
我们现在可以使用该消息; 例如,我们可以打印 sender 和 text 属性中的值,如下所示
System.out.println("message sender = " + message.sender);
System.out.println("message text = " + message.text);
这是一个非常简单和不复杂的类定义。 我们可以通过多种方式修改此类定义
- 我们可以通过在声明前面使用关键字 private 使属性的实现细节对调用者不可见,从而允许我们在不影响调用者的情况下更改实现。
- 如果我们选择隐藏类中的属性,那么我们通常会定义获取和设置这些属性的过程; 按照 Java 的惯例,这些过程将被定义为
– public String getSender()
– public String getText()
– public void setSender(String sender)
– public void setText(String text) - 在某些情况下,我们可能希望拥有“只读”属性; 在这些情况下,我们不会为此类属性定义 setter。
- 我们可以使用 private 关键字而不是 public 使类的构造函数对调用者不可见。 当我们有另一个类负责创建和管理消息池(可能在另一个进程中甚至在另一个系统上执行)时,我们可能希望这样做。
现在,假设我们想要一种记录消息生成时间的类型的消息。 我们可以这样声明它
class TimedMessage extends Message {
long creationTime;
public TimedMessage(String sender, String text) {
super(sender, text);
this.creationTime = System.currentTimeMillis();
}
}
在这里我们看到一些新的东西
- TimedMessage 正在扩展 Message 类——也就是说,TimedMessage 正在从 Message 继承属性和行为。
- 构造函数使用传入的 sender 和 text 的值调用其父类或超类中的构造函数,即 super(sender, text),以确保其继承的属性被正确初始化。
- TimedMessage 添加了一个新属性 creationTime,构造函数将其设置为当前的系统时间(以毫秒为单位)。
- Java 中的时间(以毫秒为单位)以长整型(64 位)值保存(0 是 1970 年 1 月 1 日 00:00:00 UTC)。
- 稍微跑题一下,名称 creationTime 表明它应该是一个只读属性,这也表明其他属性也应该是只读的; 也就是说,TimedMessage 实例可能不应该被重用,也不应该更改它们的属性。
Object 类
“Object 类”听起来像是一种自相矛盾的说法,不是吗?但请注意,我们定义的第一个类 Message 看起来 似乎没有继承任何东西——但它实际上继承了。所有没有明确继承其他类的类都以 Object 类作为其直接且唯一的父类;因此,所有类都以 Object 类作为其最终的超类。
你可以在 Java 的文档中了解更多关于 Object 类的知识。 让我们(简要地)回顾一些有趣的细节
- Object 有一个构造函数 Object(),也就是说,没有参数。
- Object 为其所有子类提供了一些有用的方法,包括:
– clone(),它创建并返回当前实例的副本
– equals(Object anotherObject),它确定 anotherObject 是否等于当前 Object 的实例
– finalize(),用于在不再使用当前实例时对其进行垃圾回收(见上文)
– getClass(),它返回用于声明当前实例的类
— 此方法返回的值是 Class 类的一个实例,它允许在运行时了解声明类——这个过程被称为内省。 - hashCode() 是一个整数值,为当前实例提供一个几乎唯一的值。
– 如果两个不同实例的哈希码相等,则它们可能相等;需要对属性(以及可能的方法)进行详细比较才能确定完全相等性;
– 如果哈希码不相等,则实例也不相等。
– 因此,哈希码可以加速相等性测试。
– 哈希码也可用于创建 HashMap(映射是一种关联数组或字典,它使用哈希码来加速查找)和一个 HashSet(集合是对象的集合;程序员可以测试一个实例是否在集合中;哈希码用于加速测试)。 - notify()、notifyAll()、wait()、wait(long timeout) 和 wait(long timeout, int nanos) 在单独线程上执行的协作实例之间进行通信。
- toString() 生成实例的可打印版本。
总结
我们已经涉及了面向对象编程的一些重要方面,采用 Java 风格。 有六个重要的相关主题将在以后的文章中介绍
- 命名空间和包
- 重写子类中的方法——例如,String 类有它自己特定的 hashCode() 方法,可以识别其作为字符数组的含义;这是通过重写从 Object 继承的 hashCode() 方法实现的
- 接口,它允许描述必须由实现该接口的类提供的行为;当唯一感兴趣的是特定行为时,可以通过该接口引用实现给定接口的类的实例
- 基本类型或类的数组以及类的集合(例如列表、映射和集合)
- 方法的重载——其中几个具有相同名称和相似行为的方法具有不同的参数
- 使用 Java 发行版未附带的库
您想接下来阅读什么? 在评论中告诉我们,敬请关注!
2 条评论