《Java白皮书1996自译》03:面向对象

詹姆斯·高斯林

第三章、Java是面向对象的

我的目标极其崇高,我将及时完成它!

吉尔伯特和沙利文——米卡多

为了跟上现代软件开发实践,Java从头到尾都是面向对象的。设计一门面向对象的语言的意义并不仅仅是紧跟最新的编程潮流。面向对象的范例很好地满足了客户机-服务器和分布式软件的需求。随着越来越多的组织将其应用程序迁移到分布式客户机-服务器模型,对象技术的好处正在迅速得到实现。

不幸的是,“面向对象”仍然被误解,被过度宣传为能够解决我们所有软件问题的银弹,或者披上宗教的外衣。玩世不恭者认为面向对象编程只是一种组织源代码的新方法。虽然这种观点可能有一些优点,但它并不能说明全部问题,因为您可以使用面向对象编程技术实现过程技术无法实现的结果。

将对象与普通过程或函数区别开来的一个重要特征是,对象的生存期可以比创建它的对象的生存期更长。这方面的对象是微妙的,往往被忽视。在分布式客户机-服务器世界中,您现在可以在一个地方创建对象、在网络中传递对象、在其他地方(可能在数据库中)存储对象,以供将来检索。

作为一种面向对象的语言,Java借鉴了以前面向对象语言的最佳概念和特性,主要是Eiffel、SmallTalk、Objective C和c++。Java在扩展对象模型和消除c++的主要复杂性方面都超越了c++。除了基本数据类型之外,Java中的所有东西都是对象,如果需要,甚至基本类型也可以封装在对象中。

3.1、Java中的对象技术

要真正被认为是“面向对象的”,一门编程语言至少应该支持以下四个特性:

● 封装Encapsulation——实现信息隐藏和模块化(抽象)。

● 多态性Polymorphism——发送给不同对象的同一消息导致的行为取决于接收消息的对象的性质。

● 继承Inheritance——定义基于现有类的新类和行为,以获得代码重用和代码组织。

● 动态绑定Dynamic binding——对象可以来自任何地方,可能跨越网络。您需要能够向对象发送消息,而不必在编写代码时知道它们的特定类型。动态绑定在程序执行时提供最大的灵活性。

Java很好地满足了这些需求,并且增加了相当多的运行时支持,使您的软件开发工作更容易。

3.2、什么是对象?

简单地说,对象技术是分析、设计和编程方法的集合,它将设计重点放在对真实世界中对象的特性和行为建模上。确实,这个定义似乎有点循环,所以让我们试着进入晴空。

什么是对象?它们是软件编程模型。在你的日常生活中,你的周围都是物体:汽车、咖啡机、鸭子、树等等。软件应用程序包含对象:用户界面上的按钮、电子表格和电子表格单元格、属性列表、菜单等等。这些对象具有状态和行为。您可以使用称为对象的软件构造来表示所有这些东西,对象也可以通过它们的状态和行为来定义。

在你日常的交通需求中,汽车可以通过一个物体来建模。汽车具有状态(行驶速度、行驶方向、油耗等)和行为(启动、停止、转弯、滑行、撞树)。

你开车去办公室,在那里跟踪你的股票投资组合。在你与股票市场的日常互动中,一只股票可以被一个对象建模。股票有状态(日高、日低、开盘价、收盘价、每股收益、相对强度)和行为(改变价值、进行分割、有股息)。

在看到你的股票价格下跌后,你跑到咖啡馆喝了一杯热咖啡来安慰自己。浓缩咖啡机可以建模为一个对象。它有状态(水温,料斗中的咖啡量)和行为(散发蒸汽,制造噪音,酿造一杯完美的java)。

3.3、对象基础

对象基础

对象的行为由它的方法定义。方法操作实例变量以创建新状态;对象的方法也可以创建新对象。

左边的小图片是一个常用的对象的图形表示。这张图展示了一个软件对象的概念结构——它有点像一个细胞,它的外膜是它与世界的接口,内核是由外膜保护的。

对象的实例变量(数据)在对象中被打包或封装。实例变量被对象的方法包围。对于某些定义良好的异常,对象的方法是其他对象访问或更改其实例变量的惟一方法。在Java中,类可以将它们的实例变量声明为public,在这种情况下,实例变量对其他对象是全局可访问的。可访问性声明将在后面的访问说明符中讨论。稍后,您还将发现关于类变量和类方法的讨论。

3.3.1、类

