JavaSE——面向对象高级(继承、final关键字、抽象类与接口、多态、Object类、内部类、包装类、可变参数)
第3節 面向對象高級
一、繼承
1.1 概述和使用
繼承是java面向對象編程技術的一塊基石,因為它允許創建分等級層次的類。繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。
格式如下所述:
class 父類 { }class 子類 extends 父類 { }繼承的限制:Java中只有單繼承,多重繼承,沒有多繼承。
看下面的例子:
package com.kaikeba.objectoriented.senior;public class inherit2 {public static void main(String[] args) {Student2 s = new Student2();s.setName("張三");s.setAge(18);s.say();} }class Person2 {private String name;private int age;public String sex;public Person2() {}public Person2(String name, int age) {this.name = name;this.age = 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 say() {System.out.println("我是:"+name+",我今年"+age+"歲了");} }class Student2 extends Person2 { }結果如下:
我是:張三,我今年18歲了子類繼承父類后,可以使用父類的屬性和方法(public和protected修飾的)。
1.2 子類實例化內存分析
Student s = new Student()內部的創建過程:
1、先查看Student是否有父,若有,先在堆內存中創建一個父對象的空間,再創建一個Student對象空間,同時super指向父類對象,可以找到父對象。
2、s.setName("張三");若發現Student類中沒有setName方法,就會使用Person的setName,并將name賦值到Person對象中,若有就用自己的,這個后面說。
補充: 父類中只有用public和protected修飾符修飾的屬性和方法才可以被子類使用,比如Student對象無法使用Person私有的屬性,但是可以通過set和get來操作屬性。
1.3 super關鍵字
this表示當前對象:
????- this.屬性 區別成員變量和局部變量
????- this.() 調用本類的某個方法
????- this() 表示調用本類構造方法,只能用在構造方法的第一行語句。
????- this關鍵字只能出現在非static修飾的代碼中
super表示父類對象:
????- super.屬性 表示父類對象中的成員變量
????- super.方法()表示父類對象中定義的方法
????- super() 表示調用父類構造方法
????????可以指定參數,比如super("Tom",23);
????????任何一個構造方法的第一行默認是super();
????????可以寫上,如果未寫,會隱式調用super();
????- super()只能在構造方法的第一行使用。
????- this()和super()都只能在構造的第一行出現,所以只能選擇其一。寫了this()就不會隱式調用super()。
super調用父類構造方法,必須滿足父類中有對應的構造方法,見下述代碼:
package com.kaikeba.objectoriented.senior;public class inherit2 {public static void main(String[] args) {Student2 s = new Student2();s.say();} }class Person2 {private String name;private int age;public String sex;public Person2() {}public Person2(String name, int age) {this.name = name;this.age = age;}public void say() {System.out.println("我是:"+name+",我今年"+age+"歲了,性別:"+sex);} }class Student2 extends Person2 {public Student2() {//super調用了父類的全參構造方法super("無名稱", 1);super.sex = "男";} }結果如下:
我是:無名稱,我今年1歲了,性別:男1.4 重寫(Override)與重載(Overload)
子類中可能需要對父類的一些方法進行修改,這就是重寫的概念:
重寫(override)規則:
-
1.參數列表必須完全與被重寫方法的相同;
-
2.返回類型必須完全與被重寫方法的返回類型相同;
-
3.訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:如果父類的一個方法被聲明為public,那么在子類中重寫該方法就不能聲明為protected;
-
4.父類的成員方法只能被它的子類重寫。
-
5.聲明為static和private的方法不能被重寫,但是能夠被再次聲明。
面試中可能會考察重載(Overload)與重寫(Override)的區別:
1. 發生的位置:
????1. 重載:一個類中
????2. 重寫:子類對父類方法進行重寫
2. 參數列表規則:
????1. 重載:必須不同(長度,類型,順序不同均可)
????2. 重寫:必須相同
3. 返回值類型:
????1. 重載:與返回值類型無關
????2. 重寫:返回值類型必須一致
4. 訪問權限:
????1. 重載:與訪問權限無關
????2. 重寫:子類的方法權限必須高于等于父類的方法權限
5. 異常處理:
????1. 重載:與異常無關
????2. 重寫:異常范圍可以更小,但是不能拋出新的異常
二、final關鍵字
????變量成為常量,無法對其再次進行賦值;
????final修飾的局部變量只能賦值一次(可以先聲明后賦值);
????final修飾的成員屬性,必須在聲明時賦值。
public class finalkeyword {final int a = 10; //成員屬性public static void main(String[] args) {final int a = 10; //局部變量} }????全局常量(public static final)
????????常量的命名規則:
????????????由一個或多個單詞組成,單詞與單詞之間必須使用下劃線隔開,單詞中所有字母大寫,例如:SQL_INSERT。
????final修飾的類,不可以被繼承。
????final修飾的方法,不可以被子類重寫。
三、抽象類
3.1 概念
抽象類必須使用abstract class聲明;
格式:
abstract class 類名{ //抽象類 }3.2 抽象方法
只聲明而未實現的方法稱為抽象方法(未實現指的是:沒有"{}"方法體 ),抽象方法必須使用abstract關鍵字聲明。
格式:
abstract class 類名 { //抽象類public abstract void 方法名(); //抽象方法,只聲明而未實現 }一個抽象類中可以沒有抽象方法,抽象方法必須寫在抽象類或者接口中。
3.3 幾個原則
抽象類本身是不能直接進行實例化操作的,即:不能直接使用關鍵字new完成;
一個抽象類必須被子類所繼承,被繼承的子類(如果不是抽象類)則必須覆寫抽象類中的全部抽象方法。
3.4 常見問題
????不能,因為final修飾的類是不能被子類繼承的,而抽象類必須有子類才有意義,所以不能。
????能有構造方法,而且子類對象實例化的時候的流程與普通類的繼承是一樣的,都是要先調用父類中的構造方法(默認是無參的),之后再調用子類自己的構造方法。
????
3.5 抽象類與普通類的區別
抽象類必須用public或protected修飾(如果為private修飾,那么子類無法繼承,也就無法實現其抽象方法),默認缺省為public。
抽象類不可以使用new關鍵字創建對象,但是在子類創建對象時,抽象父類也會被JVM實例化。
如果一個子類繼承抽象類,那么必須實現其所有的抽象方法。如果有未實現的抽象方法,那么子類也必須定義為abstract類。
四、接口
4.1 概念
如果一個類中全部方法都是抽象方法,全部屬性都是全局常量,那么此時就可以將這個類定義成一個接口。定義格式:
interface 接口名稱{全局常量;抽象方法; }接口的抽象程度更高,后續項目中使用的也比抽象類更多一些。
4.2 面向接口編程思想
這種思想是定義(規范,約束)與實現(名實分離原則)的分離。從整體的角度先構思好接口,然后再去具體實現它。優點:
降低程序的耦合性;
易于程序的擴展;
有利于程序的維護。
4.3 全局常量和抽象方法的簡寫
因為接口本身都是由全局常量和抽象方法組成的,所以接口中的成員定義可以簡寫:4.4 接口的 實現implements 與 繼承extends
接口可以多實現: 格式:class 子類 implements 父接口1,父接口2...{} 以上的代碼稱為接口的實現。那么如果一個類即要實現接口,又要繼承抽象類的話,則按照以下的格式編寫即可:class 子類 extends 父類 implements 父接口1,父接口2...{} 接口因為都是抽象部分, 不存在具體的實現, 所以允許多繼承,例如:interface C extends A,B{}實現后的接口是一個類,繼承后的接口還是一個接口。
注意: 如果一個接口想要使用,必須依靠子類。子類(如果不是抽象類的話)要實現接口中的所有抽象方法。
4.5 接口和抽象類的區別
抽象類要被子類繼承,接口要被類實現;
接口只能聲明抽象方法,抽象類中可以聲明抽象方法,也可以寫非抽象方法;
接口里定義的變量只能是公共的靜態的常量,抽象類中的變量是普通變量;
抽象類使用繼承來使用,無法多繼承;接口使用實現來使用,可以多實現;
抽象類中可以包含static方法,但是接口中不允許(靜態方法不能被子類重寫,因此接口不能聲明靜態方法) ;
接口不能有構造方法,但是抽象類可以有。
五、多態
5.1 概念
多態就是對象的多種表現形式(多種體現形態)。5.2 多態的體現
對象的多態性 :在類中有子類和父類之分,子類就是父類的一種形態,對象多態性就從此而來。
方法的重載和重寫也是多態的一種,不過是方法的多態性 :
???? 重載:一個類中方法的多態性體現
???? 重寫:子父類中方法的多態性體現
5.3 多態的使用:對象的類型轉換
類似于基本數據類型的轉換:
向上轉型 :將子類實例變為父類實例
???? 格式:父類 父類對象名 = 子類實例 ;
向下轉型 :將父類實例變為子類實例
???? 格式:子類 子類對象 = (子類) 父類實例 ;
package com.kaikeba.objectoriented.senior;public class polymorphicDemo {/*** 父類引用指向子類對象* @param args*/public static void main(String[] args) { // Person5 p = null; // Student5 s = new Student5(); // p = s; //s是p的一種形態 // p.say();Student5 a = new Student5();Nurse b = new Nurse();Person5 p1 = a;Person5 p2 = b;p1.say();p2.say();// Student5 a2 = (Student5) p1;Student5 a3 = (Student5) p2;a3.say();} }Student實例可以向上轉型為父類Person實例,Person實例也可以向下轉型回Student實例,但是不能將Nurse實例轉為Student實例。
5.4 instanceof
作用:
????判斷某個對象是否是指定類的實例,則可以使用instanceof關鍵字。
格式:
????實例化對象 instanceof 類 //此操作返回boolean類型的數據
package com.kaikeba.objectoriented.senior;public class instanceofDemo {public static void main(String[] args) {Nurse6 n = new Nurse6();say(n);Student6 s = new Student6();say(s);}public static void say(Person6 p) {//如何判斷傳入的對象是此類型的哪種形態(哪個子類的對象)if(p instanceof Student6) {Student6 s = (Student6) p;s.say();}else {System.out.println("必須傳入學生形態,才可以執行");}} }結果如下:
必須傳入學生形態,才可以執行 鋤禾日當午六、Object類
6.1 概念
Object類是所有類的父類(基類),如果一個類沒有明確的繼承某一個具體的類,則將默認繼承Object類。 例如我們定義了一個類: public class Person { }其實它被使用時是這樣的: public class Person extends Object { }觀察Object類的構造器:只有一個無參構造方法。
6.2 Object的多態
由于Object是所有類的父類,使用Object可以接收任意的引用數據類型,觀察下面的代碼: package com.kaikeba.objectoriented.senior;public class objectDemo {public static void main(String[] args) {String text = "123";say(text);int a = 10;say(a);}public static void say(Object o) {System.out.println(o);} }6.3 toString方法
Object類下有一些方法:
目前只要掌握toString和equals即可,而且可以查看java API中文手冊,或者查看源碼來了解方法的具體作用。
先來看一下toString的源碼:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode()); }返回對象的字符串表示形式,默認的是包名類名@哈希碼 ,觀察如下代碼:
package com.kaikeba.objectoriented.senior;import java.util.Objects;public class Person7 {private String name;private int age;public Person7() {}public Person7(String name, int age) {this.name = name;this.age = 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;} } package com.kaikeba.objectoriented.senior;public class toStringDemo {public static void main(String[] args) {Person7 p = new Person7("張三",18);//System.out.println方法中默認調用了toString方法System.out.println(p);} }輸出的結果為:
com.kaikeba.objectoriented.senior.Person7@16e8e0a這樣對對象的描述一般不是很直觀,所以一般建議繼承的子類都要重寫toString方法,在Person7類中加入重寫方法:
@Overridepublic String toString() {return "Person7{" +"name='" + name + '\'' +", age=" + age +'}';}輸出結果就變為:
Person7{name='張三', age=18}6.4 equals方法
指示某個其他對象是否“等于”此對象。查看一下equals的源碼:
public boolean equals(Object obj) {return (this == obj); }默認的比較方式為 “==”,對于任何非空引用值x和y ,當且僅當x和y引用同一對象時,此方法返回true 。
但是這樣一般不太合理,一般而言,兩個對象的每個屬性都相同,那么我們就認為其相同,而不是非要兩個引用都指向同一個對象才相同,所以一般建議重寫Object類中的equals方法。
equals方法重寫的五個特性:(面試可能會問)
-
自反性 :對于任何非空的參考值x,x.equals(x)應該返回true;
-
對稱性 :對于任何非空的引用值x和y,x.equals(y)應該返回true當且僅當y.equals(x)返回true;
-
傳遞性 :對于任何非空引用值x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)應該也返回true;
-
一致性 :對于任何非空引用值x和y,多次調用x.equals(y)始終返回true或始終返回false,前提是未修改對象上的equals比較中使用的信息;
-
非空性 :對于任何非空的參考值x,x.equals(null)應該返回false。
我們將上述的main函數修改如下:
package com.kaikeba.objectoriented.senior;public class toStringDemo {public static void main(String[] args) {Person7 p1 = new Person7("張三",18);System.out.println(p1);Person7 p2 = new Person7("張三",18);System.out.println(p2);System.out.println(p1.equals(p2));} }結果如下所示:
Person7{name='張三', age=18} Person7{name='張三', age=18} false因為equals默認的是使用了==來比較,p1和p2雖然屬性值一樣,但是畢竟是兩個對象,地址值不一樣,所以返回false;如果想通過屬性值來比較兩個對象是否相同,可以通過重寫equals方法來實現,在Person7類中重寫如下方法:
@Overridepublic boolean equals(Object o) {//如果兩個引用指向一樣,則肯定相同if (this == o) return true;//若o為空(能到這this肯定不為空)或this和o的類型不一致,肯定不同if (o == null || getClass() != o.getClass()) return false;Person7 person7 = (Person7) o;//類型一致,比較屬性值是否一致return age == person7.age && Objects.equals(name, person7.name);}或:
public boolean equals(Object o) {if(this == o) {return true;}if(o == null) {return fasle;}if(o instanceof Person7) {Person7 p2 = (Person7) o;if(this.name.equals(p2.name) && this.age == p2.age) {return true;} }return false; }在運行main函數,結果就變為true了,這也是比較合理的:
Person7{name='張三', age=18} Person7{name='張三', age=18} true七、內部類
7.1 概念
在Java中,可以將**一個類定義在另一個類里面或者一個方法里面** ,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:????1. 成員內部類
????2. 局部內部類
????3. 匿名內部類
????4. 靜態內部類
7.2 成員內部類
成員內部類是最普通的內部類,它的定義為位于另一個類的內部,形如下面的形式: class Outer {private double x = 0;public Outer(double x) {this.x = x;}class Inner { //內部類public void say() {System.out.println("x=" + x);}} }如何使用成員內部類:
Outer o = new Outer(); //先創建外部類對象 Outer.Inner i = o.new Inner(); //通過外部類對象來new內部類對象特點:
成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。不過要注意的是,當成員內部類擁有和外部類同名的成員變量或者方法時,會發生隱藏現象,即默認情況下訪問的是成員內部類的成員。如果要訪問外部類的同名成員,需要以下面的形式進行訪問: 外部類.this.成員變量 外部類.this.成員方法看一個完整的例子:
package com.kaikeba.objectoriented.senior;public class Outer {private int x;public int getX() {return x;}public void setX(int x) {this.x = x;}class Inner {private int x = 200;public void say() {System.out.println("內部類的變量x的值:" + x);System.out.println("外部類的變量x的值:" + Outer.this.x);}} } package com.kaikeba.objectoriented.senior;public class memberInnerClass {public static void main(String[] args) {Outer o = new Outer();o.setX(100);Outer.Inner i = o.new Inner();i.say();} }結果如下:
內部類的變量x的值:200 外部類的變量x的值:1007.3 局部內部類
局部內部類是定義在一個方法或者一個作用域里面的類,它和成員內部類的區別在于局部內部類的訪問僅限于方法內或該作用域內。看個例子直觀感受一下:
package com.kaikeba.objectoriented.senior;public class localInnerClass {public static void main(String[] args) {class Person{public void say() {System.out.println("鋤禾日當午");}}Person p = new Person();p.say();} }類的優點是復用性高,但是局部內部類出了方法或者作用域就不能用了,所以它不具備較高的復用性,使用的較少,但是創建一個只使用一次的對象可以考慮使用局部內部類:
package com.kaikeba.objectoriented.senior;public class localInnerClass2 {public static void main(String[] args) {class ChildImp implements Child {@Overridepublic void say() {System.out.println("新編寫的局部內部類的say方法內部"+a);}}ChildImp c = new ChildImp();haha(c);}public static void haha(Child c) {} }注意:局部內部類就像是方法里面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的。
7.4 匿名內部類
匿名內部類由于沒有名字,所以它的創建方式有點奇怪,創建格式如下: new 父類構造器(參數列表) 實現接口() {//匿名內部類的類體部分 }在這里我們看到使用匿名內部類我們必須要繼承一個父類或者實現一個接口,當然也僅能繼承一個父類或者實現一個接口,同時它也是沒有class關鍵字,這是因為匿名內部類是直接使用new來生成一個對象的引用,當然這個引用時隱式的。
package com.kaikeba.objectoriented.senior;public class anonymousInnerClass {public static void main(String[] args) {Child c = new Child() {@Overridepublic void say() {System.out.println();}};haha(c);}public static void haha(Child c) {} }注意:
使用匿名內部類是,我們必須繼承一個類或者實現一個接口,但是兩者不可兼得,同時也只能繼承一個類或實現一個接口;
匿名內部類中是不能定義構造函數的;
匿名內部類中不能存在任何的靜態成員變量和靜態方法;
匿名內部類為局部內部類,所以局部內部類的所有限制同樣對匿名內部類生效;
匿名內部類不能是抽象的,它必須要實現繼承的類或者實現接口的所有抽象方法;
只能訪問final型的局部變量 (面試題):因為內部類會被單獨編譯成一個字節碼文件,為了保證這個單獨的文件中用到的變量,與外面的變量的值絕對是一致的,系統從規則上限制了這個變量的值不可更改。
7.5 靜態內部類
靜態內部類也是定義在另一個類里面的類,只不過在類的前面多了一個關鍵字static。
靜態內部類是不需要依賴于外部類對象的,這點和類的靜態成員屬性有點類似,并且它不能使用外部類的非static成員變量或者方法。
使用格式如下所示:
public class Test {public static void main(String[] args) {Outer.Inner inner = new Outer.Inner();} }class Outer {public Outer() {}static class Inner {public Inner() {}} }八、包裝類
8.1 概述
在java中有一個設計的原則“一切皆對象”,那么這樣一來Java中的基本的數據類型,就完全不符合這種設計思想,因為Java中的八種基本數據類型并不是引用數據類型,所以Java中為了解決這樣的問題,引入了**八種基本數據類型的包裝類** 。 以上的八種包裝類,可以將基本數據類型按照類的形式進行操作。也可以分為兩種大的類型:-
Number:Byte、Short、Integer、Long、Float、Double都是Number的子類表示是一個數字。
-
Object:Character、Boolean都是Object的直接子類。
包裝類也是類,其在內存中的表示和一般的類一樣:
8.2 裝箱和拆箱操作
以下以Integer和Float為例進行操作:
-
裝箱:將一個基本數據類型變為包裝類;
-
拆箱:講一個包裝類變為一個基本數據類型;
看如下代碼:
package com.kaikeba.objectoriented.senior;public class wrapperClass {public static void main(String[] args) {//手動裝箱JDK1.5之前Integer i = new Integer(200);//手動拆箱JDK1.5之前int a = i.intValue();//自動裝箱JDK1.5開始Integer j = 200;//自動拆箱JDK1.5開始int b = j;} } 在JDK1.5之前,如果想要裝箱,直接使用各個包裝類的構造方法即可,例如:int temp = 10; //基本數據類型Integer x = new Integer(temp); //將基本數據類型變為包裝類 在JDK1.5,Java新增了自動裝箱和自動拆箱,可以直接通過包裝類進行四則運算和自增自減操作,例如:Float f = 10.3f; //自動裝箱float x = f; //自動拆箱System.out.println(f * f); //直接利用包裝類完成System.out.println(x * x); //利用基本數據類型完成因為所有的數值型的包裝類都是Number的子類,Number的類中定義了如下的操作方法,以下的全部方法都是進行拆箱操作:
可以拆箱為不同的基本數據類型。
8.3 字符串轉換
使用包裝類還有一個很優秀的地方在于:可以將一個字符串變為指定的基本數據類型 ,此點一般在接收輸入數據上使用較多。
在Integer類中提供了以下的操作方法:
public static int parseInt(String s):將String變為int型數據
在Float類中提供了以下的操作方法:
public static float parseFloat(String s):將String變為float型數據
…
觀察如下代碼:
package com.kaikeba.objectoriented.senior;import java.util.Scanner;public class wrapperClass2 {public static void main(String[] args) {Scanner input = new Scanner(System.in);System.out.println("請輸入內容:");String text = input.nextLine();int x = Integer.parseInt(text);System.out.println(x+1);} }結果如下:
請輸入內容: 100 101nextLine接收的是String類型的輸入,如果直接+1則當成字符串來操作了,需要用parseInt轉為int類型,然后+1就是算術運算了。
九、可變參數
一個方法中定義完了參數,則在調用的時候必須傳入與其一一對應的參數,但是在JDK1.5之后提供了新的功能,可以根據需要自動傳入任意個數的參數 。
語法:返回值類型 方法名稱(數據類型...參數名稱) {//參數在方法內部,以數組的形式來接收}注意:可變參數只能出現在參數列表的最后。 package com.kaikeba.objectoriented.senior;public class variableParameters {public static void main(String[] args) {System.out.println(sum());System.out.println(sum(100));System.out.println(sum(100, 200));System.out.println(sum(100, 200, 300));System.out.println(sum(100, 200, 300, 500));}/*** int... nums,表示的是可變參數,調用時可以傳遞0-n個數字* 在方法的內部,可變參數以數組作為載體體現*/public static int sum(int... nums) {int n = 0;for(int i=0; i < nums.length; i++) {n += nums[i];}return n;} }結果如下所示:
0 100 300 600 1100總結
以上是生活随笔為你收集整理的JavaSE——面向对象高级(继承、final关键字、抽象类与接口、多态、Object类、内部类、包装类、可变参数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QT中读写.ini配置文件的操作
- 下一篇: java美元兑换,(Java实现) 美元