抽象類
在繼承體系下,父類有些方法可能是要被重寫的,如果我們事先就知道某些方法需要重寫的話,我們可以不用在父類里面具體實現這個方法,這時候我們會用到抽象方法,這時候我們會用到關鍵字abstract關鍵字來修飾
public abstract class Animal {protected abstract void eat();
}
例如上面的Animal 類,每一個動物都會吃,但是每一個動物卻吃的食物不同,父類的eat方法無法完全描述某個對象,這時候子類就需要重寫這個方法,如果我們不想在父類具體實現這個eat方法的話,我們可以寫成抽象方法~~
說完抽象方法,我們來類比一下抽象類:
如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類
抽象類也是用abstract 修飾的~~
注意要點
有抽象方法的類,一定是抽象類,所以如果方法被abstract修飾,那類也必須由abstract修飾,否則編譯報錯
抽象類是不能進行實例化的,但是可以有繼承的向上轉型和向下轉型~~
被private、static、final 修飾的方法不能是抽象方法
因為抽象方法就是為了被子類重寫的,根據重寫的規則,被private、static、final修飾的方法確實不能被重寫
抽象類被繼承后,繼承后子類要重寫父類中的抽象方法,除非子類也是抽象類,必須要使用 abstract 修飾,無論是誰繼承了抽象類都必須重寫所有的抽象方法,否則編譯報錯~~
當Dog繼承Animal,必須重寫Aniaml所有的抽象方法~~
如果Dog還是抽象類,Cat 繼承 Dog,并且在 Cat 不是抽象的情況下,我們要在 Cat 這個類重寫所有的抽象方法(即包括 Animal 也包括 Dog 的抽象方法)
抽象類的作用
抽象類就是用來被繼承的
誰繼承了抽象類,都必須重寫抽象方法,否則編譯報錯,這也是為了多加一層編譯器的校驗
接口
接口就是公共的行為規范標準,大家在實現時,只要符合規范標準,就可以通用。
在Java中,接口可以看成是:多個類的公共規范,是一種引用數據類型
我們使用 intarface 來定義接口,就是把class替換成interface
public interface Ieat {void eat();
}
接口雖然不是類,但是接口編譯完成后字節碼文件的后綴格式也是.class
接口的使用規則
接口的成員方法是默認都是public static final 修飾的
接口的成員方法默認都是 public abstract 修飾的
當你在接口里定義了一個成員變量的時候,你必須對其進行初始化!!!
如果你想具體實現某些方法,你可以使用 static 或者 default 來進行修飾:
public interface Ieat {static void eat(){//...}default void eat2(){//...}
}
訪問權限也是和之前講的是一樣的,被static就是默認權限的靜態方法,被default 修飾就是默認訪問權限。
接口不能有實例化代碼塊、靜態代碼塊,也不能有構造方法~~
如果類沒有實現接口中的所有的抽象方法,則類必須設置為抽象類,如果被繼承就必須重寫所有的抽象方法!!!
這個和抽象類是類似的~~
軟性規則:
創建接口時, 接口的命名一般以大寫字母 I 開頭.
接口的命名一般使用 “形容詞” 詞性的單詞.
阿里編碼規范中約定, 接口中的方法和屬性不要加任何修飾符號, 保持代碼的簡潔性.
接口的繼承
接口與接口之間可以多繼承。即:用接口可以達到多繼承的目的。
接口可以繼承一個或者多個接口, 達到復用的效果. 使用 extends 關鍵字。
interface IA{void eat();
}interface IB{void sleep();
}interface C extends IA,IB{}
接口的作用
解決了 Java 不能多繼承的問題!!!
意味著一個類可以有多個接口!!!
如果子類由繼承父類還有多個接口的時候,我們要先繼承后接口(先extends 再 implements)
快捷鍵(搭建接口當中的抽象方法)
IDEA 中使用 ctrl + i 快速搭建接口當中的抽象方法~~
或者使用 alt + enter 進行選擇implements methods 進行快速搭建接口,你選擇Make ‘Dog’ abstract 的話就是講這個類變為抽象類~~
接口的好處
public class Animal {protected String name;protected int age;
}public interface Irun {void run();
}public class Cat extends Animal implements Irun{public Cat(String name, int age) {this.name = name;this.age = age;}@Overridepublic void run() {System.out.println(this.name + "正在跑步");}
}public class Dog extends Animal implements Irun{public Dog(String name, int age) {this.name = name;this.age = age;}@Overridepublic void run() {System.out.println(this.name + "正在跑步");}
}public class Test {public static void walk(Irun irun){irun.run();}public static void main(String[] args) {walk(new Dog("旺財",11));walk(new Cat("小咪",10));}
}
接口也可以有動態綁定和多態~~
由于接口可以實現多態,所以程序員可以不關注類型,只要有這個接口的類,都能調用里面的接口方法,而不用去關心這是什么類。
Object 類
Object是Java默認提供的一個類。Java里面除了Object類,所有的類都是存在繼承關系的。默認會繼承Object父類。即所有類的對象都可以使用Object的引用。
我們這里先重點關注一下上面標出來的三個方法:toString,equals,hashCode,
toString(打印對象)
class Person{public String name;public int age;}public class Test {public static void main(String[] args) {Person person = new Person();System.out.println(person);}
}
我們在數組里知道直接打印數組名的話會出現包含數組的地址的一串字符串~~
如果直接打印對象的話,也會出現和數組類似的情況,這是為什么?
Java所有的類都會繼承Object類,在調用println的時候,我們來看看一共調用了哪些方法:
首先println 方法如下:
之后無論是走if 語句還是else 語句,都會調用toString方法
最后就會來到toString 方法,這里getClass.getName()就是類名,然后加@符號,最后調用hashCode找到地址。
但是如果我們重寫了 toString 方法的話,根據前面的知識,優先調用子類的方法來打印對象內容。
class Person{public String name;public int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
這樣的話,我們就會優先調用子類自己的toString 方法
我們可以使用編譯器自動生成toString方法:
和之前搭建getter、setter還有構造方法是一樣的,只是這里選擇的是toString()
class A{public String name;public int age;@Overridepublic String toString() {return "A{" +"name='" + name + '\'' +", age=" + age +'}';}
}
equals
在Java當中,如果使用 == 來進行比較時
如果比較的是基本數據類型的話,就是比較兩個的數值相不相同
如果比較的是引用數據類型,就會比較他們的地址相不相同
來我們看一下源碼:
還是一樣的,直接調用equals方法,還是比較兩個對象的地址,所以如果想比較兩個對象的內容相不相同就必須重寫equals方法~~
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}
class Person{public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}
}public class Test {public static void main(String[] args) {Person person1 = new Person("張三",10);Person person2 = new Person("張三",10);System.out.println(person1.equals(person2));}
}
比較對象中內容是否相同的時候,一定要重寫equals方法。
hashCode
源碼:
native 說明這是本地方法,這個是有C/C++代碼編寫的,我們是看不到的
簡單來說,hashCode方法可以找到對象的內存地址
public class Test {public static void main(String[] args) {Person person1 = new Person("張三",10);Person person2 = new Person("張三",10);System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}
由于這是兩個不同的對象,所以他們的內存地址是不一樣的~~
但是如果我們認為當兩個對象的內容是一樣的,那地址就應該是一樣的話,我們就需要重寫hashCode 方法~~
@Overridepublic int hashCode() {return Objects.hash(name, age);}
這樣他們的地址就會是一樣的顯示~~
快捷鍵搭建
以上三個方法都能使用快捷鍵快速搭建:
內部類
內部類就是在類里面再定義一個類,這個類定義的位置和外部類的成員是相同的。
靜態內部類
被static修飾的內部成員類稱為靜態內部類。
class A{public int age;public static int price;public A(){System.out.println("A()......");}public void methodA1(){System.out.println("methodA1()......");}public static void methodA2(){System.out.println("methodA2()......");}static class B{public void methodB(){//age = 10;//err,靜態內部類只能訪問外部類的靜態成員//A();//不要在靜態內部類中調用外部類的構造方法,構造方法是沒有靜態的,所以構造方法一定不是靜態方法//methodA1(); //err,靜態內部類只能訪問外部類的靜態成員,methodA1不是類方法(靜態成員方法)price = 10;methodA2();}}//.....
}
注意事項
靜態內部類只能訪問外部類的靜態成員
創建靜態內部類
A.B b = new A.B();
我們可以將靜態內部類當成外部類的一個靜態成員,靜態成員的訪問不需要創建對象,我們可以通過類名來訪問,于是我們通過 A.B 就訪問到了靜態內部類,然后就通過new A.B 就可以完成創建了
實例內部類
未被static 修飾的實例內部類就是實例內部類
實例內部類可以自由訪問外部類的任意成員,如果實例內部類和外部類有重名的成員時,在內部類中優先訪問自己的,如果真的相訪問外部類同名的成員時,我們可以使用 外部類類名.this.成員 即可~~
class A{public int age;public static int price;public A(){System.out.println("A()......");}public void methodA1(){System.out.println("methodA1()......");}public static void methodA2(){System.out.println("methodA2()......");}public void methodA3(){System.out.println("methodA3()......");}class C{public int age = 10;public void methodC(){System.out.println(age);methodA3();System.out.println(A.this.age);methodA1();A.this.methodA1();}public void methodA1(){System.out.println("C::methodA1()......");}}//.....
}public class Test{public static void main(String[] args) {A.C c = new A().new C();c.methodC();}
}
創建實例內部類
我們要先創建外部類,再去創建實例內部類
A.C c = new A().new C();
當然也可以分部去寫:
A a = new A();
A.C c = a.new C();
注意事項
1.外部類中的任何成員都 可以在實例內部類方法中直接訪問
2.實例內部類所處的位置與外部類成員位置相同,因此也受public、private等訪問限定符的約束
3.在實例內部類方法中訪問同名的成員時,優先訪問自己的,如果要訪問外部類同名的成員,必須:外部類名
稱.this.同名成員 來訪問
4.實例內部類對象必須在先有外部類對象前提下才能創建
5.實例內部類的非靜態方法中包含了一個指向外部類對象的引用
6.外部類中,不能直接訪問實例內部類中的成員,如果要訪問必須先要創建內部類的對象。
匿名內部類
class A{public void test1(){System.out.println("heihei");}
}public class Test{public static void main(String[] args) {new A(){}.test1();}}
通過后面的 .方法 來調用相應的方法。
我們也可以重寫匿名內部類的方法
但是要注意不能使用對象來接收匿名內部類
接口也可以使用:
interface A{void test1();
}public class Test{public static void main(String[] args) {new A(){public void test1(){System.out.println("haha");}};}}
和上面不一樣的是,接口是一定要重寫其中的抽象方法的,并且花括號后面是不能直接 .方法 的,而是要通過被接收后,然后去再去調用相應的方法~~
因此接口是可以被接收的,接收后也是可以繼續使用的:
局部內部類
局部內部類是定義在方法里的,因此它的生命周期和方法是一樣。
public void A() {//...class D{ //......}//...}