JAVA的初始化的坑以及内存分配

最近在看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。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值