java类加载与初始化

第一段:

class A{

  public A(){

    this.list();

  }

  public void list(){

    System.out.println("in a list..");

  }

}

class B extends A{

  private final static B instance  = new B(); //这里会调用list,是在还没构造结束的时候就调用了,但这里不会错

  public static B getInstance(){

    System.out.println("getting instance b..");

    return instance;

  }

  public void list(){

    System.out.println("in b list...");

  }

}

public class LoadClassErrorDemo {

  public static void main(String[] args) {

    B.getInstance();

  }

}

运行结果:

in b list...

getting instance b..

结果分析:通过getInstance()方法获取B类实例的时候需要加载B类,加载的过程中需要调用B类的构造方法, B类的构造方法最先调用A类的构造方法,A的构造方法需要使用list方法,但是该方法在B类中已经重载了,所以真正调用的是B类的list。

这种设计是有问题的,A类作为一个可以继承的类,但不能预料自己会被哪些类继承,也无法控制后续子类对list的修改,是上层代码依赖下层代码的严重设计漏洞。修改方式:

1 将A类的list方法不对外公开,即private;

2 将A类的构造方法拆分,将动态的部分和静态的部分拆开,静态在自身设定并封闭,动态支持扩展;

3 这种情况应该将A设计为抽象类,让子类去完成自己list方法,但自身不提供。

第二段:

class A{

  public A(){

    this.list();

  }

  public void list(){

    System.out.println("in a list..");

  }

}

class B extends A{

  private final static B instance  = new B(); //这里会调用list,是在还没构造结束的时候就调用了  

  public static B getInstance(){

    System.out.println("getting instance b..");

    return instance;

  }

  public void list(){

    System.out.println("in b list...");

    try{

      Thread.sleep(1000);

    }catch(Exception e){

      e.printStackTrace();

    }

    instance.list2();

  }

  public void list2(){}

}

public class LoadClassErrorDemo {

  public static void main(String[] args) {

    new Thread(){

      public void run(){

        try{

          Thread.sleep(1000);

        }catch(Exception e){

          e.printStackTrace();

        }

        B.getInstance().list2();

      }

    }.start();

  }

}

运行结果:

in b list...

Exception in thread "Thread-0" java.lang.ExceptionInInitializerError

at chapter03.load.LoadClassErrorDemo$1.run(LoadClassErrorDemo.java:42)

Caused by: java.lang.NullPointerException

at chapter03.load.B.list(LoadClassErrorDemo.java:26)

at chapter03.load.A.(LoadClassErrorDemo.java:4)

at chapter03.load.B.(LoadClassErrorDemo.java:11)

at chapter03.load.B.(LoadClassErrorDemo.java:12)

... 1 more

说明:在调用A类构造方法时使用了B类的list方法,B类的list方法需要调用instance的list2方法,在调用之前需要保证instance实例已经生成,但是instance必须在B类加载并初始化之后才有效,所以无法调用。这里将A的list方法改为private也就不存在这个问题,这种间接调用导致实例未生成需要注意下。

类的加载分为3个部分:读取文件,从父到子的ClassLoader加载,多个路径中都没有找到则抛出ClassNotFoundException ; 链接,对字节码进行解析,校验,不符合标准则抛出ClassNotFoundError ; 初始化,调用这个Class对象自身的构造函数,静态变量,static块赋值。

所有的类必须在使用前加载和初始化,如果多个线程同时尝试访问该类,必须等待static块执行完成,否则都将阻塞。 所以static中代码不宜太多,尤其是IO操作,否则阻塞时间可能很长。

简单写下java初始化:

1 局部变量使用之前必须初始化,但是类的成员声明时如果不给定值则有默认的值,句柄为null(思考下原因:类的每个方法可能初始化或使用对象变量,使用对象前必须设定值不科学);

2 自动初始化和赋值初始化不受构造函数影响:

class Counter {

  int i;
  Counter() { i = 7; }

}//这里会先将i的值置零然后调用构造函数i赋值为7

class A2{

  int v;

  LoadClassErrorDemo l;

  public A2(){

    f1();

    v=7;

    System.out.println("in the end v="+v);

  }

  void f1(){

    System.out.println("in method f1 v="+v);

    System.out.println("in method l="+l);

  }

  void f2(int x){

    System.out.println(x);

  }

}

public class InitTest {

  public static void main(String[] args) {

    new A2();

  }

}

运行结果:

in method f1 v=0

in method l=null

in the end v=7

3 静态数据成员的初始化(部分来源于java编程思想中的):若数据是静态的( static),如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于 static 值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。

class Bowl {

  Bowl(int marker) {

    System.out.println("Bowl(" + marker + ")");

  }

  void f(int marker) {

    System.out.println("f(" + marker + ")");

  }

}

class Table {

  static Bowl b1 = new Bowl(1);

  Table() {

    System.out.println("Table()");

    b2.f(1);

  }

  void f2(int marker) {

    System.out.println("f2(" + marker + ")");

  }

  static Bowl b2 = new Bowl(2);

}

class Cupboard {

  Bowl b3 = new Bowl(3);

  static Bowl b4 = new Bowl(4);

  Cupboard() {

    System.out.println("Cupboard()");

    b4.f(2);

  }

  void f3(int marker) {

    System.out.println("f3(" + marker + ")");

  }

