这篇文章实际的内容并没有多少,或者说对实际编程影响并不大,主要是为了可能的笔试面试之类的,还有就是有点好玩而已。
有一天我在书上看到很有意思的代码:
class A{
{
System.out.println("----->A的初始化块");
}
static {
System.out.println("----->A的静态初始化块");
}
public A(){
System.out.println("----->A的构造器");
}
}
class B extends A{
{
System.out.println("---->B的初始化块");
}
static {
System.out.println("---->B的静态初始化块");
}
public B(){
System.out.println("---->B的构造器");
}
}
class C extends B{
{
System.out.println("--->C的初始化块");
}
static {
System.out.println("--->C的静态初始化块");
}
public C(){
System.out.println("--->C的构造器");
}
}
public class ForBlogTest {
public static void main(String[] args) {
C c = new C();
System.out.println("----------------------------------分割线----------------------------------");
C c1 = new C();
}
}
你假如已经知道他的输出了,那么可以不用看这篇文章了。以下是他的输出结果。
下面我就来解释以下,当然这是建立在我对jvm还没有深入了解的基础上讨论的,相信不就后,我会站在那个角度来对他进行解释。
首先输出的是A的静态初始块,为什么,我们可以把这个问题分解为两个。1,为什么输出静态初始块; 2,为什么先输出A 。
我们先来说说第一个问题:因为在我们new 一个变量时,编译器首先会各种方式找到你调用的类,然后对它进行加载。因为静态的变量,方法,初始块都属于类,所以在加载一个类时,也会加载属于他的初始块,方法,和变量。所以,我们第一个输出的是初始块,但为什么又只输出一次呢。因为在我们的内存中已经加载了该类,所以下一次new一个新对象时,根本不需要再重新加载,因此也就只掉用一次初始块了。
那第二个问题呢:因为在初始一个对对象的时候,编译器会先找到它的父类,并先调用他的初始化操作,所以可以得到,每次初始一个对象是,最先调用的是Object的初始化操作。
那么又为什么是先输出初始块呢。因为编译器会在生成class文件中将初始块里的代码加到所调用的构造器的器中,并在最前面。所以,我们总能看到初始块里的代码是先于构造器中的代码输出的。到这,这个例子也就理解差不多了。那我们再来看个东西
class D{
private int num = 3;
public D(){
num = 4;
}
public int getNum(){
return num;
}
}
调用getNum(),将会得到什么呢。没错就是4。其实假如再加一段代码,我们就可以把它和上面所说的联系起来了。
class D{
private int num = 3;
{
num = 4;
}
public D(){
}
public int getNum(){
return num;
}
}
你觉得这个会输出什么呢。的确,是4.那么当我们这样写呢。
class D{
{
num = 4;
}
private int num = 3;
public D(){
}
public int getNum(){
return num;
}
}
num的值就变成了3。所以我们可以得出结论。其实在类中初始化的变量的优先级和初始块了初始化变量的优先级是一样的。因此,不难理解初始块里的代码为什么先于构造器中的输出了。好了,还不够,还有点有意思的东西。
class E{
public E(){
echo();
}
public void echo() {
System.out.println("-------->这是父类中的echo");
}
}
class F extends E{
public void echo(){
System.out.println("----->这是子类中的echo");
}
}
public class ForBlogTest{
public static void main(String[] args) {
F f = new F();
}
}
请问,这会输出什么。
答案是:----->这是子类中的echo
是不是感觉挺好玩的。在编译过程中什么事都是按照平常的进行。当时在运行时,子类的方法直接将父类的覆盖了,所以就输出了子类echo中的内容。这个原理就有点类似于
class E{
public void echo() {
System.out.println("-------->这是父类中的echo");
}
}
class F extends E{
public void echo(){
System.out.println("----->这是子类中的echo");
}
public void test(){
}
}
public class ForBlogTest{
public static void main(String[] args) {
E f = new F();
f.echo();
}
}
这个echo是输出子类中echo里的代码,但是,我们调用f.test();时,连编译器都不能通过。但是强制类型转化后又能调用,说明在运行时,内存中加载的的确是F中的方法和变量。