类是一个软件结构,它定义了特定具体对象的数据(状态)和方法(行为),这些对象随后从该类构造而来。在Java术语中,类是由成员(字段或方法)构建的。字段是类的数据。方法是对数据进行操作的语句序列。字段通常特定于一个对象——也就是说,从类定义构造的每个对象都有自己的字段副本。这样的字段称为实例变量。类似地,方法通常也声明为对类的实例变量进行操作,因此称为实例方法。

类本身不是对象。一个类就像一个蓝图,它定义了一个对象在被创建或从类声明的规范中实例化时的外观和行为。通过实例化前面定义的类,可以获得具体的对象。您可以从一个类定义实例化许多对象,就像您可以从单个架构师的绘图构建许多相同的房子一样。下面是一个非常简单的类Point的基本声明

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Point extends Object {

	// instance variable
	// 实例变量
	public double x;

	// instance variable
	// 实例变量
	public double y;

}

如前所述,这个声明仅仅定义了一个模板,从这个模板中可以实例化真实的对象,如下所述。

3.3.2、从其类实例化对象

在声明了上面的Point类的大小和形状之后,任何其他对象现在都可以创建Point对象——Point类的一个实例——使用如下代码片段:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Client {

	public static void main(String[] args) {

		// declares a variable to refer to a Point object
		// 声明一个变量来引用Point对象
		Point myPoint;

		// allocates an instance of a Point object
		// 分配Point对象的实例
		myPoint = new Point();
	}

}

现在,你可以通过引用变量的名称来访问这个Point对象的变量,这些变量是用对象的名称限定的:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Client1 {

	public static void main(String[] args) {

		// declares a variable to refer to a Point object
		// 声明一个变量来引用Point对象
		Point myPoint;

		// allocates an instance of a Point object
		// 分配Point对象的实例
		myPoint = new Point();

		myPoint.x = 10.0;

		myPoint.y = 25.7;
	}

}

这个引用方案与C结构引用类似,因为Point的实例变量在类声明中声明为public,所以可以工作。如果实例变量没有被声明为public,那么在声明该点的包外部的对象就不能以这种直接的方式访问它的实例变量。然后,Point类声明需要提供访问器方法来设置和获取它的变量。在讨论了构造函数之后,我们将更详细地讨论这个主题。

3.3.3、构造器

在Java中声明类时,可以声明可选的构造函数,这些构造函数在实例化该类中的对象时执行初始化。您还可以声明一个可选的终结器,稍后讨论。让我们回到之前的Point类:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Point extends Object {

	// instance variable
	// 实例变量
	public double x;

	// instance variable
	// 实例变量
	public double y;

	// constructor to initialize to default zero value
	// 构造器初始化为默认零值
	Point() {

		x = 0.0;

		y = 0.0;

	}

	// constructor to initialize to specific value
	// 构造器初始化到特定值
	Point(double x, double y) {

		// set instance variables to passed parameters
		// 用传递的参数给实例变量赋值
		this.x = x;

		this.y = y;

	}

}

与代码片段中的类同名的方法称为构造函数。当您创建(实例化)Point类的对象时,将调用构造函数方法来执行所需的任何初始化——在本例中,是将实例变量设置为初始状态。

这个例子是以前Point类的一个变体。现在,当你想创建和初始化点对象时,你可以让它们初始化为默认值,或者你可以初始化为特定的值:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Client2 {

	public static void main(String[] args) {
		Point lowerLeft;

		Point upperRight;

		// initialize to default zero value
		// 初始化为默认零值
		lowerLeft = new Point();

		// initialize to non- zero
		// 初始化为非零
		upperRight = new Point(100.0, 200.0);

	}

}

创建新Point对象时使用的特定构造函数由新调用中的参数类型和数量决定。

this变量

上面例子中的变量是什么?这指的是你现在“在”的对象。换句话说,这指的是接收对象。你可以用这个来明确你指的是哪个变量。在双参数点法中,这个。x表示该对象的x实例变量,而不是Point方法的x参数。

在上面的例子中,构造函数只是为Point类提供了方便。但是,在需要构造函数的情况下,特别是在实例化的对象本身必须实例化其他对象的情况下,会出现这种情况。让我们通过声明一个使用两个Point对象定义其边界的Rectangle类来说明其中的一种情况:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Rectangle extends Object {

	private Point lowerLeft;

	private Point upperRight;

	Rectangle() {

		lowerLeft = new Point();

		upperRight = new Point();

	}

	// instance methods appear in here
	// 实例方法出现在这里
	// ...
	
}

