面向對象編程(高級) 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 類變數和類方法(static) 類變數 類變數-提出問題 提出問題的主要目的就是讓大家思考解決之道,從而引出我要講的知識點.說:有一群小孩在玩堆雪人,不時有新的小 ...
面向對象編程(高級)
筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html)
類變數和類方法(static)
類變數
類變數-提出問題
提出問題的主要目的就是讓大家思考解決之道,從而引出我要講的知識點.說:有一群小孩在玩堆雪人,不時有新的小孩加入,請問如何知道現在共有多少人在玩?,編寫程式解決。
傳統的方法來解決
使用我們現有的技術來解決這個問題,大家看看如何?
✔思路
-
在main方法中定義一個變數count
-
當一個小孩加入游戲後count++,最後個count 就記錄有多少小孩玩游戲
public class ChildGame { public static void main(String[] args) { //定義一個變數count,統計有多少小孩加入了游戲 int count = 0; Child child1 = new Child("白精"); child1.join(); count++; Child child2 = new Child("老白"); child2.join(); count++; Child child3 = new Child("老白"); child3.join(); child3.count++; System.out.println("共有" + count + "小孩加入了游戲..."); } } class Child {//類 private String name; public Child(String name) { this.name = name; } public void join() { System.out.println(name + "加入了游戲.."); } }
✔問題分析:
- count是一個獨立於對象,很尷尬
- 以後我們訪問count很麻煩,沒有使用到OOP
- 3.因此,我們引出類變數/靜態變數
類變數快速入門
思考:如果,設計一個int count表示總人數,我們在創建一個小孩時,就把count加1,並且 count是所有對象共用的就ok了!,我們使用類變數來解決
public class ChildGame {
public static void main(String[] args) {
Child child1 = new Child("白精");
child1.join();
child1.count++;
Child child2 = new Child("老白");
child2.join();
child2.count++;
Child child3 = new Child("老白");
child3.join();
child3.count++;
//......
System.out.println("共有" + child1.count + "小孩加入了游戲...");
System.out.println(child1.count);//3
System.out.println(child2.count);//3
System.out.println(child3.count);//3
System.out.println(Child.count);//3
}
}
class Child {//類
private String name;
//定義一個變數 count,是一個類變數(靜態變數)static靜態
//該變數最大的特點就是會被Child類的所有的對象實例共用
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了游戲..");
}
}
其中child1,child2以及child3中的count指向相同空間,被共用,為同一值
jdk8以及之前,count(也就是靜態域)在方法區中。jdk8之後,jdk存放在堆中
有些書說在方法區... jdk版本有關係,記住一點: static變數是對象共用
不管static變數在哪裡,共識:
- static變數是同一個類所有對象共用
- static類變數,在類載入的時候就生成了.
什麼是類變數
類變數也叫靜態變數/靜態屬性,是該類的所有對象共用的變數,任何一個該類的對象去訪問它時,取到的都是相同的值,同樣任何一個該類的對象去修改它時,修改的也是同一個變數。這個從前面的圖也可看出來。
如何定義類變數
定義語法:
訪問修飾符 static 數據類型 變數名;[推薦]
static 訪問修飾符 數據類型 變數名;
class A{
public static string name = "abc";
static public int totalNum = 100;
}
如何訪問類變數
類名.類變數名
或者對象名.類變數名【靜態變數的訪問健飾符的訪問許可權和範圍和普通屬性是一樣的。】
推薦使用:類名.類變數名;
public class Test {
public static void main(String[] args) {
//類名.類變數名
// 說明:類變數是隨著類的載入而創建,所以即使沒有創建對象實例也可以訪問
System.out.println(A.name);
A a = new A();
System.out.println("a.name=" +a.name);
}
}
class A {
//類變數
public static String name = "韓順平";
}
類變數使用註意事項和細節討論
-
什麼時候需要用類變數
當我們需要讓某個類的所有對象都共用一個變數時,就可以考慮使用類變數(靜態變數):比如:定義學生類,統計所有學生共交多少錢。Student (name, fee) -
類變數與實例變數(普通屬性)區別
類變數是該類的所有對象共用的,而實例變數是每個對象獨享的。
-
加上static稱為類變數或靜態變數,否則稱為 實例變數/普通變數/非靜態變數
-
類變數可以通過類名.類變數名或者對象名.類變數名來訪問,但java設計者推薦我們使用 類名.類變數名 方式訪問。【前提是 滿足訪問修飾符的訪問許可權和範圍】
-
實例變數不能通過類名.類變數名方式訪問。
-
類變數是在類載入時就初始化了,也就是說,即使你沒有助建家,只安尖加鄆,就可以使用類變數了。
-
類變數的生命周期是隨類的載入開始,隨著類消亡而銷毀。
類方法
類方法基本介紹
類方法也叫靜態方法。
形式如下:
訪問修飾符 static 數據返回類型 方法名(){ }【推薦】
static 訪問修飾符 數據返回類型 方法名(){ }
類方法的調用:
使用方式:類名.類方法名 或者 對象名.類方法名【前提是滿足訪問修飾符的訪問許可權和範圍】
類方法應用案例
請大家看一個靜態方式小案例。(統計學費總和)
public class Test {
public static void main(String[] args) {
//創建2個學生對象,叫學費
Stu tom = new Stu("tom");
tom.payFee(100);
Stu mary = new Stu("mary");
mary.payFee(200);
//輸出當前收到的總學費
Stu.showFee();//300
}
}
class Stu {
private String name;//普通成員
// 定義一個靜態變數,來累積學生的學費
private static double fee = 0;
public Stu(String name) {
this.name = name;
}
//說明
//1.當方法使用了static修飾後,該方法就是靜態方法
//2.靜態方法就可以訪問靜態屬性/變數
public static void payFee(double fee) {
Stu.fee += fee;//累積到
}
public static void showFee() {
System.out.println("總學費有:" + Stu.fee);
}
}
類方法經典的使用場景
當方法中不涉及到任何和對象相關的成員,則可以將方法設計成靜態方法,提高開發效率。(不需要額外創建對象)
比如:工具類中的方法utils
Math類、Arrays類、Collections集合類看下源碼:
小結
在程式員實際開發,往往會將一些通用的方法,設計成靜態方法,這樣我們不需要創建對象就可以使用了,比如列印一維數組,冒泡排序,完成某個計算任務等..
舉例
public class Test {
public static void main(String[] args) {
System.out.println(MyTools.calSum(10,30));
}
}
class MyTools {
//開發自己的工具類時,可以將方法做成靜態的,方便調用class MyTools {
//求出兩個數的和
public static double calSum(double n1, double n2) {
return n1 + n2;
}
}
類方法使用註意事項和細節討論
- 類方法和普通方法都是隨著類的載入而載入,將結構信息存儲在方法區:類方法中無this的參數
普通方法中隱含著this的參數 - 類方法可以通過類名調用,也可以通過對象名調用。
- 普通方法和對象有關,需要通過對象名調用,比如對象名.方法名(參數),不能通過類名調
用。 - 類方法中不允許使用和對象有關的關鍵字,比如this和super。普通方法(成員方法)可以。[舉例]
- 類方法(靜態方法)中只能訪問靜態變數或靜態方法。【如何理解】
- 普通成員方法,既可以訪問普通變數(方法),也可以訪問靜態變數(方法)。
小結:靜態方法,只能訪問靜態的成員,非靜態的方法,可以訪問靜態成員和非靜態成員(必須遵守訪問許可權)
理解main方法語法
●深入理解main方法
解釋main方法的形式:public static void main(String[] args){}
-
main方法時虛擬機調用
-
java虛擬機需要調用類的main()方法,所以該方法的訪問許可權必須是public
-
java虛擬機在執行main()方法時不必創建對象,所以該方法必須是static
-
該方法接收String類型的數組參數,該數組中保存執行java命令時傳遞給所運行的類的參數,案例演示,接收參數.
-
java 執行的程式 參數1 參數2 參數3
特別提示:
-
在main()方法中,我們可以直接調用main方法所在類的靜態方法或靜態屬性。
-
但是,不能直接訪問該類中的非靜態成員,必須創建該類的一個實例對象後,才能通過這個對象去訪問類中的非靜態成員
代碼塊
●基本介紹
代碼化塊又稱為初始化塊,屬於類中的成員[即 是類的一部分],類似於方法,將邏輯語句封裝在方法體中,通過{}包圍起來。
但和方法不同,沒有方法名,沒有返回,沒有參數,只有方法體,而且不用通過對象或類顯式調用,而是載入類時,或創建對象時隱式調用。
●基本語法
[修飾符]{
代碼
};
註意:
- 修飾符可選,要寫的話,也只能寫static
- 代碼塊分為兩類,使用static修飾的叫靜態代碼塊,沒有static修飾的,叫普通代碼塊。
- 邏輯語句可以為任何邏輯語句(輸入、輸出、方法調用、迴圈、判斷等)
- ;號可以寫上,也可以省略。
實例
package com.hspedu.p386;
public class codeBlock01 {
public static void main(String[] args) {
Movie movie = new Movie("你好,李煥英");
}
}
class Movie {
private String name;
private double price;
private String director;
//3個構造器-》重載
//老韓解讀
//(1)下麵的三個構造器都有相同的語句
//(2)這樣代碼看起來比較冗餘
//(3)這時我們可以把相同的語句,放入到一個代碼塊中,即可
//(4)這樣當我們不管調用哪個構造器,創建對象,都會先調用代碼塊的內容
//(5)代碼塊調用的順序優先於構造器。。
{
System.out.println("電影屏幕打開...");
System.out.println("廣告開始...");
System.out.println("電影正是開始...");
}
public Movie(String name) {
System.out.println("Hovie(Strihg name)被調用。.。");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
this.name = name;
this.price = price;
this.director = director;
}
}
代碼塊使用註意事項和細節討論
-
static代碼塊也叫靜態代碼塊,作用就是對類進行初始化,而且它隨著類的載入而執行,並且只會執行一次。如果是普通代碼塊,每創建一個對象,就執行。
-
類什麼時候被載入
①創建對象實例時(new)
②創建子類對象實例,父類也會被載入
③使用類的靜態成員時(靜態屬性,靜態方法)案例演示: A類 extends B類 的靜態塊
-
普通的代碼塊,在創建對象實例時,會被隱式的調用。被創建一次,就會調用一次。
如果只是使用類的靜態成員時,普通代碼塊並不會執行。
小結:
-
static代碼塊是類載入時,執行,只會執行一次
-
普通代碼塊是在創建對象時調用的,創建一次,調用一次
-
類載入的3種情況,需要記住.
public class Test { public static void main(String[] args) { // new A(); // new A(); // // System.out.println("------------------"); // new B(); // System.out.println("------------------"); // System.out.println(A.a); System.out.println(B.b); } } class A { public static int a=10; static {//只在載入類時運行 System.out.println("111111"); } {//普通代碼塊,在new對象時,被調用,而且是每創建一個對象,就調用一次 //可以這樣簡單的,理解普通代碼塊是構造器的補充 System.out.println("普通"); } } class B extends A{ static { System.out.println("2222222"); } }
- 創建一個對象時,在一個類調用順序是:(重點,難點)∶
①調用靜態代碼塊和靜態屬性初始化(註意:靜態代碼塊和靜態屬性初始化調用的優先順序一樣,如果有多個靜態代碼塊和多個靜態變數初始化,則按他們定義的順序調用)
②調用普通代碼塊和普通屬性的初始化(註意:普通代碼塊和普通屬性初始化調用的優先順序一樣,如果有多個普通代碼塊和多個普通屬性初始化,則按定義順序調用)
③調用構造方法。
-
構造器的最前面其實隱含了super()和調用普通代碼塊,新寫一個類,靜態相關的代碼塊,屬性初始化,在類載入時,就執行完畢,因此是優先於構造器和普通代碼塊執行的
class A2 { public void A() { //構造器 //這裡有隱藏的執行要求 //(1) super():這個知識點,在前面講解繼承的時候,老師說 // (2)調用普通代碼塊的 System.out.println("ok"); } }
-
我們看一下創建一個子類時(繼承關係),他們的靜態代碼塊,靜態屬性初始化,普通代碼塊,普通屬性初始化,構造方法的調用順序如下:
①父類的靜態代碼塊和靜態屬性(優先順序一樣,按定義順序執行)
②子類的靜態代碼塊和靜態屬性(優先順序一樣,按定義順序執行)
③父類的普通代碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)
④父類的構造方法
⑤子類的普通代碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)
⑥子類的構造方法//面試題
public class Test {
public static void main(String[] args) {
//老師說明
//(1)進行類的載入
//1.1 先載入父類A02 1.2 再載入Bo2//(2)創建對象
new B02();//對象
}
}
class A02 {//父類
private static int n1 = getVal01();
static {
System.out.println("A02的一個靜態代碼塊..");//2
}
{
System.out.println("A02的第一個普通代碼塊..");//5
}
public int n3 = getVal02();
public static int getVal01() {
System.out.println("getVal01");//1
return 10;
}
public int getVal02() {
System.out.println("getVal02");//6
return 10;
}
public A02() {
System.out.println("A02的構造器");//7
}
}
class B02 extends A02 { //
private static int n3 = getVal03();
static {
System.out.println("BO2的一個靜態代碼塊..");//4
}
public int n5 = getVal04();
{
System.out.println("B02的第一個普通代碼塊..");//9
}
public static int getVal03() {
System.out.println("getVal03");//3
return 10;
}
public int getVal04() {
System.out.println("getVal04");//8
return 10;
}
public B02() {
System.out.println("BO2的構造器");//10
}
}
- 靜態代碼塊只能直接調用靜態成員(靜態屬性和靜態方法),普通代碼塊可以調用任意成員。
單例設計模式
什麼是單例模式
- 所謂類的單例設計模式,就是採取一定的方法保證在整個的軟體系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法
- 單例模式有兩種方式: 1)餓漢式 2)懶漢式
單例模式應用實例
演示餓漢式和懶漢式單例模式的實現。步驟如下:
- 構造器私有化=》防止直接new
- 類的內部創建對象
- 向外暴露一個靜態的公共方法。getInstance
- 代碼實現
餓漢式
public class Test {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
}
}
class GirlFriend {
private String name;
//為了能夠在靜態方法中,返回gf對象,需要將其修飾為static
private static GirlFriend gf = new GirlFriend("小紅紅");
//如何保障我們只能創建一個 GirlFriend對象
//步驟
//步驟[單例模式-餓漢式]
//1。將構造器私有化
// 2.在類的內部直接創建
//3.提供一個公共的static方法,返回 gf對象
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
}
懶漢式
package com.hspedu.test;
public class Test {
public static void main(String[] args) {
//new Cat("大黃");
Cat instance = Cat.getInstance();
System.out.println(instance);
}
}
class Cat {
private String name;
private static Cat cat;//步驟
//1.仍然構造器私有化
//2.定義一個static屬性對象
//3.提供一個public的static方法,可以返回一個Cat對象
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
if (cat == null) {//如果沒有創建cat封象
cat = new Cat("小可愛");
}
return cat;
}
}
餓漢式VS懶漢式
-
二者最主要的區別在於創建對象的時機不同:餓漢式是在類載入就創建了對象實例,而懶漢式是在使用時才創建。
-
餓漢式不存線上程安全問題,懶漢式存線上程安全問題。(後面學習線程後,會完善)
-
餓漢式存在浪費資源的可能。因為如果程式員一個對象實例都沒有使用,那麼餓漢式創建的對象就浪費了,懶漢式是使用時才創建,就不存在這個問題。
在我們javaSE標準類中,java.lang.Runtime就是經典的單例模式。
final關鍵字
●基本介紹
final中文意思:最後的,最終的.
final 可以修飾類、屬性、方法和局部變數.
在某些情況下,程式員可能有以下需求,就會使用到final:
-
當不希望類被繼承時,可以用final修飾.
final class A{ } class B extends A {}//會報錯
-
當不希望父類的某個方法被子類覆蓋/重寫(override)時,可以用final關鍵字修飾。【案例演示:訪問修飾符 final 返回類型方法名】
class c { //如果我們要求hi不能被子類重寫 //可以使用final修飾hi方法 public final void hi() {} } class D extends C { @0verride public void hi {//報錯 System.out.println("重寫了C類的hi方法.."); } }
-
當不希望類的的某個屬性的值被修改,可以用final修飾.【案例演示: public final double TAX_RATE=0.08】
-
當不希望某個局部變數被修改,可以使用final修飾【案例演示: final double TAX_RATE=0.08)
class F { public void cry() { //這時,NUM也稱為局部常量 final double NUM = 0.01; NUM= 0.9;//報錯 System.out.println("NUM=" + NUM); } }
final使用註意事項和細節討論
-
final修飾的屬性又叫常量,一般用XX_XX_XX 來命名
-
final修飾的屬性在定義時,必須賦初值,並且以後不能再修改,賦值可以在如下位置之一【選擇一個位置賦初值即可】:
①定義時:如public final double TAX_RATE=0.08;
②在構造器中
③在代碼塊中。 -
如果final修飾的屬性是靜態的,則初始化的位置只能是
①定義時
②在靜態代碼塊賦值,不能在構造器中賦值。 -
final類不能繼承,但是可以實例化對象。
-
如果類不是final類,但是含有final方法,則該方法雖然不能重寫,但是可以被繼承。
-
一般來說,如果一個類已經是final類了,就沒有必要再將方法修飾成final方法。(多此一舉)
-
final不能修飾構造方法(即構造器)
-
final和static往往搭配使用,效率更高,底層編譯器做了優化處理。
-
包裝類(Integer,Double,Float,Boolean等都是final),String也是final類。
抽象類
當父類的某些方法,需要聲明,但是又不確定如何實現時,可以將其聲明為抽象方法,那麼這個類就是抽象類
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
//思考:這裡eat這裡你實現了,其實沒有什麼意義//即:父類方法不確定性的問題
//===>考慮將該方法設計為抽象(abstract)方法//===>所謂抽象方法就是沒有實現的方法
//===>所謂沒有實現就是指,沒有方法體
//===>當一個類中存在抽象方法時,需要將該類聲明為abstract類
// public void eat() {
// System.out.println("這是一個動物,但是不知道吃什麼..");
// }
public abstract void eat() ;
}
抽象類的介紹
-
用abstract關鍵字來修飾一個類時,這個類就叫抽象類
訪問修飾符 abstract 類名{
} -
用abstract關鍵字來修飾一個方法時,這個方法就是抽象方法
訪問修飾符 abstract 返回類型 方法名(參數列表);//沒有方法體 -
抽象類的價值更多作用是在於設計,是設計者設計好後,讓子類繼承並實現抽象類()
-
抽象類,是考官比較愛問的知識點,在框架和設計模式使用較多
抽象類使用的註意事項和細節討論
-
抽象類不能被實例化
public class AbstractDetail01 { public static void main(String[] args) { //抽象類,不能被實例化 new A();//報錯 } } abstract class A { }
-
抽象類不一定要包含abstract方法。也就是說,抽象類可以沒有abstract方法
-
一旦類包含了abstract方法,則這個類必須聲明為abstract
class B {//報錯 public abstract void hi(); }
-
abstract只能修飾類和方法,不能修飾屬性和其它的。
-
抽象類可以有任意成員【抽象類還是類】,比如:非抽象方法、構造器、靜態屬性等等
-
抽象方法不能有主體,即不能實現.如圖所示
abstract void aaa(){......}//報錯,不能存在“{}” -
如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明為abstract類。[舉例 A類,B類,C類]
//如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明為abstract類 abstract class E { public abstract void hi(); } abstract class F extends E { } class G extends E { @Override public void hi() { //這裡相等於G子類實現了父類E的抽象方法,所謂實現方法,就是有方法體 } }
-
抽象方法不能使用private、final和static來修飾,因為這些關鍵字都是和重寫相違背的。
抽象類最佳實踐-模板設計模式
最佳實踐
需求
- 有多個類,完成不同的任務job
- 要求統計得到各自完成任務的時間
- 請編程實現
感情的自然流露
- 先用最容易想到的方法
- 分析問題,提出使用模板設計模式
public class Test {
public static void main(String[] args) {
new A().job();
new B().job();
}
}
//如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明為abstract類
class A {
public void job() {
//得到開始的時間
long start = System.currentTimeMillis();
long num = 0;
for (long i = 1; i <= 80000000; i++) {
num += i;
}
//得的結束的時間
long end = System.currentTimeMillis();
System.out.println("執行時間" + (end - start));
}
}
class B {
public void job() {
//得到開始的時間
long start = System.currentTimeMillis();
long num = 0;
for (long i = -10; i <= 800000; i++) {
num += i;
}
//得的結束的時間
long end = System.currentTimeMillis();
System.out.println("執行時間" + (end - start));
}
}
最佳實踐(使用動態綁定機制)
設計一個抽象類(Template),能完成如下功能:
- 編寫方法calculateTime( ,可以計算某段代碼的耗時時間
- 編寫抽象方法job()
- 編寫子類AA、BB,繼承抽象類Template,並實現job方法。
- 編寫一個測試類Test,看看是否好用。
public class Test {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
abstract class Template {//抽象類-模板設計模式
public abstract void job();//抽象方法
public void calculateTime() {//實現方法,調用job方法
//得到開始的時間
long start = System.currentTimeMillis();
job();
//得的結束的時間
long end = System.currentTimeMillis();
System.out.println("執行時間" + (end - start));
}
}
//如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明為abstract類
class AA extends Template{
public void job() {
long num = 0;
for (long i = 1; i <= 80000000; i++) {
num += i;
}
}
}
class BB extends Template{
public void job() {
//得到開始的時間
long num = 0;
for (long i = -10; i <= 800000; i++) {
num += i;
}
}
}
介面
為什麼有介面
先看一張圖:
usb插槽就是現實中的介面。
你可以把手機,相機,u盤都插在usb插槽上,而不用擔心那個插槽是專門插哪個的,原因是做usb插槽的廠家和做各種設備的廠家都遵守了統一的規定包括尺寸,排線等等。
介面快速入門
這樣的設計需求在java編程/php/.net/go中也是會大量存在的,我曾經說過,一個程式就是一個世界,在現實世界存在的情況,在程式中也會出現。我們用程式來模擬一下。
介面
public interface UsbInterface {
//規定介面的相關方法
public void start();
public void stop();
}
相機
public class Camera implements UsbInterface{//實現介面,就是把介面方法實現
//IDEA中alt+insert,選擇implement methods(或ctrl+i)快速創建相應的方法
@Override
public void start() {
System.out.println("相機開始工作");
}
@Override
public void stop() {
System.out.println("相機停止工作");
}
}
手機
//Phone 類 實現 UsbInterface
//解讀1.即 Phone類需要實現UsbInterface介面 規定/聲明的方法
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手機開始工作。..");
}
@Override
public void stop() {
System.out.println("手機停止工作..... ");
}
}
電腦
public class Computer {
//編寫一個方法,電腦工作
public void work(UsbInterface usbInterface) {
usbInterface.start();
usbInterface.stop();
}
}
調用
public class Test {
public static void main(String[] args) {
//創建手機,相機對象
Camera camera = new Camera();
Phone phone = new Phone();
//創建電腦
Computer computer = new Computer();
computer.work(phone);//把手機接入到電腦
System.out.println("-----------------");
computer.work(camera);
}
}
●基本介紹
介面就是給出一些沒有實現的方法,封裝到一起,到某個類要使用的時候,在根據具體情況把這些方法寫出來。語法:
interface 介面名{
//屬性
//方法(1.抽象方法 2.預設實現方法 3.靜態方法)
}
class 類名 implements 介面{
自己屬性;
自己方法;
必須實現的介面的抽象方法
}
小結:
- 在Jdk7.0前介面里的所有方法都沒有方法體,即都是抽象方法。(已被淘汰,基本不用)
- Jdk8.0後介面可以有靜態方法,預設方法,也就是說介面中可以有方法的具體實現
interface AInterface {
//寫屬性
public int n1 = 10;//寫方法
//在介面中,抽象方法,可以省略abstract關鍵字
public void hi();
//在jdk8後,可以有預設實現方法,需要使用default關鍵字修飾
default public void ok() {
System.out.println("ok ...");
}
//在jdk8後,可以有靜態方法
public static void cry() {
System.out.println("cry........");
}
}
深入討
對初學者講,理解介面的概念不算太難,難的是不知道什麼時候使用介面,下麵我例舉幾個應用場景:
- 說現在要製造戰鬥機,武裝直升機.專家只需把飛機需要的功能/規格定下來即可,然後讓別的人具體實現就可。
- 說現在有一個項目經理,管理三個程式員,功能開發一個軟體,為了控制和管理軟體,項目經理可以定義一些介面,然後由程式員具體實現。
註意事項和細節
-
介面不能被實例化
-
介面中所有的方法是public方法,介面中抽象方法,可以不用寫public和abstract,如下:
void aa();實際上是public abstract void aa();
-
一個普通類實現介面,就必須將該介面的所有方法都實現。
class Phone implements UsbInterface {//報錯 }
-
抽象類實現介面,可以不用實現介面的方法。
abstract class Phone implements UsbInterface {//不報錯 }
-
一個類同時可以實現多個介面
class Pig implements IB,IC { }
-
介面中的屬性,只能是final的,而且是public static final修飾符。比如:
int a=1;實際上是public static final int a=1;(必須初始化) -
介面中屬性的訪問形式:介面名.屬性名
-
一個介面不能繼承其它的類,但是可以繼承多個別的介面
interface A extends B,C{} -
介面的修飾符只能是public和預設,這點和類的修飾符是一樣的。
實現介面Vs繼承類
▶介面和繼承解決的問題不同
繼承的價值主要在於:解決代碼的復用性和可維護性。
介面的價值主要在於:設計,設計好各種規範(方法),讓其它類去實現這些方法。即更加的靈活..
▶介面比繼承更加靈活
介面比繼承更加靈活,繼承是滿足is -a的關係(小明是學生),而介面只需滿足like - a的關係(飛機像鳥一樣飛)
▶介面在一定程度上實現代碼解耦[即:介面規範性+動態綁定]
介面的多態特性(和類的繼承中的多態類似)
-
多態參數(前面案例體現)
在前面的Usb介面案例,Usb usb,既可以接收手機對象,又可以接收相機對象,就體現了介面多態(介面引用可以指向實現了介面的類的對象)
-
多態數組
演示一個案例:給Usb數組中,存放 Phone 和相機對象,Phone類還有一個特有的方法call),請遍歷Usb數組,如果是Phone對象,除了調用Usb介面定義的方法外,還需要調用Phone特有方法call.
Usb usbs[] = new Usb[2]; usbs[0]= new Phone(); usbs[1] = new Camera(); for (int i = 0; i< usbs.length; i++){ usbs[i].start(); usbs[i].stop(); if (usbs[i] instanceof Phone){ ((Phone)usbs[i]).call(); } }
-
介面存在多態傳遞現象。
class InterfacePolyPass { public static void main(String[] args) { //介面類型的變數可以指向,實現了該介面的類的對象實例 IG ig = new Teacher(); //如果IG 繼承了IH 介面,而Teacher類實現了 IG介面 // 那麼,實際上就相當於Teacher類也實現了IH介面, //這就是所謂的介面多態多態傳遞現象. IH ih = new Teacher(); } } interface IH { void hi(); } interface IG extends IH { } class Teacher implements IG { @Override public void hi() { } }
類定義的進一步完善
內部類
基本介紹
一個類的內部又完整的嵌套了另一個類結構。被嵌套的類稱為內部類(inner class),嵌套其他類的類稱為外部類(outer class)。是我們類的第五大成員【思考:類的五大成員是哪些?屬性 方法 構造器 代碼塊 內部類】,內部類最大的特點就是可以直接訪問私有屬性,並且可以體現類與類之間的包含關係。(難點)
●基本語法
class Outer{//外部類
class Inner{//內部類
}
}
class Other{//外部其他類
}
內部類的分類
定義在外部類局部位置上(比如方法內):
- 局部內部類(有類名)
- 匿名內部類(沒有類名,重點!!!!!!)
定義在外部類的成員位置上:
- 成員內部類(沒用static修飾)
- 靜態內部類(使用static修飾)
局部內部類的使用
說明:局部內部類是定義在外部類的局部位置,比如方法中,並且有類名.
-
可以直接訪問外部類的所有成員,包含私有的
-
不能添加訪問修飾符,因為它的地位就是一個局部變數。局部變數是不能使用修飾符的。但是可以使用final修飾,因為局部變數也可以使用final
-
作用域:僅僅在定義它的方法或代碼塊中。
-
局部內部類---訪問---->外部類的成員[訪問方式:直接訪問]
-
外部類---訪問---->局部內部類的成員
訪問方式:創建對象,再訪問(註意:必須在作用域內) -
外部其他類---不能訪問----->局部內部類(因為局部內部類地位是一個局部變數)
-
如果外部類和局部內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問
System.out.println("外部類的n2=”+外部類名.this.n2);
記住:(1)局部內部類定義在方法中/代碼塊 (2)作用域在方法體或者代碼塊中 (3)本質仍然是一個類
public class Test {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02 {//外部類
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}//私有方法
public void m1() {//方法
//1.局部內部類是定義在外部類的局部位置,通常在方法
// 3.不能添加訪問修飾符,但是可以使用final修飾
// 4.作用域︰僅僅在定義它的方法或代碼塊中
String name = "xXx";
final class Inner02 {//局部內部類(本質仍然是一個類)
private int n1=800;
//2.可以直接訪問外部類的所有成員,包含私有的
public void f1() {
//5.局部內部類可以直接訪問外部類的成員,比如下麵外部類n1 和 m2()
//7.如果外部類和局部內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,
// 使用 外部類名.this.成員)去訪問
System.out.println("n1=" + n1 +"外部類的n1="+Outer02.this.n1);
m2();
}
}
//6.外部類在方法中,可以創建Inner02對象,然後調用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
// class Inner022 extends Inner02{
// }
}
{ //代碼塊
class Inner03 {
}
}
}
匿名內部類的使用(重要!!!)
//(1)本質是類 (2)內部類 (3)該類沒有名字 (4)同時還是一個對象
必須繼承—個抽象類或者實現一個介面
說明:匿名內部類是定義在外部類的局部位置,比如方法中,並且沒有類名
-
匿名內部類的基本語法
new 類或介面(參數列表){ 類體 };
-
匿名內部類的語法比較奇特,請大家註意,因為匿名內部類既是一個類的定義同時它本身也是一個對象,因此從語法上看,它既有定義類的特征,也有創建對象的特征,對前面代碼分析可以看出這個特點,因此可以調用匿名內部類方法。
-
可以直接訪問外部類的所有成員,包含私有的
-
不能添加訪問修飾符,因為它的地位就是一個局部變數。
-
作用域:僅僅在定義它的方法或代碼塊中。
-
匿名內部類---訪問---->外部類成員[訪問方式:直接訪問]
-
外部其他類---不能訪問----->匿名內部類(因為匿名內部類地位是一個局部變數)
-
如果外部類和匿名內部類的成員重名時,匿名內部類訪問的話,預設遵循就近原則如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問
例子(基於介面)
public class Test {
public static void main(String[] args) {
new Outer04().method();
}
}
class Outer04 {//外部類
private int n1 = 10;//屬性
public void method() {//方法
//基於介面的匿名內部類//老韓解讀
//1.需求:想使用IA介面,並創建對象
//2.傳統方式,是寫一個類,實現該介面,並創建對象
//3.老韓需求是 Tiger/Dog 類只是使用一次,後面再不使用
//4.可以使用匿名內部類來簡化開發
//5.tiger的編譯類型? IA
//6.tiger的運行類型?就是匿名內部類(對象名.getClass()可獲取名字) Outer04$1
/*我們看底層
class XXXX implements IA {
@override
public void cry(O {
System.out'.println("老虎叫喚...");
}
}
*/
//7.jdk底層在創建匿名內部類Outer04$1,立即馬上就創建了Outer04$1實例,並且把地址
// 返回給tiger
//8.匿名內部類使用一次,就不能再使用(但此處的tiger對象可重覆使用)
IA tiger = new IA(){
@Override
public void cry(){
System.out.println("老虎叫喚");
}
};
System.out.println("tiger的運行類型=" + tiger.getClass());
tiger.cry();
tiger.cry();
}
}
interface IA {//介面
public void cry();
}
例子(基於方法)
public class Test {
public static void main(String[] args) {
new Outer04().method();
}
}
class Outer04 {//外部類
private int n1 = 10;//屬性
public void method() {//方法
//演示基於類的匿名內部類
// 分析
//1.father編譯類型 Father
//2.father運行類型Outer04$2
//3.底層會創建匿名內部類
/*
class Outer04$1 extends Father{
override
public void test() {
System.out.println("匿名內部類重寫了test方法");
}
}
*/
//4.同時也直接返回了匿名內部類Outer04$2的對象
Father tiger = new Father("jack") {
public void test() {
System.out.println("匿名內部類重寫了test方法");
}
};
System.out.println("tiger的運行類型=" + tiger.getClass());//Outer04$1
tiger.test();
//基於抽象類的匿名內部類
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("小狗吃骨頭...");
}
};
animal.eat();
}
}
class Father {//類
public Father(String name) {//構造器
}
public void test() {//方法
}
}
abstract class Animal {//抽象類
abstract void eat();
}
成員內部類的使用
說明:成員內部類是定義在外部類的成員位置,並且沒有static修飾。
-
可以直接訪問外部類的所有成員,.包含私有的
-
可以添加任意訪問修飾符(public、protected、預設、private),因為它的地位就是一個成員。
-
作用域
和外部類的其他成員一樣,為整個類體比如前面案例,在外部類的成員方法中創建成員內部類對象,再調用方法.
-
成員內部類---訪問---->外部類(比如:屬性)[訪問方式:直接訪問](說明)
-
外部類---訪問------>內部類(說明)訪問方式:創建對象,再訪問
-
外部其他類---訪問---->成員內部類
-
如果外部類和內部類的成員重名時,內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問
public class Test {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//6.外部其他類,使用成員內部類的兩種方式
// outer08.new Inner08();相當於把 new Inner08()當做是outer08成員
//這就是一個語法,不要特別的糾結.
Outer08.Innter08 innter08 = outer08.new Innter08();
//第二方式在外部類中,編寫一個方法,可以返回 Inner08對象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
}
}
class Outer08 {//外部類
private int n1 = 10;
public String name = "張三";
private void hi() {
System.out.println("hi()方法...");
}
//1.註意:成員內部類,是定義在外部內的成員位置上
//2.可以添加任意訪問修飾符(public、protected 、預設、private),因為它的地位就是一個成員
private class Inner08 {//成員內部類
private double sal = 99.8;
public void say() {
//4.可以直接訪問外部類的所有成員,包含私有的
System.out.println("n1 = " + n1 + " name = " + name);
hi();//4
}
}
//方法,返回一個Inner08實例
public Inner08 getInner08Instance(){
return new Inner08();
}
//寫方法
public void t1() {
//4.使用成員內部類
//創建成員內部類的對象,然後使用相關的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(innerO8.sal);
}
}
靜態內部類的使用
說明:靜態內部類是定義在外部類的成員位置,並且有static修飾
- 可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員
- 可以添加任意訪問修飾符(public.protected、預設、private),因為它的地位就是一個成員。
- 作用域:同其他的成員。為整個類體
- 靜態內部類---訪問---->外部類(比如:靜態屬性)[訪問方式:直接訪問所有靜
態成員] - 外部類---訪問------>靜態內部類訪問方式:創建對象,再訪問
- 外部其他類---訪問--->靜態內部類
- 如果外部類和靜態內部類的成員重名時,靜態內部類訪問的時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.成員)去訪問
public class Test {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他類使用靜態內部類
// 方式1
// 因為靜態內部類,是可以通過類名直接訪問(前提是滿足訪問許可權)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式2
//編寫一個方法,可以返回靜態內部類的對象實例。
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = outer10.getInner10_();
System.out.println("============");
inner10_.say();
}
}
class Outer10 {//外部類
private int n1 = 10;
private static String name = "張三";
private static void cry(){}
// Inner10就是靜態內部類
//1.放在外部類的成員位置
//2.使用static修飾
//3,可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員
//4,可以添加任意訪問修飾符(public、 protected 、預設、private),因為它的地位就是一個成員//5。作用域:同其他的成員,為整個類體
//5.作用域:同其他的成員,為整個類體
static class Inner10 {
private static String name ="李四";
public void say() {
System.out.println(name);
System.out.println(Outer10.name);//7
cry();
}
}
public void m1() {
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10(){
return new Inner10();
}
public static Inner10 getInner10_(){
return new Inner10();
}
}
這裡:小結
(1)內部類有四種局部內部類,匿名內部類成員內部類,靜態內部類
(2)重點還是掌握匿名內部類使用
new 類/介面(參數列表){
//...
};
(3)成員內部類,靜態內部類是放在外部類的成員位置,本質就是一個成員.
(4)其他細節看之前筆記...