Java中一些基礎概念的使用詳解

來源:互聯網
上載者:User

  類的初始化順序
  在Java中,類裡面可能包含:靜態變數,靜態初始化塊,成員變數,初始化塊,建構函式。在類之間可能存在著繼承關係,那麼當我們執行個體化一個對象時,上述各部分的載入順序是怎樣的?

  首先來看代碼:

複製代碼 代碼如下:class Parent
{
public static StaticVarible staticVarible= new StaticVarible("父類-靜態變數1");
public StaticVarible instVarible= new StaticVarible("父類-成員變數1");

static
{
System.out.println("父類-靜態塊");
}

{
System.out.println("父類-初始化塊");
}

public static StaticVarible staticVarible2= new StaticVarible("父類-靜態變數2");
public StaticVarible instVarible2= new StaticVarible("父類-成員變數2");

public Parent()
{
System.out.println("父類-執行個體建構函式");
}
}

class Child extends Parent
{
public static StaticVarible staticVarible= new StaticVarible("子類-靜態變數1");
public StaticVarible instVarible= new StaticVarible("子類-成員變數1");

static
{
System.out.println("子類-靜態塊");
}

public Child()
{
System.out.println("子類-執行個體建構函式");
}

{
System.out.println("子類-初始化塊");
}

public static StaticVarible staticVarible2= new StaticVarible("子類-靜態變數2");
public StaticVarible instVarible2= new StaticVarible("子類-成員變數2");

}

class StaticVarible
{
public StaticVarible(String info)
{
System.out.println(info);
}
}

  然後執行下面的語句:複製代碼 代碼如下:Child child = new Child();

輸出結果如下:複製代碼 代碼如下:父類-靜態變數1
父類-靜態塊
父類-靜態變數2
子類-靜態變數1
子類-靜態塊
子類-靜態變數2
父類-成員變數1
父類-初始化塊
父類-成員變數2
父類-執行個體建構函式
子類-成員變數1
子類-初始化塊
子類-成員變數2
子類-執行個體建構函式

  結論  
  從上述結果可以看出,在執行個體化一個對象時,各部分的載入順序如下:

  父類靜態成員/父類靜態初始化塊 -> 子類靜態成員/子類初始化塊 -> 父類成員變數/父類初始化塊 -> 父類建構函式 -> 子類成員變數/子類初始化塊 -> 子類建構函式

  和String相關的一些事兒
  首先,我們聊一聊Java中堆和棧的事兒。

•棧:存放基本類型,包括char/byte/short/int/long/float/double/boolean
•堆:存放參考型別,同時一般會在棧中保留一個指向它的指標,記憶體回收判斷一個對象是否可以回收,就是判斷棧中是否有指標指向堆中的對象。
  String作為一種特殊的資料類型,它不完全等同於基本類型,也不是全部的參考型別,許多面試題都有它的身影。

  String類型變數的儲存結構
  String的儲存結構分為兩部分,我們以String a = "abc";為例,描述String類型的儲存方式:

  1)在棧中建立一個char數組,值分為是'a','b','c'。

  2)在堆中建立一個String對象。

  Java中的字串池
  為了節省空間的和資源,JVM會維護一個字串池,或者說會緩衝一部分曾經出現過的字串。

  例如下面的代碼:

複製代碼 代碼如下:String v1 = "ab";
String v2 = "ab";

  實際上,v1==v2,因為JVM在v1聲明後,已經對“ab”進行了緩衝。

  那麼JVM對字串進行緩衝的依據是什嗎?我們來看下面的代碼,非常有意思:

複製代碼 代碼如下:public class StringTest {
public static final String constValue = "ab";
public static final String staticValue;

static
{
staticValue="ab";
}

public static void main(String[] args)
{
String v1 = "ab";
String v2 = "ab";
System.out.println("v1 == v2 : " + (v1 == v2));
String v3 = new String("ab");
System.out.println("v1 == v3 : " + (v1 == v3));
String v4 = "abcd";
String v5 = "ab" + "cd";
System.out.println("v4 == v5 : " + (v4 == v5));
String v6 = v1 + "cd";
System.out.println("v4 == v6 : " + (v4 == v6));
String v7 = constValue + "cd";
System.out.println("v4 == v7 : " + (v4 == v7));
String v8 = staticValue + "cd";
System.out.println("v4 == v8 : " + (v4 == v8));
String v9 = v4.intern();
System.out.println("v4 == v9 :" + (v4 == v9));
String v10 = new String(new char[]{'a','b','c','d'});
String v11 = v10.intern();
System.out.println("v4 == v11 :" + (v4 == v11));
System.out.println("v10 == v11 :" + (v10 == v11));
}
}

  請注意它的輸出結果:複製代碼 代碼如下:v1 == v2 : true