在本例中,矩形构造函数对于确保在实例化矩形对象时实例化两个点对象是至关重要的,否则,矩形对象随后将尝试引用尚未分配的点,并将失败。

3.3.4、方法和消息

方法和消息

如果一个对象希望另一个对象代替它做一些工作,那么按照面向对象编程的说法,第一个对象向第二个对象发送一条消息。作为响应,第二个对象选择要调用的适当方法。Java方法调用看起来类似于C和c++中的函数。

使用面向对象编程的消息传递范例,您可以构建整个网络和对象网络,这些对象在它们之间传递消息以更改状态。这种编程技术是创建复杂真实世界系统的模型和仿真的最佳方法之一。让我们从上面重新定义Point类的声明,使它的实例变量是私有的,并为它提供访问这些变量的方法。

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Point extends Object {

	// instance variable
	// 实例变量
	private double x;

	// instance variable
	// 实例变量
	private double y;

	// constructor to initialize to default zero value
	// 构造器初始化为默认零值
	Point() {

		x = 0.0;

		y = 0.0;

	}

	// constructor to initialize to specific value
	// 构造器初始化到特定值

	Point(double x, double y) {

		this.x = x;

		this.y = y;

	}

	// accessor method
	// 访问器方法
	public void setX(double x) {

		this.x = x;

	}

	// accessor method
	// 访问器方法
	public void setY(double y) {

		this.y = y;

	}

	// accessor method
	// 访问器方法
	public double getX() {

		return x;

	}

	// accessor method
	// 访问器方法
	public double getY() {

		return y;

	}

}

这些方法声明提供了Point类如何从外部世界访问其变量的方式。另一个对象想要操作点对象的实例变量,现在必须通过访问器方法:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Client {

	public static void main(String[] args) {

		// declares a variable to refer to a Point object
		// 声明一个变量来引用Point对象
		Point myPoint;

		// allocates an instance of a Point object
		// 分配Point对象的实例
		myPoint = new Point();

		// sets the x variable via the accessor method
		// 通过访问器方法设置x变量
		myPoint.setX(10.0);

		// sets the y variable via the accessor method
		// 通过访问器方法设置y变量
		myPoint.setY(25.7);
	}

}

将实例变量设置为public或private是设计人员在声明类时所做的设计权衡。通过将实例变量公开,您可以公开类实现的细节,从而提供更高的效率和表达式的简洁性,但这可能会妨碍将来的维护工作。通过隐藏类的内部实现细节,您可以在将来更改类的实现而不破坏任何使用该类的代码。

3.3.5、终结器(Finalizers)

您还可以声明一个可选的终结器,它将在垃圾收集器释放对象时执行必要的销毁操作。这个代码片段演示了类中的finalize方法。

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Example {

	private InputStream inputStream;

	// Close the stream when garbage is collected.
	// 收集垃圾时关闭流。
	protected void finalize() {

		try {

			inputStream.close();

		} catch (Exception e) {

		}

	}

}

当对象即将被垃圾收集时,将调用finalize方法,这意味着对象必须以有序的方式关闭自己。在上面的特定代码片段中,finalize方法只是关闭对象使用的I/O文件流,以确保关闭流的文件描述符。

3.3.6、子类(Subclasses)

子类是根据现有对象定义新对象和增强对象的机制。例如:斑马是有条纹的马。如果您希望创建一个斑马对象,您会注意到斑马有点像马,只有条纹。在面向对象术语中,您将创建一个名为Zebra的新类,它是Horse类的一个子类。在Java语言中,您可以这样做:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Horse {

}

public class Zebra extends Horse {

	// Your new instance variables and new methods go here
	// 你的新实例变量和新方法到这里

}

马的定义,无论它在哪里,都将定义描述马的行为的所有方法:吃、嘶叫、小跑、飞奔、巴克,等等。您需要重写的惟一方法是绘制隐藏的方法。您可以从已经编写的完成所有工作的代码中获益——您不必重新发明轮子,或者在本例中,不必重新发明蹄子。extends关键字告诉Java编译器Zebra是Horse的一个子类。据说斑马是一个派生类——它是从马派生出来的,马被称为超类。

