Java面向對象的三大特徵:封裝、繼承、多態。
封裝
Java中的封裝是指一個類把自己內部的實現細節進行隱藏,只暴露對外的接口(setter和getter方法)。封裝又分爲屬性的封裝和方法的封裝。把屬性定義爲私有的,它們通過setter和getter方法來對屬性的值進行設定和獲取。
封裝的思想保證了類內部數據結構的完整性,使用戶無法輕易直接操作類的內部數據,這樣降低了對內部數據的影響,提高了程序的安全性和可維護性。
封裝的目的:
只能通過規定的方法訪問數據
隱藏類的實現細節
方便加入控制語句
方便實現修改
public class Person {
private int id;
private String name;
private Person person;
public int getId() {
return id;
}
public String getName() {
return name;
}
public Person getPerson() {
return person;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPerson(Person person) {
this.person = person;
}
}
private實現封裝
private/ public 這兩個關鍵字表示 “訪問權限控制” .
被 public 修飾的成員變量或者成員方法, 可以直接被類的調用者使用.
被 private 修飾的成員變量或者成員方法, 不能被類的調用者使用.
換句話說, 類的使用者根本不需要知道, 也不需要關注一個類都有哪些 private 的成員. 從而讓類調用者以更低的成本來使用類.
直接使用 public
class Person {
public String name = "張三";
public int age = 18;
}
class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println("我叫" + person.name + ", 今年" + person.age + "歲");
}
}
// 執行結果
我叫張三, 今年18歲
這樣的代碼導致類的使用者(main方法的代碼)必須要瞭解 Person 類內部的實現, 才能夠使用這個類. 學習成本較高
一旦類的實現者修改了代碼(例如把 name 改成 myName), 那麼類的使用者就需要大規模的修改自己的代碼, 維護成本較高
使用 private 封裝屬性, 並提供 public 方法供類的調用者使用
class Person {
private String name = "張三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "歲");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 執行結果
我叫張三, 今年18歲
此時字段已經使用 private 來修飾. 類的調用者(main方法中)不能直接使用. 而需要藉助 show 方法. 此時類的使用者就不必瞭解 Person 類的實現細節.
同時如果類的實現者修改了字段的名字, 類的調用者不需要做出任何修改(類的調用者根本訪問不到 name, age這樣的字段).
注意事項:
①private 不光能修飾字段, 也能修飾方法
②通常情況下我們會把字段設爲 private 屬性, 但是方法是否需要設爲 public, 就需要視具體情形而定. 一般我們希望一個類只提供 “必要的” public 方法, 而不應該是把所有的方法都無腦設爲 public.
getter和setter方法
當我們使用 private 來修飾字段的時候, 就無法直接使用這個字段了.
代碼示例:
class Person {
private String name = "張三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "歲");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.age = 20;
person.show();
}
}
// 編譯出錯
Test.java:13: 錯誤: age可以在Person中訪問private
person.age = 20;
^
1 個錯誤
此時如果需要獲取或者修改這個 private 屬性, 就需要使用 getter / setter 方法.
代碼示例:
class Person {
private String name;//實例成員變量
private int age;
public void setName(String name){
//name = name;//不能這樣寫
this.name = name;//this引用,表示調用該方法的對象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("caocao");
String name = person.getName();
System.out.println(name);
person.show();
}
// 運行結果
caocao
name: caocao age: 0
注意事項
<1> getName 即爲 getter 方法, 表示獲取這個成員的值.
<2> setName 即爲 setter 方法, 表示設置這個成員的值.
<3> 當set方法的形參名字和類中的成員屬性的名字一樣的時候,如果不使用this, 相當於自賦值. this 表示當前實例的引用.
<4> 不是所有的字段都一定要提供 setter / getter 方法, 而是要根據實際情況決定提供哪種方法.
<5> 在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法.在 VSCode 中可以使用鼠標右鍵菜單 -> 源代碼操作 中自動生成 setter / getter 方法.
字段 vs 屬性
我們通常將類的數據成員稱爲 “字段(field)” , 如果該字段同時提供了 getter / setter 方法, 那麼我們也可以將它稱爲 “屬性(property )”.
繼承
繼承就是子類繼承父類的特徵和行爲,使得子類對象具有父類的信息,同時可以擴展。基本語法:
class 子類 extends 父類 {
}
a.使用 extends 指定父類.
b.Java 中一個子類只能繼承一個父類 (而C++/Python等語言支持多繼承).單繼承多實現。
c.子類會繼承父類的所有 public 的字段和方法.
d.對於父類的 private 的字段和方法, 子類中是無法訪問的.
e.子類的實例中, 也包含着父類的實例. 可以使用 super 關鍵字得到父類實例的引用
f. 子類可以擁有屬於自己的屬性和方法
子類訪問父類成員:
父類無參的構造方法:super();
父類有參的構造方法:super(name);
訪問父類屬性:super.name;
訪問父類方法:super.方法名();
子類不能被繼承的父類成員:
構造方法
private成員
子類與父類不在同包,使用默認訪問權限的成員
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在喫" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 調用父類的構造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飛 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("貓糧");
Bird bird = new Bird("圓圓");
bird.fly();
}
}
protected 關鍵字
如果把字段設爲 private, 子類不能訪問. 但是設成 public, 又違背了我們 “封裝” 的初衷.兩全其美的辦法就是 protected 關鍵字.對於類的調用者來說, protected 修飾的字段和方法是不能訪問的.對於類的子類和同一個包的其他類來說, protected 修飾的字段和方法是可以訪問的.
// Animal.java
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在喫" + food);
}
}
// Bird.java
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
// 對於父類的 protected 字段, 子類可以正確訪問
System.out.println(this.name + "正在飛 ︿( ̄︶ ̄)︿");
}
}
// Test.java 和 Animal.java 不在同一個包中了.
public class Test {
public static void main(String[] args) {
Animal animal = new Animal("小動物");
System.out.println(animal.name); // 此時編譯出錯, 無法訪問 name
}
}
小結: Java 中對於字段和方法共有四種訪問權限
private: 類內部能訪問, 類外部不能訪問
默認(也叫包訪問權限): 類內部能訪問, 同一個包中的類可以訪問, 其他類不能訪問.
protected: 類內部能訪問, 子類和同一個包中的類可以訪問, 其他類不能訪問.
public : 類內部和類的調用者都能訪問
final 關鍵字
final 關鍵字, 修飾一個變量或者字段的時候, 表示常量 (不能修改)
final int a = 10;
a = 20; // 編譯出錯
final 關鍵字也能修飾類, 此時表示被修飾的類就不能被繼承.
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 編譯出錯
Error:(3, 27) java: 無法從最終com.bit.Animal進行繼承
final 關鍵字的功能是 限制 類被繼承
“限制” 這件事情意味着 “不靈活”. 在編程中, 靈活往往不見得是一件好事. 靈活可能意味着更容易出錯.
是用 final 修飾的類被繼承的時候, 就會編譯報錯, 此時就可以提示我們這樣的繼承是有悖這個類設計的初衷的.
我們平時是用的 String 字符串類, 就是用 final 修飾的, 不能被繼承.
super關鍵字
代碼中由於使用了重寫機制, 調用到的是子類的方法. 如果需要在子類內部調用父類方法可以使用super 關鍵字。
super只能出現在子類的方法和構造方法中;
super調用構造方法時,只能是第一句;
super不能訪問法父類的private成員;
注意 super 和 this 功能有些相似, 但是還是要注意其中的區別
多態
多態就是統一接口,使用不同的實現,而執行不同的操作。
多態的實現方式: 靜態多態(重載)、動態多態(重寫)
重寫和重載的區別:
重寫:把在子類中把父類本身有的方法重新寫一遍。
重載:在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同甚至是參數順序不同)則視爲重載。
向上轉型和向下轉型:
向上轉型: 父類引用指向子類對象
父類 = new 子類();
向下轉型: 子類引用指向父類對象
父類 對象1 = new 子類();
子類 對象2 = (子類)對象1;
向上轉型:父類引用指向子類對象,將會丟失子類和父類中不一致的方法,並且父類引用調用的變量只能是父類中的。
class Person{
private String name = "Person";
int age=0;
public void function() {
System.out.println("this is person");
}
}
public class Child extends Person{
public String grade;
int age=10;
public void function() {
System.out.println("this is child");
}
public void b1(){
System.out.println("子類新方法");
} //B類定義了自己的新方法
@Test
public void test(){
Person p = new Child();
p.function();
System.out.println(p.age);
}
}
//輸出:
“this is child”
0
並且我們是不能使用p.b1()的,編譯錯誤,因爲,此時該對象p已經失去了該方法。