java类的初始化

时间:2020-12-05 17:27:53

1、构造方法其实就是一个隐式的static方法(thinking in java),构造器是一种特殊的static方法。

      如果是从导致类加载的角度来看,那么算static的,因为访问类的static方法或static属性,或者调用构造函数会导致类被加载。
      如果从static方法内部不能调用非static方法来说,构造器里面又能调用非static方法


2、当我们指定了其他的构造方法,而整个类有且仅有一个这个构造方法:编译器将不允许你以其他任何方式创建没有在类定义的构造器对象

      相当于我们对类讲:我已经为你定义了构造你的方法,除了这个方法以外,你不需要用其他的方法去创造对象,即使它曾经是默认构造器


3、可以在一个构造方法里面调用另外一个构造方法,但是禁止在其他地方调用构造方法


4、重构:重构就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

      重载: 在一个类定义中,可以编写几个同名的方法,但是只要它们的签名参数列表不同,Java就会将它们看做唯一的方法。简单的说,一个类中的方法与另一个方法同名,但是参数表不同,这种方法称之为重载方法。不能使用返回值作为评判标准,因为当你调用的时候,没有指定返回值,编译器不知道你要调用哪一个

      重写:即把父类的方法覆盖了,重新实现;即是同一个函数

      参考文章:http://blog.csdn.net/u011031854/article/details/11570885


5、成员的初始化,如果没有指定值,我们的编译器会在你创建成员变量的地方为他们提供初始化的值


6、java编译器在处理类的初始化的时候其实是经过三个阶段:

(1、加载:由类加载器执行,查找字节码并从字节码创建一个class对象(可以这样子说:类在初次使用的时候才会发生加载

(2、链接:在链接阶段将验证类中的字节码,为静态区域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用

(3、初始化:首先初始化静态块、静态变量、静态方法,如果该类有超类,则对其初始化


在加载过程中,类的初次使用有如下几种形式:
1) 创建类的实例
2) 访问某个类或者接口的静态变量,或者对该静态变量赋值(如果访问静态编译时常量(即编译时可以确定值的常量)不会导致类的初始化)
3) 调用类的静态方法
4) 反射(Class.forName(xxx.xxx.xxx))
5) 初始化一个类的子类(相当于对父类的主动使用),不过直接通过子类引用父类元素,不会引起子类的初始化
6) Java虚拟机被标明为启动类的类(包含main方法的)
类与接口的初始化不同,如果一个类被初始化,则其父类或父接口也会被初始化,但如果一个接口初始化,则不会引起其父接口的初始化。


接下来就是对空间的分配,但此时仅仅是分配空间并把所有内存置为0

在初始化的过程中,在同一个概念层次来说,即static层次、普通成员变量层次等,变量定义的先后顺序决定了初始化的先后顺序

public class House {
public House(){
System.out.println("In the house:");
Dog dog6 = new Dog(6);
}
static {
Dog dog5 = new Dog(5);
}
private Dog dog7 = new Dog(7);
private static Dog dog1 = new Dog(1);//在dog1、2、3这几个对象,如果没有new,对象是不会被初始化的
private static Dog dog3 = new Dog(3);
private static Dog dog2 = new Dog(2);

public static void createDog(){
Dog dog4 = new Dog(4);
}
}//初始化顺序是5 1 3 2 7 6


其次,我们能发现静态块是会被初始化,尝试把静态块语句放到后面,输出的顺序也会变,就是说静态块的初始化符合“变量定义的先后顺序决定了初始化的先后顺序”这个原则。如果类里面有形如static final修饰的值,我们称之为编译时常量,直接调用   类名.值   的方式访问不会初始化类

构造方法实质上是一个特殊的静态方法,但是构造方法会在所有的变量初始化后进行初始化,而普通成员变量会在static成员变量初始化之后初始化

所以初始化顺序:静态对象——非静态对象——构造方法


假设有一个Dog的类:

