1. 程式人生 > >黑馬程式設計師---面向物件上(封裝,繼承,多型)

黑馬程式設計師---面向物件上(封裝,繼承,多型)

------<a href="http://www.itheima.com" target="blank">Java培訓、Android培訓、iOS培訓、.Net培訓</a>、期待與您交流!

面向物件(Object-Oriented,簡稱OO)就是一種常見的程式結構設計方法。
面向物件思想的基礎是將相關的資料和方法放在一起,組合成一種新的複合資料型別,然後使用新建立的複合資料型別作為專案的基礎。

 面向物件思想:面向物件是基於面向過程的程式設計思想

面向物件是一個很抽象的概念,它相對面向過程而言。

過程與物件都是一種解決問題的思想。

面向過程:強調的是功能行為,一種過程

,先幹啥,再幹啥;

面向物件:將功能封裝到物件裡,強調的是具備某功能的物件;

按照面向物件的思想,可以把任何的東西看做物件!

面向物件思想特點:
 是一種更符合我們思想習慣的思想
 可以將複雜的事情簡單化
 將我們從執行者變成了指揮者

面向物件的三個特徵:

封裝(Encapsulation);

繼承(Inheritance);

多型(Polymorphism)

我的總結:

面向過程:強調的是具體的功能實現;(執行者)

面向物件:強調的是具備功能的物件。(管理者)

類與物件
(class)Java語言的最小程式設計單位,也是設計和實現Java程式的基礎,本部分將深入介紹類的相關知識。


類的概念
類是一組事物共有特徵和功能的描述。類是對於一組事物的總體描述,是按照面向物件技術進行設計時最小的單位,也是組成專案的最基本的模組。類的概念是抽象的,類似於建築設計中的圖紙,是對於現實需要代表的具體內容的抽象。類只包含框架結構,而不包含具體的資料。所以類代表的是總體,而不代表某個特定的個體。

 物件:是該類事物的具體體現

舉例:
 類 學生
 物件 班長就是一個物件

我的總結:類是抽象的,物件是具體的,實實在在的!

類的定義:

[修飾符] class 類名{

   1~n個構造方法;

0~n個欄位;

0~n個方法

}

定義類,其實就是定義類裡面的物件

物件包含:

狀態;(屬性)

功能、行為;(方法)

通過類來描述物件;

狀態--------成員變數;

功能、行為——方法;

Eg

class Person{

//屬性

<span style="font-size:18px;">class Person{
 
//屬性
 
private String name;
 
private int age;
 
private int sal;
 
//方法
 
public void show(){
 
System.out.println("個人情況:"+name+age+sal);
 
}
 
}
</span>


變數的定義
 成員變數: 這個變數是否屬於事物中的屬性
 特點:變數的使用範問越小越好成員變數和區域性變數的區別:
 在類中的位置不同
  成員變數 類中方法外
  區域性變數 方法內或者方法宣告上
 在記憶體中的位置不同
  成員變數 堆記憶體
  區域性變數 棧記憶體
 生命週期不同
  成員變數 隨著物件的存在而存在,隨著物件的消失而消失
  區域性變數 隨著方法的呼叫而存在,隨著方法的呼叫完畢而消失
 初始化值不同
  成員變數 有預設的初始化值
  區域性變數 沒有預設的初始化值,必須先定義,賦值,才能使用。成員變數和區域性變數的區別:
 在類中的位置不同
  成員變數 類中方法外
  區域性變數 方法內或者方法宣告上
 在記憶體中的位置不同
  成員變數 堆記憶體
  區域性變數 棧記憶體
 生命週期不同
  成員變數 隨著物件的存在而存在,隨著物件的消失而消失
  區域性變數 隨著方法的呼叫而存在,隨著方法的呼叫完畢而消失
 初始化值不同
  成員變數 有預設的初始化值
  區域性變數 沒有預設的初始化值,必須先定義,賦值,才能使用。

變數的定義
 成員變數: 這個變數是否屬於事物中的屬性
 特點:變數的使用範問越小越好

方法的形式引數是類名的時候如何呼叫:

