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

合计数的玩笑

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


下面的程序在一个类中计算并缓存了一个合计数,并且在另一个类中打印了这个合计数。那么,这个程序将打印出什么呢?这里给一点提示:你可能已经回忆起来了,在代数学中我们曾经学到过,从1到n的整数总和是n(n+1)/2。
class Cache {
static {
initializeIfNecessary();
}
private static int sum;
public static int getSum() {
initializeIfNecessary();
return sum;
}
private static boolean initialized = false;
private static synchronized void initializeIfNecessary() {
if (!initialized) {
for (int i = 0; i < 100; i++)
sum += i;
initialized = true;
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println(Cache.getSum());
}
}


草草地看一遍,你可能会认为这个程序从1加到了100,但实际上它并没有这么做。再稍微仔细地看一看那个循环,它是一个典型的半开循环,因此它将从0循环到99。有了这个印象之后,你可能会认为这个程序打印的是从0到99的整数总和。用前面提示中给出的公式,我们知道这个总和是99×100/2,即4,950。

但是,这个程序可不这么想,它打印的是9900,是我们所预期值的整整两倍。是什么导致它如此热情地翻倍计算了这个总和呢?

该程序的作者显然在确保sum在被使用前就已经在初始化这个问题上,经历了众多的麻烦。该程序结合了惰性初始化和积极初始化,甚至还用上了同步,以确保缓存在多线程环境下也能工作。看起来这个程序已经把所有的问题都考虑到了,但是它仍然不能正常工作。它到底出了什么问题呢?

该程序受到了类初始化顺序问题的影响。为了理解其行为,我们来跟踪其执行过程。在可以调用Client.main之前,VM必须初始化Client类。这项初始化工作异常简单,我们就不多说什么了。Client.main方法调用了Cache.getsum方法,在getsum方法可以被执行之前,VM必须初始化Cache类。
回想一下,类初始化是按照静态初始器在源代码中出现的顺序去执行这些初始器的。Cache类有两个静态初始器:在类顶端的一个static语句块,以及静态域initialized的初始化。静态语句块是先出现的,它调用了方法initializeIfNecessary,该方法将测试initialized域。因为该域还没有被赋予任何值,所以它具有缺省的布尔值false。与此类似,sum具有缺省的int值0。因此,initializeIfNecessary方法执行的正是你所期望的行为,将4,950添加到了sum上,并将initialized设置为true。

在静态语句块执行之后,initialized域的静态初始器将其设置回false,从而完成Cache的类初始化。遗憾的是,sum现在包含的是正确的缓存值,但是initialized包含的却是false:Cache类的两个关键状态并未同步。

此后,Client类的main方法调用Cache.getSum方法,它将再次调用initializeIfNecessary方法。因为initialized标志是false,所以initializeIfNecessary方法将进入其循环,该循环将把另一个4,950添加到sum上,从而使其值增加到了9,900。getSum方法返回的就是这个值,而程序打印的也是它。
很明显,该程序的作者认为Cache类的初始化不会以这种顺序发生。由于不能在惰性初始化和积极初始化之间作出抉择,所以作者同时运用这二者,结果产生了大麻烦。要么使用积极初始化,要么使用惰性初始化,但是千万不要同时使用二者。

如果初始化一个域的时间和空间代价比较低,或者该域在程序的每一次执行中都需要用到时,那么使用积极初始化是恰当的。如果其代价比较高,或者该域在某些执行中并不会被用到,那么惰性初始化可能是更好的选择[EJ Item 48]。另外,惰性初始化对于打破类或实例初始化中的循环也可能是必需的。

通过重排静态初始化的顺序,使得initialized域在sum被初始化之后不被复位到false,或者通过移除initialized域的显式静态初始化操作,Cache类就可以得到修复。尽管这样所产生的程序可以工作,但是它们仍旧是混乱的和病构的。Cache类应该被重写为使用积极初始化,这样产生的版本很明显是正确的,而且比最初的版本更加简单。

使用这个版本的Cache类,程序就可以打印出我们所期望的4950:

class Cache {
private static final int sum = computeSum();
private static int computeSum() {
int result = 0;
for (int i = 0; i < 100; i++)
result += i;
return result;
}
public static int getSum() {
return sum;
}
}


请注意,我们使用了一个助手方法来初始化sum。助手方法通常都优于静态语句块,因为它让你可以对计算命名。只有在极少的情况下,你才必须使用一个静态语句块来初始化一个静态域,此时请将该语句块紧随该域声明之后放置。这提高了程序的清晰度,并且消除了像最初的程序中出现的静态初始化与静态语句块互相竞争的可能性。

总之,请考虑类初始化的顺序,特别是当初始化显得很重要时更是如此。请你执行测试,以确保类初始化序列的简洁。请使用积极初始化,除非你有某种很好的理由要使用惰性初始化,例如性能方面的因素,或者需要打破初始化循环。

2
0
分享到:
评论

相关推荐

    FastReport 报表合计数