v1 == v3 : false
v4 == v5 : true
v4 == v6 : false
v4 == v7 : true
v4 == v8 : false
v4 == v9 :true
v4 == v11 :true
v10 == v11 :false

  我們會發現,並不是所有的判斷都返回true,這似乎和我們上面的說法有矛盾了。其實不然,因為

  結論
  1. JVM只能緩衝那些在編譯時間可以確定的常量,而非運行時常量。

    上述代碼中的constValue屬於編譯時間常量,而staticValue則屬於運行時常量。

  2. 通過使用 new方式建立出來的字串,JVM緩衝的方式是不一樣的。

    所以上述代碼中,v1不等同於v3。

  String的這種設計屬於享元模式嗎?
  這個話題比較有意思,大部分講設計模式的文章,在談到享元時,一般就會拿String來做例子,但它屬於享元模式嗎?

  字串與享元的關係,大家可以參考下面的文章:深入C#字串和享元(Flyweight)模式的流量分析
  字串的反轉輸出
  這種情況下,一般會將字串看做是字元數組,然後利用反轉數組的方式來反轉字串。

  眼花繚亂的方法調用
  有繼承關係結構中的方法調用
  繼承是物件導向設計中的常見方式,它可以有效實現”代碼複用“,同時子類也有重寫父類方法的自由,這就對到底是調用父類方法還是子類方法帶來了麻煩。

  來看下面的代碼:

複製代碼 代碼如下:public class PropertyTest {

public static void main(String[] args)
{
ParentDef v1 = new ParentDef();
ParentDef v2 = new ChildDef();
ChildDef v3 = new ChildDef();
System.out.println("=====v1=====");
System.out.println("staticValue:" + v1.staticValue);
System.out.println("value:" + v1.value);
System.out.println("=====v2=====");
System.out.println("staticValue:" + v2.staticValue);
System.out.println("value:" + v2.value);
System.out.println("=====v3=====");
System.out.println("staticValue:" + v3.staticValue);
System.out.println("value:" + v3.value);
}
}

class ParentDef
{
public static final String staticValue = "父類靜態變數";
public String value = "父類執行個體變數";
}

class ChildDef extends ParentDef
{
public static final String staticValue = "子類靜態變數";
public String value = "子類執行個體變數";
}

  輸出結果如下:複製代碼 代碼如下:=====v1=====
staticValue:父類靜態變數
value:父類執行個體變數
=====v2=====
staticValue:父類靜態變數
value:父類執行個體變數
=====v3=====
staticValue:子類靜態變數
value:子類執行個體變數

  結論
  對於調用父類方法還是子類方法,只與變數的宣告類型有關係,與執行個體化的類型沒有關係。

  到底是值傳遞還是引用傳遞
  對於這個話題,我的觀點是值傳遞,因為傳遞的都是儲存在棧中的內容,無論是基本類型的值,還是指向堆中對象的指標,都是值而非引用。並且在值傳遞的過程中,JVM會將值複製一份,然後將複製後的值傳遞給調用方法。

  按照這種方式,我們來看下面的代碼:

複製代碼 代碼如下:public class ParamTest {

public void change(int value)
{
value = 10;
}

public void change(Value value)
{
Value temp = new Value();
temp.value = 10;
value = temp;
}

public void add(int value)
{
value += 10;
}

public void add(Value value)
{
value.value += 10;
}

public static void main(String[] args)
{
ParamTest test = new ParamTest();
Value value = new Value();
int v = 0;
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
System.out.println("=====change=====");
test.change(v);
test.change(value);
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
value = new Value();
v = 0;
System.out.println("=====add=====");
test.add(v);
test.add(value);
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
}
}

