Java基礎筆記(一) 面向對象
[TOC]
1、定義類
[修飾符] class 類名
{
零到多個構造器定義
零到多個Field
零到多個方法
}
其中的修飾符可以是public
、final
、abstract
或省略。
- 類也是引用數據類型,用類定義的變量也只是一個引用(或者說指針),里面只是存放了一個地址值。
-
static
修飾的方法不能直接訪問沒有static
修飾的成員。(理由很簡單:靜態成員是獨立于具體對象而存在,屬于類本身,而非靜態成員是依賴于具體的對象的) - Java里方法的參數傳遞方式只有一種:值傳遞。基本數據類型和引用數據類型都是將實參的一個副本傳給形參,只不過引用數據類型拷貝的是地址值!
2、可變參數函數
JDK 1.5之后,Java允許為方法指定數量不確定的形參,通過在最后一個形參的類型后增加三個點(…),代碼實例如下:
public class MyClass {
/**
* 可變參數的方法
*/
public static void func(int a, String... str) {
for(String s : str) {
System.out.println(s);
}
System.out.println(a);
}
/**
* 程序入口-main
*/
public static void main(String[] args) {
func(15, "第一個字符串","第二個字符串","第三個字符串");
}
}
當然,如果你覺得這樣麻煩,可以直接用一個數組代替
3、方法重載
Java允許同一個類里定義多個同名方法,只要形參列表不同就行。
在Java中,根據變量定義位置的不同,可以將變量分為兩大類:成員變量 和 局部變量
1、成員變量的初始化和內存中的運行機制
- 當系統加載類、或者創建該類的實例時,系統自動為成員變量分配內存空間,并自動指定初始值。
- 類 Field(靜態成員變量),系統會在類加載時為其分配內存空間,并指定默認初始值。
- 實例 Field 是在創建實例時分配內存空間并指定初始值的,注意:實例變量指向的是這部分內存。
2、局部變量的初始化和內存中的運行機制
- 局部變量定義后,必須經過顯式初始化后才能使用,系統不會為局部變量執行初始化。
- 也就是說,定義局部變量后,系統并未為這個變量分配內存空間,直到等到程序為這個變量賦初值時才分配,并將初值保存在這塊內存中。
- 局部變量總是保存在其所在方法的棧內存中,所以它不屬于任何類或實例。
- 如果是基本類型的局部變量,則棧內存中是變量的值;如果是引用類型的局部變量,則棧內存中存放的是地址,引用堆內存中的實際對象。
4、類的封裝
封裝是面向對象的三大特征之一。為了實現良好的封裝,需要:
- 將 Field 和實現細節隱藏起來,不允許外部直接訪問。
- 把方法作為外部接口暴露出來,讓方法來控制對 Field 進行安全的訪問和操作。
Java中提供了4個訪問控制級別:private
、protected
、public
和不加任何訪問控制符(default
),它們的訪問控制級別由小到大:
package、import 和 import static
包(package):為了解決類的命名沖突,Java引入了包機制,提供了類的多層命名空間。
如果一個類被放于某個包中,則我們應該在該Java源文件的第一個非注釋行添加如下代碼:
package packageName;
import
語句可以導入指定包下某個類或全部類,但import
語句并不是必需的,只要堅持在類里面使用其他類的全名,則可以無須使用import
語句。
注意:在JDK 1.5以后增加了一種靜態導入(import static
)的語法,用于導入指定類的某個靜態 Field、方法或該類全部的靜態 Field、方法。
import static package.subpackage...className.fieldName; // 導入某一靜態變量
import static package.subpackage...className.methodName; // 導入某一靜態方法
import static package.subpackage...className.*; // 導入該類的所有靜態Field、方法
用一句話歸納import
和import static
的作用:使用import可以省略寫包名,而使用import static則可以連類名都省略。
4、Java的常用包
Java的核心類都放在java
這個包及其子包下,Java擴展的許多類都放在javax
包及其子包下。下面幾個包是Java語言中的常用包:
-
java.lang
:這個包下包含了Java語言的核心類,如 String、Math、System 和 Thread 類等,使用這個包下的類無須使用 import 語句導入,系統會自動導入這個包下的所有類。 -
java.util
:這個包下包含了Java的大量工具類/接口和集合框架類/接口,例如 Arrays、List 和 Set 等。 -
java.net
:這個包下包含了一些Java網絡編程相關的類/接口。 -
java.io
:這個包下包含了一些Java輸入/輸出編程相關的類/接口。 -
java.text
:這個包下包含了一些Java格式化相關的類。 -
java.sql
:這個包下包含了Java進行 JDBC 數據庫編程的相關類/接口。 -
java.awt
:這個包下包含了抽象窗口工具集(Abstract Window Toolkits)的相關類/接口,這些類主要用于構建 GUI 程序。 -
java.swing
:這個包下包含了 Swing 圖形用戶界面編程的相關類/接口,這些類可用于構建平臺無關的 GUI 程序。
5、構造器
構造器也就是構造函數!!!
Java類可以包含一個或一個以上的構造器。一旦程序員提供了自定義的構造器,系統就不再提供默認的無參構造器了。(所以如果為一個類編寫了有參數的構造器,通常建議為該類也額外提供一個無參數的構造器)
6、類的繼承
繼承是面向對象的三大特征之一。Java的繼承具有單繼承的特點,每個子類只有一個直接父類。
繼承的語法格式:
修飾符 class Derive extends Base
{
// 類定義部分
}
Java使用extends
作為繼承的關鍵字,extends
在英文中是擴展的意思。
重寫父類的方法要遵循“兩同兩小一大”的規則:
- “兩同”:方法名相同,形參列表相同。
- “兩小”:子類方法返回值類型應比父類方法返回值類型更小或相等,子類方法聲明拋出的異常類應比父類方法聲明拋出的異常類更小或相等。
- “一大”:子類方法的訪問權限應比父類方法的訪問權限更大或相等。
如果需要在子類方法中調用父類中被覆蓋的方法,若被覆蓋的是實例方法,使用super
作為調用者;若被覆蓋的是類方法,使用父類類名作為調用者。
7、構造器的執行順序
子類不會獲得父類的構造器,但子類構造器里可以調用父類構造器。有如下幾種情況:
- 子類構造器函數體的第一行使用
super
顯式調用父類構造器。 - 子類構造器函數體的第一行使用
this
顯示調用本類中重載的構造器,執行本類中另一個構造器時即會調用父類構造器。 - 子類構造器函數體中既沒有
super
調用,也沒有this
調用,系統將會在執行子類構造器之前,隱式調用父類無參數的構造器。
不管上面哪種情況,當調用子類構造器來初始化子類對象時,父類構造器總會在子類構造器之前執行;不僅如此,執行父類構造器時,系統會再次上溯執行其父類構造器……依此類推,創建任何Java對象,最先執行的總是java.lang.Object
類的構造器。
8、多態
多態是面向對象的三大特征之一。
Java引用變量有兩個類型:一個是編譯時類型,一個是運行時類型。編譯時類型由聲明該變量時的類型決定,運行時類型由實際賦給該變量的對象決定。兩個類型不一致時,就可能出現多態。
當把一個子類對象直接賦給父類引用變量時,這個引用變量的編譯時類型是 BaseClass,而運行時類型是 SubClass,當運行時調用該引用變量的方法時,其方法行為總是表現出子類方法的行為特征,而不是父類方法的行為特征。也就是說:相同類型的變量 調用同一個方法時,呈現出多種不同的行為特征,這就是多態。
多態的兩個前提: 要有繼承(inheritance),要有方法重寫(override)。
9、instanceof 運算符
instanceof
是Java中的一個二元運算符,它的作用是在運行時判斷左邊對象是否是右邊類(或其子類)的實例。如果是,返回true
,否則返回false
。
public class MyClass {
public static void main(String[] args) {
String str = ""; // str 是String類型引用變量
Object obj = ""; // obj 的編譯時類型是 Object,但實際類型是 String
System.out.println("str 是 String 的實例:" + (str instanceof String)); // true
System.out.println("str 是 Object 的實例:" + (str instanceof Object)); // true
System.out.println("obj 是 String 的實例:" + (obj instanceof String)); // true
System.out.println("obj 是 Object 的實例:" + (obj instanceof Object)); // true
System.out.println("obj 是 Math 的實例:" + (obj instanceof Math)); // false
}
}
需要注意:instanceof
運算符前面操作數的編譯時類型要么與后面的類相同,要么與后面的類具有父子繼承關系,否則會引起編譯錯誤。
instanceof
運算符的常用之處:在進行強制類型轉換之前,首先判斷前一個對象是否是后一個類的實例,是否可以成功轉換,從而保證代碼更加健壯。
10、初始化塊
初始化塊是Java類里可出現的第4種成員(前面依次有 Field、方法和構造器)。與構造器的作用類似,初始化塊也可以對Java對象進行初始化操作。
初始化塊的語法格式如下:
[修飾符] {
// 可執行代碼
...
}
初始化塊的修飾符只能是static
,使用 static 修飾的初始化塊被稱為靜態初始化塊。
一個類里可以有多個初始化塊,先定義的初始化塊先執行,后定義的初始化塊后執行,下面是一個例子:
public class MyClass {
{
int a = 5;
System.out.println("第一個初始化塊!");
}
{
System.out.println("第二個初始化塊!");
}
public MyClass() {
System.out.println("類的無參數構造器!");
}
public static void main(String[] args) {
new MyClass(); // 創建一個對象
}
}
控制臺輸出結果:
第一個初始化塊!
第二個初始化塊!
類的無參數構造器!
可以看出,初始化塊是在構造器之前執行的。創建一個 Java 對象時,不僅會執行該類的普通初始化塊和構造器,而且系統會一直上溯到java.lang.Object
類,先執行 java.lang.Object 類的初始化塊,開始執行 java.lang.Object 的構造器,依次向下執行其父類的初始化塊,開始執行其父類的構造器……最后才執行該類的初始化塊和構造器,返回該類的對象。
雖然 Java 允許一個類中定義多個的普通初始化塊,但這沒有任何意義,所以如果要使用初始化塊的話定義一個就行了。
11、初始化塊和構造器的區別
初始化塊總是在構造器之前執行。雖然它們的作用非常相似,但依然存在一些差異的。
與構造器不同的是,初始化塊是一段固定執行的代碼,它不能接受任何參數。因此,如果有一段初始化的代碼對所有對象完全相同,且無須接收任何參數,就可以把這段初始化代碼提取到初始化塊中。
通過把多個構造器中的相同代碼提取到初始化塊中,能更好地提高初始化代碼的復用,提高整個應用的可維護性。
12、靜態初始化塊
初始化塊的修飾符只能是static
,使用 static 修飾的初始化塊被稱為靜態初始化塊。
靜態初始化塊,也屬于類的靜態成員,因此靜態初始化塊不能訪問非靜態成員(包括實例Field和實例方法)。靜態初始化塊用于對整個類進行初始化處理,通常用于對類Field執行初始化處理。
系統將在類初始化階段執行靜態初始化塊,而不是在創建對象時才執行。因此,靜態初始化塊總是比普通初始化塊先執行。與普通初始化塊類似的是,系統在類初始化階段不僅會執行本類的靜態初始化塊,還會一直上溯到 java.lang.Object 類(如果它包含靜態初始化塊),從上往下依次執行其父類的靜態初始化塊……最后才執行該類的靜態初始化塊。經過這個過程,才完成了該類的初始化。而只有類完成初始化以后,才可以在系統中使用這個類,包括訪問這個類的類Field、類方法,或者用這個類來創建實例。
13、包裝類
Java 是面向對象的編程語言,但它也包含了 8 種基本數據類型。基本數據類型的數據不具備“對象”的特性:沒有Field、方法可以被調用。
所有引用類型的變量都繼承了Object
類,都可當成 Object 類型變量使用,但基本數據類型的變量卻不可以。為了解決這個問題,Java 提供了包裝類(Wrapper Class),可以把 8 個基本類型的值包裝成對象使用。
把基本數據類型變量 包裝成 對應的包裝類對象 是通過對應包裝類的構造器來實現的,不僅如此,8個包裝類中除了 Character 之外,還可以通過傳入一個字符串來構建包裝類對象。
public class MyClass {
public static void main(String[] args) {
boolean b1 = true;
int i1 = 5;
Boolean _b = new Boolean(b1);
Integer _i = new Integer(i1);
Float _f = new Float("3.14");
// 取出包裝類對象里的值
boolean b2 = _b.booleanValue();
int i2 = _i.intValue();
float f2 = _f.floatValue();
}
}
可能你會覺得,這樣的轉換有些繁瑣。但從 JDK 1.5 開始提供了自動裝箱(Autoboxing) 和 自動拆箱(AutoUnboxing)功能,即可以把一個基本類型變量直接賦給對應的包裝類變量或 Object 變量(自動裝箱),也可以把包裝類對象直接賦給一個對應的基本類型變量。
14、基本類型變量與字符串的轉換
15、toString( )方法
toString()
方法是 Object 類里的一個實例方法,而所有的 Java 類都是 Object 類的子類,因此所有的 Java 對象都具有 toString() 方法。
不僅如此,所有的 Java 對象都可以和字符串進行連接運算,也可以使用System.out.println()
進行輸出。當進行上面的操作時,系統會自動調用 Java 對象的 toString()
方法,使用其返回的字符串。
Object 類的toString
方法是一個“自我描述”的方法,它總是返回該對象實現類的“類名@hashCode”值。但是這個返回值并不能真正實現“自我描述”的功能,這時可以對這個方法進行重寫。
16、==和equals的區別
Java 程序中判斷兩個變量是否相等有兩種方式:一種是使用==
運算符,另一種是使用equals
方法。
- 對于基本類型變量來說,它們并沒有
equals
方法,只能使用==
判斷兩個變量的值是否相等。 - 對于引用類型變量來說,
==
運算符是判斷兩個引用變量是否指向內存中的同一個對象,也就是比較對象的內存地址;而equals
是比較兩個對象的值是否相等(String類,Integer類等等)。
需要知道的是,equals 方法是 Object 類的一個實例方法。在 Object 類中equals
方法和==
沒有任何區別,都是判斷兩個變量是否指向同一個對象。<u>而String類,Integer類等等一些類,是重寫了equals方法,才使得equals和“==”不同。</u>
所以,當自己創建類時,想要自定義相等的標準,必須重寫equals方法。
17、final修飾符
Java 提供了final
關鍵字來修飾變量、方法和類。系統不允許為 final
變量重新賦值,子類不允許覆蓋父類的final
方法,不允許繼承final
類。
- final 成員變量必須由程序員顯式地指定初始值,系統不會對 final 成員變量進行隱式初始化。對于 final 修飾的類 Field,必須在聲明該Field時或在靜態初始化塊中指定初始值;對于 final 修飾的實例 Field,必須在聲明該Field時、普通初始化塊或構造器中指定初始值。
- 前面說過,系統不會為局部變量執行隱式初始化,必須由程序員顯式指定。對于 final 修飾的局部變量,可以在聲明時指定初始值,也可以在后面的代碼中對其賦值,但只能一次。
- final 修飾基本類型變量時,表示變量的值不能被改變;final 修飾引用類型變量時,表示該變量所引用的地址不能被改變,即一直引用同一個對象,但這個對象是可以改變的。
- 當定義 final 變量時就為該變量指定了初始值,而且該初始值可以在編譯時就被確定下來,那么這個變量就變成了“宏變量”。編譯器會把程序中所有用到該變量的地方直接替換成該變量的值。
18、抽象類與抽象方法
Java 中使用abstract
修飾符來定義抽象類和抽象方法。有抽象方法的類必須定義成抽象類,但抽象類里可以沒有抽象方法。
抽象類不能被實例化,只能當作父類被其他子類繼承。抽象方法沒有函數體,必須由子類提供實現(即重寫)。
與abstract
不能同時使用的關鍵字:
- final和abstract不能同時使用,因為它們是對立的。
- static和abstract不能同時修飾某個方法,因為如果一個抽象方法被定義成靜態方法,通過類名調用該方法將出現錯誤。
- private和abstract不能同時修飾某個方法,因為抽象方法必須被子類重寫才有意義,而子類不能訪問和重寫父類的 private 方法。
19、接口(interface)
上面說到,抽象類既可以包含抽象方法,也可以普通方法。而接口(interface)是一種更徹底的抽象,接口里的所有方法都是抽象方法。
接口定義的是多個類共同的公共行為規范,故它里面通常是定義一組公用方法。基本語法如下:
[修飾符] interface 接口名 extends 父接口1,父接口2...
{
零個到多個常量定義...
零個到多個抽象方法定義...
}
修飾符可以是 public 或者省略,如果省略了 public 訪問控制符,則默認采用包權限訪問控制符。另外,與類繼承不同的是,接口繼承中一個接口可以有多個直接父接口(接口只能繼承接口而不能繼承類)。
由于接口是一種規范,因此接口里不能包含構造器和初始化塊。接口里可以包含3種成員: Field(只能是常量)、方法(只能是抽象方法)、內部類(包括內部接口、枚舉)。
- 接口里所有成員都是
public
訪問權限。 - 接口里的常量 Field 是使用
public static final
修飾符來修飾,不管定義時是否指定。 - 接口里的方法是自動使用
public abstract
修飾符修飾,不管定義方法時是否指定。 - 接口里的內部類(接口、枚舉類)是自動使用
public static
修飾符修飾,不管定義時是否指定。
接口不能用于創建實例,其主要用途是被實現類實現。實現使用implements
關鍵字:
[修飾符] class 類名 extends 父類 implements 接口1,接口2...
{
// 類體部分
}
一個類只能有一個直接父類,但一個類可以實現多個接口。實現接口與繼承父類相似,也可以獲得所實現的接口里定義的成員,因此可以把實現接口理解為一種特殊的繼承。
20、接口與抽象類的比較
相同點:
- 接口和抽象類都不能被實例化,它們都用于被其他類實現或繼承。
- 接口和抽象類都可以包含抽象方法,實現接口或繼承抽象類的普通子類都必須實現這些抽象方法。
不同點:
- 接口里只能包含抽象方法,而抽象類既可以抽象方法也可以包含普通方法,還可以沒有抽象方法。
- 接口里不能定義靜態方法(因為全部是抽象方法),抽象類里可以定義靜態方法。
- 接口里只能定義靜態常量 Field,而抽象類既可以定義普通 Field,也可以定義靜態常量 Field。
- 接口里不包含構造器和初始化塊,而抽象類里完全可以包含。
- 一個類最多只能有一個直接父類,但卻可以直接實現多個接口(彌補Java單繼承的不足)。
21、內部類
在Java類里只能包含5種成員:Field、方法、構造器、初始化塊、內部類(包括接口和枚舉類)。前四種類成員已經介紹過了,下面介紹一下內部類。
內部類也叫嵌套類,語法格式:
public class OuterClass
{
// 此處可以定義內部類
}
通常,我們把內部類作為成員內部類來定義,而不是作為局部內部類(在方法里定義的內部類)。
非靜態內部類里不允許定義靜態成員,而靜態內部類里可以定義靜態成員,也可以定義非靜態成員。
根據靜態成員不能訪問非靜態成員的規則,外部類的靜態方法不能使用非靜態內部類,靜態內部類也不能訪問外部類的非static成員。
22、枚舉類
枚舉類是一種不能自由創建對象的類,它的對象在定義類時已經固定下來。枚舉類特別適合定義像行星、季節這樣的類,它們能創建的實例是有限且確定的。
在 Java 1.5 以前,要定義一個枚舉類,必須手動去實現。下面就是一個 Season 枚舉類的例子:
public class Season {
private final String name;
private final String description;
public static final Season SPRING = new Season("春天","春暖花開");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season FALL = new Season("秋天","秋高氣爽");
public static final Season WINTER = new Season("冬天","圍爐賞雪");
// 私有化構造器
private Season(String name, String description) {
this.name = name;
this.description = description;
}
// 只為兩個 Field 提供 getter 方法
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
上面的 Season 類是一個不可變類,它只能創建4種對象,可以通過Season.SPRING的方式來取得 Season 對象。
Java 1.5 新增了一個enum
關鍵字,用以定義枚舉類:
public enum Season {
SPRING,SUMMER,FALL,WINTER;
}
枚舉類(enum)是一種特殊的類,它一樣可以有自己的 Field、方法和構造器,可以實現一個或者多個接口。因為它特殊,所以有幾點需要注意:
- 用
enum
定義的枚舉類默認繼承了java.lang.Enum
類,而不是繼承 Object 類。 - 用
enum
定義的非抽象的枚舉類默認使用 final 修飾,因此枚舉類不能派生子類。 - 枚舉類的構造器默認使用且只能使用
private
訪問控制符修飾。 - 枚舉類的所有實例必須在枚舉類的第一行顯式列出,并且系統會自動添加
public static final
修飾。