下面是一个创建子类的例子,它是我们的Point类的一个变体,从前面的例子中创建一个新的三维点,称为ThreePoint:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Point extends Object {

	// instance variable
	// 实例变量
	public double x;

	// instance variable
	// 实例变量
	public double y;

	// constructor to initialize to default zero value
	// 构造器初始化为默认零值
	Point() {

		x = 0.0;

		y = 0.0;

	}

	// constructor to initialize to specific value
	// 构造器初始化到特定值
	Point(double x, double y) {

		// set instance variables to passed parameters
		// 用传递的参数给实例变量赋值
		this.x = x;

		this.y = y;

	}

}
/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class ThreePoint extends Point {

	// the z coordinate of the point
	// 点的z坐标
	protected double z;

	// default constructor
	// 默认构造器
	ThreePoint() {

		// initialize the coordinates
		// 初始化坐标
		x = 0.0;

		y = 0.0;

		z = 0.0;

	}

	// specific constructor
	// 具体的构造器
	ThreePoint(double x, double y, double z) {

		// initialize the coordinates
		// 初始化坐标
		this.x = x;

		this.y = y;

		this.z = z;

	}

}

注意,ThreePoint为该点的z坐标添加了一个新的实例变量。x和y实例变量是从原始的Point类继承的,因此不需要在ThreePoint中声明它们。但是,请注意,我们必须使Point的实例变量受保护,而不是像前面的示例那样是私有的。如果我们将Point的实例变量设为私有,那么即使它的子类也无法访问它们,编译也会失败。

子类使您能够使用已经开发的、更重要的是经过测试的现有代码,用于更通用的情况。您将覆盖特定行为所需的类的各个部分。因此,子类使您可以重用现有的代码,从而节省了设计、开发和测试的时间。Java运行时系统提供了几个实用程序函数库,这些函数都经过了测试,并且是线程安全的。

Java中的所有类最终都继承自Object。Object是所有类中最通用的。声明的新类将向其超类添加功能。您沿着类层次结构走得越远——也就是说,您从Object得到的越远——您的类就变得越专门化。

单继承和类层次结构

Java实现了所谓的单继承模型。一个新类只能子类化(在Java术语中扩展)另一个类。最终,所有类都继承自Object类,形成一个以Object为根的树结构。该图演示了Java实用程序包Java .util中的类的类层次结构。

HashTable类是Dictionary的子类,Dictionary又是Object的子类。Dictionary继承Object的所有变量和方法(行为),然后添加自己的新变量和行为。类似地,HashTable继承了对象的所有变量和行为,加上Dictionary的所有变量和行为,并继续添加自己的变量和行为。

然后,Properties类HashTable的子类,继承其类层次结构的所有变量和行为。以类似的方式,Stack和ObserverList是Vector的子类,而Vector又是Object的子类。面向对象方法的威力是显而易见的——不需要任何子类来重新实现它们的超类的基本功能,只需要添加它们自己的专门行为。

类层次结构

然而,上面的图指出了单继承模型的一个小缺点。注意,图中有两种不同的枚举器类,它们都继承自Object。一个枚举器类

实现遍历集合的行为,逐个获取该集合的元素。枚举器类定义了HashTable和Vector都认为有用的行为。其他尚未定义的集合类(如list或queue)也可能需要枚举类的行为。不幸的是,它们只能从一个超类继承。

解决这个问题的一种可能的方法是增强层次结构中的某个超类,在许多子类明显可以使用该行为时添加此类有用的行为。这种做法会导致混乱和膨胀。如果每次所有后续子类都需要一些常见的有用行为,那么像Object这样的类就会不断地进行修改,它就会变得非常庞大和复杂,而且它的行为规范也会不断地变化。这种“解决方案”是站不住脚的。该问题的优雅且可行的解决方案是通过Java接口提供的,这是下一个主题的主题。

3.3.7、Java语言接口

在Java中引入接口是为了增强Java的单继承模型。Java的设计者认为多重继承给程序员和编译器编写者带来了太多的问题,并且认为单一继承模型总体上更好。前面关于单继承模型的讨论中描述的一些问题通过使用接口以一种更优雅的方式得到了解决。

Java语言中的接口只是对象声明它实现的方法的规范。接口不包括实例变量或实现代码——只声明常量和方法。Java语言中接口的概念是从协议的Objective-C概念中借来的。

一个类只能继承一个超类,而一个类可以实现任意多的接口。使用前面讨论中的示例,HashTableEnumerator和VectorEnumerator类都实现了特定于HashTable和Vector类的特征的枚举接口。当您定义一个新的集合类(例如,一个队列类)时,您还可能定义一个实现枚举接口的QueueEnumerator类。

接口的概念非常强大——实现给定接口的类只需要在类层次结构的适当级别上实现。这张图说明了接口的使用。

