Java學習筆記二 面向對象(Object Oriented) 屬性(成員變數)跟隨對象放在堆裡面,局部變數(如 p1)放在棧裡面。只有成員變數的前面能添加許可權修飾符,且成員變數自帶預設值。 在一個類中,一個方法可以調用這個類中的其餘方法(包括自身,即遞歸)以及成員變數,不能在方法中再定義方法。 方 ...
Java學習筆記二
面向對象(Object Oriented)
屬性(成員變數)跟隨對象放在堆裡面,局部變數(如 p1)放在棧裡面。只有成員變數的前面能添加許可權修飾符,且成員變數自帶預設值。
在一個類中,一個方法可以調用這個類中的其餘方法(包括自身,即遞歸)以及成員變數,不能在方法中再定義方法。
方法重載(Overload,兩同一不同)
在同一個類中,允許存在一個以上的同名方法,只要它們的參數列表不同即可。
與許可權修飾符、返回值類型、形參名無關,比如
public void add(int m,int n)
和public void add(int n,int m)
就不算重載。
可變形參
方法名(參數的類型名...參數名),例如public static void test(int a ,String...books){};
。
-
可變參數:方法的參數個數:0 個或多個。
-
可變形參的方法與同名的方法之間,彼此構成重載。
-
可變形參與形參是數組是等價的,二者不能同時聲明,否則報錯。
public static void test(int a ,String[] books){};
與例子寫法是等價的。 -
可變形參需要放在形參列表的最後。
-
在一個方法中,最多聲明一個可變形參。
參數傳遞機制-值傳遞
形參是基本數據類型:將實參的“數據值”傳遞給形參。
形參是引用數據類型:將實參的“地址值”傳遞給形參。
前提是實參和形參類型一致。
import
導入其他包下的類。格式:import 包名.類名;
。
import 包名.*
:導入包下麵的所有類。- 類所在包的其他類 和 java.lang 無需導入,可以直接使用。
- 導包導入的是當前文件夾里的類,不包括其子文件夾里的類。
比如使用
import com.*;
是指導入 com 目錄下的所有類,這裡只有 Test 類,並不會導入在 com 的下一級 lc 目錄中的 Phone 類。
-
如果在代碼中使用不同包下的同名的類,那麼就需要使用類的全類名的方式指明調用的是哪個類。
如:
java.sql.Date date = new java.sql.Date(time);
封裝性
許可權修飾符
Java 規定了 4 種許可權修飾符,藉助這些許可權修飾符修飾類及類的內部成員。當這些成員被調用時,體現了可見性的大小。
許可權修飾符 | 本類內部 | 本包內 | 其他包的子類 | 其他包的非子類 |
---|---|---|---|---|
private | √ | |||
預設 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
外部類只能用 public 和預設修飾。
- 在其他包的子類中,可以直接使用或通過子類對象間接使用父類中 protected 修飾的屬性或方法。
- 在其他包的子類中 ,new 出來的父類對象,無法使用父類中 protected 修飾的屬性或方法。
- 其他包的類,通過調用子類對象,無法使用父類中 protected 修飾的屬性或方法。
- 在其他包的子類中,通過其他子類的對象,無法使用父類的protected修飾的屬性和方法。
這裡的“使用”是指:直接使用屬性或通過“對象.屬性”訪問,而非通過如 getter 的方法訪問。
構造器(Constructor)
搭配 new 關鍵字創建對象。
public class Student {
private String name;
private int age;
// 無參構造,當程式員沒顯式給出構造器時,系統會預設調用無參構造且構造器的修飾符與類相同;當程式員給出有參構造時,則預設無參構造會失效。
public Student() {}
// 有參構造,有參構造可以給私有變數賦初值。
public Student(String n,int a) {
name = n;
age = a;
}
//構造器名與類名相同,構造器沒有返回值。構造器可以重載。
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getInfo(){
return "姓名:" + name +",年齡:" + age;
}
}
//調用無參構造創建學生對象
Student s1 = new Student();
//調用有參構造創建學生對象
Student s2 = new Student("張三",23);
Java Bean
一種特殊的類,具有以下屬性:
1、具有一個 public 修飾的無參構造函數。
2、所有屬性由 private 修飾。
3、通過 public 修飾的 get 方法和 set 方法獲得或修改屬性。
4、可序列化。
比如,可以實現 Serializable 介面,用於實現bean的持久性。
this 關鍵字
當形參與成員變數同名時,如果在方法內或構造器內需要使用成員變數,必須添加 this 來表明該變數是類的成員變數。即:我們可以用 this 來區分成員變數和局部變數。
this代表當前對象或即將創建的對象,Base sub = new Sub();
這裡的對象是 Sub 對象,而不是看 sub 左邊的引用類型,就判斷為 Base 對象。
this 可以作為一個類中構造器相互調用的特殊格式。
- this():調用本類的無參構造器。
- this(實參列表):調用本類的有參構造器。
this()和 this(實參列表)只能聲明在構造器首行,且不能出現遞歸調用。
public class Student{
private String name;
private int age;
//無參構造
public Student(){}
//有參構造
public Student(String name){
this.name = name;
}
public Student(String name,int age){
this(name); //此行代表先調用 public Student(String name) 這個構造器,減少了代碼量。
this.age = age; //這裡的 this 代表即將創建的對象。
}
public void setName(String name){
this.name = name; //this代表當前對象,“this.”修飾的變數代表成員變數。
}
}
繼承性
繼承的出現讓類與類之間產生了 is-a 的關係,比如: A student is a person。
關鍵字:extends,Java 中的繼承是一種擴展。
子類會繼承父類所有的實例變數和實例方法。
子類不能直接訪問父類中私有的(private)的成員變數和方法,需通過 get/set 方法。
一個父類可以同時擁有多個子類,但一個子類只有一個父類。
類載入的時候,先載入父類,再載入子類,即先載入父類的靜態代碼塊。
public class B extends A{}
重寫(overwrite、override)
子類重寫從父類繼承來的方法,以便適應新需求。
@Override:寫在方法上面,用來檢測是否滿足重寫的要求。這個註解就算不寫,只要滿足要求,也能夠重寫。
重寫的要求:
-
方法名和參數列表不能變。
-
重寫的許可權修飾符不能小於父類被重寫方法的許可權修飾符。
① 父類私有方法不能重寫。 ② 跨包父類中預設的方法也不能重寫
-
若返回值類型是基本數據類型和 void,則不能改變。
-
若返回值類型是引用數據類型,則返回值類型不能大於父類中被重寫的方法的返回值類型。(例如:Student < Person)。
也就是,返回值類型不變或是返回值類型的子類。
-
子類方法拋出的異常不能大於父類被重寫方法的異常。
-
子類與父類中同名同參數的方法必須同時聲明為非 static 。
static 修飾的方法不能被重寫。
super()
super()
使得在子類中可以使用父類的屬性、方法、構造器。
一般可以省略,但是當出現重寫了方法或子類、父類有重名屬性的情況,調用父類的方法或屬性需要加上“super.”。
方法前面沒有 super.和 this.:先從子類找,如果沒有,再從父類找,再沒有,繼續往上追溯。
方法前面有 this.:先從子類找,如果沒有,再從直接父類找,再沒有,繼續往上追溯。
方法前面有 super.:忽略子類的方法,直接從父類找,如果沒有,繼續往上追溯。
總結:有
super.
則直接從父類找,沒有super.
則從子類開始找。當方法需使用重名的屬性時,採用就近的原則。
- 子類中定義過的方法(即子類中重寫過的方法和子類中新定義的方法,不包括從父類繼承但沒重寫的方法)優先找子類的屬性。
- 父類中擁有的方法找的是父類的屬性,無法訪問子類的屬性。
- 當子類中重寫過的方法前加上
super.
後,使用的是父類的方法,所以就近找父類的屬性。父類中的
setInfo()
沒有在子類中重寫,若在代碼中調用,則修改的是父類的 Info 屬性。
子類調用父類的構造器
super(形參列表)
,必須聲明在構造器的首行。
對於每個構造器,"this(形參列表)" 和 "super(形參列表)"只能二選一,沒有給出"this(形參列表)",則預設調用"super()",即調用父類中空參的構造器,那麼父類空參構造器中已初始化的屬性無需在子類構造器中反覆初始化,除非初始值不同。
當我們用子類構造器創建對象時,子類構造器會直接或間接調用到其父類的構造器,而其父類的構造器會直接或間接地調用它自己父類的構造器,直到調用了 Object 類中的構造器,所以記憶體中就有父類聲明的屬性及方法。
多態性
多態的使用前提:① 類的繼承關係 ② 方法的重寫
Java 中多態性的體現:父類的引用可以指向子類的對象。比如:Person p = new Student();
一個引用類型的變數可能指向(引用)多種不同類型的對象。
常用多態的地方:
-
方法的形參
-
方法的返回值
使用父類做方法的形參,是多態使用最多的場合。即使增加了新的子類,方法也無需改變,提高了擴展性,符合開閉原則(對擴展開放,對修改關閉)。
public class Person{
private Pet pet;
public void adopt(Pet pet) {//形參是父類類型,實參傳入子類對象
this.pet = pet;
}
public void feed(){
pet.eat(); //pet 實際引用的對象類型不同,執行的 eat 方法也不同。
}
}
...
Person person = new Person();
Dog dog = new Dog();
person.adopt(dog);//實參是 dog 子類對象,形參是父類 Pet 類型
弊端:由於聲明為父類的引用,導致無法調用子類特有的屬性和方法。
使用了多態後,同名屬性則會使用父類的,子類獨有的屬性無法使用,子類新定義的方法無法使用,只有重寫過的方法能使用。除非向下轉型才能使用子類的屬性或新定義的方法。
根據左邊的引用類型,決定調用父類的屬性還是子類的屬性,而方法是調用的子類對象重寫過後的方法。
Base b = new Sub(); //引用類型是 Base,調用Base的屬性,即 1。 System.out.println(b.a); Sub s = new Sub(); //引用類型是 Sub,調用 Sub 的屬性,即 2。 System.out.println(s.a); class Base{ int a = 1; } class Sub extends Base{ int a = 2; }
虛方法調用(Virtual Method Invocation)
編譯時,按照左邊變數的引用類型處理,只能調用父類中有的屬性和方法,不能調用子類特有的變數和方法。但是,運行時,仍然是對象本身的類型,所以執行的方法是子類重寫過後的方法體。
Java 中的虛方法是指在編譯階段不能確定方法的調用入口地址,在運行階段才能確定實際被執行的方法。
向下轉型
instanceof
引用類型 a = new 對象X()
;
a instanceof 類A
:對象 a 是否為類 A 或類 A 的子類創建的對象 ,返回值為 boolean 型。
-
對象 a 聲明的引用類型要麼是類A,要麼與類 A 有繼承關係,才能過編譯。
//Base是父類,sub和sub1是兩個子類。 Sub b = new Sub(); //聲明的引用類型為Sub System.out.println(b instanceof Sub1); //這裡會報錯,因為Sub和Sub1沒有繼承關係。 Base b = new Sub(); //聲明的引用類型為Base System.out.println(b instanceof Sub1); //不會報錯
向下轉型:在方法體中,將接收到的對象從父類引用類型,轉為子類引用類型,然後調用子類特有的變數和方法。
if(pets[i] instanceof Dog){ //
Dog dog = (Dog) pets[i];
dog.watchHouse(); //Dog 特有的方法
}
Object
如果一個類沒有顯式繼承父類,那麼預設則繼承自 Object 類。
Object 類沒有屬性,只有方法。
clone()
克隆出一個新對象。
Animal a1 = new Animal();
...
Animal a2 = (Animal)a1.clone(); //因為 clone() 的返回值為 Object,這裡需要強轉,a2 和 a1 是 2 個不同的對象。
finalize()
當 GC(Garbage Collection,垃圾回收器)要回收對象時,可以調用此方法。在子類中重寫此方法,可在釋放對象前進行某些操作。
在 JDK 9 中此方法已經被標記為過時的。
getClass()
獲取對象的運行時類型,而不是編譯時類型。
Base sub = new Sub();
System.out.println(sub.getClass());//class com.lc.Sub
equal()[重要]
Object 里的 equal() 實際用了“==”,即對於基本數據類型,比較值是否相等,而對於引用數據類型,比較地址值是否相等。
格式:obj1.equals(obj2)
所有類都繼承了 Object,也就獲得了 equals()方法。還可以重寫。
File、String、Date 及包裝類重寫了 equals()方法,比較類型及內容而不考慮引用的是否是同一個對象。
toString()[重要]
列印引用數據類型變數預設調用 toString()。
在自定義類,沒有重寫 toString() 的情況下,列印的是“對象的運行時類型 @ 對象的 hashCode 值
的十六進位形式"
System.out.println(base); //com.lc.Base@1b6d3586
可以根據用戶需求重寫 toString()方法,如 String 類重寫了 toString() 方法,返回字元串的值。
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age +'}';
}
static
成員變數按照是否使用 static 可以分為:
-
使用:靜態變數或類變數。
隨類載入而載入,記憶體空間里就一份(因為類只載入一次),被類的對象共用。jdk6 放在方法區,jdk7 及以後放在堆空間。可以通過類或對象調用。
-
不使用:非靜態變數或實例變數。
每個對象擁有一份實例變數,隨對象的創建而載入,存放在堆空間的對象實體中。只能通過對象調用。
static 修飾的方法:類方法、靜態方法。
隨類載入而載入,可以通過類或對象調用,靜態方法內可以使用靜態變數和其他的靜態方法,不可以調用非靜態的屬性和方法。
靜態方法中不能使用 this 和 super。
在靜態類中使用靜態變數和其他靜態方法時,可以省略“類名.”。
public class ChildTest {
public static void main(String[] args) {
Base base = null;
System.out.println(base.a); //這裡會輸出 1,即便 base 指向的是 null
}
}
class Base{
static int a = 1;
}
類的屬性可以為自己的實例對象,JVM 把 A 載入進記憶體後,執行到A a = new A();
時,發現 A 已在記憶體中,則能過編譯。
A aa = new A();
...
class A {
static A a = new A(); //隨類載入而載入,只執行一次,靜態變數 a 里也有屬性 a,只不過指向的是自己。
}
類成員-代碼塊
如果成員變數的初始值不是一個常量值,而是需要通過複雜的計算或讀取文件、或讀取運行環境信息等方式才能獲取的一些值,可以採用代碼塊。
public class A{
int a = 10;
public A(){
}
{
... //非靜態代碼塊
}
static{
... //靜態代碼塊
}
}
靜態代碼塊和非靜態代碼塊的相同點:
-
用於初始化類的信息。
-
內部可以聲明變數、調用屬性或方法、編寫輸出語句等操作。
-
如果聲明多個代碼塊,則按照聲明的先後順序(即在類中,誰在上面,誰先執行)執行。
靜態代碼塊的執行總先於非靜態代碼塊的執行。
不同點:
-
靜態代碼塊隨類的載入而執行,只執行一次;非靜態代碼塊隨對象的創建而執行,可執行多次。
理解的時候,可以把非靜態代碼塊當作類中直接調用父類的那些構造器的一部分,放在那些構造器的前幾行(但在父類的構造器後),一調用構造器就先執行非靜態代碼塊。
若調用的構造器使用了this(...),非靜態代碼塊不看成這些構造器的一部分,而看成在this(...)裡面,直到找到那個直接調用父類的構造器,將代碼視為其一部分。
public A(){ //執行的時候可以理解成這樣 [super();] //父類構造器,可以不寫,預設調用父類空參構造器。 { ... //非靜態代碼塊 } System.out.println("我是直接調用父類的構造器"); ... } public A(int a){ this(); //使用了this(),則沒有調用父類的構造器,屬於間接調用,因為A()中調用了父類構造器。 System.out.println("我不是直接調用父類的構造器"); } Base base = new Base(1,"lc"); /* new Base(1,"lc")調用的是Base(int b, String name)構造器,其中又調用了this(b),即public Base(int b)。 public Base(int b)又調用this(),即public Base()。 public Base()沒有顯式給出調用父類構造器,則預設調用父類空參構造器。從以上描述可以看出,public Base()是類中直接調用父類的構造器,非靜態代碼塊可以看為它的一部分,但是在父類的構造器之後。 public A()是類中直接調用其父類Object的構造器,而Object沒有任何輸出,然後執行類A的非靜態代碼塊,再執行類A構造器的其餘代碼,執行完再執行Base的非靜態代碼塊,再執行Base()的其餘代碼以及其他構造器的代碼。 */ /* 結果為: 我是代碼塊A 我是構造器D 我是代碼塊Base 我是構造器A 我是構造器B 我是構造器C */ class Base extends A{ int b = 1; String name; { System.out.println("我是代碼塊Base"); } public Base(){ System.out.println("我是構造器A"); } public Base(int b) { this(); this.b = b; System.out.println("我是構造器B"); } public Base(int b, String name) { this(b); this.name = name; System.out.println("我是構造器C"); } } class A{ { System.out.println("我是代碼塊A"); } public A() { System.out.println("我是構造器D"); } }
-
靜態代碼塊內部只能調用靜態的屬性或方法,不能調用非靜態的屬性、方法。非靜態代碼塊中非靜態的或靜態的屬性、方法都可以調用。
非靜態代碼塊的意義:如果多個重載的構造器有公共代碼,並且這些代碼都是先於構造器其他代碼執行的,那麼可以將這部分代碼抽取到非靜態代碼塊中,減少冗餘代碼。
靜態代碼塊的意義:當靜態變數的初始化很複雜時,可以使用靜態代碼塊。
給實例屬性賦值的執行先後次序:
- 預設值
- 顯式初始化或代碼塊初始化(誰放上邊,誰先執行)
- 構造器中初始化
- 通過“對象.屬性”賦值
final
final 代表最終的,即修飾的內容不能修改。
final 能夠修飾的結構:類、方法、變數。
-
final 修飾類:該類無法被繼承。
-
final 修飾方法:該方法無法被程式。
-
final 修飾變數(成員變數和局部變數都行),一旦賦值,就無法更改。
-
成員變數賦值
-
顯式賦值
-
代碼塊中賦值
-
構造器中賦值
-
-
局部變數賦值
- 方法內的局部變數,調用之前必須賦值。
- 方法的形參,調用時傳給形參的值無法更改。
-
-
final 和 static 一起修飾的成員變數,稱為全局常量。
final 修飾引用類型變數時,其指向的對象的地址值不能變,即不能再給這個變數賦另外的對象,但對象裡面的屬性值能修改。
abstract
abstract 代表抽象的,可以修飾類和方法。
-
修飾類:該類為抽象類,不能實例化,包含構造器。
-
修飾方法:只有方法的聲明,沒有方法體。無需具體實現,實現是子類的事。
有抽象方法的類一定是抽象類,沒有抽象方法的類也可以是抽象類。
子類必須重寫完所有抽象方法才能實例化。
public abstract void eat(); //抽象方法聲明,沒有“{}”。
抽象類也可以繼承抽象類。
可以創建引用類型為抽象類的數組,但存放的元素必須是,繼承了抽象類並重寫完所有抽象方法的類所創建的實例。
介面
介面是一種規範。想要有某種功能,實現對應的介面就行。類和介面是“has-a”的關係,介面和類不是繼承關係,而是實現關係。一個類可以實現多個介面。
關鍵字:interface
屬性預設用
public final static
修飾,可以省略不寫。聲明抽象方法預設用
public abstract
修飾,可以省略不寫。不可以聲明構造器、代碼塊。
類必須重寫介面中所有的抽象方法,否則必須聲明為抽象類。
介面可以繼承介面,且可以多繼承。
interface cc extends AA,BB{}
介面也有多態,也可以作函數的形參,
介面名 base = new 實現類();
,只能調用介面有的方法,不能調實現類獨有的方法。可以創建引用類型為介面的數組,但存放的元素必須是,重寫完所有抽象方法的實現類所創建的實例。
Eatable[] eatable = new Eatable[3]; eatable[0] = new Chinese(); eatable[1] = new American();