Java中一些基礎(chǔ)概念的使用詳解
類的初始化順序
在Java中,類里面可能包含:靜態(tài)變量,靜態(tài)初始化塊,成員變量,初始化塊,構(gòu)造函數(shù)。在類之間可能存在著繼承關(guān)系,那么當(dāng)我們實(shí)例化一個(gè)對(duì)象時(shí),上述各部分的加載順序是怎樣的?
首先來看代碼:
class Parent
{
public static StaticVarible staticVarible= new StaticVarible("父類-靜態(tài)變量1");
public StaticVarible instVarible= new StaticVarible("父類-成員變量1");
static
{
System.out.println("父類-靜態(tài)塊");
}
{
System.out.println("父類-初始化塊");
}
public static StaticVarible staticVarible2= new StaticVarible("父類-靜態(tài)變量2");
public StaticVarible instVarible2= new StaticVarible("父類-成員變量2");
public Parent()
{
System.out.println("父類-實(shí)例構(gòu)造函數(shù)");
}
}
class Child extends Parent
{
public static StaticVarible staticVarible= new StaticVarible("子類-靜態(tài)變量1");
public StaticVarible instVarible= new StaticVarible("子類-成員變量1");
static
{
System.out.println("子類-靜態(tài)塊");
}
public Child()
{
System.out.println("子類-實(shí)例構(gòu)造函數(shù)");
}
{
System.out.println("子類-初始化塊");
}
public static StaticVarible staticVarible2= new StaticVarible("子類-靜態(tài)變量2");
public StaticVarible instVarible2= new StaticVarible("子類-成員變量2");
}
class StaticVarible
{
public StaticVarible(String info)
{
System.out.println(info);
}
}
然后執(zhí)行下面的語句:
Child child = new Child();
輸出結(jié)果如下:
父類-靜態(tài)變量1
父類-靜態(tài)塊
父類-靜態(tài)變量2
子類-靜態(tài)變量1
子類-靜態(tài)塊
子類-靜態(tài)變量2
父類-成員變量1
父類-初始化塊
父類-成員變量2
父類-實(shí)例構(gòu)造函數(shù)
子類-成員變量1
子類-初始化塊
子類-成員變量2
子類-實(shí)例構(gòu)造函數(shù)
結(jié)論
從上述結(jié)果可以看出,在實(shí)例化一個(gè)對(duì)象時(shí),各部分的加載順序如下:
父類靜態(tài)成員/父類靜態(tài)初始化塊 -> 子類靜態(tài)成員/子類初始化塊 -> 父類成員變量/父類初始化塊 -> 父類構(gòu)造函數(shù) -> 子類成員變量/子類初始化塊 -> 子類構(gòu)造函數(shù)
和String相關(guān)的一些事兒
首先,我們聊一聊Java中堆和棧的事兒。
•棧:存放基本類型,包括char/byte/short/int/long/float/double/boolean
•堆:存放引用類型,同時(shí)一般會(huì)在棧中保留一個(gè)指向它的指針,垃圾回收判斷一個(gè)對(duì)象是否可以回收,就是判斷棧中是否有指針指向堆中的對(duì)象。
String作為一種特殊的數(shù)據(jù)類型,它不完全等同于基本類型,也不是全部的引用類型,許多面試題都有它的身影。
String類型變量的存儲(chǔ)結(jié)構(gòu)
String的存儲(chǔ)結(jié)構(gòu)分為兩部分,我們以String a = "abc";為例,描述String類型的存儲(chǔ)方式:
1)在棧中創(chuàng)建一個(gè)char數(shù)組,值分為是'a','b','c'。
2)在堆中創(chuàng)建一個(gè)String對(duì)象。
Java中的字符串池
為了節(jié)省空間和資源,JVM會(huì)維護(hù)一個(gè)字符串池,或者說會(huì)緩存一部分曾經(jīng)出現(xiàn)過的字符串。
例如下面的代碼:
String v1 = "ab";
String v2 = "ab";
實(shí)際上,v1==v2,因?yàn)镴VM在v1聲明后,已經(jīng)對(duì)“ab”進(jìn)行了緩存。
那么JVM對(duì)字符串進(jìn)行緩存的依據(jù)是什么?我們來看下面的代碼,非常有意思:
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));
}
}
請(qǐng)注意它的輸出結(jié)果:
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
我們會(huì)發(fā)現(xiàn),并不是所有的判斷都返回true,這似乎和我們上面的說法有矛盾了。其實(shí)不然,因?yàn)?/P>
結(jié)論
1. JVM只能緩存那些在編譯時(shí)可以確定的常量,而非運(yùn)行時(shí)常量。
上述代碼中的constValue屬于編譯時(shí)常量,而staticValue則屬于運(yùn)行時(shí)常量。
2. 通過使用 new方式創(chuàng)建出來的字符串,JVM緩存的方式是不一樣的。
所以上述代碼中,v1不等同于v3。
String的這種設(shè)計(jì)屬于享元模式嗎?
這個(gè)話題比較有意思,大部分講設(shè)計(jì)模式的文章,在談到享元時(shí),一般就會(huì)拿String來做例子,但它屬于享元模式嗎?
字符串與享元的關(guān)系,大家可以參考下面的文章:深入C#字符串和享元(Flyweight)模式的使用分析
字符串的反轉(zhuǎn)輸出
這種情況下,一般會(huì)將字符串看做是字符數(shù)組,然后利用反轉(zhuǎn)數(shù)組的方式來反轉(zhuǎn)字符串。
眼花繚亂的方法調(diào)用
有繼承關(guān)系結(jié)構(gòu)中的方法調(diào)用
繼承是面向?qū)ο笤O(shè)計(jì)中的常見方式,它可以有效的實(shí)現(xiàn)”代碼復(fù)用“,同時(shí)子類也有重寫父類方法的自由,這就對(duì)到底是調(diào)用父類方法還是子類方法帶來了麻煩。
來看下面的代碼:
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 = "父類靜態(tài)變量";
public String value = "父類實(shí)例變量";
}
class ChildDef extends ParentDef
{
public static final String staticValue = "子類靜態(tài)變量";
public String value = "子類實(shí)例變量";
}
輸出結(jié)果如下:
=====v1=====
staticValue:父類靜態(tài)變量
value:父類實(shí)例變量
=====v2=====
staticValue:父類靜態(tài)變量
value:父類實(shí)例變量
=====v3=====
staticValue:子類靜態(tài)變量
value:子類實(shí)例變量
結(jié)論
對(duì)于調(diào)用父類方法還是子類方法,只與變量的聲明類型有關(guān)系,與實(shí)例化的類型沒有關(guān)系。
到底是值傳遞還是引用傳遞
對(duì)于這個(gè)話題,我的觀點(diǎn)是值傳遞,因?yàn)閭鬟f的都是存儲(chǔ)在棧中的內(nèi)容,無論是基本類型的值,還是指向堆中對(duì)象的指針,都是值而非引用。并且在值傳遞的過程中,JVM會(huì)將值復(fù)制一份,然后將復(fù)制后的值傳遞給調(diào)用方法。
按照這種方式,我們來看下面的代碼:
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;
}
它的輸出結(jié)果:
v:0
value.value:0
=====change=====
v:0
value.value:0
=====add=====
v:0
value.value:10
我們看到,在調(diào)用change方法時(shí),即使我們傳遞進(jìn)去的是指向?qū)ο蟮闹羔?,但最終對(duì)象的屬性也沒有變,這是因?yàn)樵赾hange方法體內(nèi),我們新建了一個(gè)對(duì)象,然后將”復(fù)制過的指向原對(duì)象的指針“指向了“新對(duì)象”,并且對(duì)新對(duì)象的屬性進(jìn)行了調(diào)整。但是“復(fù)制前的指向原對(duì)象的指針”依然是指向“原對(duì)象”,并且屬性沒有任何變化。
final/finally/finalize的區(qū)別
final可以修飾類、成員變量、方法以及方法參數(shù)。使用final修飾的類是不可以被繼承的,使用final修飾的方法是不可以被重寫的,使用final修飾的變量,只能被賦值一次。
使用final聲明變量的賦值時(shí)機(jī):
1)定義聲明時(shí)賦值
2)初始化塊或靜態(tài)初始化塊中
3)構(gòu)造函數(shù)
來看下面的代碼:
class FinalTest
{
public static final String staticValue1 = "靜態(tài)變量1";
public static final String staticValue2;
static
{
staticValue2 = "靜態(tài)變量2";
}
public final String value1 = "實(shí)例變量1";
public final String value2;
public final String value3;
{
value2 = "實(shí)例變量2";
}
public FinalTest()
{
value3 = "實(shí)例變量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語句被執(zhí)行");
}
try
{
System.out.println("Hello World");
return "Hello World";
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("Finally語句被執(zhí)行");
}
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語句被執(zhí)行");
}
}
}
private static Test finallyTest3()
{
try
{
return new Test();
}
finally
{
System.out.println("Finally語句被執(zhí)行");
}
}
}
執(zhí)行結(jié)果如下:
java.lang.RuntimeException
at sample.interview.FinallyTest.finallyTest1(FinallyTest.java:16)
at sample.interview.FinallyTest.main(FinallyTest.java:7)
Finally語句被執(zhí)行
Hello World
Finally語句被執(zhí)行
Finally語句被執(zhí)行
Finally語句被執(zhí)行
Finally語句被執(zhí)行
Test實(shí)例被創(chuàng)建
Finally語句被執(zhí)行
注意在循環(huán)的過程中,對(duì)于某一次循環(huán),即使調(diào)用了break或者continue,finally也會(huì)執(zhí)行。
finalize則主要用于釋放資源,在調(diào)用GC方法時(shí),該方法就會(huì)被調(diào)用。
來看下面的示例:
class FinalizeTest
{
protected void finalize()
{
System.out.println("finalize方法被調(diào)用");
}
public static void main(String[] args)
{
FinalizeTest test = new FinalizeTest();
test = null;
Runtime.getRuntime().gc();
}
}
執(zhí)行結(jié)果如下:
finalize方法被調(diào)用
關(guān)于基本類型的一些事兒
基本類型供分為9種,包括byte/short/int/long/float/double/boolean/void,每種基本類型都對(duì)應(yīng)一個(gè)“包裝類”,其他一些基本信息如下:
. 基本類型:byte 二進(jìn)制位數(shù):8
. 包裝類:java.lang.Byte
. 最小值:Byte.MIN_VALUE=-128
. 最大值:Byte.MAX_VALUE=127
. 基本類型:short 二進(jìn)制位數(shù):16
. 包裝類:java.lang.Short
. 最小值:Short.MIN_VALUE=-32768
. 最大值:Short.MAX_VALUE=32767
. 基本類型:int 二進(jìn)制位數(shù):32
. 包裝類:java.lang.Integer
. 最小值:Integer.MIN_VALUE=-2147483648
. 最大值:Integer.MAX_VALUE=2147483647
. 基本類型:long 二進(jìn)制位數(shù):64
. 包裝類:java.lang.Long
. 最小值:Long.MIN_VALUE=-9223372036854775808
. 最大值:Long.MAX_VALUE=9223372036854775807
. 基本類型:float 二進(jìn)制位數(shù):32
. 包裝類:java.lang.Float
. 最小值:Float.MIN_VALUE=1.4E-45
. 最大值:Float.MAX_VALUE=3.4028235E38
. 基本類型:double 二進(jìn)制位數(shù):64
. 包裝類:java.lang.Double
. 最小值:Double.MIN_VALUE=4.9E-324
. 最大值:Double.MAX_VALUE=1.7976931348623157E308
. 基本類型:char 二進(jìn)制位數(shù):16
. 包裝類:java.lang.Character
. 最小值:Character.MIN_VALUE=0
. 最大值:Character.MAX_VALUE=65535
關(guān)于基本類型的一些結(jié)論(來自《Java面試解惑》)
•未帶有字符后綴標(biāo)識(shí)的整數(shù)默認(rèn)為int類型;未帶有字符后綴標(biāo)識(shí)的浮點(diǎn)數(shù)默認(rèn)為double類型。
•如果一個(gè)整數(shù)的值超出了int類型能夠表示的范圍,則必須增加后綴“L”(不區(qū)分大小寫,建議用大寫,因?yàn)樾懙腖與阿拉伯?dāng)?shù)字1很容易混淆),表示為long型。
•帶有“F”(不區(qū)分大小寫)后綴的整數(shù)和浮點(diǎn)數(shù)都是float類型的;帶有“D”(不區(qū)分大小寫)后綴的整數(shù)和浮點(diǎn)數(shù)都是double類型的。
•編譯器會(huì)在編譯期對(duì)byte、short、int、long、float、double、char型變量的值進(jìn)行檢查,如果超出了它們的取值范圍就會(huì)報(bào)錯(cuò)。
•int型值可以賦給所有數(shù)值類型的變量;long型值可以賦給long、float、double類型的變量;float型值可以賦給float、double類型的變量;double型值只能賦給double類型變量。
關(guān)于基本類型之間的轉(zhuǎn)換
下面的轉(zhuǎn)換是無損精度的轉(zhuǎn)換:
•byte->short
•short->int
•char->int
•int->long
•float->double
下面的轉(zhuǎn)換是會(huì)損失精度的:
•int->float
•long->float
•long->double
除此之外的轉(zhuǎn)換,是非法的。
和日期相關(guān)的一些事兒
Java中,有兩個(gè)類和日期相關(guān),一個(gè)是Date,一個(gè)是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));
}
}
輸出結(jié)果如下:
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方法已經(jīng)變成deprecated了,因此我們盡量使用calendar.get方法來獲取日期的細(xì)節(jié)信息。
另外,注意DateFormat,它不僅可以對(duì)日期的輸出進(jìn)行格式化,而且可以逆向操作,將符合Format的字符串轉(zhuǎn)換為日期類型。
相關(guān)文章
SpringCloud 如何使用feign時(shí)的復(fù)雜參數(shù)傳遞
這篇文章主要介紹了SpringCloud 如何使用feign時(shí)的復(fù)雜參數(shù)傳遞方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java pom.xml parent引用報(bào)錯(cuò)問題解決方案
這篇文章主要介紹了Java pom.xml parent引用報(bào)錯(cuò)問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Tomcat 服務(wù)器 在45秒內(nèi)未啟動(dòng)成功的解決方法
下面小編就為大家?guī)硪黄猅omcat 服務(wù)器 在45秒內(nèi)未啟動(dòng)成功的解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11SpringBoot實(shí)現(xiàn)簡易支付寶網(wǎng)頁支付功能
小編最近實(shí)現(xiàn)一個(gè)功能基于springboot程序的支付寶支付demo,非常不錯(cuò)適合初學(xué)者入門學(xué)習(xí)使用,今天把SpringBoot實(shí)現(xiàn)簡易支付寶網(wǎng)頁支付功能的示例代碼分享給大家,感興趣的朋友參考下吧2021-10-10java對(duì)象與json對(duì)象之間互相轉(zhuǎn)換實(shí)現(xiàn)方法示例
這篇文章主要介紹了java對(duì)象與json對(duì)象之間互相轉(zhuǎn)換實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了java對(duì)象與json對(duì)象相互轉(zhuǎn)換實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-10-10對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯
最近項(xiàng)目要求部署到其他公司的服務(wù)器上,但是又不想將源碼泄露出去,要求對(duì)正式環(huán)境的啟動(dòng)包進(jìn)行安全性處理,防止客戶直接通過反編譯工具將代碼反編譯出來,本文介紹了如何對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯,需要的朋友可以參考下2023-10-10myBatis使用@GeneratedValue(generator?=?“...“,?strategy?=?
這篇文章主要介紹了myBatis使用@GeneratedValue(generator?=?“...“,?strategy?=?...)注解問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java中比較器Comparator和Comparable的區(qū)別
這篇文章主要介紹了Java中比較器Comparator和Comparable的區(qū)別,我們?cè)谑褂?Collections.sort()對(duì)鏈表進(jìn)行排序時(shí),常常需要根據(jù)不同情況自定義排序規(guī)則,今天我們來看看比較器之間的區(qū)別,需要的朋友可以參考下2023-08-08