最近在看JAVA程序猿16课,看到了变量的初始化和内存分配,然后觉得挺多坑的,就总结了下来。(顺便,书真的挺好的)
PART 1:首先,看一个例子:
package test;
public class TestInit
{
public static void main(String[] args)
{
new Dervied();
}
}
class Base
{
static
{
System.out.println("Base static block");
}
public static int a = 10;
{
System.out.println("base block");
}
public Base()
{
// TODO Auto-generated constructor stub
System.out.println("base constructor");
}
}
class Dervied extends Base
{
public static int b = 10;
static
{
System.out.println("de static block");
}
{
System.out.println("de block");
}
public Dervied()
{
// TODO Auto-generated constructor stub
System.out.println("de constructor");
}
}
//输出的结果是
Base static block
de static block
base block
base constructor
de block
de constructor
使用,javap -c *.class工具查看编译结果,字节码,可以看到运行的顺序。
在这个例子当中,我们把static变量和static block的位置兑换了一下,他的字节码
看到Field的编译提前了,类似的非static变量和非static的Field调换位置,也可以发现他们的编译顺序是调换了的。所以得出的结论是:
(父类static变量,父类static block)-->(子类static 变量,子类static block)-->(父类的非static block,非static Field)-->父类构造器-->(子类非static block,子类非static FIeld)-->子类构造器,这是初始化的顺序,在没有添加final关键字和其他关键字,只有static和权限修饰符之下的情况。
其中,括号内的表示需要看他们的顺序,比如父类的static变量和static block,谁在代码顺序的前面编译时候就先执行。
//注,javap <optioins> <classes>的使用
1,-c:分解方法代码
2,-l指定显示的行号和局部变量的列表 3,-public|protected|package|private:用于指定显示那种级别的类成员,分别对应JAVA的四种访问权限
4,-verbose:指定显示更进一步的详细信息
PART 2:
先看例子:
package test;
public class PriceTest
{
public static void main(String[] args)
{
System.out.println(Prince.INISTANCE.currentPrice);
Prince p = new Prince(2.8);
System.out.println(p.currentPrice);
}
}
class Prince
{
static Prince INISTANCE = new Prince(2.8);
static double initPrice = 20;//加上final之后,编译会提前
double currentPrice;
public Prince(double discount)
{
// TODO Auto-generated constructor stub
this.currentPrice = initPrice - discount;
}
}
//结果是
-2.8
17.2
为什么不都是17.2呢?首先,根据前面的结论,我们知道系统先回编译INISTANCE 这个变量,他是static的,然后,他回去先执行new Prince(2.8),去掉用构造方法,但是,这个时候,我们的initPrice的值是0.0(默认值,不是初始值),所以是-2.8,然后第二次的时候,系统调用了new一个对象之前,我们的static变量和static block以及执行完(这里没有block)
PART 3:
package test1;
public class Test
{
public static void main(String[] args)
{
new Derived();
}
}
class Base
{
public int i = 2;
public Base()
{
System.out.println(this.i);
this.display();
System.out.println(this.getClass());
}
public void display()
{
System.out.println(i);
}
}
class Derived extends Base
{
public int i = 22;
public Derived()
{
i = 222;
}
public void display()
{
System.out.println(i);
}
}
//结果
2
0
class test1.Derived
为什么this.i = 2,而输出的display()当中的i是0。首先,我们知道,JAVA为每一个成员变量(不管是不是静态的)都会有一个默认值(没有初始化),就是声明这个变量的时候,比如int的默认值是0,就是int a,a作为成员变量,他的默认值就是0,初始化是指为a赋值。
然后是this关键字:代表的是正在初始化的对象或者是传入的对象,在这里,我们看到this.getClass()输出的是Derived类不是Base,所以这里的this是Derived对象而不是Base对象,因为我们再Test的main函数里面new Derived()。然后Derived是继承了Base,调用了Base的默认构造器,到了Base构造器里面,this就是指Derived对象,但是,他是Base的引用指向了Derived对象。当编译类型和运行类型不同的时候,通过变量访问他的引用的对象的实例变量时,该实例变量由声明该变量的类型决定,但是通过该变量调用它引用的对象的实例方法的时候,该方法将有他的实际所引用的对象决定。这里的引用是this,属于Base类型,它指向的对象是Derived对像。所以他调用成员变量就是调用Base的成员变量而不是Derived的。然后它调用方法,假如该方法子类存在而且是父类可以访问到的(就是override的),就是调用子类方法,否则是调用父类的对应方法。这里的结果就是这样,this.i是Base的i,然后this.display()或者是diaplay()就是Derived的方法(Derived存在该方法,override了,不存在的时候调用的是父类的),但是这时候子类的i还没有初始化,所以是0。
PART4:继承方法和继承变量的不同:
package test;
public class Test
{
public static void main(String[] args)
{
Base1 b = new Base1();
System.out.println(b.count);
b.display();
Deriver d = new Deriver();
System.out.println(d.count);
d.display();
Base1 db = new Deriver();
System.out.println(db.count);
db.display();
Base1 d2b = d;
System.out.println(d2b.count);
}
}
class Base1
{
int count = 2;
public void display()
{
System.out.println(this.count);
}
}
class Deriver extends Base1
{
int count = 20;
@Override
public void display()
{
System.out.println(this.count);
}
}
//结果
2
2
20
20
2
20
2
当使用父类引用指向子类对象的时候,父类引用调用的成员变量时父类引用的成员变量,父类引用调用成员方法的时候,假如是子类有override的就是调用子类的方法,否则是父类的方法。在子类的对象(也就是堆当中),除了存在子类的对象的变量的值之外,还有一块内存用于给父类的变量。
PART 5:final修饰符
一句话:final修饰的变量在声明时候初始化,那么他的作用就相当于一个宏变量。他的值是在编译时期就已经决定下来。在运行的时候,遇到改变了就会进行替换。
在PART 2的initPrice 变量中,加上final关键字,结果会不一样,都是17.2.
package test;
public class FinalTest
{
public static void main(String[] args)
{
final String book = "疯狂Java讲义:" + 99.0;// 宏变量
final String str = "疯狂Java讲义:99.0";
final String book2 = "疯狂Java讲义:" + String.valueOf(99.0);// 无法确定值。不能再编译期间确定,不会被当做宏变量,因为调用了String的方法,具有不缺定性
System.out.println(book == "疯狂Java讲义:99.0");
System.out.println(book2 == "疯狂Java讲义:99.0");
System.out.println(str == book);
System.out.println(str == book2);
}
}
//结果:
true
false
true
false
我们知道String字符串都是的存储是在常量池当中,首先是当常亮池没有这个字符串,我们天津进去,当有这个字符串的时候,我们就不会在新建一个字符串放在常量池当中。直接比如String a="A";String b = "A";。开始A不存在常量池,存放进去,a指向A,然后b的值"A"以在常量池存在,直接b指向A,这时候a == b的值是true。在这个demo也是类似的。
package test;
public class FinalInstanceVaribaleTest
{
public static void main(String[] args)
{
String s1 = "ABC";
String s2 = "AB" + "C";
System.out.println(s1 == s2);
String s3 = "DEF";
String s4 = s1 + s3;
String s5 = "ABCDEF";
System.out.println(s4 == s5);
}
}
//结果
true
false
把s1和s3的修饰符改为final之后,是
true
true
原因:s1和数是变量,s4不能再编译时期确定值,s5在编译时期确定值,然后使用了final之后s1和s3是宏变量,确定了s4,所以是true。