接口的使用

在本例中,接口由矩形表示。您可以看到Cloneable接口是由多个类实现的。此外,HashtableEnumerator和VectorEnumerator类都实现了枚举接口。任何给定的类都可以实现任意数量的接口,并且可以以任意方式实现。接口的实际实现细节隐藏在类定义中,应该是可替换的,而不以任何方式影响接口的外部视图。但是,请回忆一下,接口只是声明方法;它没有实现它们。当从类(在c++等语言中)继承时,继承类的实现也被继承,因此与在多重继承接口中重用的代码数量相比,可以重用更多的代码。由于这个原因,从接口继承为多重继承提供了一种合理的替代方法,但是这种实践不应该被看作是更强大的、但常常令人困惑的从多个类继承的实践的替代方法。

3.3.8、访问控制

在Java中声明一个新类时,可以指示它的成员允许的访问级别——即它的实例变量和方法。Java提供了四种级别的访问。必须显式指定三个级别:public、protected和private。声明为public的成员可用于任何其他类。声明受保护的成员只能被该类的子类访问,其他地方不能。声明为私有的成员只能从声明它们的类中访问——甚至它们的子类也不能访问它们。

第四个访问级别没有名称——它通常被称为“friendly”,是您在不指定其他方式的情况下获得的访问级别。“友好”访问级别表示类的成员可以被同一包中的所有对象访问,但是不能被包外的对象访问。包是将相关的类和接口集合分组在一起的有用工具,下面将对此进行讨论。

3.3.9、包(Packages)

Java包是类和接口的集合,它们以某种有用的方式相互关联。这样的类需要能够直接访问彼此的实例变量和方法。例如,如果Point的实例变量可以直接用于Rectangle类,那么由Point和Rectangle类组成的几何包可能更容易实现,也更简洁,而且更有效。然而,在geometry包之外,实现的细节对外界是隐藏的,这使您可以自由地更改实现细节,而不必担心会破坏使用这些类的代码。包是通过将每个包的类和接口的源文件存储在文件系统的一个单独目录中来创建的。

包的主要好处是能够将许多类定义组织到单个单元中。例如,所有的Java I/O系统代码都被收集到一个名为Java .io的包中。从程序员的角度来看,第二个好处是“友好的”实例变量和方法对同一包中的所有类都可用,但对包外定义的类不可用。

3.3.10、类变量和类方法

Java在提供类方法和类变量方面遵循了其他面向对象语言的约定。通常,在类定义中声明的变量是实例变量——在从类创建(实例化)的每个单独对象中都有一个这样的变量。另一方面,类变量是类本身的本地变量——只有一个变量的副本,并且它由类中实例化的每个对象共享。

要在Java程序中声明类变量和类方法,可以将它们声明为静态的。这个简短的代码片段说明了类变量的声明:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Rectangle extends Object {

	static final int version = 2;

	static final int revision = 0;

}

Rectangle类声明了两个静态变量来定义该类的版本和修订级别。现在,从这个类创建的每个Rectangle实例都将共享这些相同的变量。注意它们也被定义为final因为你想让它们是常数。

类方法对于整个类是通用的。什么时候使用类方法?通常,当你有一个类的每个对象都有的行为时。例如,假设您有一个Window类。您可以向类询问的一个有用信息是当前打开的窗口的当前数量。此信息由窗口的每个实例共享,只能通过从窗口的其他实例获得的知识获得。由于这些原因,必须只有一个类方法来返回打开窗口的数量。

通常,类方法只能对类变量进行操作。类方法不能访问实例变量,也不能调用实例方法。与类变量一样,通过将类方法定义为静态方法来声明类方法。

我们说“一般”,是因为您可以将对象引用传递给类方法,然后类方法可以操作对象的公共实例变量,并通过引用调用对象的实例方法。但是,通常最好只在类级别上执行类操作,而在对象级别上执行对象操作。

3.3.11、抽象方法

抽象方法是面向对象范式中的一个强大的构造。为了理解抽象方法,我们看一下抽象超类的概念。抽象超类是一个类,在这个类中声明的方法实际上不是由该类实现的——它们只提供位置占位符,后续子类必须覆盖这些占位符并提供它们的实际实现。

这些听起来都非常抽象,那么为什么需要一个抽象的超类呢?让我们来看一个具体的例子,没有双关语的意思。假设你要去一家餐厅吃饭,你决定今晚吃鱼。嗯,鱼有点抽象——你一般不会只点鱼;服务员很可能会问你想要哪种鱼。当你真正到餐厅的时候,你会发现他们有什么样的鱼,然后点一种特定的鱼,比如鲟鱼、鲑鱼或奥帕卡帕卡。

