Java回顧之一些基礎概念


  第一篇:Java回顧之I/O

  第二篇:Java回顧之網絡通信

  第三篇:Java回顧之多線程

  第四篇:Java回顧之多線程同步

  第五篇:Java回顧之集合

  第六篇:Java回顧之序列化

  第七篇:Java回顧之反射

 

  這兩天,無意間在網上翻到一本關於Java面試解惑的文章集,里面提到了很多基礎的概念,但一不留神,還是可能會“掉到坑里”。里面的文章寫的很不錯,大家可以通過下面的地址下載:http://zangweiren.iteye.com/blog/241218

  在看上述文章的時候,隨手寫了一些測試代碼,以便加深理解。這也就是這篇文章的來源了。

  類的初始化順序

  在Java中,類里面可能包含:靜態變量,靜態初始化塊,成員變量,初始化塊,構造函數。在類之間可能存在着繼承關系,那么當我們實例化一個對象時,上述各部分的加載順序是怎樣的?

  首先來看代碼:

 1 class Parent
 2 {
 3     public static StaticVarible staticVarible= new StaticVarible("父類-靜態變量1");    
 4     public StaticVarible instVarible= new StaticVarible("父類-成員變量1");
 5     
 6     static
 7     {
 8         System.out.println("父類-靜態塊");
 9     }
10     
11     {
12         System.out.println("父類-初始化塊");
13     }
14     
15     public static StaticVarible staticVarible2= new StaticVarible("父類-靜態變量2");    
16     public StaticVarible instVarible2= new StaticVarible("父類-成員變量2");
17     
18     public Parent()
19     {
20         System.out.println("父類-實例構造函數");
21     }
22 }
23 
24 class Child extends Parent
25 {
26     public static StaticVarible staticVarible= new StaticVarible("子類-靜態變量1");    
27     public StaticVarible instVarible= new StaticVarible("子類-成員變量1");
28     
29     static
30     {
31         System.out.println("子類-靜態塊");
32     }
33     
34     public Child()
35     {
36         System.out.println("子類-實例構造函數");
37     }
38     
39     {
40         System.out.println("子類-初始化塊");
41     }
42     
43     public static StaticVarible staticVarible2= new StaticVarible("子類-靜態變量2");    
44     public StaticVarible instVarible2= new StaticVarible("子類-成員變量2");
45     
46     
47 }
48 
49 class StaticVarible
50 {
51     public StaticVarible(String info)
52     {
53         System.out.println(info);
54     }
55 }

  然后執行下面的語句:

1 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會維護一個字符串池,或者說會緩存一部分曾經出現過的字符串。

  例如下面的代碼:

1 String v1 = "ab";
2 String v2 = "ab";

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

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

 1 public class StringTest {
 2     public static final String constValue = "ab";
 3     public static final String staticValue;
 4     
 5     static
 6     {
 7         staticValue="ab";
 8     }
 9     
10     public static void main(String[] args)
11     {
12         String v1 = "ab";
13         String v2 = "ab";
14         System.out.println("v1 == v2 : " + (v1 == v2));
15         String v3 = new String("ab");
16         System.out.println("v1 == v3 : " + (v1 == v3));
17         String v4 = "abcd";
18         String v5 = "ab" + "cd";
19         System.out.println("v4 == v5 : " + (v4 == v5));
20         String v6 = v1 + "cd";
21         System.out.println("v4 == v6 : " + (v4 == v6));
22         String v7 = constValue + "cd";
23         System.out.println("v4 == v7 : " + (v4 == v7));
24         String v8 = staticValue + "cd";
25         System.out.println("v4 == v8 : " + (v4 == v8));
26         String v9 = v4.intern();
27         System.out.println("v4 == v9 :" + (v4 == v9));
28         String v10 = new String(new char[]{'a','b','c','d'});
29         String v11 = v10.intern();
30         System.out.println("v4 == v11 :" + (v4 == v11));
31         System.out.println("v10 == v11 :" + (v10 == v11));
32     }
33 }

  請注意它的輸出結果:

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來做例子,但它屬於享元模式嗎?

  字符串與享元的關系,大家可以參考下面的文章:http://www.cnblogs.com/winter-cn/archive/2012/01/21/2328388.html

  字符串的反轉輸出

  這種情況下,一般會將字符串看做是字符數組,然后利用反轉數組的方式來反轉字符串。

  眼花繚亂的方法調用

  有繼承關系結構中的方法調用

  繼承是面向對象設計中的常見方式,它可以有效的實現”代碼復用“,同時子類也有重寫父類方法的自由,這就對到底是調用父類方法還是子類方法帶來了麻煩。

  來看下面的代碼:

 1 public class PropertyTest {
 2 
 3     public static void main(String[] args)
 4     {
 5         ParentDef v1 = new ParentDef();
 6         ParentDef v2 = new ChildDef();
 7         ChildDef v3 = new ChildDef();
 8         System.out.println("=====v1=====");
 9         System.out.println("staticValue:" + v1.staticValue);
10         System.out.println("value:" + v1.value);
11         System.out.println("=====v2=====");
12         System.out.println("staticValue:" + v2.staticValue);
13         System.out.println("value:" + v2.value);
14         System.out.println("=====v3=====");
15         System.out.println("staticValue:" + v3.staticValue);
16         System.out.println("value:" + v3.value);
17     }
18 }
19 
20 class ParentDef
21 {
22     public static final String staticValue = "父類靜態變量";
23     public String value = "父類實例變量";
24 }
25 
26 class ChildDef extends ParentDef
27 {
28     public static final String staticValue = "子類靜態變量";
29     public String value = "子類實例變量";
30 }

  輸出結果如下:

=====v1=====
staticValue:父類靜態變量
value:父類實例變量
=====v2=====
staticValue:父類靜態變量
value:父類實例變量
=====v3=====
staticValue:子類靜態變量
value:子類實例變量

  結論

  對於調用父類方法還是子類方法,只與變量的聲明類型有關系,與實例化的類型沒有關系。

  到底是值傳遞還是引用傳遞

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

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

 1 public class ParamTest {
 2 
 3     public void change(int value)
 4     {
 5         value = 10;
 6     }
 7     
 8     public void change(Value value)
 9     {
10         Value temp = new Value();
11         temp.value = 10;
12         value = temp;
13     }
14     
15     public void add(int value)
16     {
17         value += 10;
18     }
19     
20     public void add(Value value)
21     {
22         value.value += 10;
23     }
24     
25     public static void main(String[] args)
26     {
27         ParamTest test = new ParamTest();
28         Value value = new Value();
29         int v = 0;
30         System.out.println("v:" + v);
31         System.out.println("value.value:" + value.value);
32         System.out.println("=====change=====");
33         test.change(v);
34         test.change(value);
35         System.out.println("v:" + v);
36         System.out.println("value.value:" + value.value);
37         value = new Value();
38         v = 0;
39         System.out.println("=====add=====");
40         test.add(v);
41         test.add(value);
42         System.out.println("v:" + v);
43         System.out.println("value.value:" + value.value);
44     }
45 }
46 
47 class Value
48 {
49     public int value;
50 }

  它的輸出結果:

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)構造函數

  來看下面的代碼:

 1 class FinalTest
 2 {
 3     public static final String staticValue1 = "靜態變量1";
 4     public static final String staticValue2;
 5     
 6     static
 7     {
 8         staticValue2 = "靜態變量2";
 9     }
10     
11     public final String value1 = "實例變量1";
12     public final String value2;
13     public final String value3;
14     
15     {
16         value2 = "實例變量2";
17     }
18     
19     public FinalTest()
20     {
21         value3 = "實例變量3";
22     }
23 }

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

  我們來看下面的代碼:

 1 public class FinallyTest {
 2 
 3     public static void main(String[] args)
 4     {
 5         finallyTest1();
 6         finallyTest2();
 7         finallyTest3();
 8     }
 9     
10     private static String finallyTest1()
11     {
12         try
13         {
14             throw new RuntimeException();
15         }
16         catch(Exception ex)
17         {
18             ex.printStackTrace();
19         }
20         finally
21         {
22             System.out.println("Finally語句被執行");
23         }
24         try
25         {
26             System.out.println("Hello World");
27             return "Hello World";
28         }
29         catch(Exception ex)
30         {
31             ex.printStackTrace();
32         }
33         finally
34         {
35             System.out.println("Finally語句被執行");
36         }
37         return null;
38     }
39     
40     private static void finallyTest2()
41     {
42         int i = 0;
43         for (i = 0; i < 3; i++)
44         {
45             try
46             {
47                 if (i == 2) break;
48                 System.out.println(i);
49             }
50             finally
51             {
52                 System.out.println("Finally語句被執行");
53             }
54         }
55     }
56     
57     private static Test finallyTest3()
58     {
59         try
60         {
61             return new Test();
62         }
63         finally
64         {
65             System.out.println("Finally語句被執行");
66         }
67     }
68 }

  執行結果如下:

