国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

解析Java程序中對象內(nèi)存的分配和控制的基本方法

2019-11-26 14:24:29
字體:
供稿:網(wǎng)友

一、對象與內(nèi)存控制的知識點

1.java變量的初始化過程,包括局部變量,成員變量(實例變量和類變量)。
2.繼承關(guān)系中,當(dāng)使用的對象引用變量編譯時類型和運行時類型不同時,訪問該對象的屬性和方法是有區(qū)別的。
3.final修飾符特性。

二、java變量的劃分與初始化過程

java程序的變量大體可以分為成員變量和局部變量,成員變量可以分為實例變量(非靜態(tài)變量)和類變量(靜態(tài)變量),一般我們遇到的局部變量會在下列幾種情況中出現(xiàn):
(1)形參:在方法簽名中定義的局部變量,由調(diào)用方為其賦值,隨著方法結(jié)束消亡。
(2)方法內(nèi)的局部變量:在方法內(nèi)定義的局部變量必須在方法內(nèi)顯示的初始化(賦初始值),隨著變量初始化完成開始,到方法結(jié)束而消亡。
(3)代碼塊內(nèi)的局部變量:在代碼塊內(nèi)定義的局部變量必須在代碼塊內(nèi)顯示的初始化(賦初始值),隨著初始化完成開始生效,隨著代碼塊的結(jié)束而消亡。

package com.zlc.array; public class TestField {  {    String b ;    //如果不初始化,編譯器就會報The local variable b may not have been initialized    System.out.println(b);  }  public static void main(String[] args) {    int a ;    //如果不初始化,編譯器就會報The local variable a may not have been initialized    System.out.println(a);  }}

使用static修飾的成員變量是類變量,屬于類本身,沒有用static修飾的成員變量是實例變量,屬于該類的實例,在同一個JVM里面,每個類只能對應(yīng)一個Class對象,但每個類可以創(chuàng)建多個java對象。(也就是說一個類變量只需一塊內(nèi)存空間,而該類每創(chuàng)建一次實例,就需要為實例變量分配一塊空間)
   
實例變量的初始化過程:從語法角度來說,程序可以在三個地方對實例變量執(zhí)行初始化:
(1)定義實例變量時指定初始值。
(2)非靜態(tài)塊中對實例變量指定初始值。
(3)構(gòu)造器中對實例變量指定初始值。
其中(1)和(2)這兩種方式初始化時間都比(3)在構(gòu)造器中要早,(1)和(2)兩種初始化順序是按照他們在源碼中的排列順序決定的。 
 

package com.zlc.array; public class TestField {  public TestField(int age){    System.out.println("構(gòu)造函數(shù)中初始化 this.age = "+this.age);    this.age = age;  }  {    System.out.println("非靜態(tài)塊中初始化");    age = 22;  }  //定義的時候初始化  int age = 15;  public static void main(String[] args) {    TestField field = new TestField(24);    System.out.println("最終 age = "+field.age);  }}

 

運行結(jié)果為:非靜態(tài)塊中初始化
構(gòu)造函數(shù)中初始化 this.age = 15
最終 age = 24
如果會使用javap的話,可以通過javap -c XXXX(class文件)看下改java類是如何編譯的。
定義實例變量時指定初始值,初始化塊中為實例變量指定初始值語句地位是平等的,當(dāng)經(jīng)過編譯器編譯處理之后,他們都被提到構(gòu)造器中,上面所說的 int age = 15;會劃分下面兩個步驟執(zhí)行:
1)int age;創(chuàng)建java對象時系統(tǒng)根據(jù)該語句為該對象分配內(nèi)存。
2)age = 15;這條語句會被提取到j(luò)ava類的構(gòu)造器中執(zhí)行。
   
類變量的初始化過程:從語法角度來說,程序可以從兩個地方對類變量進行初始化賦值。
(1)定義類變量時指定初始值。
(2)靜態(tài)塊中對類變量指定初始值。
兩種執(zhí)行順序和他們在源碼中的排列順序相同,我們舉個小變態(tài)的例子:

package com.zlc.array;  class TestStatic {  //類成員 DEMO TestStatic實例  final static TestStatic DEMO = new TestStatic(15);  //類成員 age  static int age = 20;  //實例變量 curAge  int curAge;     public TestStatic(int years) {    // TODO Auto-generated constructor stub    curAge = age - years;  }} public class Test{  public static void main(String[] args) {    System.out.println(TestStatic.DEMO.curAge);    TestStatic staticDemo = new TestStatic(15);    System.out.println(staticDemo.curAge);  }}

輸出結(jié)果有兩行打印,一個是打印TestStatic類屬性DEMO的實例變量,第二個通過java對象staticDemo輸出TestStatic的實例屬性,根據(jù)我們上面分析的實例變量和類變量的初始化流程可以進行推斷:
1)初始化第一階段,加載類的時候為類變量DEMO、age分配內(nèi)存空間,此時DEMO和age的默認(rèn)值分別是null和0。
2)初始化第二階段,程序按順序依次給DEMO、age賦初始值,TestStatic(15)需要調(diào)用TestStatic的構(gòu)造器,此時age = 0 所以打印結(jié)果為 -15,而當(dāng)staticDemo被初始化的時候,age已經(jīng)被賦值等于20了,所以輸出結(jié)果為5。