class Value
{
public int value;
}

  它的輸出結果:複製代碼 代碼如下:v:0
value.value:0
=====change=====
v:0
value.value:0
=====add=====
v:0
value.value:10

  我們看到,在調用change方法時,即使我們傳遞進去的是指向對象的指標,但最終對象的屬性也沒有變,這是因為在change方法體內,我們建立了一個對象,然後將”複製過的指向原對象的指標“指向了“新對象”,並且對新對象的屬性進行了調整。但是“複製前的指向原對象的指標”依然是指向“原對象”,並且屬性沒有任何變化。

  final/finally/finalize的區別
  final可以修飾類、成員變數、方法以及方法參數。使用final修飾的類是不可以被繼承的,使用final修飾的方法是不可以被重寫的,使用final修飾的變數,只能被賦值一次。

  使用final聲明變數的賦值時機:

  1)定義聲明時賦值

  2)初始化塊或靜態初始化塊中

  3)建構函式

  來看下面的代碼:

複製代碼 代碼如下:class FinalTest
{
public static final String staticValue1 = "靜態變數1";
public static final String staticValue2;

static
{
staticValue2 = "靜態變數2";
}

public final String value1 = "執行個體變數1";
public final String value2;
public final String value3;

{
value2 = "執行個體變數2";
}

public FinalTest()
{
value3 = "執行個體變數3";
}
}

  finally一般是和try...catch放在一起使用,主要用來釋放一些資源。

  我們來看下面的代碼:

複製代碼 代碼如下:public class FinallyTest {

public static void main(String[] args)
{
finallyTest1();
finallyTest2();
finallyTest3();
}

private static String finallyTest1()
{
try
{
throw new RuntimeException();
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("Finally語句被執行");
}
try
{
System.out.println("Hello World");
return "Hello World";
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("Finally語句被執行");
}
return null;
}

private static void finallyTest2()
{
int i = 0;
for (i = 0; i < 3; i++)
{
try
{
if (i == 2) break;
System.out.println(i);
}
finally
{
System.out.println("Finally語句被執行");
}
}
}

private static Test finallyTest3()
{
try
{
return new Test();
}
finally
{
System.out.println("Finally語句被執行");
}
}
}

  執行結果如下:複製代碼 代碼如下:java.lang.RuntimeException
at sample.interview.FinallyTest.finallyTest1(FinallyTest.java:16)
at sample.interview.FinallyTest.main(FinallyTest.java:7)
Finally語句被執行
Hello World
Finally語句被執行

Finally語句被執行

Finally語句被執行
Finally語句被執行
Test執行個體被建立
Finally語句被執行

  注意在迴圈的過程中,對於某一次迴圈,即使調用了break或者continue,finally也會執行。

  finalize則主要用於釋放資源,在調用GC方法時,該方法就會被調用。

  來看下面的樣本:

複製代碼 代碼如下:class FinalizeTest
{
protected void finalize()
{
System.out.println("finalize方法被調用");
}

public static void main(String[] args)
{
FinalizeTest test = new FinalizeTest();
test = null;
Runtime.getRuntime().gc();
}
}

  執行結果如下:複製代碼 代碼如下:finalize方法被調用

  關於基本類型的一些事兒
  基本類型供分為9種,包括byte/short/int/long/float/double/boolean/void,每種基本類型都對應一個“封裝類”,其他一些基本資料如下:
複製代碼 代碼如下:. 基本類型:byte 二進位位元:8
. 封裝類:java.lang.Byte
. 最小值:Byte.MIN_VALUE=-128
. 最大值:Byte.MAX_VALUE=127
. 基本類型:short 二進位位元:16
. 封裝類:java.lang.Short
. 最小值:Short.MIN_VALUE=-32768
. 最大值:Short.MAX_VALUE=32767
. 基本類型:int 二進位位元:32
. 封裝類:java.lang.Integer
. 最小值:Integer.MIN_VALUE=-2147483648
. 最大值:Integer.MAX_VALUE=2147483647
. 基本類型:long 二進位位元:64
. 封裝類:java.lang.Long
. 最小值:Long.MIN_VALUE=-9223372036854775808
. 最大值:Long.MAX_VALUE=9223372036854775807
. 基本類型:float 二進位位元:32
. 封裝類:java.lang.Float
. 最小值:Float.MIN_VALUE=1.4E-45
. 最大值:Float.MAX_VALUE=3.4028235E38
. 基本類型:double 二進位位元:64
. 封裝類:java.lang.Double
. 最小值:Double.MIN_VALUE=4.9E-324
. 最大值:Double.MAX_VALUE=1.7976931348623157E308
. 基本類型:char 二進位位元:16
. 封裝類:java.lang.Character
. 最小值:Character.MIN_VALUE=0
. 最大值:Character.MAX_VALUE=65535

  關於基本類型的一些結論(來自《Java面試解惑》)