java.lang.RuntimeException
    at sample.interview.FinallyTest.finallyTest1(FinallyTest.java:16)
    at sample.interview.FinallyTest.main(FinallyTest.java:7)
Finally語句被執行
Hello World
Finally語句被執行
0
Finally語句被執行
1
Finally語句被執行
Finally語句被執行
Test實例被創建
Finally語句被執行

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

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

  來看下面的示例:

 1 class FinalizeTest
 2 {
 3     protected void finalize()
 4     {
 5         System.out.println("finalize方法被調用");
 6     }
 7     
 8     public static void main(String[] args)
 9     {
10         FinalizeTest test = new FinalizeTest();
11         test = null;
12         Runtime.getRuntime().gc();
13     }
14 }

  執行結果如下:

finalize方法被調用

  關於基本類型的一些事兒

  基本類型供分為9種,包括byte/short/int/long/float/double/boolean/void,每種基本類型都對應一個“包裝類”,其他一些基本信息如下:

1. 基本類型:byte 二進制位數:8
2. 包裝類:java.lang.Byte
3. 最小值:Byte.MIN_VALUE=-128
4. 最大值:Byte.MAX_VALUE=127
5. 基本類型:short 二進制位數:16
6. 包裝類:java.lang.Short
7. 最小值:Short.MIN_VALUE=-32768
8. 最大值:Short.MAX_VALUE=32767
9. 基本類型:int 二進制位數:32
10. 包裝類:java.lang.Integer
11. 最小值:Integer.MIN_VALUE=-2147483648
12. 最大值:Integer.MAX_VALUE=2147483647
13. 基本類型:long 二進制位數:64
14. 包裝類:java.lang.Long
15. 最小值:Long.MIN_VALUE=-9223372036854775808
16. 最大值:Long.MAX_VALUE=9223372036854775807
17. 基本類型:float 二進制位數:32
18. 包裝類:java.lang.Float
19. 最小值:Float.MIN_VALUE=1.4E-45
20. 最大值:Float.MAX_VALUE=3.4028235E38
21. 基本類型:double 二進制位數:64
22. 包裝類:java.lang.Double
23. 最小值:Double.MIN_VALUE=4.9E-324
24. 最大值:Double.MAX_VALUE=1.7976931348623157E308
25. 基本類型:char 二進制位數:16
26. 包裝類:java.lang.Character
27. 最小值:Character.MIN_VALUE=0
28. 最大值: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。我們來看下面的示例:

 1 public class DateTest {
 2 
 3     public static void main(String[] args) throws ParseException
 4     {
 5         test1();
 6         test2();
 7         test3();
 8     }
 9     
10     private static void test1() throws ParseException
11     {
12         Date date = new Date();
13         System.out.println(date);
14         DateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
15         System.out.println(sf.format(date));
16         String formatString = "2013-05-12";
17         System.out.println(sf.parse(formatString));
18     }
19     
20     private static void test2()
21     {
22         Date date = new Date();
23         System.out.println("Year:" + date.getYear());
24         System.out.println("Month:" + date.getMonth());
25         System.out.println("Day:" + date.getDate());
26         System.out.println("Hour:" + date.getHours());
27         System.out.println("Minute:" + date.getMinutes());
28         System.out.println("Second:" + date.getSeconds());
29         System.out.println("DayOfWeek:" + date.getDay());
30     }
31     
32     private static void test3()
33     {
34         Calendar c = Calendar.getInstance();
35         System.out.println(c.getTime());
36         System.out.println(c.getTimeZone());
37         System.out.println("Year:" + c.get(Calendar.YEAR));
38         System.out.println("Month:" + c.get(Calendar.MONTH));
39         System.out.println("Day:" + c.get(Calendar.DATE));
40         System.out.println("Hour:" + c.get(Calendar.HOUR));
41         System.out.println("HourOfDay:" + c.get(Calendar.HOUR_OF_DAY));
42         System.out.println("Minute:" + c.get(Calendar.MINUTE));
43         System.out.println("Second:" + c.get(Calendar.SECOND));
44         System.out.println("DayOfWeek:" + c.get(Calendar.DAY_OF_WEEK));
45         System.out.println("DayOfMonth:" + c.get(Calendar.DAY_OF_MONTH));
46         System.out.println("DayOfYear:" + c.get(Calendar.DAY_OF_YEAR));
47     }
48 }

  輸出結果如下:

Sat May 11 13:44:34 CST 2013
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的字符串轉換為日期類型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM