Java中最容易讓人迷惑的幾個知識點,總結如下:
一:引用變量的強制類型轉換
在Java中,人們常常提到引用類型的變量,其實質引用變量只能調用它在編譯時類型的方法,而不能調用它運行時類型的方法,即使它實際所引用對象確實包括該方法。如果需要讓這個引用變量來調用它運行時類型的方法,則必須把它強制類型轉換成運行時類型。
格式:類型轉換運算符是小括號,如(type)variable 將variable變量轉換爲一個type類型的變量。
eg Object obj="java" ; String objStr=(String)obj;
強制類型轉換時需要注意一下幾點:
(1)基本類型之間的轉換只能在數值類型(整數類型,字符型和浮點型)之間進行,注意,數值型不能和布爾類型之間進行轉換。
(2)引用類型之間的轉換隻能把一個父類型變量轉換成子類型(簡單的說就是父類引用指向子類對象,被稱爲向上轉型)。
instanceof運算符:instanceof運算符的前一個操作數是一個引用類型的變量,後一個操作數通常是一個類,也可以是一個接口,它用於判斷前面的對象是否是後面對象的類,或者其子類,實現類的實例。如果是,則返回true,否則返回false。
經典實例:
public static void main(String[] args) {
//定義一個Object類的變量
Object obj="heima";
//返回true
System.out.println("字符串是否是Object類的實例:"+(obj instanceof Object));
//String是Object類的子類,所以返回true
System.out.println("字符串是否是String類的實例:"+(obj instanceof String));
//Math是Object類的子類,所以能正常編譯。所以返回false
System.out.println("字符串是否是Math類的實例:"+(obj instanceof Math));
//String實現了Comparable接口,所以返回true
System.out.println("字符串是否是Comparable接口的實例:"+(obj instanceof Comparable));
String str="java";
//String類不是Math類,也不是Math類的父類,所以下面的代碼不能進行編譯
System.out.println("字符串是否是Math類的實例:"+(str instanceof Math));
}
Instanceof和(type)是Java提供的兩個相關的運算符,一般先用instanceof判斷一個對象是否可以強制類型轉換,然後再使用(type)運算符進行強制類型轉換,從而保證程序不會出現錯誤。
二:初始化塊
初始化塊分爲:靜態初始化塊(在大括號前使用static修飾)和普通初始化塊。
初始化塊的作用和構造器非常類型,主要用於初始化。
初始化塊格式:
[修飾符]{
初始化塊的可執行代碼[包含任何可執行性語句,包括定義局部變量,調用其他對象的方法,使用分支語句和循環語句等等]
}
雖然Java中允許在類中定義多個初始化塊,但這沒太多的意義,因爲初始化塊是在創建Java對象時隱式執行的,而且總是全部執行,因此當我們完全可以把多個普通初始化塊合併成一個初始化塊。這樣可以挺高程序的可讀性。
注意:初始化塊,聲明實例屬性指定默認值都可認爲是對象的初始化代碼,他們的執行順序與源代碼中的順序相同。
經典實例:
public class Test {
//先執行初始化塊,給變量num賦值爲10
{
num=10;
}
//在執行將變量a的值賦值爲6
int num=6;
public static void main(String[] args) {
//下面打印的結果是:變量num=6
System.out.println("變量num="+new Test().num);
}
}
上面的實例充分說明他們的執行順序與源代碼中的順序相同。
當Java創建一個對象時,系統先爲該對象的所有實例屬性分配內存(前提是該類已經被加載過了),接着程序 開始對這個實例屬性執行初始化,其順序是:先執行初始化塊或者聲明屬性時指定的初始值,再執行構造器裏指定的初始值。通過其他命令可以解析Java執行的源碼,其實質是,程序在執行初始化時,會將初始化塊中的代碼和聲明屬性的代碼都移植到構造函數中,這些代碼都位於構造函數自身代碼之前。所以他們總是比構造函數自身代碼先執行。
靜態初始化塊:
定義初始化塊時使用static修飾符,叫做靜態初始化塊。靜態初始化塊與類相關,所以系統將在類初始化階段執行靜態初始化塊,因此靜態初始化塊總是比普通初始化塊先執行。靜態初始化塊屬於類的靜態成員,同樣需要遵循靜態成員不能訪問非靜態成員的規則,因此靜態初始化塊不能訪問非靜態成員,包括不能訪問實例屬性和方法。
初始化塊與構造函數類似,創建一個Java對象時,不僅會執行該類的普通初始化和構造器,系統會一直追溯到java.lang.Object類,先執行java.lang.Object類的初始化塊,開始執行Object的構造函數,依次向下執行其父類的初始化塊,和構造函數.......。最後才能執行自身的初始化塊和構造函數。
經典實例:
public class Test extends Root {
static{
System.out.println("我是Test的靜態初始化塊");
}
{
System.out.println("我是Test的普通初始化塊");
}
public Test() {
System.out.println("我是Test的構造函數");
}
public Test(String msg){
System.out.println(msg);
}
}
class Test2 extends Root{
static{
System.out.println("我是Test2的靜態初始化塊");
}
{
System.out.println("我是Test2的普通初始化塊");
}
public Test2() {
System.out.println("我是Test2的構造函數");
}
public static void main(String[] args) {
new Test2();
new Test2();
}
}
class Root{
static{
System.out.println("我是Root的靜態初始化塊");
}
{
System.out.println("我是Root的普通初始化塊");
}
public Root() {
System.out.println("我是Root的構造函數");
}
}
打印結果:
我是Root的靜態初始化塊
我是Test2的靜態初始化塊
我是Root的普通初始化塊
我是Root的構造函數
我是Test2的普通初始化塊
我是Test2的構造函數
我是Root的普通初始化塊
我是Root的構造函數
我是Test2的普通初始化塊
我是Test2的構造函數
三:"=="和equals比較運算符
Java程序中判斷兩個變量是否相等有兩種方式:一種是利用==運算符,一種是利用equals方法。
當使用==來判斷兩個變量是否相等時,如果2個變量是基本類型的變量,且都是數值型(不一定要求數據類型嚴格相同),則只要兩個變量的值相等,這返回true。但對於兩個引用類型的變量,必須他們指向同一個對象時,==判斷纔會返回true。
equals方法是Object類提供的一個實例方法,因此所有引用變量都可以調用該方法來判斷是否與其他引用變量相等。但這個判斷是兩個對象相等的標準與==符號沒有區別,同樣要求兩個應用變量來指向同一個對象纔會返回true。因此這個Object提供的equals方法沒有太大的實際意義,如果希望採用自定義的相等標準,可以採用重寫equals方法來實現【實際上,重新equals方法就是提供自定義相等的標準,你認爲怎麼樣相等,就怎麼樣相等。一切都是你做主】。
經典實例一:
public static void main(String[] args) {
int num=65;
float fnum=65.0f;
char ch='A';
System.out.println("65和65.0f是否相等:"+(num==fnum));//true
System.out.println("65和'A'是否相等:"+(num==ch));//true
String str1=new String("ABC");
String str2=new String("ABC");
System.out.println("str1和str2是否相等="+(str1==str2));//false
//String類已經重新了equals方法,String的equals方法判斷兩個字符串是否相等的
//標準是:只要兩個字符串包含的字符序列相同,就返回true,否則返回false。
System.out.println("str1和str2是否相等="+(str1.equals(str2)));//true
}
經典實例二:
public class Test1 {
private String idCard;
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
//重寫equals方法
public boolean equals(Object obj) {
//比較的兩個對象是否是同一對象
if (this == obj) {
return true;
}
//只有當obj是Test1對象
if (obj != null && obj.getClass() == Test1.class) {
Test1 test = (Test1) obj;
if (this.getIdCard().equals(test.getIdCard())) {
return true;
}
}
return false;
}
}
四:final修飾符
final關鍵字可用於修飾類,變量和方法,被final修飾的類,方法和變量是不可變的。
final修飾變量,被修飾的變量被賦初始值之後,不能對它重新賦值。final修飾方法,被final修飾的方法不能被重寫。final修飾類,被final修飾的類不能派生子類。
final修飾實例變量可以在三個位置指定初始值:
(1)在定義final實例變量時指定初始值。
(2)在非靜態初始化塊中爲final實例變量指定初始值。
(3)在構造器中爲final實例變量指定初始值。
對於普通的實例變量,Java程序可以對它執行默認的初始化,也就是將實例變量的值指定爲默認的初始值0或null;但對於final實例變量,則必須由程序員顯示指定初始值。
final修飾類變量可以在兩個地方指定初始值
(1)定義final類變量時指定初始值。
(2)在靜態初始化塊中爲final類變量指定初始值。
除上面兩個地方可以爲final類變量指定初始值外,final類變量不能再次賦值。
final修飾局部變量:
Java本來就要求局部變量必須被顯式的賦初始值,final修飾的局部變量一樣需要被顯式的賦初始值。與普通初始值變量不同的是,final修飾的局部變量被賦初始值之後,就不能再對final局部變量重新賦值。
對於一個使用final修飾的變量(類變量,實例變量,局部變量)而言,如果定義該final變量時就指定初始值或確定的表達式(如果被賦的表達式只是基本的算術運算表達式或字符串連接運算,沒有訪問普通變量,調用方法),而且這個初始值可以在編譯時就確定下來,那麼這個final變量將不再是一個變量,系統會將其當成"宏變量"處理。也就是說,所有出現該變量的地方,系統將直接把它當成對應的值處理。
經典實例:
public static void main(String[] args) {
//下面定義五個宏變量。
final int a=5;
final int b=5+3;
final double c=1.2/3;
final String str1="Java"+" Android";
final String str2="Java"+99;
//str3變量的值因爲調用了方法,所以在編譯時不能確定值,因此str3不是宏變量。
final String str3="Java"+String.valueOf(99);
System.out.println(str2=="Java99");//返回true
System.out.println(str3=="Java99");//返回false
}
Java要求所有被內部類訪問的局部變量都使用final修飾,對應普通局部變量而言,它的作用域就是停留在該方法內,當方法執行結束,該局部變量也隨之消失;但內部類則可能產生隱式的“閉包”,閉包將使得局部變量脫離它所在的方法繼續存在【此處說的內部類指的是局部內部類,因爲只有局部內部類(包括匿名內部類)纔可以訪問局部變量,普通靜態內部類,非靜態內部類不可訪問方法體的局部變量】。
五:抽象類和抽象方法
在Java中被abstract修飾的類,叫抽象類,被abstract修飾的方法,叫抽象方法。
抽象類和抽象方法原則:
(1)抽象類必須使用abstract修飾符修飾,抽象方法必須使用abstract修飾符修飾,抽象方法不能有方法體。
(2)抽象類不能被實例化,即使抽象類中不包含抽象方法,同樣也不能創建實例。
(3)抽象類可以包含屬性,方法(普通方法和抽象方法都可以),構造器,初始化塊,內部類,枚舉類六種成分。抽象類的構造器不能用於創建實例,主要是用於被子類調用。
(4)含有抽象方法的類,只能被定義爲抽象類。
注意:
(1)抽象方法和空方法體的方法是不同概念,例如public abstract void test();是一個抽象方法,它根本沒有方法體,方法後面沒有大括號,但public void test(){}方法是一個普通的方法,他已經定義了方法體,只是方法體是空,因此該方法不能用abstract修飾。
(2)abstract不能用於修飾屬性,不能用於修飾局部變量,即使沒有抽象變量,沒有抽象屬性等說法;abstract也不能修飾構造器,沒有抽象構造器。抽象類中定義的構造器只能是普通的構造器。
(3)當使用static修飾一個方法時,該方法屬於類,如果該方法被定義成抽象方法,則將導致通過該類來調用該方法時出現錯誤,因此static和abstract不能同時修飾某個方法。
(4)abstract關鍵字修飾的方法必須被子類重寫纔有意義,因此abstract方法不能定義爲private訪問權限。
抽象類的作用:
抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象。從多個具有相同特徵的類中抽象出一個抽象類,以這個抽象類作爲其子類的模版,從而避免子類設計的隨意性。抽象類體現的就是一種模板模式的設計,抽象類作爲多個子類的通用模板,子類在抽象類的基礎上進行擴展,改造,但子類總體上會大致保留抽象類的行爲方式。
六:接口
接口是從多個相似類中抽象出來的規範,接口不提供任何實現。可以看成是抽象類的一種特殊。
由於接口定義的是一種規範,因此接口裏不能包含構造器和初始化塊定義,接口裏可以包含屬性(只能是常量,默認是public static final,只能是public static final)、方法(只能是抽象方法,默認修飾符是public,也只能是public)、內部類(包括內部接口)和枚舉定義。
接口支持多繼承,一個接口可以有多個直接的父接口。和類的繼承相似,子接口可以擴展某個父接口,將會獲得父接口裏所有的抽象方法,常量屬性,內部類和枚舉類的定義。
格式: interface D extends A,B,C{ }
注意:
(1)接口不能創建實例,但接口可以用於聲明引用類型的變量。當使用接口來聲明引用類型的變量時,這個引用類型的變量必須引用到其實現類的對象。
(2)一個類可以實現一個和多個接口,多個接口之間使用逗號隔開。
總結接口和抽象類:
相同特點:
(1)接口和抽象類都不能實例化,它們都位於繼承樹的頂端,用於被其他類實現和繼承。
(2)接口和抽象類都可以包含抽象方法,實現接口或繼承抽象類的普通之類都必須實現這些抽象方法。
不同特點:
(1)二者的設計目的不同,接口作爲系統與外界交換的窗口,接口體現的是一種規範。從某種程度上看,接口類似於整個系統的"總綱",他制定了系統各模塊應該遵循的標準,因此一個系統中的接口不應該經常修改,一定接口被修改,對整個系統甚至其他系統的影響都非常大(導致大部分類都需要修改)。抽象類就不一樣,抽象類作爲系統中多個子類的共同父類,它所體現的是一種模板式的設計,抽象類作爲多個子類的抽象父類,可以被當成系統實現過程中的中間產品,這個中間產品已經實現了系統的大部分功能,但這個產品不是最終產品,必須進一步完善。
(2)接口裏只能包括抽象方法,不能包含已經提供實現的方法 ;抽象類則可以包含已經實現的方法。
(3)接口裏不能定義靜態方法;抽象類可以定義靜態方法。
(4)接口裏只能定義靜態常量屬性,不能定義普通屬性;抽象類裏既可以定義普通屬性,也可以定義靜態常量屬性。
(5)接口不包含構造器;抽象類可以包含構造器,抽象類裏的構造器並不是用於創建對象,而是讓子類調用這個構造器來完成屬與抽象類的初始化操作。
(6)接口裏不能包含初始化塊,但抽象類則完全可以包含初始化塊。
(7)一個類最多只能有一個直接父類,包括抽象類;但一個類可以直接實現多個接口,通過實現多個接口可以彌補Java中的單繼承。