当前位置:首页 > 开发 > 编程语言 > 编程 > 正文

所谓高手程序员犯下的错误

发表于: 2014-09-19   作者:annan211   来源:转载   浏览次数:
摘要: 看下面一段代码,这是一个网上程序员写下的一段代码,摘录而来,你能根据 http://annan211.iteye.com/blog/2118115 来分析其出错的原因吗? package com.ccb.framework.enums; import java.util.Collections; import java.util.HashMap; impor
看下面一段代码,这是一个网上程序员写下的一段代码,摘录而来,你能根据

http://annan211.iteye.com/blog/2118115

来分析其出错的原因吗?

package com.ccb.framework.enums;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class CachingEnumResolver {
	//单态实例 一切问题皆由此行引起
	private static final CachingEnumResolver SINGLE_ENUM_RESOLVER = new  
	CachingEnumResolver(); //[1]
	/*MSGCODE->Category内存索引*/
	private static Map CODE_MAP_CACHE;//[2]
	static {
		CODE_MAP_CACHE = new HashMap();
		//为了说明问题,我在这里初始化一条数据
		CODE_MAP_CACHE.put("0","北京市");
	}
	
	//private, for single instance
	private CachingEnumResolver() {
		//初始化加载数据  引起问题,该方法也要负点责任
		initEnums();
	}
	
	/**
	 * 初始化所有的枚举类型
	 */
	public static void initEnums() {		
		// ~~~~~~~~~问题从这里开始暴露 ~~~~~~~~~~~//
		if (null == CODE_MAP_CACHE) {
			System.out.println("CODE_MAP_CACHE为空,问题在这里开始暴露.");
			CODE_MAP_CACHE = new HashMap();
		}
		CODE_MAP_CACHE.put("1", "北京市");
		CODE_MAP_CACHE.put("2", "云南省");
		
		//..... other code...
	}
	
	public Map getCache() {
		return Collections.unmodifiableMap(CODE_MAP_CACHE);
	}
	
	/**
	 * 获取单态实例
	 * 
	 * @return
	 */
	public static CachingEnumResolver getInstance() {
		return SINGLE_ENUM_RESOLVER;
	}
	
	public static void main(String[] args) {
		System.out.println(CachingEnumResolver.getInstance().getCache());
	}
}


写下这段代码的作者的本意是希望 输出一个包含 3个键值对的map,但是结果却只输出了
CODE_MAP_CACHE为空,问题在这里开始暴露.
{0=北京市}

要想发现问题的根源 和 解决问题,我们还需要从基础做起,从类的加载说起。

Java 文件被jvm编译器 变异成字节码 也就是class 文件,class 文件中包含了这个类所有的基本信息。字节码被加载进入内存,或者主动或被动的被初始化,这里的主动和被动 如下解释:

主动引用:

创建某个类的新实例--新实例可通过new 关键字,反射,克隆,反序列化创建
调用某个类的静态方法
调用某个类或接口的静态字段(final修饰的除外)

使用Java的某些反射方法
初始化某个类的子类
虚拟机启动时包含有main的启动类


以上6种情形会造成类的初始化。

这里有必要把类的加载和初始化区分开,加载是指把class字节码载入内存,一个类的初始化过程包含以下几个步骤
  1 加载
 
加载 :
     这里的加载是指将class文件载入内存,由类加载器执行,并且创建一个Class 对象放入堆中。
  
   
  2 连接 
 
连接:
     这里的连接 是指 将已经加载的Java 二进制代码 组合到jvm运行时内存当中。
     连接又包含以下几步骤:
        验证阶段:
            验证即是检验有无恶意代码,检查final 类有没有被继承等不符合Java规范的代码快存在。
        准备阶段:
            准备阶段主要是创建静态域,分配空间。将基本类型的变量0,boolean类型的变量设置为false,引用类型设置为 null.
        解析阶段:
            解析的过程就是对类中的接口、类、方法、变量的符号引用进行解析并定位,解析成直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址),并保证这些类被正确的找到。解析的过程可能导致其它的类被加载。需要注意的是,根据不同的解析策略,这一步不一定是必须的,有些解析策略在解析时递归的把所有引用解析,这是early resolution,要求所有引用都必须存在;还有一种策略是late resolution,这也是Oracle 的JDK所采取的策略,即在类只是被引用了,还没有被真正用到时,并不进行解析,只有当真正用到了,才去加载和解析这个类。
       
  

  3 初始化
 
