一、接口的思想(接口的第二個做用)(掌握) java
二、抽象類和接口的區別(瞭解)編程
三、多態技術(掌握) api
四、Object類概述(掌握) app
接口的第一個做用:給事物體系增長額外功能(擴展功能)。函數
接口的第二個做用:給事物雙方定義規則。一方使用規則,另外一方實現規則。工具
說明:筆記本就是在使用這個規則,外圍設備(鼠標、鍵盤等)在實現這個規則。學習
接口和抽象類都是描述事物的共性行爲,而且描述的行爲通常都是抽象的。須要子類或實現類對這些行爲進行實現或複寫。測試
接口和抽象類的區別:this
一、接口中只能定義抽象方法;抽象類中除了具備抽象方法外,還能夠定義其它方法;spa
二、接口能夠多實現;而抽象類只能單一繼承;
三、接口用來描述事物擴展功能(額外功能);抽象類用來描述事物的共性內容(描述不清楚);
四、接口中沒有構造函數;抽象類中具備構造函數;
抽象類和接口的區別代碼體現以下:
//定義接口
interface Inter
{
void show();
//接口中只能有抽象方法
/*void test()
{
System.out.println("test");
}*/
}
interface InterA
{
void show();
}
//定義一個類來實現接口
class InterfaceImpl implements Inter,InterA//接口能夠多實現
{
public void show()
{
System.out.println("接口show");
}
}
//定義一個抽象類
abstract class Abs
{
//抽象類中除了定義抽象方法還能夠定義其餘方法
void demo()
{
System.out.println("demo");
}
//抽象類中能夠有構造函數,接口中沒有
Abs()
{
System.out.println("抽象類中的構造函數");
}
}
class AbstractDemo extends Abs
{
}
class AbstractAndInter
{
public static void main(String[] args)
{
InterfaceImpl ip=new InterfaceImpl();
ip.show();
AbstractDemo abs=new AbstractDemo();
}
}
面嚮對象語言三大特徵:封裝、繼承和多態。
多態:表示的是一個事物的多種表現形態。同一個事物,以不一樣的形態表現出來。
多態來源於生活,在生活中咱們常常會對某一類事物使用它的共性統稱來表示某個具體的事物,這時這個具體的事物就以其餘的形式展現出來。
蘋果:說蘋果,說水果。
狗:說狗,說動物。
貓:說貓,說動物。
在Java中的多態代碼體現:
使用父類的引用,表示本身的子類對象。
Cat c = new Cat(); 使用貓類型表示本身,這裏不會發生多態現象
Animal a = new Cat(); 使用動物的類型再表示貓,這時就發生的多態的現象。
在Java中要使用多態技術:
前提:必需要有繼承/實現;
好處:能夠經過父類統一管理子類;
多態技術在java中的代碼體現:
//演示多態技術
abstract class Animal
{
abstract void eat();
void show()
{
System.out.println("show run .....");
}
void show2()
{
System.out.println("show2 run .....");
}
}
class Cat extends Animal
{
void eat()
{
System.out.println("貓吃魚");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨頭");
}
}
class DuoTaiDemo
{
public static void main(String[] args)
{
Cat c = new Cat();
demo(c);
Dog d = new Dog();
demo(d);
}
/*
在調用方法的時候發生了多態的現象
Animal a = new Cat(); 這裏發生了多態
Animal a = new Dog(); 這裏也是多態
Dog a=new Animal();子類引用是不能夠指向父類對象的
咱們在使用多態的時候,永遠只能使用父類的類型接受子類的對象,而不能使用
子類的類型接受父類的對象。
*/
public static void demo( Animal a )
{
a.eat();
a.show();
a.show2();
}
}
注意:咱們在使用多態的時候,永遠只能使用父類的類型接受子類的對象,而不能使用子類的類型接受父類的對象。
//演示多態弊端
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("貓吃魚");
}
//貓有本身的特有行爲 抓老鼠
void catchMouse()
{
System.out.println("貓抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨頭");
}
//狗也有本身的行爲 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo2
{
public static void main(String[] args)
{
Cat c = new Cat();
c.eat();
c.catchMouse();
Dog d = new Dog();
d.eat();
d.lookHome();
//使用多態調用方法
Animal a = new Dog();
a.eat();
a.lookHome();
}
}
多態的弊端:
把一個子類類型提高成了父類的類型,那麼在程序編譯的過程當中,編譯不會考慮具體是哪一個子類類型,而只會根據當前的父類類型去操做,經過父類的引用在調用方法的時候,只會去父類類型所屬的類中找有沒有這些成員,
若是有編譯經過,若是沒有編譯失敗。
多態弊端總結:
在使用多態技術的時候,程序在編譯的時候,使用多態調用成員(變量和函數),要求被調用的成員在父類中必定要存在,若是父類中沒有編譯就會失敗。(不能使用子類特有功能或者屬性)
注意:只要有多態的地方,必定發生類型的提高(確定是把子類對象使用父類類型在表示)。
在使用多態時,存在一個弊端:不能使用子類中特有的功能(函數)。
若是在多態中,必需要使用子類特有的功能,須要在多態操做時進行類型的轉換。
複習下以前學習過的類型轉換:
自動類型提高 例: byte b=10; int num=b;
強制類型轉換 例: double d=3.14; int n=(int)d;
Animal an = new Dog();
Animal是父類類型(父引用類型) an是父引用 new Dog()是子類對象
在以上代碼中,已存在了類型的轉換(向上轉型):父類 父引用=new子類();
若是必定要在父類引用中使用子類對象特有的功能,就須要向下轉型(大類型向下轉換):
說明:子類對象中特定的功能只能子類對象本身調用。
若是已經發生多態現象,可是咱們還想調用子類的特有屬性或者行爲,這時須要使用強制類型轉換,把當前父類類型轉成具體的子類類型。
多態中的類型轉換有兩種:
1)向上轉型(隱式的類型提高) 父引用指向子類對象 例:Animal an = new Dog();
2)向下轉型(強制類型轉換或者把父類類型轉成子類類型) 把父引用強制轉爲子類引用 例:Dog d=(Dog) an;
強制類型轉換格式:子類類型 子類引用名=(子類類型)父類引用名;
多態類型轉換的代碼體現:
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("貓吃魚");
}
//貓有本身的特有行爲 抓老鼠
void catchMouse()
{
System.out.println("貓抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨頭");
}
//狗也有本身的行爲 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
Cat c = new Cat();
c.eat();
c.catchMouse();
Dog d = new Dog();
d.eat();
d.lookHome();
//使用多態調用方法
Animal a = new Dog();
a.eat();
/*
若是已經發生多態現象,可是咱們還想調用子類的特有屬性或者行爲,這時須要使用
強制類型轉換,把當前父類類型轉成具體的子類類型。
在多態中的類型轉換問題:
一、隱式的類型提高。只要有多態就會發生類型提高(向上轉型)。
二、把父類類型轉成子類類型(強制類型轉換,向下轉型)。
何時使用向下轉型:
只要在程序中咱們須要使用子類的特有屬性或行爲(方法、函數)的時候,纔會使用向下轉型。
*/
//(Dog)(a).lookHome();
Dog dd = (Dog)a; //多態的轉型
dd.lookHome();
注意:
1)何時使用向下轉型:
只要在程序中咱們須要使用子類的特有屬性或行爲(方法、函數)的時候,纔會使用向下轉型。
在多態類型轉換時常常會發生一個異常錯誤:ClassCastException(類型轉換異常)。
多態類型轉換常見異常代碼演示:
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("貓吃魚");
}
//貓有本身的特有行爲 抓老鼠
void catchMouse()
{
System.out.println("貓抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨頭");
}
//狗也有本身的行爲 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
Animal a= new Cat();
demo(a);//傳遞的是貓的對象
}
public static void demo( Animal a )
{
a.eat();
//把Animal類型的a轉成 Dog類的d
Dog d=(Dog)a;//將傳遞過來的貓的對象強制轉換爲狗是不能夠的,會發生轉換異常
/*
向下轉型有風險,使用需謹慎。
在Java中要使用向下轉型,必須先作類型的判斷,而後在轉型
Java中的類型判斷 須要使用關鍵字 instanceof
格式:
被轉的引用變量名 instanceof 被轉成的類型
若是引用變量所在的那個對象 和被轉成的類型一致,這個表達式返回的是true,不然是false
在多態中使用轉型的時候,必定要判斷,防止類型轉換異常的發生:
若是在程序發生ClassCastException,必定是把不是這種類型的對象轉成了這種類型。
*/
if( a instanceof Dog )
{
Dog d = (Dog)a;
d.lookHome();
}
else if( a instanceof Cat )
{
Cat c = (Cat)a;
c.catchMouse();
}
}
}
向下轉型有風險,使用需謹慎。在Java中要使用向下轉型,必須先作類型的判斷,而後在轉型Java中的類型判斷 須要使用關鍵字 instanceof。
格式:
被轉的引用變量名 instanceof 被轉成的類型
若是引用變量所在的那個對象 和被轉成的類型一致,這個表達式返回的是true,不然是false。
在多態中使用轉型的時候,必定要判斷,防止類型轉換異常的發生:
若是在程序發生ClassCastException,必定是把不是這種類型的對象轉成了這種類型。
總結:
只要有多態,就會有類型的轉換。
把子類對象賦值給父類的引用,這時發生了向上的轉型(隱式類型轉換)。
若是咱們須要使用子類的特有行爲或屬性,這時必須向下轉型,須要把父類的引用轉成具體所指的那個對象的類型。
在向下轉型的時候必定要作類型的判斷,防止ClassCastException異常的發生。
判斷格式:
if( 父類引用變量名 instanceOf 子類對象所屬的類名 )
{
進行轉換。
}
學習多態中的成員使用規律:須要掌握的是以多態形式使用成員,須要考慮程序的編譯和運行2個階段。
3.6.1多態調用成員變量
在使用多態時,子父類中存在相同的成員變量:
結論:
在多態中,使用父類的引用(f)訪問成員變量,子父類中存在如出一轍的成員變量時:
1)代碼在編譯的時期(javac 源文件):須要查看父類中有沒有這個成員變量,若是有,編譯經過,沒有編譯失敗。
2)編譯經過的前提下,運行(java 類文件)時期:這時操做的依然是父類中的成員變量。
記住:多態時,子父類中存在如出一轍的成員變量時,引用變量,編譯運行都看引用類(父類)中的變量。(編譯時以等號左邊做爲參考,運行時也是以等號左邊做爲參考)
注意:若是發生多態時,只要是在其餘類中使用成員變量,那麼這個成員變量必須存在於父類中,不管子類中是否含有對應的成員變量,若是父類中沒有成員變量,那麼編譯和運行都不會經過,和子類中是否含有成員變量沒有關係。
3.6.2多態調用非靜態成員函數
在多態中,使用父類引用調用成員函數的時候,通常函數都複寫存在。
在使用多態時,子父類中存在如出一轍的成員方法時:
結論:
在多態中,使用父類的引用(f)調用函數的時候,子父類中存在如出一轍的成員方法時:
1)代碼在編譯的時期(javac 源文件):要看父類中有沒有這個函數,有,編譯經過,沒有編譯失敗。
2)編譯經過的前提下,運行(java 類文件)時期:運行的是子類中複寫父類以後的那個函數。若是沒有複寫,運行的確定仍是父類的函數。
記住:多態時,子父類中存在如出一轍的成員方法時,編譯時以等號左邊做爲參考,運行時是以等號右邊做爲參考。
3.6.3多態調用靜態成員函數
在使用多態時,子父類中存在如出一轍的靜態方法時:
靜態的成員是隨着類的加載而存在,和建立的對象沒有任何關係,只跟類有關係。在java中,使用對象去調用靜態成員,底層JVM仍是會以對象所屬的類型(類)去調用靜態成員。所以使用多態調用靜態函數的時候,編譯運行都要看父類中的函數。
結論:
在使用多態時,子父類中存在如出一轍的靜態方法時:
編譯時期是以等號左邊做爲參考,運行時期也是以等號左邊做爲參考。
也就是說,在使用多態時,子父類中存在如出一轍的靜態方法時,與子類是否存在靜態函數沒有關係,只和父類中有關係。
總結多態中成員使用規律:
成員變量和靜態成員函數,編譯運行都看左邊(父類中的)。只有非靜態成員函數,編譯看父類,運行看子類對象。
練習的目的:須要掌握多態中,自始至終只有子類對象存在,沒有父類的對象,而且把子類對象交給父類的引用在使用。
練習黑旋風和黑旋風老爸的故事。
黑旋風:
講課(){}
看電影(){}
黑旋風老爸:
講課(){}
釣魚(){}
//多態練習
class Dad
{
void teach()
{
System.out.println("黑旋風老爸講論語");
}
void fish()
{
System.out.println("黑旋風老爸釣魚");
}
}
class Hxf extends Dad
{
void teach()
{
System.out.println("黑旋風講Java");
}
void lookFilm()
{
System.out.println("黑旋風在看速7");
}
}
class DuoTaiTest
{
public static void main(String[] args)
{
/*
Hxf hxf = new Hxf ();
hxf .teach();
hxf .fish();
*/
Dad dad = new Hxf (); //多態
dad .teach();
dad .fish();
//dad.lookFilm();//編譯報錯,由於父類中沒有lookFilm這個函數
/*
如今我就想調用子類中特有的函數,怎麼辦?
咱們這裏須要使用強制類型轉換,將父類轉換爲子類
*/
Hxf hxf = (Hxf)dad; //黑旋風卸妝 向下轉型
hxf .lookFilm();
}
}
在學習面向對象編程思想,遇到需求時,先去找有沒有解決問題的功能存在。這些解決問題的功能一般是封裝在類中(功能類),使用這些功能類基本能夠解決開發中大部分的問題(例:折半查找、選擇排序等)。
問題:這些解決問題的功能類都在哪裏?
在java設計時,已經提供了不少解決問題的封裝類。這些解決問題的封裝類,咱們統稱爲:API
在開發中,只要去相應的包(文件夾)中去找對應的封裝類就能夠解決問題。
API:application programming interface。應用程序接口。咱們這裏一般把api簡稱爲幫助文檔。
想要使用java提供的解決各類問題的API,就須要先學會如何查閱API文檔。
使用"索引"查找相應的信息
以下圖操做,點擊選項,選擇顯示標籤
點擊完顯示標籤後,會出現以下圖所示界面:
而後點擊索引,會出現以下圖所示的界面:
在查找框裏輸入要查找的類或者接口便可。
在搜索框裏輸入要查找的類,選中並雙擊或者回車。
在開發中,除了查閱API之外,還常常會查看JDK的源代碼,幫助解決開發中的問題。
在安裝JDK時,隨着JDK版本的安裝,在JDK安裝目錄也存在一個當前版本的JDK源碼文件
查看源代碼的步驟:(前提:須要知道要查找的功能類屬於哪一個包)
在全部類中的構造函數中有個隱式的super語句,找父類。若是一個類沒有顯示指定它的父類,那麼這個類的父類默認就是Object類。Object類的構造函數中是沒有隱式的super的。
經過API的查閱,能夠獲得:
一、Object是java提供的功能類(API中的類)和開發人員本身書寫的類的父類;
二、由於全部的類都繼承了Object類,因此繼承了Object類的子類可使用Ojbect類中的功能(函數);
疑問:既然本身定義的類也要繼承Object類,那爲何在代碼中沒有顯式書寫繼承Object?
Object類屬於java.lang包下。而java.lang包會被JVM在運行時自動加載,繼承了Object的子類也不須要顯式書寫,JVM會自動爲書寫的類添加繼承。
Object類中的經常使用函數:
equals 方法 toString 方法
需求:判斷學生是否爲同齡人
/*
判斷兩個學生是不是同齡人
*/
//定義一個學生類
class Student
{
//屬性
String name;
int age;
//定義構造函數給屬性初始化值
Student(String name,int age)
{
this.name=name;
this.age=age;
}
/*
定義一個函數根據外界傳遞過來的值比較年齡是否相等,
使用return關鍵字將比較的結果返回給調用者
Student a=new Student("技導",18)
由於compareAge函數是s對象調用的,因此在這個函數中的隱式變量this
記錄着s對象的堆內存地址名
*/
public boolean compareAge(Student a)
{
/*
this.age表示黑旋風的年齡17
a.age表示技導的年齡18
*/
return this.age==a.age;
}
}
class ObjectDemo1
{
public static void main(String[] args)
{
/*
建立兩個對象
下面的兩個對象表示在對空間中開闢兩個不一樣的空間
一個空間叫作s,另外一個空間叫作s1
*/
Student s=new Student("黑旋風",17);
Student s1=new Student("技導",17);
//使用黑旋風的對象s調用compareAge函數
//使用flag來接受返回回來的值
boolean flag=s.compareAge(s1);
/*
若是返回回來的值是true,說明是同齡人
若是返回回來的值是false,說明不是同齡人
*/
if(flag==true)
{
System.out.println("是同齡人");
}else
{
System.out.println("不是同齡人");
}
}
}
使用以上方式能夠解決問題。
面向對象:遇到需求時,先去找有沒有存在已經解決問題的功能(功能是封裝在類中)。
有,就直接使用封裝了功能的功能類解決問題。
以上需求中,是須要解決判斷是否爲同齡人的功能。(其實就是一個判斷是否相等的功能)
首先,去找java API中是否有比較功能。
問題:Student類中不具有比較功能,可是,Student類繼承了Object類,因此能夠去Object類中找是否存在解決問題的功能
Object類中的功能:
使用Object類中的eqauls函數解決需求中的問題:
以上程序運行結果不正確。
分析:爲何使用Object類中的equals功能會存在結果不正確呢?
查看Object類中的equals功能的源代碼
上述代碼中的Object類中的this 表示的是調用這個equals函數的那個對象,obj是調用equals方法時傳遞進來的那個對象,而this中保存的是對象的內存地址,obj中接受的也是傳遞進來的那個對象內存地址。因此這裏使用== ,實際上是在比較2個對象的內存地址是否相等。(就是堆內存中的地址)
結論:Object類中的equals方法中,比較的是堆中的地址是否相等
而咱們真正在開發中要比較2個對象是否相等,不該該去比較內存地址,而應該比較的是對象中的數據是否相同。單單使用Object類中的equals功能,並不能直接解決咱們需求中的問題。遇到這種狀況,在開發中的作法是:重寫Object類中的equals函數,所以全部的程序中都應該複寫Object類中的equals。
/*
判斷兩個學生是不是同齡人
*/
//定義一個學生類
class Student
{
//屬性
String name;
int age;
//定義構造函數給屬性初始化值
Student(String name,int age)
{
this.name=name;
this.age=age;
}
//重寫Object類中的equals函數(重寫:和父類中的方法如出一轍)
public boolean equals(Object obj) {
/*
由於這裏發生了多態,因此不能使用父類的對象obj調用父類中不存在的屬性age,
因此會報錯。因此咱們應該使用子類Student對象來調用子類中的屬性age
而這裏obj是父類對象,咱們須要使用向下轉型將父類對象obj轉換爲子類對象
由於發生向下類型轉換,爲了防止發生轉換異常,因此咱們要判斷子類對象類型
*/
Student s=null;
if(obj instanceof Student)
{
s=(Student)obj;
}
return this.age==s.age;
}
}
class ObjectDemo2
{
public static void main(String[] args)
{
/*
建立兩個對象
下面的兩個對象表示在對空間中開闢兩個不一樣的空間
一個空間叫作s,另外一個空間叫作s1
*/
Student s=new Student("黑旋風",17);
Student s1=new Student("技導",17);
//使用黑旋風的對象s調用compareAge函數
//使用flag來接受返回回來的值
//boolean flag=s.compareAge(s1);
/*
public boolean equals(Object obj) {
這裏的this記錄着調用這個方法的對象s的堆中內存地址名
obj表示傳遞進來的參數對象s1,Object obj=new Student("技導",17);這裏發生多態
obj裏面存放的也是s1中的堆中內存地址
s和s1的堆中內存地址名不一樣
return (this == obj);
}
public boolean compareAge(Student a)
{
return this.age==a.age;
}
*/
boolean flag=s.equals(s1);
System.out.println(flag);
/*
若是返回回來的值是true,說明是同齡人
若是返回回來的值是false,說明不是同齡人
*/
if(flag==true)
{
System.out.println("是同齡人");
}else
{
System.out.println("不是同齡人");
}
}
}
總結:
關係運算中的==和equals的區別:
需求:輸出Student類的具體信息,也就是根據輸出Student類的對象來輸出Student的具體名字和姓名。
以上程序的運行結果,不符合需求。要求是想要輸出學生類中的具體信息,好比經過打印對象的名字stu,咱們但願打印出具體的stu對象所擁有的名字和年齡,而打印一串地址名在開發中沒有什麼太大意義。
問題:爲何輸出stu時,顯示的結果:Student@7ea06d25?爲何輸出的是一個引用地址而不是咱們想要的對象的屬性的值呢?咱們應該怎麼作才能打印出屬性的值而不是打印一串地址的值呢?
這裏咱們須要藉助Object類中的toString方法來解決,toString是一個方法,它須要對象來調用,toString的意思是將調用它的對象轉換成字符串形式。
咱們在打印對象的時候也能夠按照以下方法去作:
System.out.println(stu.toString());打印的結果和咱們寫System.out.println(stu);是同樣的。
在上述打印語句中println()的方法中打印對象stu的時候,若是不加 .toString()方法,在println()的方法中默認也是使用對象stu調用toString()方法,因此在開發中寫與不寫toString()方法是同樣的。
根據以上分析咱們想要創建自定義對象的表現形式,咱們須要覆蓋超類(全部類的父類)中的toString()方法就能夠了。
在Student類中能夠重寫toString方法,讓程序輸出學生類的具體信息,代碼實現以下:
小結:
在開發中,若是子類繼承父類時,父類中已經存在瞭解決問題的功能,可是父類中的功能並不能知足子類的需求,這時,子類就須要重寫(覆蓋)父類中的方法。