在对象的世界中,抽象类就像泛型fish——抽象类定义了泛型状态和泛型行为,但是您永远不会看到抽象类的实际实现。您将看到的是抽象类的一个具体子类,就像opakapaka是一种特定的(具体的)鱼。

假设您正在创建一个绘图应用程序。应用程序的初始切割可以绘制矩形、直线、圆、多边形等。此外,您还可以对形状执行一系列操作——移动、重塑、旋转、填充颜色等等。您可以将这些图形形状中的每一个都作为一个单独的类——您将拥有一个Rectangle类、一个Line类等等。每个类都需要实例变量来定义它的位置、大小、颜色、旋转等等,这些变量又指示了设置和获取这些变量的方法。

此时,您意识到可以将所有实例变量收集到一个名为graphics的抽象超类中,并实现大多数方法来操作这个抽象超类中的变量。抽象超类的骨架可能是这样的:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public abstract class Graphical extends Object {

	// lower left of bounding box
	// 包围框的左下角
	protected Point lowerLeft;

	// upper right of bounding box
	// 包围框的右上角
	protected Point upperRight;

	// more instance variables
	// 更多的实例变量
	public void setPosition(Point ll, Point ur) {

		lowerLeft = ll;

		upperRight = ur;

	}

	// abstract method
	// 抽象方法
	abstract void drawMyself();

}

现在,您不能实例化这个图形化类,因为它被声明为抽象的。您只能实例化它的一个子类。您可以将Rectangle类或Circle类实现为图形化的子类。在Rectangle中,您将提供绘制矩形的drawMySelf方法的具体实现,因为drawMySelf的定义必须对于从图形类继承的每个形状都是惟一的。让我们看看矩形类声明的一小段,其中它的drawMySelf方法以一种有点PostScript’y的方式操作:

/*
作者:AT阿宝哥
日期:2016年9月18日
愿景:参考官方资料,做最好的课程,成就更多职业人!
邮箱:[email protected]
CSDN:https://blog.csdn.net/goldentec
简书:https://www.jianshu.com/u/8a6075d7a2e0
说明:

注意:
    
*/
public class Rectangle extends Graphical {

	static final int version = 2;

	static final int revision = 0;

	// really does the drawing
	// 绘画
	void drawMyself() {

		moveTo(lowerLeft.x, lowerLeft.y);

		lineTo(upperRight.x, lowerLeft.y);

		lineTo(upperRight.x, upperRight.y);

		lineTo(lowerLeft.x, upperRight.y);

		// and so on and so on
		// 如此等等
	}

	void moveTo(double x, double y) {

	}

	void lineTo(double x, double y) {

	}

}

但是请注意,在图形类的声明中,setPosition方法被声明为一个常规(public void)方法。可以由抽象超类实现的所有方法都可以在那里声明并定义它们的实现。然后,从抽象超类继承的每个类也将继承这些方法。

您可以继续以这种方式添加新的图形,这些图形是图形的子类,而且大多数情况下,您需要实现的只是特定图形特有的方法。您可以获得重用抽象超类中定义的所有代码的好处。

3.4、总结

本章介绍了作为面向对象语言的Java的基本方面。总结:

类定义模板,从中实例化(创建)不同的具体对象。

实例变量保存特定对象的状态。

对象通过相互发送消息进行通信。对象通过选择要执行的方法来响应消息。

方法定义从类实例化的对象的行为。它是一个对象的方法来操作它的实例变量。与常规过程语言不同,面向对象语言中的类可能具有与其他类名称相同的方法。给定对象以由该对象的性质决定的方式响应消息,从而提供多态行为。

子类提供了一种方法,通过这种方法,新类可以从任何已定义的类继承实例变量和方法。新声明的类可以添加新的实例变量(额外的状态),可以添加新的方法(新的行为),或者可以覆盖其超类的方法(不同的行为)。子类提供代码重用。

总的来说,面向对象编程的概念为软件开发人员创建了一个强大而简单的范例,使他们能够共享和重用代码,并构建在其他人的工作之上。


好好学习,天天向上!继续下一章…


发布了36 篇原创文章 · 获赞 30 · 访问量 5924

猜你喜欢

转载自blog.csdn.net/goldentec/article/details/104777109
今日推荐