 形式引數:方法的引數列表中的引數
 實際引數:呼叫方法時,傳遞的引數

 基本資料型別:
  形式引數的改變 對 實際引數沒有影響
 引用資料型別:
  形式引數的改變 對 實際引數   有影響
  
  class型別的引數,也屬於引用資料型別

<span style="font-size:18px;">呼叫方式如下:
		
	class Student {
		public void study(){
			System.out.println("正在複習中");
		}
	}	
	
	class Test {
		public static void main(String[] args){
			//建立Test物件
			Test t = new Test();
			Student s = new Student();
			t.method( s );
		}
		
		public void method( Student s ){
		
			//this --> t物件
			s.study();
		}
	}</span>

構造方法

構造方法
 構造方法作用概述
  給物件的資料進行初始化
 構造方法格式:
  修飾符 類名(引數列表){...}
  
  方法名與類名相同
  沒有返回值型別,連void都沒有
  沒有具體的返回值
  
 構造方法注意事項
  如果你不提供構造方法,系統會給出空引數的構造方法
  如果你提供了構造方法,系統將不再提供空引數的構造方法
  構造方法也是可以過載的

構造方法:用來構造類的例項(每一個類都預設有一個無參的構造方法,得使用new呼叫)

欄位:類或物件所包含的資料,對類狀態的一種描述;

方法:類或物件的特徵或行為

作用:

給類中的欄位進行初始化,可以用來建立物件。

特點:

方法名與類名相同

不用定義返回值型別

不需要寫return語句

我的總結:

注意:

預設構造方法的特點。

多個構造方法是以過載的形式存在的。

構造方法的過載:(需要哪個就去適配哪個,呼叫哪個)

this([實參]);呼叫當前類的構造方法

注意: this([實參]);必須放在構造器的第一行;

物件的產生格式:

類名稱  物件名 = new  類名稱();

因為有(),所以是方法,實際上它就是構造方法,並且是非私有的構造方法。

如:CellPhone cp = new CellPhone();

Eg

<span style="font-size:18px;">class Person{
 
private String name;
 
private int age;
 
private int sal;
 

public void show(){
 
System.out.println("個人情況:"+name+age+sal);
 
}
 
 
 
public Person(String name) {
 
super();
 
this.name = name;
 
}
 
 
 
public Person(String name, int age) {
 
super();
 
this.name = name;
 
this.age = age;
 
}
 
 
 
public Person(String name, int age, int sal) {
 
super();
 
this.name = name;
 
this.age = age;
 
this.sal = sal;
 
}
 
}
</span>


成員方法的分類:
 返回值:
  有返回值的方法
  沒有返回值的方法
 
 引數列表:
  空引數方法
  有引數方法成員方法的分類:
 返回值:
  有返回值的方法
  沒有返回值的方法
 
 引數列表:
  空引數方法
  有引數方法成員方法的分類:
 返回值:
  有返回值的方法
  沒有返回值的方法
 
 引數列表:
  空引數方法
  有引數方法成員方法的分類:
 返回值:
  有返回值的方法
  沒有返回值的方法
 
 引數列表:
  空引數方法
  有引數方法

建立一個物件的步驟:
 Student s = new Student();
 
 載入Student.class檔案進記憶體
 在棧記憶體為s開闢空間
 在堆記憶體為學生物件開闢空間
 對學生物件的成員變數進行預設初始化
 對學生物件的成員變數進行顯示初始化
 通過構造方法對學生物件的成員變數賦值
 學生物件初始化完畢,把物件地址賦給s變數

靜態關鍵字static

特點:

static關鍵字
 可以修飾成員變數和成員方法
 
 static關鍵字特點
  靜態所修飾的成員,隨著類的載入而載入,優先於物件存在
  靜態所修飾的成員,會被所有物件共享
  可以通過類名呼叫
   類名.靜態成員

使用注意:

靜態方法只能訪問靜態成員

但是非靜態成員可以訪問靜態成員;

靜態方法中不可以使用thissuper關鍵字

主方法(main)是靜態的(可以利用類名去呼叫靜態的main方法,很正常!但是會陷入死迴圈,導致記憶體溢位,jvm自動停止!

public static void main(String[] agrs){}

可修飾字段,方法。

static修飾的成員表示它屬於這個類共有,而不是屬於該類的單個例項。

static 修飾的欄位 ==類欄位

static 修飾的方法 ==類方法

沒使用static修飾的欄位和方法,成員屬於類的單個例項,

不屬於類。

沒有static修飾的欄位==例項欄位

沒有static修飾的方法==例項方法

類和例項訪問欄位和方法的語法:

訪問類成員: 類.欄位    .方法

訪問例項成員: 例項.欄位    例項.方法

靜態變數和成員變數的區別
 所屬不同
  靜態變數屬於類,所以也稱為為類變數
  成員變數屬於物件,所以也稱為例項變數(物件變數)
 記憶體中位置不同
  靜態變數儲存於方法區的靜態區
  成員變數儲存於堆記憶體
 記憶體出現時間不同
  靜態變數隨著類的載入而載入,隨著類的消失而消失
  成員變數隨著物件的建立而存在,隨著物件的消失而消失
 呼叫不同
  靜態變數可以通過類名呼叫,也可以通過物件呼叫
  成員變數只能通過物件名呼叫

我的總結:

static 修飾的欄位和方法,既可以通過類呼叫,也可以使用例項呼叫;

static修飾的欄位和方法,只能使用例項來呼叫(建議使用:類名來呼叫;其實在底層,物件呼叫類成員,也會轉換類名呼叫)

static關鍵字不能與thissuper同時連用!

匿名物件

一個沒有名字的物件建立了一個物件出來,沒有賦給一個變數;

特點:

對方法或欄位只進行一次呼叫時;

可作為實際引數進行傳遞;

只在堆裡面開闢儲存區域,

只能使用一次使用完就被銷燬了;

何時使用?只拿來用一次!!

new Person();表示匿名物件,沒有名字的物件

new Person().age = 17;//使用一次之後就被銷燬了

匿名物件: 就是沒有名字的物件
   是物件的一種簡化表示形式

 匿名物件的兩種使用情況:
  物件呼叫方法僅僅一次的時候
   new Student().study();

  作為實際引數傳遞
   t.method( new Student() );

this關鍵字

特點:this表示當前物件。

當前物件  ←→  當前正在呼叫例項成員的物件

換言之:誰呼叫了方法,誰就是當前物件。

什麼時候使用this關鍵字呢?

方法間的相互呼叫;

this.欄位;

構造器中相互呼叫,但是此時this([引數])必須寫在構造方法第一行。

this不能用在static修飾的方法裡和static修飾的程式碼塊裡;

 this關鍵字:
  this:代表所在類的物件引用
  記住:
   方法被哪個物件呼叫,this就代表那個物件
  什麼時候使用this呢?
   區域性變數隱藏成員變數
   

Eg:構造方法中的this.name = name;

封裝

封裝:是指隱藏物件的屬性和實現細節,僅對外提供公共訪問方式
 好處:
  隱藏實現細節,提供公共的訪問方式
  提高了程式碼的複用性
  提高安全性。
 封裝原則:
  將不需要對外提供的內容都隱藏起來。
  把屬性隱藏,提供公共方法對其訪問

封裝的兩個含義:

1.把物件的狀態和行為看成一個統一的整體,將二者存放在一個獨立的模組中()

2."資訊隱藏",把不需要讓外界知道的資訊隱藏起來,儘可能隱藏物件功能實現細節,欄位;

封裝機制在程式中的體現是:把描述物件的狀態用欄位表示,描述物件的行為用方法表示,把欄位和方法定義在一個類中,並保證外界不能任意更改其內部的欄位值,也不允許任意調動其內部的功能方法。

程式中的一種體現:通常將類中的成員變數私有化(private),通過對外提供方法(setXxx,getXxx,可對該變數(xxx)進行訪問。

boolean 型別的變數沒有getXX,只有isXX;

Eg

<span style="font-size:18px;">class Person1{
 
private String name;
 
private int age;
 
private int sal;
 

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;
 
}
 
}
</span>

許可權修飾關鍵字

private 類訪問許可權:本類內部可以訪問,不能繼承到子類

 private關鍵字:
  是一個許可權修飾符。
  可以修飾成員(成員變數和成員方法)
  被private修飾的成員只在本類中才能訪問

default 什麼都不寫,包訪問許可權:本類內部可以訪問,同包其他類也可以訪問,同包可繼承;

protected 子類訪問許可權:本類內部可以訪問,不同包的子類也可以訪問,同包其他類也可以訪問,能繼承到子類;

public 公共訪問許可權:任何地方都可以訪問,能繼承到子類;

類的設計分析

分析思路:

根據要求寫出類所包含的欄位;

所有的欄位都必須私有化;

封裝之後的欄位可通過settergetter設值和取得;

按需求可新增若干構造方法;

根據需求可新增相應的方法;

類中的所有方法都不要直接處理(輸出列印),而是交給呼叫者去處理。

面向物件之繼承

繼承:
 概念:繼承是從已有的類中派生出新的類,
   新的類能吸收已有類的資料屬性和行為,並能擴充套件新的能力。

繼承的好處和弊端
 提高了程式碼的複用性
  多個類相同的成員可以放到同一個類中
  提高了程式碼的維護性
  如果功能的程式碼需要修改,修改一處即可
  讓類與類之間產生了關係,是多型的前提
  
 其實這也是繼承的一個弊端:類的耦合性很強

繼承特點
 Java只支援單繼承,不支援多繼承
 Java支援多層繼承(繼承體系)
 
繼承的注意事項:
 子類只能繼承父類所有非私有的成員(成員方法和成員變數)
  其實這也體現了繼承的另一個弊端:打破了封裝性
 子類不能繼承父類的構造方法,
  但是可以通過super(後面講)關鍵字去訪問父類構造方法
 不要為了部分功能而去繼承
 
我們到底在什麼時候使用繼承呢?
 繼承中類之間體現的是:”is a”的關係。
 例如: 梨是一種水果
 
this和super的區別
 this 代表本類對應的引用
 super代表父類儲存空間的標識(可以理解為父類引用)
 
 用法(this和super均可如下使用)
 訪問成員變數
  this.成員變數  super.成員變數
 訪問構造方法(子父類的構造方法問題講)
  this(…)  super(…)
 訪問成員方法(子父類的成員方法問題講)
  this.成員方法() super.成員方法()

繼承中構造方法的注意事項:
 子類通過super去顯示呼叫父類其他的帶參的構造方法
  super(引數列表)
 子類通過this去呼叫本類的其他構造方法
  this(引數列表)
 一定要注意
  super(…)或者this(….)必須出現在第一條語句上
   否則,就會有父類資料的多次初始化

首先有反映一般事物特性的類,然後在此基礎上反映出特殊事物的類;

也就是說:繼承是一種從一般到特殊的關係;

特點:

1、提高了程式碼的複用性。

2、讓類與類之間產生關係,有了這個繼承關係才有了多型的特性。

3Java語言中只支援單繼承(有別於C語言)。

因為多繼承容易帶來安全隱患(父類多了, 功能相同的話,就會出現呼叫不確定性嗎,覆寫一個方法,到底覆寫的誰的?)。

ps:介面可以實現多繼承

4Java支援多層繼承,object是每個類的超類,實現樹形結構。

我的總結:

繼承是多型的前提。

對類而言,只支援單繼承。

格式:

[修飾符] class SubClass extends SuperClass

按照這種關係,我們把SuperClass類稱為父類或基類,把SubClass稱為子類或派生類或拓展類;

我的總結:

java.lang.Object是所有類的父類,

Object要麼是直接父類要麼是間接父類。

Eg

學生屬於人的一種特殊情況,此時我把人的共性寫在Person類裡面,為了讓學生擁有這些共性(別的比如老師也可以有這些共性),然後我就讓學生來拓展Person類。

我的總結:

子類與父類的關係:

子類拓展父類(子類是父類的一種特殊情況)

主要是以父類為基礎,然後新增屬於自己的欄位和方法。

父類的私有成員子類不能繼承到;父類的構造方法不能被繼承;

Java只支援單繼承,不支援多繼承;//不然的話,比如show方法,繼承了多個,不知道到底呼叫那一個。

一個類有且只有一個直接父類;

一個類沒顯示的繼承其他的一個類的時候,預設的直接父類就是Object;

Student 的直接父類是Person,Object類也是Student類的父類,但是是間接父類;

一旦一個類顯示的繼承了其他的一個類的時候,此時預設的直接父類Object就會被取消;

Java裡一個類只能有一個直接父類;java.lang.Object是所有類的父類,Object要麼是直接父類要麼是間接父類。

子類物件例項化過程

在繼承操作中,對於子類物件的例項化:

子類物件在例項化之前必須首先呼叫父類中的構造方法之後再呼叫自身的構造方法。

子類訪問父類和方法覆寫

方法重寫
 子類中出現了和父類中一模一樣的方法宣告,也被稱為方法覆蓋,方法複寫。
 
 使用特點
  如果方法名不同,就呼叫對應的方法
  如果方法名相同,最終使用的是子類自己的
 
 方法重寫的應用:
  當子類需要父類的功能,而功能主體子類有自己特有內容時,
  可以重寫父類中的方法,
  這樣,即沿襲了父類的功能,又定義了子類特有的內容
  
 方法重寫的注意事項:
  1:父類中私有方法不能被重寫
  2:子類重寫父類方法時,訪問許可權不能更低
  3:父類靜態方法,子類也必須通過靜態方法進行重寫。
   其實這個算不上方法重寫,但是現象確實如此,
   至於為什麼算不上方法重寫,多型中我會講解

子類不能直接訪問父類的私有成員;

但是子類可以呼叫父類中的非私有方法來間接訪問父類的私有成員。

Person類中有私有欄位name,Student繼承Person

new Sudent().name; ×

new Student().getName(); √

子類拓展父類(子類是父類的一種特殊情況)

主要是以父類為基礎,然後新增屬於自己的欄位和方法。

方法覆寫產生原因:

當父類中某個方法不適合於子類時,子類出現父類一模一樣的方法.

判斷必殺技:子類方法前加上@Override能編譯通過,表明是方法的覆寫。

呼叫被覆蓋的父類方法:使用super.方法名(實參);

方法覆寫時應遵循的原則(一同兩小一大)

(一同):方法簽名必須相同;

(兩小):

子類方法的返回值型別比父類方法的返回值型別更小或相等

子類方法宣告丟擲的異常應比父類方法申明丟擲的異常更小或相等;

(一大):子類方法的訪問許可權應比父類方法更大或相等;

子類需要覆寫父類方法。

當父類的某個方法不適合於子類本身的特徵行為時就當覆寫父類中應當改變的方法。

final關鍵字
 final關鍵字是最終的意思  
  
  修飾類,類不能被繼承
  修飾變數,變數就變成了常量,只能被賦值一次
  修飾方法,方法不能被重寫

呼叫父類構造方法

表示父類物件的預設引用

如果子類要呼叫父類被覆蓋的例項方法,可用super作為呼叫者呼叫父類被覆蓋的例項方法。

使用super呼叫父類方法

使用super呼叫父類的構造方法

呼叫構造方法

本類中呼叫另一個過載構造方法用this(引數列表)

子類構造方法呼叫父類構造方法用super(引數列表)

子類呼叫父類的構造方法時:

super必須放在第一句

Java在執行子類的構造方法前會先呼叫父類無參的構造方法,其目的是為了對繼承自父類的成員做初始化操作。

子類在建立物件的時候,預設呼叫父類的無參構造方法,要是子類構造方法中顯示指定呼叫父類其他構造方法,就呼叫指定的父類構造方法,取消呼叫父類無參構造方法。

Eg

<span style="font-size:18px;">package reviewDemo;
 
 
 
class A{
 
String name;
 
A(){
 
System.out.println("父類預設隱式的構造方法!");
 
}
 

A(String name){
 
System.out.println("父類顯式的構造方法!");
 
}
 
}
 
 
 
class B extends A{
 

B(){
 
super(null);
 
System.out.println("子類預設隱式的構造方法!");
 
}
 
}
 
 
 
public class Demo10 {
 
public static void main(String[] args) {
 
new B();
 
}
 
}
</span>

面向物件之多型

多型:指同一個實體同時具有多種形式

好比,你去麵館吃麵,說我要吃麵,那麼;老闆給我牛肉麵,雞蛋麵等都可以,

這就是說""有多種形態,也就是說實體有多種形態;

編譯時的型別由宣告該變數時使用的型別決定,執行時的型別由實際賦給變數的物件決定。

如果編譯時型別和執行時型別不同,就出現多型。

Eg

前提:Student  extends   Person:

<span style="font-size:18px;">Person p = new Person();
 
Student s = new Student();
 
Person p = new Student();//多型
</span>

引用關係:父類變數指向子類例項物件

實現多型的機制:

父類的引用變數可以指向子類的例項物件,而程式呼叫的方法在執行期才動態繫結,就是引用變數所指向的真正例項物件的方法,也就是記憶體里正在執行的那個物件的方法,而不是引用變數的型別中定義的方法。

多型的作用:

把不同的子類物件都當作父類來看,可以遮蔽不同子類物件之間的差異,寫出通用的程式碼,做出通用的程式設計,以適應需求的不斷變化。

只修改方法的實現,不必修改方法的宣告

繼承是多型產生的前提條件;

分類:

編譯時多型:方法過載

執行時多型:方法覆寫

Eg

package test;

class Dog{

void eat(){

System.out.println("一般的狗吃一般的狗糧!");

}

}

class HashDogextends Dog{

void eat(){

System.out.println("哈士奇吃哈士奇的狗糧!");

}

}

class ZangAoDogextends Dog{

void eat(){

System.out.println("藏獒吃藏獒的狗糧!");

}

}

//定義一個動物園喂的方法

<span style="font-size:18px;">package test;
 
 
 
class Dog{
 
void eat(){
 
System.out.println("一般的狗吃一般的狗糧!");
 
}
 
}
 
 
 
class HashDog extends Dog{
 
void eat(){
 
System.out.println("哈士奇吃哈士奇的狗糧!");
 
}
 
}
 
 
 
class ZangAoDog extends Dog{
 
void eat(){
 
System.out.println("藏獒吃藏獒的狗糧!");
 
}
 
}
 
 
 
//定義一個動物園喂的方法
 
class Zoo{
 

void feed(Dog d){
 
d.eat();
 
}
 

}
 
 
 
public class Demo11 {
 
public static void main(String[] args) {
 

Dog hd = new HashDog();
 

Dog zd = new ZangAoDog();
 

Zoo z = new Zoo();
 
z.feed(hd);
 
z.feed(zd);
 
}
 
}
</span>

輸出:

哈士奇吃哈士奇的狗糧!

藏獒吃藏獒的狗糧!

引用變數型別轉換

向上轉型(子類→父類):(自動完成)

父類名稱 父類物件 = 子類例項;

向下轉型(父類→子類):(強制完成)

子類名稱 子類物件 = (子類名稱)父類例項;

物件名   instanceof 

判斷指定的變數名此時引用的真正型別是不是當前給出的類或子類;

我的總結:物件的型別和類必須有繼承關係

Eg

<span style="font-size:18px;">class A extends B{}
 
 
 
B b = new A();
 
If(b instanceof A){ ...
 
}
</span>