初始化
          首先执行静态代码块static{},接着是静态变量,接着是静态方法。
          当执行到 private static final CachingEnumResolver SINGLE_ENUM_RESOLVER = new CachingEnumResolver(); 时,会出现jvm对Java对象的初始化,会按照 类内部静态块 > 类静态属性 > 类内部属性 > 类构造函数  将类的属性进行初始化。
  


  根据以上的理基础  我们来分析原作者的这段代码
  第一步 字节码被加载
  第二部 被加载的字节码需要经过连接阶段,在连接阶段 先后经过 检查 准备 解析三个小阶段。 在准备阶段  将 静态变量 SINGLE_ENUM_RESOLVER   CODE_MAP_CACHE  设置为null.
  接着解析,分析是否还有其他需要被载入初始化的类。
  第三步 初始化阶段 按照  类内部静态块 > 类静态属性 > 类内部属性 > 类构造函数优先级顺序一次初始化


类初始化顺序


对于普通的Java程序,一般都不需要显式的声明来动态加载Java类,只需要用import关键字将相关联的类引入,类被第一次调用的时候,就会被加载初始化。那对于一个类对象,其内部各组成部分的初始化顺序又是如何的呢?
         一个Java类对象在初始化的时候必定是按照一定顺序初始化其静态块、静态属性、类内部属性、构造方法。这里我们讨论的初始化分别针对两个对象,一个是类本身还有一个是类实例化的对象。
         类本身的初始化会在类被加载完毕、链接完成之后,由Java虚拟机负责调用<clinit>方法完成。在这个方法中依次完成了堆类内部静态块的调用和类内部静态属性的初始化(如果存在父类,父类会优先进行初始化)。不论创建多少个实例化的对象,一个类只会被初始化一次。
         类实例化的对象通过new操作创建,Java虚拟机保证一个类在new操作实例化其对象之前已经完成了类的加载、链接和初始化。之后Java虚拟机会调用<init>方法完成类实例化对象的初始化。这个方法会优先按照代码中顺序完成对类内部个属性的初始化,之后再调用类的构造函数(如果有父类,则优先调用父类的构造函数)。
  PS:需要注意的是上述提到的<init>和<clinit>方法都是非法的Java方法名,是由编译器命名的,并不能由编码实现。
  综上所述,我们大致可以得出以下结论,对于一个类,在实例化一个这个类的对象时,我们可以保证以下这样的优先级进行初始化:类内部静态块 > 类静态属性 > 类内部属性 > 类构造函数[

所谓高手程序员犯下的错误

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
[以下文字和图片资料来自互联网] "Bill Joy MIT BBS上说微软电话面试的一道题就是“Who do you thin
递归是什么?在说此之前,我先讲个故事吧,话说从前有座山,山上有个庙,庙里有个老和尚,老和尚在
所谓大学:管理监狱化,素质流氓化,Kiss公开化,消费白领化,上课梦境化,逃课普遍化,寝室网吧化
十大编程算法助程序员走上高手之路 转自:http://mobile.51cto.com/news-450017_all.htm 本文为大家
算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要
算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要
感谢 天朝临时工 投递给天朝娱乐! 拿破仑进军巴黎时,一家报纸在几天内所用的标题:第一天:“科西
创新、创新、创新,人人都在说它,对你来说什么又是创新呢? Google Image告诉我一个改变世界的答案
标签:SQL SERVER/MSSQL/DBA/T-SQL好习惯/数据库/需要注意的地方/程序员/容易犯的错误/遇到的问题
#1. 把Array转化成ArrayList 把Array转化成ArrayList,程序员经常用以下方法: List<String>
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号