  static Bowl b5 = new Bowl(5);

}

public class StaticInitialization {

  public static void main(String[] args) {

    System.out.println("Creating new Cupboard() in main");

    new Cupboard();

    System.out.println("Creating new Cupboard() in main");

    new Cupboard();

    t2.f2(1);

    t3.f3(1);

  }

  static Table t2 = new Table();

  static Cupboard t3 = new Cupboard();

} ///:~

运行结果:

Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

f2(1)

f3(1)

首先加载StaticInitialization类的时候需要加载两个静态对象t2,t3,加载非静态成员t3时候发现b3在b4和b5加载后初始化(仔细想想很多时候非静态成员需要使用静态成员,也就理解了);另外Bowl(3)出现了3次,而Bowl(4),Bowl(5)仅出现一次,说明每创建一个Cupboard需要为其生成新的b3成员,而静态成员是共享的。

综上所述:

初始化的顺序是首先 static(如果它们尚未由前一次对象创建过程初始化),接着是非 static 对象。

现考虑一个名为 Dog 的类:
(1) 类型为 Dog 的一个对象首次创建时,或者 Dog 类的 static 方法/static 字段首次访问时, Java 解释器必须找到 Dog.class(在事先设好的类路径里搜索)。
(2) 找到 Dog.class 后(它会创建一个 Class 对象),它的所有 static 初始化模块都会运行。因此, static 初始化仅发生一次——在 Class 对象首次载入的时候。
(3) 创建一个 new Dog() 时, Dog 对象的构建进程首先会在内存堆(Heap)里为一个 Dog 对象分配足够多的存储空间。
(4) 这种存储空间会清为零,将 Dog 中的所有基本类型设为它们的默认值(零用于数字,以及 boolean 和char 的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。

时间: 2024-11-02 23:14:02

java类加载与初始化的相关文章

【转载】Java系列笔记(1) - Java 类加载与初始化

Java系列笔记(1) - Java 类加载与初始化 原文地址:http://www.cnblogs.com/zhguang/p/3154584.html 目录 类加载器 动态加载 链接 初始化 示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用. 每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一

Java系列笔记(1) - Java 类加载与初始化

目录 类加载器 动态加载 链接 初始化 示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用. 每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一个原生的类加载器,它只加载Java API等可信类,通常只是在本地磁盘中加载,这些类一般就够我们使用了.如果我们需要从远

Java 类加载与初始化

文章转自 http://www.cnblogs.com/zhguang/p/3257367.html, 该文章中的类加载概念与<java编程思想>概念有所不同.<java编程思想>里类加载包含了该文章的类加载,链接,验证,初始化等过程.请读者注意. 目录 类加载器 动态加载 链接 初始化 示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用. 每个类编译后产生一个Class对象,存储在.class文

【转载】Java 类加载与初始化

原文地址:http://www.cnblogs.com/zhguang/p/3154584.html 目录 类加载器 动态加载 链接 初始化 示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用. 每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一个原生的类加载器,

JAVA类加载和初始化

Java程序运行由java虚拟机负责.类从加载到虚拟机内存到卸载出内存,包括 加载-----链接-----初始化-----使用------卸载 链接具体包括:验证-----准备-----解析 加载:由类加载器执行,查找字节码并从这些字节码中创建一个Class对象. 链接:验证类中的字节码:为静态域分配存储内存并赋予默认值:解析这个类创建的对其他类的所有引用. 初始化:该类具有基类,则对其初始化,执行静态初始化和静态初始化块. 类初始化的时机:程序中首次使用才初始化. 首次主动使用: 1.    

Java实例变量初始化

由一道面试题所想到的--Java实例变量初始化 时间:2015-10-07 16:08:38      阅读:23      评论:0      收藏:0      [点我收藏+] 该题目源自微信公众号(程序员的那些事)的推送:携程 Java 工程师的一道面向对象面试题 题目是这样的:求下面程序的输出: public class Base { private String baseName = "base"; public Base() { callName(); } public v

Java类加载及变量初始化过程

Java虚拟机如何把编译好的.class文件加载到虚拟机里面?加载之后如何初始化类?静态类变量和实例类变量的初始化过程是否相同,分别是如何初始化的呢?这篇文章就是解决上面3个问题的. 本文前面理论部分比较枯燥,但是如果耐心读完,结合后面的实例,我相信你以后绝对不会再遇到java类初始化这样的疑惑.若有不正之处,请多多谅解并欢迎各位能够给予批评指正,提前谢谢各位. 1. Java虚拟机加载.class过程 虚拟机把Class文件加载到内存,然后进行校验,解析和初始化,最终形成java类型,这就是虚

java类加载和对象初始化

对象初始化过程:  1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化:  2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化:  3.其次,初始化父类的普通成员变量和代码块,在执行父类的构造方法: 4.最后,初始化子类的普通成员变量和代码块,在执行子类的构造方法: Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性

第16篇-JAVA 类加载与反射

第16篇-JAVA 类加载与反射 每篇一句 :敢于弯曲,是为了更坚定的站立 初学心得: 追求远中的欢声笑语,追求远中的结伴同行 (笔者:JEEP/711)[JAVA笔记 | 时间:2017-05-12| JAVA 类加载与反射 ] 1.类加载 类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象 当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载.连接.初始化三个步骤来对该类进行初始化,如果没