`
dandy
  • 浏览: 67280 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

那个点是什么

    博客分类:
  • java
阅读更多
出自《java puzzle》

下面这个程序有两个不可变的值类(value class),值类即其实例表示值的类。第一个类用整数坐标来表示平面上的一个点,第二个类在此基础上添加了一点颜色。主程序将创建和打印第二个类的一个实例。那么,下面的程序将打印出什么呢?
class Point {
	protected final int x, y;
	private String name; // Cached at construction time
	Point(int x, int y) {
	this.x = x;
	this.y = y;
	name = makeName();
	}
	protected String makeName() {
		return "[" + x + "," + y + "]";
	}
	public final String toString() {
		return name;
	}
}

public class ColorPoint extends Point {
  private final String color;
  ColorPoint(int x, int y, String color) {
   super(x, y);
   this.color = color;
  }
  protected String makeName() {
   return super.makeName() + ":" + color;
  }
  public static void main(String[] args) {
   System.out.println(new ColorPoint(4, 2, "purple"));
  }
}


main方法创建并打印了一个ColorPoint实例。println方法调用了该ColorPoint实例的toString方法,这个方法是在Point中定义的。toString方法将直接返回name域的值,这个值是通过调用makeName方法在Point的构造器中被初始化的。对于一个Point实例来说,makeName方法将返回[x,y]形式的字符串。对于一个ColorPoint实例来说,makeName方法被覆写为返回[x,y]:color形式的字符串。在本例中,x是4,y是2,color的purple,因此程序将打印[4,2]:purple,对吗?不,如果你运行该程序,就会发现它打印的是[4,2]:null。这个程序出什么问题了呢?
这个程序遭遇了实例初始化顺序这一问题。要理解该程序,我们就需要详细跟踪该程序的执行过程。下面是该程序注释过的版本的列表,用来引导我们了解其执行顺序:
class Point {
protected final int x, y;
private final String name; // Cached at construction time
Point(int x, int y) {
this.x = x;
this.y = y;
name = makeName(); // 3. Invoke subclass method
protected String makeName() {
return "[" + x + "," + y + "]";
}
public final String toString() {
return name;
}
}

public class ColorPoint extends Point {
private final String color;
ColorPoint(int x, int y, String color) {
super(x, y); // 2. Chain to Point constructor
this.color = color; // 5. Initialize blank final-Too late
}
protected String makeName() {
// 4. Executes before subclass constructor body!
return super.makeName() + ":" + color;
}
public static void main(String[] args) {
// 1. Invoke subclass constructor
System.out.println(new ColorPoint(4, 2, "purple"));
}
}
}

在下面的解释中,括号中的数字引用的就是在上述注释版本的列表中的注释标号。首先,程序通过调用ColorPoint构造器创建了一个ColorPoint实例(1)。这个构造器以链接调用其超类构造器开始,就像所有构造器所做的那样(2)。超类构造器在构造过程中对该对象的x域赋值为4,对y域赋值为2。然后该超类构造器调用makeName,该方法被子类覆写了(3)。
ColorPoint中的makeName方法(4)是在ColorPoint构造器的程序体之前执行的,这就是问题的核心所在。makeName方法首先调用super.makeName,它将返回我们所期望的[4,2],然后该方法在此基础上追加字符串“:”和由color域的值所转换成的字符串。但是此刻color域的值是什么呢?由于它仍处于待初始化状态,所以它的值仍旧是缺省值null。因此,makeName方法返回的是字符串“[4,2]:null”。超类构造器将这个值赋给name域(3),然后将控制流返回给子类的构造器。

这之后子类构造器才将“purple”赋予color域(5),但是此刻已经为时过晚了。color域已经在超类中被用来初始化name域了,并且产生了不正确的值。之后,子类构造器返回,新创建的ColorPoint实例被传递给println方法,它适时地调用了该实例的toString方法,这个方法返回的是该实例的name域的内容,即“[4,2]:null”,这也就成为了程序要打印的东西。

本谜题说明:在一个final类型的实例域被赋值之前,存在着取用其值的可能,而此时它包含的仍旧是其所属类型的缺省值。

无论何时,只要一个构造器调用了一个已经被其子类覆写了的方法,那么该问题就会出现,因为以这种方式被调用的方法总是在实例被初始化之前执行。要想避免这个问题,就千万不要在构造器中调用可覆写的方法,直接调用或间接调用都不行[EJ Item 15]。这项禁令应该扩展至实例初始器和伪构造器(pseudoconstructors)readObject与clone。(这些方法之所以被称为伪构造器,是因为它们可以在不调用构造器的情况下创建对象。)
你可以通过惰性初始化name域来订正该问题,即当它第一次被使用时初始化,以此取代积极初始化,即当Point实例被创建时初始化。
通过这种修改,该程序就可以打印出我们期望的[4,2]:purple。

class Point {
protected final int x, y;
private String name; // Lazily initialized
Point(int x, int y) {
this.x = x;
this.y = y;
// name initialization removed
}
protected String makeName() {
return "[" + x + "," + y + "]";
}
// Lazily computers and caches name on first use
public final synchronized String toString() {
if (name == null)
name = makeName();
return name;
}
}


尽管惰性加载可以订正这个问题,但是对于让一个值类去扩展另一个值类,并且在其中添加一个会对euqals比较方法产生影响的域的这种做法仍旧不是一个好主意。你无法在超类和子类上都提供一个基于值的equals方法,而同时又不违反Object.equals方法的通用约定,或者是不消除在超类和子类之间进行有实际意义的比较操作的可能性[EJ Item 7]。

循环实例初始化问题对语言设计者来说是问题成堆的地方。C++是通过在构造阶段将对象的类型从超类类型改变为子类类型来解决这个问题的。如果采用这种解决方法,本谜题中最开始的程序将打印[4,2]。我们发现没有任何一种流行的语言能够令人满意地解决这个问题。也许,我们值得去考虑,当超类构造器调用子类方法时,通过抛出一个不受检查的异常使循环实例初始化非法。

总之,在任何情况下,你都务必要记住:不要在构造器中调用可覆 写的方法。在实例初始化中产生的循环将是致命的。该问题的解决方案就是惰性初始化[EJ Items 13,48]。

2
0
分享到:
评论

相关推荐

    九点算法代码

    5. **确定最终单应性矩阵**:通过投票机制选择最佳模型,即匹配点最多的那个。最终的单应性矩阵可以用来将一个图像的点映射到另一个图像上。 6. **应用与验证**:应用单应性矩阵进行图像变换,例如,可以将一个图像...

    harris角点检测

    在局部区域内,只保留响应最大的那个点,其余点被消除。 7. **角点定位**:最后,根据特征值和对应的特征向量,对角点进行精确定位。 在Visual C++中实现这个过程,你需要使用OpenCV库,它提供了现成的函数`...

    获得线上最近的点

    4. **找到最近点**:遍历所有线,找出与用户点击点距离最近的那个线。 5. **投影到线上**:一旦找到最近的线,就需要找到这条线上最接近的点。这可以通过解决一个线性代数问题来实现,即将用户点的坐标投影到线的...

    求二位数组的“数组”。二维数组的“最大点”定义为:某个数式所在行的最大值,并且是所在列的最大值。某行或某列上可能有多个“最大点”

    题目:求二位数组的“最大点”。二维数组的“最大点”定义为:某个数式所在行的最大值,并且是所在列的最大值。某行或某列上可能有多个“最大点” 样例输入: 3 4 8 60 7 100 10 498 12 49 -71 132 4 85 ...

    三维_三维旋转_matlab_三维坐标旋转_空间点_空间点旋转_

    旋转中心是指旋转不发生改变的那个固定点,通常是一个三维坐标。旋转角度则是决定旋转程度的参数,通常以度或弧度为单位。 MATLAB提供了几种方法来实现三维旋转,其中最常用的是使用`rotate`函数或者通过构建旋转...

    点圆互动新款白板软件whiteboard v5.1

    点圆白板的新型号(应该是中控机驱动不了的那个)的白板软件(应该驱动也被他们合并到一起了) 点圆官网没了 特此分享 (点圆白板的标号不清楚,据售后说全都贴一种型号 这款适用于有mspread中控驱动不了的白板 /...

    一种自适应的Harris 角点检测算法

    如果存在,则删除响应值较低的那个角点,以避免角点聚簇现象。 3. **自动阈值调整**:根据图像的整体特性自动调整阈值\( T \),使得角点检测更为合理。例如,可以通过统计所有响应值的分布情况,选择一个合适的...

    如何决定用多少点来做FFT.docx

    寻找模值最大的那个点,其所对应的频率值即是信号的主要频率(基波频率)。 #### 四、FFT的应用场景 许多信号在时域上难以分析其特征,但通过变换到频域后就能清晰地识别。因此,FFT广泛应用于各种领域,如通信、...

    C#求解平面最接近点对问题

    一种常见的策略是先将所有点按照x坐标进行排序,然后对每个点,找到其右边所有点中与之距离最小的那个点。这个过程可以使用二分查找来优化,以提高效率。以下是一个简单的实现: ```csharp List<Point> points = ...

    1dB压缩点.pdf

    ### 1dB压缩点及其在射频放大器中的意义 #### 一、1dB压缩点的概念 1dB压缩点(P1dB),作为衡量射频放大器非线性特性的关键参数之一,在通信系统设计中占据着极其重要的地位。简单来说,1dB压缩点是指在射频放大...

    微积分基本知识点,适用于大一

    当我们说函数在某点的极限是某个值时,意味着无论我们从哪个方向接近这个点,函数的值都会无限接近于那个极限值。极限的数学表示通常用符号“lim”来表示,它能够揭示函数的变化趋势,帮助我们理解无穷小量的概念。 ...

    单点登录SSO的实现原理

    实现单点登录说到底就是要解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性。 存储信任 实现单点登录的关键是存储信任关系,可以使用 Cookie 或者 Flash 的 Shared Object API 来存储信任关系...

    VB编程大赛:求圆弧相交点代码

    VB编程大赛:求圆弧相交点代码 这是一个求两圆弧相交点的程序。界面中有图区,操作及显示区。操作分为两个步骤: 1· 创建圆弧" 。 点击“创建圆槐按钮,可进入创建圆弧工作状态。进入创建圆弧工作状态以后,光标...

    2013_驾校一点通

    《2013_驾校一点通》作为一个全面的驾驶学习平台,其2013年版可能还包含了当时的考试政策、题型变化等信息,对于那个时期的学员来说,是非常有价值的参考资料。然而,随着时间的推移,驾驶考试的相关规定和标准可能...

    Qt+OpenGL+VS2010写的输入点坐标,显示点和直线

    新建一个空的Qt工程 把所给的文件添加进去即可 界面有两个部分 视图部分 点坐标输入部分 由于用的是QSpinBox类的 editingFinished 信号 所以每次输入顶点的时候不管原先那个点的值有没有变化 都要让光标在那个框中过...

    青岛版五四制二年级数学下册知识点归纳及基本习题.pdf

    * 先看数位,数位不同,数位多的那个数就大。 * 数位不同,先从最高位依次比较,直至比较出大小。 八、算盘的认识 * 会在算盘上表示数,也要会读写出算盘上的数。 此外,本资源摘要信息还包括长度单位的认识、...

    oracle_检查点概念及说明解读.pdf

    检查点算法使用检查点队列管理脏缓存,按重做值排序,DBWR按照升序顺序写出缓存区,确保每次检查点写入都是指向最早的那个检查点。这样设计的优势在于可以有效地处理多个并发的检查点请求,同时保证DBWR的工作负载...

    基于孪生神经网络实现的点选识别python源码(带注释)+运行说明+数据集+预训练模型.zip

    基于孪生神经网络实现的点选识别python源码(带注释)+运行说明+数据集+预训练模型.zip ## 效果 4090训练100轮 测试集可以达到98.6%以上,基本上已经破解了该类验证码。 ![效果演示](./beeb1dc9cdf4f18a98a51d631745ba...

    k-mediods.rar_K mediods_K-中心点_K-中心点聚类_k-mediods聚类_k中心点

    选取使簇内总距离最小的那个点作为新的Mediod。 4. **迭代检查**: 重复上述分配和更新阶段,直到Mediods不再改变或者达到预设的最大迭代次数。 5. **结果输出**: 输出最后得到的K个Mediods和对应的簇划分。 在图像...

Global site tag (gtag.js) - Google Analytics