今天的主要內(nèi)容是構(gòu)造方法,static用法,繼承,多態(tài),抽象類,接口。由于從今天開始JAVA的學(xué)習(xí)基本上都是面向?qū)ο蟮膬?nèi)容,雖然這些內(nèi)容底層的實現(xiàn)是由C++寫的編譯器實現(xiàn)的,在用法上與C語言已經(jīng)大相徑庭,故不再比較二者的差異。開始闡釋JAVA的知識點為主。
構(gòu)造方法
所謂構(gòu)造方法,就是在創(chuàng)建對象時自動執(zhí)行的方法,主要用于對成員變量的初始化,當(dāng)然也可以用于對運行環(huán)境的初始化,如加載驅(qū)動等。
空參構(gòu)造
空參構(gòu)造即以不傳遞參數(shù)的方式下創(chuàng)建對象。如Demo d = new Demo(); 就是用空參構(gòu)造的方式創(chuàng)建對象,Demo后面的括號,就是用來傳遞參數(shù)的,當(dāng)其為空時,便是空參構(gòu)造。
空參構(gòu)造方法
在類中編寫的通過無參構(gòu)造創(chuàng)建對象系統(tǒng)自動執(zhí)行的方法。
空參構(gòu)造方法沒有返回值,連void也沒有
空參構(gòu)造方法與類同名
class Demo_constructor {
public static void main(String[] args) {
? ? Person p = new Person();
? ? System.out.println(p.name+"..."+p.age);? ? ? ? //張三...18
? ? }
}
class Person{
? ? String name;
? ? int age;
? ? public Person(){
? ? ? ? ? name = "張三";
? ? ? ? ? age = 18;
? ? }
}
代碼中通過空參構(gòu)造方法Person(),對類中的name和age賦值為張三和18,在主方法中new一個Person對象的時候沒有傳遞參數(shù),系統(tǒng)自動執(zhí)行空參構(gòu)造方法,對象中的name和age便有了初值張三和18;
有參構(gòu)造方法
當(dāng)創(chuàng)建對象時傳遞參數(shù),便自動執(zhí)行有參構(gòu)造方法。
有參構(gòu)造方法與無參大致相同,只需在編寫時寫上參數(shù)列表即可。
class Demo_constructor {
? public static void main(String[] args) {
? ? ? Person p = new Person("張三",18);
? ? ? System.out.println(p.name+"..."+p.age);? ? ? ? //張三...18
? }
}
class Person{
? String name;
? int age;
? public Person(String name,int age){
? ? ? this.name = name;
? ? ? this.age = age;
? }
}
當(dāng)局部變量名與成員變量名的相同時,局部變量會隱藏成員變量,但在開發(fā)中常要求見名知意,對于這種情況,可以用this.來引用成員變量,與局部變量區(qū)分開來。
在寫類的時候,倘若沒有寫構(gòu)造方法,系統(tǒng)會自動加上空參構(gòu)造,如果寫了有參構(gòu)造方法,沒寫空參構(gòu)造方法,系統(tǒng)不再加上空參構(gòu)造,在創(chuàng)建對象時便不可以用空參的方式創(chuàng)建,因此,在寫完有參構(gòu)造方法后盡量將空參構(gòu)造方法也寫上。
static
static是靜態(tài)的意思,在類中定義static成員變量和static成員方法,這些方法是存放在內(nèi)存的方法區(qū)中,在創(chuàng)建對象的時候,不需對這些變量和方法分配空間,可以達(dá)到資源共享的作用。
靜態(tài)成員變量
靜態(tài)成員變量由所有對象共享,在一個對象該變量修改后,所有的對象靜態(tài)變量都會修改,因為他們其實都在引用同一個變量。
class Demo_static {
? public static void main(String[] args) {
? ? ? Person p = new Person();
? ? ? p.name = "張三";
p.country ="中國";
p.speak();? ? ? ? ? ? ? ? ? ? ? ? //張三...中國
? ? ? Person p2 = new Person();
? ? ? p2.speak();? ? ? ? ? ? ? ? ? ? ? ? //null...中國
? ? ? p2.country = "America";
? ? ? p.speak();? ? ? ? ? ? ? ? ? ? ? ? //張三...America
? }
}
class Person{
? String name;
? static String country;? ? ? ? //在c語言的解釋中就是將country聲明周期延長,實現(xiàn)共享,節(jié)省空間資源
? public void speak(){
? ? ? System.out.println(name+"..."+country);
? }
}
從代碼中可以看到,創(chuàng)建對象p之后,將p的name和country修改為張三和中國,然后創(chuàng)建對象p2,雖然沒有對p2的屬性進(jìn)行修改,但由于country是static變量,所以,p2的country也為中國,而非靜態(tài)變量的name為null。然后p2將其變量country修改為America,p的變量country也變?yōu)榱薃merica。
靜態(tài)成員方法
將類中的成員方法修飾為static,可以在不創(chuàng)建對象的情況下對方法進(jìn)行引用,節(jié)省空間。其實主方法public static void main(String[] arg){}中,main就是JVM識別的入口,聲明為static就可以在不創(chuàng)建該類的情況下調(diào)用主函數(shù)。
class ArrayTool {
? ? private ArrayTool(){};
? ? //若類只有靜態(tài)方法,要設(shè)置私有構(gòu)造方法,防止創(chuàng)建對象,浪費內(nèi)存
? ? public static int getMax(int[] arr){
? ? ? ? int max = arr[0];
? ? ? ? for (int i = 1;i < arr.length ;i++ ){
? ? ? ? ? ? if (arr[i] > max){
? ? ? ? ? ? ? ? max = arr[i];
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return max;
? ? }
}
上面定義的類中含有static方法,在主函數(shù)中可以不創(chuàng)建該類的對象而直接調(diào)用方法
class Array_Test {
? ? public static void main(String[] args) {
? ? ? ? int[] arr = {11,22,33,44,55};
? ? ? ? int max = ArrayTool.getMax(arr);
? ? ? ? }
}
下面的內(nèi)存分配圖可以幫助我們更好的理解static的機(jī)制,了解static各個特點的原理
類中靜態(tài)變量與靜態(tài)方法內(nèi)存分配
注意事項
共性用static,特性用auto
靜態(tài)變量又叫類變量,非靜態(tài)變量又叫對象變量
靜態(tài)變量隨類產(chǎn)生和消失,非靜態(tài)變量隨對象產(chǎn)生和消失
靜態(tài)只能訪問靜態(tài)
繼承
有時候類與類之間有很多相同的成員變量和方法,倘若全部都要重寫,代碼的復(fù)用性太低,不利于開發(fā)。怎么通過類與類之間的聯(lián)系,減少代碼量?繼承由此誕生。
JAVA的繼承通過extends實現(xiàn),實現(xiàn)繼承的類成為子類,被繼承的類成為父類。子類繼承了父類,相同的成員變量和方法不需重寫便可使用,大大提高了代碼的復(fù)用性。
class Demo_extends {
? ? //讓類與類之間產(chǎn)生關(guān)系,提高代碼的復(fù)用性,維護(hù)性,也是多態(tài)的前提
? ? public static void main(String[] args) {
? ? ? ? Cat c = new Cat();
? ? ? ? c.color = "白";
? ? ? ? c.legs = 4;
? ? ? ? System.out.println(c.color+"..."+c.legs);
? ? }
}
class Animal{
? ? String color;
? ? int legs;
? ? public void sleep(){
? ? ? ? System.out.println("sleep");
? ? }
}
class Cat extends Animal{
}
通過extends關(guān)鍵字,Cat類繼承了Animal類,即使Cat中的代碼塊為空,仍然可以使用Animal中的成員變量和方法。
JAVA不支持多繼承,只支持單繼承,即一個子類只有一個父類,因為當(dāng)兩個父類中有同名的方式時會造成沖突,造成安全隱患。
JAVA在創(chuàng)建子類對象的時候,會先訪問父類的構(gòu)造方法,在訪問子類的構(gòu)造方法。從繼承的設(shè)計理念來不難理解為什么要這么做,因為子類繼承父類是為了使用父類的資源,所以在構(gòu)造子類時要訪問父類。
class Demo_extends3 {
? public static void main(String[] args) {
? ? ? Son s = new Son();? ? ? ? ? ? //這是父類構(gòu)造,這是子類構(gòu)造
? }? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //在構(gòu)造子類時會動訪問父類構(gòu)造
}
class Father{
? int num1;
? int num2;
? public Father(){
? ? ? System.out.println("這是父類構(gòu)造");
? }
}
class Son extends Father{
? public Son(){? ? ? ? ? ? ? ? ? ? ? ? ? ? //所有的子類構(gòu)造方法會默認(rèn)訪問父類的無參構(gòu)造方法
? ? ? //super();? ? ? ? ? ? ? ? ? ? ? ? ? ? //這是一條語句,沒寫會自動加上,用來訪問父類
? ? ? System.out.println("這是子類構(gòu)造");
? }
}
這種機(jī)制在父類對象沒有空參構(gòu)造的時候會出現(xiàn)錯誤,因為系統(tǒng)默認(rèn)的super();語句是訪問父類的空參構(gòu)造方法,在這種情況下,要在子類的構(gòu)造方法中進(jìn)行處理,即用super語句訪問父類的有參構(gòu)造方法。
class Demo_extends4 {//關(guān)于父類沒有無參構(gòu)造的解決方法
? public static void main(String[] args) {
? ? ? Son s = new Son(1);
? ? ? System.out.println("-----------------");
? ? ? Son s2 = new Son();
? }
}
class Father{
? int num;
? public Father(int num){
? ? ? System.out.println("父類有參構(gòu)造");
? }
}
class Son extends Father{
? public Son(){
? ? ? super(1);
? ? ? System.out.println("子類無參構(gòu)造");
? }
? public Son(int num){
? ? ? super(num);
? ? ? System.out.println("子類有參構(gòu)造");
? }
}
對于在創(chuàng)建的時候,是否會創(chuàng)建父類對象,因為要調(diào)用父類構(gòu)造方法,需要一個實例對象。網(wǎng)上的答案魚龍混雜,有說要的,有說不要的。但從我查閱的資料看,在創(chuàng)建子類對象的時候不會創(chuàng)建父類對象。
從內(nèi)存的使用來看,倘若在創(chuàng)建子類對象的時候創(chuàng)建父類對象,所有的對象都是Object的子類對象,那么內(nèi)存必然會被各種Object對象充斥,可用內(nèi)存少之又少,幾乎不可能有可用內(nèi)存。這與我們的顯示顯然是沖突的。
但是要使用父類構(gòu)造方法,必須有一個實例,這個實例在哪?在知乎一個回答中看到,父類,子類的實例其實是同一個,也就是說在訪問父類方法的時候,將該實例作為父類對象,在訪問子類構(gòu)造方法的時候,又把這個實例當(dāng)做子類對象。這個實例中有一部分是地址用super表示,是作為父類的儲存區(qū),而其余部分是子類的儲存區(qū)域,地址用this表示。
子類對象內(nèi)存圖
多態(tài)
編譯和運行的時候產(chǎn)生不同的形態(tài)成為多態(tài)。
多態(tài)前提:要有繼承,要有方法重寫,要有父類引用子類對象
對于多態(tài)編譯與運行時類中元素的規(guī)則
成員變量: 編譯看左邊(父類),運行看左邊(父類);
成員方法: 編譯看左邊(父類),運行看右邊(子類);? 動態(tài)綁定
靜態(tài)方法: 編譯看左邊(父類),運行看左邊(父類);
class Demo_Polymorthic {
? public static void main(String[] args) {
? ? ? Father f = new Son();
? ? ? System.out.println(f.num);? ? ? ? ? ? //10
? ? ? f.print();? ? ? ? ? ? ? ? ? ? ? ? ? ? //Son
? ? ? f.speak();? ? ? ? ? ? ? ? ? ? ? ? ? ? //fat
? }
}
class Demo_Polymorthic {
? public static void main(String[] args) {
? ? ? Father f = new Son();
? ? ? System.out.println(f.num);? ? ? ? ? ? //10
? ? ? f.print();? ? ? ? ? ? ? ? ? ? ? ? ? ? //Son
? ? ? f.speak();? ? ? ? ? ? ? ? ? ? ? ? ? ? //fat
? }
}
class Father{
? int num = 10;
? public void print(){
? ? ? System.out.println("Father");
? }
? public static void speak(){
? ? ? System.out.println("fat");
? }
}
class Son extends Father? {
? int num = 5;
? public void print(){
? ? ? System.out.println("Son");
? }
? public static void speak(){
? ? ? System.out.println("so");
? }
}
多態(tài)的好處與應(yīng)用場景
在方法中,父類對象用作參數(shù),子類方法重寫,使得代碼的復(fù)用性提高
class Demo_Polymorthic3 {
? public static void main(String[] args) {
? ? ? make(new Dog());
? ? ? make(new Cat());
? }
? public static void make(Animal a){? ? ? ? //多態(tài)常應(yīng)用于父類作參數(shù),子類方法重寫的應(yīng)用場景
? ? ? a.eat();
? }
}
class Animal{
? void eat(){
? ? ? System.out.println("動物吃飯");
? }
}
class Cat extends Animal{
? void eat (){
? ? ? System.out.println("貓吃魚");
? }
? void catchmouse(){
? ? ? System.out.println("捉老鼠");
? }
}
在多態(tài)的情況下,當(dāng)父類引用要使用子類特有的方法,要將父類引用強(qiáng)制轉(zhuǎn)換向下轉(zhuǎn)型。以下例子也許可以方便理解和記憶。
class Demo_Polymorthic2 {
? public static void main(String[] args) {
? ? ? Person p = new SuperMan();
? ? ? System.out.println(p.name);? ? ? ? ? ? ? ? //man
? ? ? p.談生意();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //談幾個億的生意
? ? ? //p.fly();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //p實際上就是子類向上提升
? ? ? SuperMan sm = (SuperMan) p;? ? ? ? ? ? ? ? //向下轉(zhuǎn)型
? ? ? sm.fly();
? }
}
class Person{
? String name="man";
? public void 談生意(){
? ? ? System.out.println("談生意");
? }
}
class SuperMan extends Person{
? String name = "sup";
? public void 談生意(){
? ? ? System.out.println("談幾個億的生意");
? }
? public void fly(){
? ? ? System.out.println("去救人");
? }
}
在上面的代碼中,父類引用p 指向子類對象superman,所以在正常引用下,superman就偽裝成普通人person,調(diào)用成員變量,當(dāng)然是用偽裝的名字man,而當(dāng)調(diào)用成員方法使,實際上就是superman在“”談生意“,而不是普通的person,故表現(xiàn)其真實的行為,即superman重寫的談生意()這一方法,調(diào)用結(jié)果是“談幾個億的大生意”。當(dāng)要用到superman特有的方法fly()時,必須向下轉(zhuǎn)型成superman,無法再偽裝了。
多態(tài)內(nèi)存圖解
抽象類
抽象,即無法具體的,所謂抽象類,即用abstract修飾符修飾的類。
abstract 修飾的方法,沒有方法體,如 abstarct eat();
抽象類的成員變量不能抽象
抽象類也有構(gòu)造方法
抽象類的抽象方法使子類必須重寫該方法
抽象類可以有非抽象方法,子類可以不重寫該方法
抽象類無法創(chuàng)造對象,無法創(chuàng)造出實例,但可以通過多態(tài)引用其子類
class Demo_Abstract {
? ? public static void main(String[] args) {
? ? ? ? Animal a = new Cat();? ? ? ? ? ? ? ? //通過多態(tài)來引用抽象類,抽象類本身無法創(chuàng)造實例
? ? }
}
abstract class Animal{
? ? int num1;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //抽象類成員變量不能抽象
? ? final int num2 = 10;
? ? Animal(){
? ? ? ? System.out.println("抽象類的有參構(gòu)造");? ? //抽象類也有構(gòu)造方法
? ? }
? ? abstract void eat();
? ? public void method(){? ? ? ? ? ? ? ? ? ? ? ? //抽象類可以有非抽象方法,子類繼承可以不重寫
? ? ? ? System.out.println("抽象類的非抽象方法");
? ? }
}
class Cat extends Animal{
? ? void eat(){? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //抽象類使得子類必須重寫其抽象方法
? ? ? ? System.out.println("貓吃魚");
? ? }
}
抽象類規(guī)定了子類必須重寫的方法,即規(guī)定了子類都必須有的方法,且要求其根據(jù)實際重寫。
接口
廣義上的接口,就是向外提供或者說制定的一種規(guī)則,如usb接口,規(guī)定了生產(chǎn)接口的廠商應(yīng)該使用的規(guī)格,線路的電壓等規(guī)則。
在JAVA中接口可以說是抽象類進(jìn)一步抽象,對實現(xiàn)接口的類制定了特定的規(guī)則。
接口沒有構(gòu)造方法
接口所有的都是抽象方法,所以實現(xiàn)接口,就是要重寫接口所規(guī)定的方法
接口所有的變量和方法都是public,因為接口的目的就是表露其規(guī)則,讓類去實現(xiàn),所以權(quán)限要足夠大。
類實現(xiàn)接口用implements,值得注意的是一個類可以實現(xiàn)多個接口,因為接口的方法都是抽象的,沒有方法體,不會產(chǎn)生沖突,這彌補(bǔ)了單繼承的缺陷。
接口與接口之間可以繼承
class Demo_Interface {
? public static void main(String[] args) {
? ? ? InterA a = new Demo();
? }
}
//設(shè)計理念,抽象類是為了規(guī)定共性的東西,接口是為了實現(xiàn)擴(kuò)展性
interface InterA{
? public static final int num = 1;? ? //接口沒有構(gòu)造方法
? public abstract void printA();? ? ? ? //接口的方法都是抽象方法
}
interface InterB{
? void printB();
}
interface InterC extends InterA,InterB{? ? //接口可以繼承接口,并且是多繼承
}
class Demo implements InterC {? ? //類可以實現(xiàn)多個接口
? public void printA(){;}
? public void printB(){;}
}
關(guān)于抽象類和接口的設(shè)計理念,抽象類規(guī)定了子類共同需要重寫的方法,是規(guī)定了子類的共性,而接口的多實現(xiàn),可以使類具有擴(kuò)展性,如下面的例子
class Demo_Interface2 {
? ? public static void main(String[] args) {
? ? ? ? Cat c = new Cat();
? ? ? ? c.eat();
? ? ? ? JumpCat j = new JumpCat();
? ? ? ? j.eat();
? ? ? ? j.jump();
? ? }
}
abstract class Animal{
? ? String name;
? ? abstract void eat();
}
interface Jump{
? ? void jump();
}
class Cat extends Animal{
? ? public void eat(){
? ? ? ? System.out.println("貓吃魚");
? ? }
}
class JumpCat extends Cat implements Jump{
? ? public void jump(){
? ? ? ? System.out.println("跳高");
? ? }
}
在抽象類Animal中定義了抽象方法eat();因為所有繼承他的子類都具有這一行為,必須根據(jù)自身重寫該方法。而接口Jump定義了jump();這一抽象方法,不同的類根據(jù)自己的需求,是否去實現(xiàn)這一接口,去擴(kuò)展該類的內(nèi)容。而且由于接口是多實現(xiàn)的,同一個類可以實現(xiàn)不同的接口,展現(xiàn)于其他類不同的行為。
以上便是今天學(xué)習(xí)的內(nèi)容中的要點及最應(yīng)該注意的地方,其中有什么錯誤的地方,特別是關(guān)于內(nèi)存的分配方面,由于是自己的理解,可能與實際有偏差,希望讀者指出并諒解。
? ? ? ? ? ? ? ?