最近有個朋友說想要說最近在學java,他對物件導向的程式語言的時候還是有些不明白,為了幫他能更快地“轉型”,我就寫了這篇文章。因為從上層建築層面而言。所有的物件導向程式語言的思路都是差不多的,而這三大特性,則是思路中的支柱點,接下來我就重點講解了一下java三大特性。
物件導向的程式語言,擁有三大特性,分別是:“封裝”,“繼承”,“多型”。
封裝
在物件導向程式設計中,封裝封裝(encapsulation)從字面上來理解就是包裝的意思,是指利用抽象資料型別將資料和基於資料的操作封裝在一起,使其構成一個不可分割的獨立實體。其實就是將物件執行所需的方法和資料封裝在程式公佈其介面,資料被保護在抽象資料型別的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯絡。也就是說使用者是無需知道物件內部的細節(當然也無從知道),但可以通過該物件對外的提供的介面來訪問該物件,通俗點就是是其他附加到這些介面上的物件不需要關心物件實現的方法即可使用這個物件。這個概念就是“不要告訴我你是怎麼做的,只要做就可以了“。
所以封裝把一個物件的屬性私有化,同時提供一些可以被外界訪問的屬性的方法,如果不想被外界方法,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法,那麼這個類也沒有什麼意義了。
比如我們將一個物件看做是一個房子,裡面的漂亮的桌布,如沙發、電視、空調等都是該房子的私有屬性,但是如果沒有牆遮擋,那不就沒有一點兒隱私了嗎!就是因為有了遮擋的牆,我們既能夠有自己的隱私 而且我們可以隨意的更改裡面的擺設而不會影響到其他的。但是如果沒有門窗,一個包裹的嚴嚴實實的黑盒子,又有什麼存在的意義呢?所以通過門窗別人也能夠看到裡面的風景。所以說門窗就是房子物件留給外界訪問的介面。
一般在類裡要將屬性前新增private修飾符。然後定義getter和setter方法。然後在我們的 main 函式裡的物件,不能再直接呼叫屬性了,只能通過getter和setter方法進行呼叫。
封裝的三大好處
1、良好的封裝能夠減少耦合。
2、類內部的結構可以自由修改。
3、可以對成員進行更精確的控制。
4、隱藏資訊,實現細節。
修飾符
大家首先要先了解一下什麼是修飾符,訪問修飾符可以用來修飾屬性和方法的訪問範圍。
在物件導向的過程中,我們通過許可權控制對封裝好的類加上許可權,來限制外來者對類的操縱,藉以達到保障類中資料和方法的安全的目的。可以這麼說:一個類就是一個封裝了相關屬性及方法的邏輯實體。對於物件中的某些屬性或者方法來說,它們可以是私有的,不能被外界訪問。也可以是共有的,能夠被外界任何人員訪問。通過這種方式,物件對內部資料提供了不同級別的保護,以防止程式中無關的部分意外的改變或錯誤的使用了物件的私有部分,從而使得程式出現不要的錯誤。
java中4中修飾符分別為public、protectd、default、private。這就說明了物件導向的封裝性,所有我們要儘量讓許可權降到最低,從而安全性提高。
如圖,代表了不同的訪問修飾符的訪問範圍,比如private修飾的屬性或者方法,只能在本類中訪問或者使用。什麼修飾符都不加的話預設是default,預設在當前類中和同一包下都可以訪問和使用。
訪問許可權 類 包 子類 其他包
public ∨ ∨ ∨ ∨
protect ∨ ∨ ∨ ×
default ∨ ∨ × ×
private ∨ × × ×
如果沒有在屬性前面新增任何修飾符,預設是default許可權,我們通過建立物件就可以直接對屬性值進行修改,沒有體現封裝的特性。這在程式設計中都是不安全的,所以我們需要利用封裝,來改進我們的程式碼。
修飾符舉例
首先我們先定義四個類Person,Parent,Teacher,Student,分別比較其他包,子類,包,本類的區別。每個類的位置圖所示。
package com.java.test;
public class Person {
public String name = "張三";
public void introduceMyself(){
System.out.println(name);
}
}複製程式碼
name是public的,若編譯沒有報錯說明public變數擁有本類的訪問許可權。
package com.java.test;
public class Student {
Person p = new Person();
public void test(){
System.out.println(p.uname);
}
}複製程式碼
Student和 Person在同一個包內,若編譯沒有報錯,說明變數在相同擁有包內的訪問許可權。
package com.java.test1;
import com.java.test.Person;
public class Teacher extends Person {
public int age;
Person p = new Person();
public void test1(){
System.out.println(p.uname);
}
}複製程式碼
Student和 Person不在同一個包內,但是Teacher繼承了Person類,若編譯沒有報錯,說明變數擁有子包內的訪問許可權
package com.java.test1;
import com.java.test.Person;
public class Parents {
public String uname = "haha";
Person p = new Person();
public void test2(){
System.out.println(p.uname);
}
}複製程式碼
Parent和Person不在同一個包內,若編譯沒有報錯,則說明變數擁有白外的訪問許可權
上面測試了之後,如果均能編譯通過,就說明用public修飾的類在本類、同包、子類、其他包中互相訪問都是可以的
同樣開始測試protected許可權問題,如果Person,Teacher,Student能編譯通過,就說明用protected修飾的類在本類、同包、子類中互相訪問都是可以的,而Parent編譯不通過說明protected不可以在包外沒有繼承關係的類中互相訪問。
同樣開始測試default許可權問題,如果Person,Student能編譯通過,就說明用default修飾的類在本類、同包、子類中互相訪問都是可以的,而Parent,Teacher編譯不通過說明default修飾的類可以在包外不管有沒有繼承關係的類都不可以互相訪問
同樣開始測試private許可權問題,如果Person能編譯通過,就說明用private修飾的類在本類、同包、子類中互相訪問都是可以的,而Parent,Teacher,Student編譯不通過說明private修飾的類只能在本類中訪問。
一般在類裡要將屬性前新增private修飾符。然後定義getter和setter方法。然後在我們的 main 函式裡的物件,不能再直接呼叫屬性了,只能通過getter和setter方法進行呼叫。
包
我先給大家講一下包的作用
有時候會遇到程式的類名可能是重複的,我們就可以用包的概念來解決我們的問題。包的作用就是管理Java檔案,解決同名檔案衝突。這就和衣櫃相類似。衣櫃是不是有不同的隔斷和抽屜,我們將衣服分門別類地放好,更有利與有利於我們管理。
定義一個包,我們使用package關鍵字,加上我們的包名。
package com.java.test;
複製程式碼
//注意:必須放在源程式的第一行,包名可用”.”號隔開 ,包的命名規範是全小寫字母拼寫
Java系統中常用的包
java.(功能).(類)
java.lang.(類) 包含java語言基礎的類
java.util.(類) 包含語言中各種工具類
java.io.(類) 包含輸入、輸出相關的類
複製程式碼
在不同包中使用另一個檔案中的類,就需要用到import關鍵字。比如import com.java.test1.test.java,同時如果import com.java.test1*這是將包下的所有檔案都匯入進來。
this 關鍵字
一、this關鍵字主要有三個應用:
(1)this呼叫本類中的屬性,也就是類中的成員變數;
(2)this呼叫本類中的其他方法;
(3)this呼叫本類中的其他構造方法,呼叫時要放在構造方法的首行。
Public Class Student {
public Student(String name) { //定義一個帶形式引數的構造方法
} public Student() { //定義一個方法,名字與類相同故為構造方法
this(“Hello!”);
} String name; //定義一個成員變數name
private void SetName(String name) { //定義一個引數(區域性變數)name
this.name=name; //將區域性變數的值傳遞給成員變數
}
}複製程式碼
如上面這段程式碼中,有一個成員變數name,同時在方法中有一個形式引數,名字也是name,然後在方法中將形式引數name的值傳遞給成員變數name。
this這個關鍵字其代表的就是物件中的成員變數或者方法。也就是說,如果在某個變數前面加上一個this關鍵字,其指的就是這個物件的成員變數或者方法,而不是指成員方法的形式引數或者區域性變數。為此在上面這個程式碼中,this.name代表的就是物件中的成員變數,又叫做物件的屬性,而後面的name則是方法的形式引數,程式碼this.name=name就是將形式引數的值傳遞給成員變數。
如果一個類中有多個構造方法,因為其名字都相同,跟類名一致,那麼這個this到底是呼叫哪個構造方法呢?其實,這跟採用其他方法引用構造方法一樣,都是通過形式引數來呼叫構造方法的。如上例中,this關鍵字後面加上了一個引數,那麼就表示其引用的是帶引數的構造方法。如果現在有三個構造方法,分別為不帶引數、帶一個引數、帶兩個引數。那麼Java編譯器會根據所傳遞的引數數量的不同,來判斷該呼叫哪個構造方法。從上面示例中可以看出,this關鍵字不僅可以用來引用成員變數,而且還可以用來引用構造方法。
內部類
內部類( Inner Class )我們從外面看是非常容易理解的,內部類就是將一個類的定義放在另一個類的定義內部。當然與之對應,包含內部類的類被稱為外部類。
很多初學者一定會問那為什麼要將一個類定義在另一個類裡面呢?
我們程式設計中有時候會存在一些使用介面很難解決的問題,這時我們就可以利用內部類提供的、可以繼承多個具體的或者抽象的類的能力來解決這些程式設計問題。可以這樣說,介面只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。
在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。
public interface Father {
}
public interface Mother {
}
public class Son implements Father, Mother {
}
public class Daughter implements Father{
class Mother_ implements Mother{
}
}複製程式碼
內部類特性
1、內部類可以用多個例項,每個例項都有自己狀態資訊,並且與其他外圍物件資訊相互獨立。
2、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面,或者繼承同一個類。
3、建立內部類物件的時刻並不依賴於外圍類物件的建立。
4、內部類並沒有令人迷惑的“is-a”關係,他就是一個獨立的實體。
5、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
package com.java.test;
public class OuterClass {
private String name ;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void display(){
System.out.println("呼叫的是OuterClass的display");
}
public class InnerClass{
public InnerClass(){
name = "chenssy";
age = 23;
}
public OuterClass getOuterClass(){
return OuterClass.this;
}
public void display(){
System.out.println("name:" + getName() +" ;age:" + getAge());
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
innerClass.getOuterClass().display();
}
}複製程式碼
name:chenssy ;age:23
呼叫的是OuterClass的display
複製程式碼
我們需要明確一點,內部類是個編譯時的概念,一旦編譯成功後,它就與外圍類屬於兩個完全不同的類(當然他們之間還是有聯絡的)。
我們還看到了如何來引用內部類:引用內部類我們需要指明這個物件的型別:OuterClasName.InnerClassName
。同時如果我們需要建立某個內部類物件,必須要利用外部類的物件通過.new來建立內部類: OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
同時如果我們需要生成對外部類物件的引用,可以使用OuterClassName.this,這樣就能夠產生一個正確引用外部類的引用了。
在Java中內部類主要分為成員內部類、區域性內部類、匿名內部類、靜態內部類。
成員內部類
成員內部類也是最普通的內部類,它是外圍類的一個成員,所以他是可以無限制的訪問外圍類的所有 成員屬性和方法,儘管是private的,但是外圍類要訪問內部類的成員屬性和方法則需要通過內部類例項來訪問。
在成員內部類中要注意兩點,
- 成員內部類中不能存在任何static的變數和方法;
- 成員內部類是依附於外圍類的,所以只有先建立了外圍類才能夠建立內部類。
public class OuterClass {
private String str;
public void outerDisplay(){
System.out.println("outerClass...");
}
public class InnerClass{
public void innerDisplay(){
//使用外圍內的屬性
str = "chenssy...";
System.out.println(str);
//使用外圍內的方法
outerDisplay();
}
}
/*推薦使用getxxx()來獲取成員內部類,尤其是該內部類的建構函式無引數時 */
public InnerClass getInnerClass(){
return new InnerClass();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.getInnerClass();
inner.innerDisplay();
}
}複製程式碼
個人推薦使用getxxx()來獲取成員內部類,尤其是該內部類的建構函式無引數時 。
句區域性內部類
區域性內部類,是巢狀在方法和作用域內的,對於這個類的使用主要是應用與解決比較複雜的問題,想建立一個類輔助我們的解決方案,到那時不希望這個類是公共的,所以我們就可以建立區域性內部類。
區域性內部類和成員內部類一樣被編譯,他只能在該方法和屬性中使用,不在該方法和屬性就會失效。
定義在方法裡:
public class Parcel5 {
public Destionation destionation(String str){
class PDestionation implements Destionation{
private String label;
private PDestionation(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
return new PDestionation(str);
}
public static void main(String[] args) {
Parcel5 parcel5 = new Parcel5();
Destionation d = parcel5.destionation("chenssy");
}
}複製程式碼
定義在作用域內:
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("chenssy");
String string = ts.getSlip();
}
}
public void track(){
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 parcel6 = new Parcel6();
parcel6.track();
}
}複製程式碼
匿名內部類
public class OuterClass {
public InnerClass getInnerClass(final int num,String str2){
return new InnerClass(){
int number = num + 3;
public int getNumber(){
return number;
}
}; /* 注意:分號不能省 */
}
public static void main(String[] args) {
OuterClass out = new OuterClass();
InnerClass inner = out.getInnerClass(2, "chenssy");
System.out.println(inner.getNumber());
}
}
interface InnerClass {
int getNumber();
}
複製程式碼
1、 匿名內部類是沒有訪問修飾符的。
2、 new 匿名內部類,這個類首先是要存在的。如果我們將那個InnerClass介面註釋掉,就會出現編譯出錯。
3、 注意getInnerClass()方法的形參,第一個形參是用final修飾的,而第二個卻沒有。同時我們也發現第二個形參在匿名內部類中沒有使用過,所以當所在方法的形參需要被匿名內部類使用,那麼這個形參就必須為final。
4、 匿名內部類是沒有構造方法的。因為它連名字都沒有何來構造方法。
靜態內部類
static可以修飾成員變數,方法,程式碼塊,其他還可以修飾內部類,使用static修飾的內部類我們稱之為靜態內部類,不過我們更喜歡稱之為巢狀內部類。靜態內部類與非靜態內部類之間最大的區別就是非靜態內部類在編譯完成之後會隱含的儲存著一個引用,該以及用是指向他的外圍內。但是靜態內部類卻沒有,這就意味著靜態內部類建立不需要依賴外圍類,並且他不能使用任何外圍類的非static成員變數和方法。
public class OuterClass {
private String sex;
public static String name = "chenssy";
/**
*靜態內部類
*/
static class InnerClass1{
/* 在靜態內部類中可以存在靜態成員 */
public static String _name1 = "chenssy_static";
public void display(){
/*
* 靜態內部類只能訪問外圍類的靜態成員變數和方法
* 不能訪問外圍類的非靜態成員變數和方法
*/
System.out.println("OutClass name :" + name);
}
}
/**
* 非靜態內部類
*/
class InnerClass2{
/* 非靜態內部類中不能存在靜態成員 */
public String _name2 = "chenssy_inner";
/* 非靜態內部類中可以呼叫外圍類的任何成員,不管是靜態的還是非靜態的 */
public void display(){
System.out.println("OuterClass name:" + name);
}
}
/**
* @desc 外圍類方法
* @author chenssy
* @data 2013-10-25
* @return void
*/
public void display(){
/* 外圍類訪問靜態內部類:內部類. */
System.out.println(InnerClass1._name1);
/* 靜態內部類 可以直接建立例項不需要依賴於外圍類 */
new InnerClass1().display();
/* 非靜態內部的建立需要依賴於外圍類 */
OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
/* 方位非靜態內部類的成員需要使用非靜態內部類的例項 */
System.out.println(inner2._name2);
inner2.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.display();
}
}複製程式碼