三、在繼承關(guān)系中繼承成員變量和繼承成員方法的區(qū)別

當(dāng)創(chuàng)建任何java對象時,程序總會先調(diào)用父類的非靜態(tài)塊、父類構(gòu)造器,最后才調(diào)用本類的非靜態(tài)塊和構(gòu)造器。通過子類的構(gòu)造器調(diào)用父類的構(gòu)造器一般分為兩種情況,一個是隱式調(diào)用,一個通過super顯示調(diào)用父類的構(gòu)造器。
子類的方法可以調(diào)用父類的實例變量,這是因為子類繼承了父類就會獲取父類的成員變量和方法,但父類的方法不能訪問子類的實例變量,因為父類不知道它將被哪個類繼承,它的子類將會增加什么樣的成員變量,當(dāng)然在一些極端的例子里面還是可以實現(xiàn)父類調(diào)用子類變量的,比如:子類重寫了父類的方法,一般都會打印出默認(rèn)值,因為這個時候子類的實例變量還沒有初始化。

package com.zlc.array;class Father{  int age = 50;  public Father() {    // TODO Auto-generated constructor stub        System.out.println(this.getClass());        //this.sonMethod();無法調(diào)用    info();  }  public void info(){    System.out.println(age);  }}public class Son extends Father{  int age = 24;  public Son(int age) {    // TODO Auto-generated constructor stub    this.age = age;  }  @Override  public void info() {    // TODO Auto-generated method stub    System.err.println(age);  }  public static void main(String[] args) {    new Son(28);  }    //子類特有的方法    public void sonMethod(){         System.out.println("Son method");    }}

按照我們正常推斷,通過子類隱式的調(diào)用父類的構(gòu)造器,而在父類的構(gòu)造器中調(diào)用了info()方法(注意:我這里沒有說調(diào)用父類的),按道理來說是輸出了父類的age實例變量,打印結(jié)果預(yù)計是50,但實際輸出的結(jié)果為0,分析原因:
1)java對象的內(nèi)存分配不是在構(gòu)造器中完成的,構(gòu)造器只是完成了初始化賦值的過程,也就是在調(diào)用父類的構(gòu)造器之前,jvm已經(jīng)給這個Son對象分類好了內(nèi)存空間,這個空間存放了兩個age屬性,一個是子類的age,一個是父類的age。
2)在調(diào)用new Son(28)的時候,當(dāng)前的this對象代表著是子類Son的對象,我們可以通過把對象.getClass()打印出來就會得到class com.zlc.array.Son的結(jié)果,但是當(dāng)前初始化過程又是在父類的構(gòu)造器中進行的,通過this.sonMethod()又無法被調(diào)用,這是因為this的編譯類型是Father的緣故。
3)在變量的編譯時類型和運行時類型不同時,通過該變量訪問它的引用對象的實例變量時,該實例變量的值由聲明該變量的類型決定,但通過該變量調(diào)用它引用的對象的實例方法時,該方法的行為由它實際引用的對象決定,所以這里調(diào)用的是子類的info方法,所以打印的是子類的age,由于age還沒來得急初始化所以打印默認(rèn)值0。
通俗的來說也就是,當(dāng)聲明的類型和真正new的類型不一致的時候,使用的屬性是父類的,調(diào)用的方法是子類的。
通過javap -c我們更能直接的體會為什么繼承屬性和方法會有很大的區(qū)別,如果我們把上面例子里面,子類Son的info重寫方法去掉,這個時候調(diào)用的會是父類的info方法,是因為在進行編譯的時候會把父類的info方法編譯轉(zhuǎn)移到子類里面去,而聲名的成員變量會留在父類中不進行轉(zhuǎn)移,這樣子類和父類擁有了同名的實例變量,而如果子類重寫了父類的同名方法,則子類的方法會完全覆蓋掉父類的方法(至于為什么java要這么設(shè)計,個人也不太清楚)。同名變量能同時存在不覆蓋,同名方法子類會徹底覆蓋父類同名方法。
總的來說對于一個引用變量而言,當(dāng)通過該變量訪問它所引用的對象的實例變量時,該實例變量的值取決于聲明該變量時類型,當(dāng)通過該變量訪問它所引用的對象的方法時,該方法行為取決于它所實際引用的對象的類型。
最后拿個小case復(fù)習(xí)下:

package com.zlc.array;class Animal{  int age ;  public Animal(){       }  public Animal(int age) {    // TODO Auto-generated constructor stub    this.age = age;  }  void run(){    System.out.println("animal run "+age);  }}class Dog extends Animal{  int age;  String name;  public Dog(int age,String name) {    // TODO Auto-generated constructor stub    this.age = age;    this.name = name;  }  @Override  void run(){    System.out.println("dog run "+age);  }}public class TestExtends {  public static void main(String[] args) {    Animal animal = new Animal(5);    System.out.println(animal.age);    animal.run();         Dog dog = new Dog(1, "xiaobai");    System.out.println(dog.age);    dog.run();         Animal animal2 = new Dog(11, "wangcai");    System.out.println(animal2.age);    animal2.run();         Animal animal3;    animal3 = dog;    System.out.println(animal3.age);    animal3.run();  }}

想要調(diào)用父類的方法:可以通過super來調(diào)用,但super關(guān)鍵字沒有引用任何對象,它不能當(dāng)做真正的引用變量來使用,有興趣的朋友可以自己研究下。
上面介紹的都是實例變量和方法,類變量和類方法要簡單多了,直接使用類名.方法就方便了很多,也不會遇到那么多麻煩。

四、final修飾符的使用(特別是宏替換)

(1)inal可以修飾變量,被final修飾的變量被賦初始值之后,不能對他重新賦值。

(2)inal可以修飾方法,被final修飾的方法不能被重寫。

(3)inal可以修飾類,被final修飾的類不能派生子類。


被final修飾的變量必須顯示的指定初始值:
對于是final修飾的是實例變量,則只能在下列三個指定位置賦初始值。
(1)定義final實例變量時指定初始值。
(2)在非靜態(tài)塊中為final實例變量指定初始值。
(3)在構(gòu)造器中為final實例變量指定初始值。
最終都會被提到構(gòu)造器中進行初始化。
對于用final指定的類變量:只能在指定的兩個地方進行賦初始值。
(1)定義final類變量的時候指定初始值。
(2)在靜態(tài)塊中為final類變量指定初始值。
同樣經(jīng)過編譯器處理,不同于實例變量的是,類變量都是提到靜態(tài)塊中進行賦初始值,而實例變量是提到構(gòu)造器中完成。
   被final修飾的類變量還有一種特性,就是“宏替換”,當(dāng)被修飾的類變量滿足在定義該變量的時候就指定初始值,而且這個初始值在編譯的時候就能確定下來(比如:18、"aaaa"、16.78等一些直接量),那么該final修飾的類變量不在是一個變量,系統(tǒng)就會當(dāng)成“宏變量”處理(就是我們常說的常量),如果在編譯的時候就能確定初始值,則就不會被提到靜態(tài)塊中進行初始化了,直接在類定義中直接使該初始值代替掉final變量。我們還是舉那個年齡減去year的例子:

package com.zlc.array;  class TestStatic {  //類成員 DEMO TestStatic實例  final static TestStatic DEMO = new TestStatic(15);  //類成員 age  final static int age = 20;  //實例變量 curAge  int curAge;     public TestStatic(int years) {    // TODO Auto-generated constructor stub    curAge = age - years;  }} public class Test{  public static void main(String[] args) {    System.out.println(TestStatic.DEMO.curAge);    TestStatic static1 = new TestStatic(15);    System.out.println(static1.curAge);  }}

這個時候的age 被final修飾了,所以在編譯的時候,父類中所有的age都變成了20,而不是一個變量,這樣輸出的結(jié)果就能達到我們的預(yù)期。
特別是在對字符串進行比較的時候更能顯示出

package com.zlc.array; public class TestString {  static String static_name1 = "java";  static String static_name2 = "me";  static String statci_name3 = static_name1+static_name2;     final static String final_static_name1 = "java";  final static String final_static_name2 = "me";  //加final 或者不加都行 前面

主站蜘蛛池模板:
和林格尔县|
韶山市|
井冈山市|
清涧县|
韶关市|
云霄县|
依安县|
云龙县|
綦江县|
个旧市|
澄城县|
繁峙县|
定襄县|
正安县|
湖南省|
龙州县|
绥芬河市|
鄢陵县|
泗阳县|
屯门区|
慈溪市|
济阳县|
新乡县|
洛南县|
铜陵市|
确山县|
肥城市|
桐梓县|
定兴县|
阳原县|
涞源县|
长武县|
宁化县|
嘉定区|
晋中市|
宣恩县|
龙口市|
兰溪市|
安福县|
韶山市|
海门市|