(1、即使没有显式的使用static关键字,构造器实际上也是静态方法,因此,在首次创造Dog对象额时候,或者Dog类的静态方法/静态域首次被访问,java解释器必须查找类路径,定位Dog.class
(2、载入Dog.class,有关静态初始化的所有动作都会执行,因此,静态初始化只有在class对象首次加载的时候进行一次
(3、当使用new Dog()来创建对象的时候,首先在堆里为Dog对象分配足够的存储空间
(4、存储空间会清零,这就自动的将Dog对象的所有基本类型数据设置为默认值,引用为null

(5、执行出现在定义处的初始化动作

(6、执行构造器

参考:

http://blog.csdn.net/zhengzhb/article/details/7517213


7、为了更好理解初始化顺序,引用一道阿里巴巴的面试题:http://my.oschina.net/chape/blog/299533?fromerr=G7bNLvjy

public class InitializeDemo {
private static int k = 1;
private static InitializeDemo t1 = new InitializeDemo("t1");
private static InitializeDemo t2 = new InitializeDemo("t2");
private static int i = print("i");
private static int n = 99;
static {
print("静态块");
}
private int j = print("j");
{
print("构造块");
}
public InitializeDemo(String str) {
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
new InitializeDemo("init");
}
}
我们可以看到他的输出结果:

1:j   i=0    n=0

2:构造块   i=1    n=1

3:t1   i=2    n=2
4:j   i=3    n=3
5:构造块   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:静态块   i=7    n=99
9:j   i=8    n=100
10:构造块   i=9    n=101
11:init   i=10    n=102

在main方法开始加载这个类的时候,所有的static属性的成员、方法都已经在链接阶段分配好存储控件并且进行默认值的初始化,也就是说,那些static的k、i、n全是0

记住一个原则:顺序执行初始化

第一步:k=1出来了,但是并没有任何打印信息

第二步:初始化t1,注意static的那些变量只有一份,而且他们的空间已经有了,都是0,这时候我们只需要考虑t1自己的变量和构造方法,这时候就是j,于是输出j   i=0    n=0

第三步:t1的成员变量初始化完毕,轮到他自己的构造块和构造方法输出构造块   i=1    n=1t1   i=2    n=2

第四步:t1初始化完毕,轮到t2,按照上面的思路,同样t2的j是他自己的,先把j输出来:j   i=3    n=3,同理是构造块和构造方法:构造块   i=4    n=4和t2   i=5    n=5

第五步:这时候t1、t2搞定,回来到本体,轮到i:i   i=6    n=6

后面的就不写了,顺序执行,一个道理,到了这里大家也不难明白

再看看另外一道:http://topic.csdn.net/u/20120222/22/dc082753-6298-4709-ba5a-a6df55c3a207.html

public class Test{  
private static Test tester = new Test();
private static int count1;
private static int count2 = 2;
public Test(){
count1++;
count2++;
System.out.println("" + count1 + count2);
}
public static Test getTester(){
return tester;
}

public static void main(String[] args){
Test.getTester();
}
输出1 1

只是输出1 1,但其实执行下来count1 = 1,count2 = 2;

这个也不难理解


8、类型信息

public class Initable {
static final int staticFinal = 47;
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static{
System.out.println("Initializing Initable");
}
}

public class Initable2 {
static int staticNonFinal = 147;
static{
System.out.println("Initializing Initable2");
}
}

public class Initable3 {
static int staticNonFinal = 74;
static{
System.out.println("Initializing Initable3");
}
}

public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
// TODO Auto-generated method stub
Class initable = Initable.class;
System.out.println("After creating Initable ref");
System.out.println(Initable.staticFinal);
System.out.println(Initable.staticFinal2);

System.out.println(Initable2.staticNonFinal);

Class initable3 = Class.forName("com.example.test.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}

运行输出:

After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

编译时常量static final int initable = 47; 访问的时候并不会发生初始化


9、继承初始化顺序:基类—— 子类

public class Insect {
private int i = 9;
protected int j;
public Insect() {
System.out.println("i="+i+" j="+j);
j=39;
}
private static int x1 = printInit("static Insect x2 initialized");
static int printInit(String s){
System.out.println(s);
return 47;
}
}

public class Beetle extends Insect {
private int k = printInit("Beetle k initialized");
public Beetle() {
System.out.println("k="+k);
System.out.println("j="+j);
}
private static int x2 = printInit("static Beetle x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}

运行结果:

static Insect x2 initialized
static Beetle x2 initialized
Beetle constructor
i=9 j=0
Beetle k initialized
k=47
j=39

因为main方法在bettle类里面,在使用加载beetle的过程中,编译器注意到他并不是基类,于是去寻找基类,得到Insect后:

1)、先按照初始化顺序初始化static成员,输出static Insect x2 initialized,然后子类的static成员被初始化(原因是子类的static成员可能依赖于父类的static成员是否能被正确的初始化)

2)、由于本例是因为在加载main的时候就需要对insect初始化,并未执行new Bettle(),所以会先输出Beetle constructor,但是如果把main放在另外的新类,就会最首先输出Beetle constructor这句话。言归正传,初始化父类的普通成员变量后接下来就是父类的构造方法,然后子类的普通成员变量,子类的构造方法

所以:父类静态成员——子类静态成员——父类普通成员以及构造方法——子类普通成员以及构造方法

关于接口:在接口里面的域是隐式的static和final的,会在类第一次加载的时候被初始化


在继承里面,我们可以看看这一种情况:

public class Glyph {
void draw(){
System.out.println("Glyph.draw");
}
public Glyph() {
System.out.println("Glyph before draw");
draw();
System.out.println("Glyph after draw");
}
}

public class RoundGlyph extends Glyph {
private int radius = 1;
public RoundGlyph(int r) {
System.out.println("RoundGlyph,radius = "+radius);
}
void draw(){
System.out.println("RoundGlyph,radius = "+radius);
}
}

如果new一下子类对象
运行输出:

Glyph before draw
RoundGlyph,radius = 0
Glyph after draw
RoundGlyph,radius = 1


我们可以发现,当调用被覆盖的方法时,该方法仍未被初始化,未初始化的成员是默认值,这也验证了上面那道面试题在初始化时成员会先开辟空间并且提供默认值这种情况,其实,java编程思想里面也详细描述了上面这个代码的过程(这样的优点是所有东西能在调用前至少初始化成0):

(1、在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零

(2、如前所述那样调用基类的构造器,此时,调用被覆盖后的draw()方法,在调用RoundGlyph构造器之前调用,由于步骤一,我们会发现radius为0

(3、按照声明的顺序初始化

(4、调用导出类(子类)的构造器主体


10、简单提及一下java的垃圾回收:我们都知道java有一个垃圾回收器,不需要程序员手动清理多余的内存,关于垃圾回收器仍然有不少细节,例如:

1、垃圾回收器只知道释放那些被new(堆)出来的内存,而不会回收其部分的内存。

2、垃圾回收器不一定释放没用到的对象

3、回收之前会执行finalize()方法,但是这不意味者在finalize()里面做清理工作——因为你不知道他什么时候要回收,也意味着清理工作的执行时间你不可控

4、停止——复制:垃圾回收器回收策略之一,先暂停程序的运行,然后把存活的对象复制一份到另外一个堆,没有被复制的全部是垃圾,在这种方法效率不高,除了复制动作浪费时间,还浪费空间

5、标记——清扫:遍历所有的引用,寻找存活的对象,加以标记,当遍历完成之后,把没有标记的对象释放,但是剩下的空间是不连续的

6、这两种方式是结合使用的,统称“自适应”技术,java虚拟机以“块”来处理这些事,内存大的单独放在一个块,小的多个放在一个块,用代数来记录他是否存活,被引用了代数则增加,很明显,小的对象使用复制的方法整理,而大的对象不需要复制,只会增加代数