•未帶有字元尾碼標識的整數預設為int類型;未帶有字元尾碼標識的浮點數預設為double類型。
•如果一個整數的值超出了int類型能夠表示的範圍,則必須增加尾碼“L”(不區分大小寫,建議用大寫,因為小寫L與阿拉伯數字1很容易混淆),表示為long型。
•帶有“F”(不區分大小寫)尾碼的整數和浮點數都是float類型的;帶有“D”(不區分大小寫)尾碼的整數和浮點數都是double類型的。
•編譯器會在編譯期對byte、short、int、long、float、double、char型變數的值進行檢查,如果超出了它們的取值範圍就會報錯。
•int型值可以賦給所有數實值型別的變數;long型值可以賦給long、float、double類型的變數;float型值可以賦給float、double類型的變數;double型值只能賦給double類型變數。
  關於基本類型之間的轉換
  下面的轉換是無損精度的轉換:

•byte->short
•short->int
•char->int
•int->long
•float->double
  下面的轉換是會損失精度的:

•int->float
•long->float
•long->double
  除此之外的轉換,是非法的。

  和日期相關的一些事兒
  Java中,有兩個類和日期相關,一個是Date,一個是Calendar。我們來看下面的樣本:

複製代碼 代碼如下:public class DateTest {

public static void main(String[] args) throws ParseException
{
test1();
test2();
test3();
}

private static void test1() throws ParseException
{
Date date = new Date();
System.out.println(date);
DateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sf.format(date));
String formatString = "2013-05-12";
System.out.println(sf.parse(formatString));
}

private static void test2()
{
Date date = new Date();
System.out.println("Year:" + date.getYear());
System.out.println("Month:" + date.getMonth());
System.out.println("Day:" + date.getDate());
System.out.println("Hour:" + date.getHours());
System.out.println("Minute:" + date.getMinutes());
System.out.println("Second:" + date.getSeconds());
System.out.println("DayOfWeek:" + date.getDay());
}

private static void test3()
{
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
System.out.println(c.getTimeZone());
System.out.println("Year:" + c.get(Calendar.YEAR));
System.out.println("Month:" + c.get(Calendar.MONTH));
System.out.println("Day:" + c.get(Calendar.DATE));
System.out.println("Hour:" + c.get(Calendar.HOUR));
System.out.println("HourOfDay:" + c.get(Calendar.HOUR_OF_DAY));
System.out.println("Minute:" + c.get(Calendar.MINUTE));
System.out.println("Second:" + c.get(Calendar.SECOND));
System.out.println("DayOfWeek:" + c.get(Calendar.DAY_OF_WEEK));
System.out.println("DayOfMonth:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("DayOfYear:" + c.get(Calendar.DAY_OF_YEAR));
}
}

  輸出結果如下:複製代碼 代碼如下:Sat May 11 13:44:34 CST 2013
-05-11
Sun May 12 00:00:00 CST 2013
Year:113
Month:4
Day:11
Hour:13
Minute:44
Second:35
DayOfWeek:6
Sat May 11 13:44:35 CST 2013
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
Year:2013
Month:4
Day:11
Hour:1
HourOfDay:13
Minute:44
Second:35
DayOfWeek:7
DayOfMonth:11
DayOfYear:131

  需要注意的是,Date中的getxxx方法已經變成deprecated了,因此我們盡量使用calendar.get方法來擷取日期的細節資訊。
  另外,注意DateFormat,它不僅可以對日期的輸出進行格式化,而且可以逆向操作,將符合Format的字串轉換為日期類型。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.