    FastReport 报表合计数,通常情况下都是在数据尾显示合计数, 但是如果要将合计数放在报表头,不能将合计字段拖到表头的,否则它会显示为0,因为FastReport是自从上往下输出数据的,是需要写脚本来实现。 在...

    CAD 合计数字

    CAD

    做了调整分录后实质性底稿中的审定表合计数与明细表不一致.pdf

    调整分录后实质性底稿中的审定表合计数与明细表不一致解决方案 在会计系统中,调整分录是调整财务报表的重要步骤,但是有时在调整分录后,实质性底稿中的审定表合计数与明细表可能会不一致,这将导致财务报表的准确...

    求出Excel各项目极值的合计数.rar

    求出Excel各项目极值的合计数.rar,在日常工作中,用户可能会遇到求极值的问题,本例技巧将向读者介绍数据库函数在三维引用中的使用。本例所示,在两列数据中,求出各项目最大金额的总和。为了便于读者理解,还将其中...

    oracle非数字合计,将字段中含有汉子的数据自动转换为 数字0

    根据提供的文件信息,本文将详细介绍如何实现“oracle非数字合计”,即将字段中含有汉字或其他非数字字符的数据自动转换为数字0的过程。 ### 一、问题背景 在实际工作中,可能会遇到这样的情况:某个字段中本应...

    NC6多子表表头表体合计.docx

    ### NC65多子表开发及其合计功能实现 #### 多子表开发概述 NC65作为一款先进的企业管理软件,在其开发过程中支持多种定制化需求,其中包括多子表的开发。多子表的设计使得用户可以在同一张单据上管理多个不同类型的...

    《科达电脑外部设备管理信息系统》1.pdf

    3. 货款合计数和税款合计数:系统需要能够计算并显示货款合计数(含税)和税款合计数。 4. 数据添加功能:系统需要能够对电脑外部设备的数据进行添加、修改、删除和查询。 系统设计 为了满足上述要求,本系统将...

    专利授权数_合计.xlsx

    专利授权数_合计.xlsx

    datagridview带合计功能

    DataGridView带合计功能实现 DataGridView 是一个常用的网格控件,用于显示和编辑数据。然而,DataGridView 并不提供直接的合计功能,这使得开发者需要自己编写代码来实现合计功能。下面是实现 DataGridView 带合计...

    c# datagridview 行合计

    在这个场景中,我们将讨论如何实现“c# datagridview 行合计”功能,即在`DataGridView`的最底行添加一个固定的合计行,该合计行不会随滚动条的滚动而移动。 首先,我们需要了解`DataGridView`的基本结构。`...

    给Extjs的GridPanel增加“合计”行

    在实际应用中,我们经常需要在GridPanel底部显示一行“合计”行,以便对某一列或多列的数据进行求和或其他统计操作。这篇博文“给Extjs的GridPanel增加‘合计’行”将指导我们如何实现这一功能。 首先,我们需要...

    SQL 合计函数.rar

    在SQL(Structured Query Language)中,合计函数是用于对数据集进行数学运算并返回单一值的函数。这些函数广泛应用于数据分析、报表制作以及数据库查询,帮助我们快速获取表中的汇总信息。本资料“SQL 合计函数.rar...

    datagridview合计行演示

    datagridview合计行演示代码 private void Form1_Load(object sender, EventArgs e) { dataGridViewTotal1.dt = GetData(); dataGridViewTotal1.DataGridView1.DataSource = GetData(); } private DataTable ...

    datagridview加入合计行

    ### 数据GridView添加合计行知识点解析 #### 一、前言 在开发Windows Forms应用程序时,经常需要处理数据展示及汇总需求。`DataGridView`控件因其功能强大且易于使用而被广泛采用。本文将深入探讨如何在...

    DataGridView底部合计行

    本主题聚焦于“DataGridView底部合计行”的实现,这在处理财务、统计等需要计算汇总值的应用场景中非常常见。下面我们将详细探讨如何在`DataGridView`中添加并保持合计行始终处于底部。 首先,理解“合计行”是关键...

    flex datagrid 表格 合计

    Flex DataGrid 是 Adobe Flex 框架中的一个组件,它用于在应用程序中显示表格数据。在Flex中,实现数据网格的总计功能是一项常见的需求,它能够帮助用户快速地理解和分析大量数据。本项目提供了一个已经实现了总计...

    ext grid 合计行

    找了半天,结果在extjs的老家找到一个前辈写的代码,可以在grid上面加上合计, &lt;br&gt;但是却只能合计grid里面的数据,但是我们平常一般是只显示20行或者30行,这样的合计就没有什么意义,我们的合计数据是单独从...

    DataGridView显示合计行 包含例子

    本教程将深入讲解如何在`DataGridView`中实现合计行,并且确保这个合计行始终保持在可视区域的底部,即使在滚动时也不例外。我们将主要关注C#编程语言和WinForms环境。 首先,我们需要在`DataGridView`中添加一个...

    C# DataGridView 添加合计行简单代码

    网上一些增加合计行的代码太复杂,自己写了一个简单的

Global site tag (gtag.js) - Google Analytics