1. 程式人生 > >java基礎-初級(三)【類的三大特性詳解】

java基礎-初級(三)【類的三大特性詳解】

目錄

 

3、類的三大特性詳解(封裝、繼承、多型)

3.1 封裝

3.2 繼承

      3.2.1 super關鍵字的使用

      3.2.2 阻止繼承:final

      3.2.2.3 抽象類和介面

      3.2.4 物件克隆

      3.2.5 內部類

3.3 多型


3、類的三大特性詳解(封裝、繼承、多型)

3.1 封裝

封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被外部類定義的程式碼隨機訪問。要訪問該類的程式碼和資料,必須通過嚴格的介面控制。封裝最主要的功能在於我們能修改自己的實現程式碼,而不用修改那些呼叫我們程式碼的程式片段。適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

封裝的通俗解釋就是將類中的資訊進行私有保護,類就像住的房子一樣,屬性和方法等資訊像房子裡的(private)私有物品,一般不對外開放,從而達到了封裝的效果。別人想使用你的東西(屬性或方法),要經過你的同意才可以使用,並且這些私有(private)物品(屬性或方法)外部無法進行修改。

下面就是對屬性和方法進行封裝的簡單例子:

import java.util.Date;

public class Worker {

    private String name;
    private Date birthday;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
   
    
    private String WorkAdress(){
        return "北京";
    }
    public String AA(){
        return WorkAdress()+"朝陽區";
    }
    
}

封裝的優點:

  • 1. 良好的封裝能夠減少耦合。

  • 2. 類內部的結構可以自由修改。

  • 3. 可以對成員變數進行更精確的控制。

  • 4. 隱藏資訊,實現細節。

3.2 繼承

繼承就是子類繼承父類的特徵和行為,使得子類物件(例項)具有父類的例項域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。繼承可以使用 extends 和 implements (後面介面講)這兩個關鍵字來實現繼承。需要注意的是 Java 不支援多繼承,但支援多重繼承。

下面是一個繼承的簡單例項------經理(Manager)繼承Employee(員工):

import java.util.Date;

public class Employee {
    private String name;
    private double salary;
    private Date hireDay;

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public double getSalary() {return salary;}

    public void setSalary(double salary) {this.salary = salary;}

    public Date getHireDay() {return hireDay; }

    public void setHireDay(Date hireDay) {this.hireDay = hireDay;}
    
    //獲取當前員工的工資
    public void raiseSalary(double byPercent){
        double raise = salary*byPercent/100;
        salary+=raise;
    }
}

public class Manager extends Employee{
    
    private double bonus;//獎金

    public Manager(String name, double salary, Date hireDay, double bonus) {
        super(name, salary, hireDay);
        this.bonus = bonus;
    }

    @Override
    public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary+bonus;
    }
}

Manager稱作為子類、派生類,Employee稱作超類、基類或父類 。經理屬於員工,但是他卻有比其他員工多的功能,例如經理有獎金(bonus),員工沒有(所有還是努力工作當經理!!)。

在重寫(@Override)的getSalary的方法中,使用了super關鍵字,表示引用父類的方法,如果不新增super關鍵字是無法使用的。下面瞭解一下super關鍵字的使用

3.2.1 super關鍵字的使用

super關鍵字表示對某個類的父類的引用。

super有兩種通用形式:第一種用來訪問被子類的成員隱藏的父類成員;第二種則是可以呼叫父類的建構函式。使用形式如下:

  • 呼叫父類的成員            super.屬性

在Java語言中,用過繼承關係實現對成員的訪問是按照最近匹配原則進行的,規則如下:

(1)在子類中訪問成員變數和方法時將優先查詢是否在本類中已經定義,如果該成員在本類中存在,則使用本類的,否則,按照繼承層次的順序往父類查詢,如果未找到,繼續逐層向上到其祖先類查詢。

(2)super特指訪問父類的成員,使用super首先到直接父類查詢匹配成員,如果未找到,再逐層向上到祖先類查詢。

  • 呼叫父類的建構函式     super(引數)

子類與父類的構造方法呼叫的順序或關係如下

(1)按繼承關係,構造方法是從頂向下進行呼叫的。

(2)如果子類沒有構造方法,則它預設呼叫父類無參的構造方法,如果父類中沒有無引數的構造方法,則將產生錯誤。

(3)如果子類有構造方法,那麼建立子類的物件時,先執行父類的構造方法,再執行子類的構造方法。

(4)如果子類有構造方法,但子類的構造方法中沒有super關鍵字,則系統預設執行該構造方法時會產生super()程式碼,即該構造方法會呼叫父類無引數的構造方法。

(5)對於父類中包含有引數的構造方法,子類可以通過在自己的構造方法中使用super關鍵字來引用,而且必須是子類建構函式方法中的第一條語句。

(6)Java語言中規定當一個類中含有一個或多個有參構造方法,系統不提供預設的構造方法(即不含引數的構造方法),所以當父類中定義了多個有引數構造方法時,應考慮寫一個無引數的構造方法,以防子類省略super關鍵字時出現錯誤

3.2.2 阻止繼承:final

有些時候一些類為了防止類或方法等中的資訊遭到篡改,所以不讓其他類繼承,例如String類;則使final關鍵字.

final 關鍵字宣告類可以把類定義為不能繼承的,即最終類;或者用於修飾方法,該方法不能被子類重寫。

3.2.2.3 抽象類和介面

抽象類

抽象類是將一些類中公用的屬性和方法抽象到一個單獨的類中,這個類作為其他類的基類。

public abstract class Person {

    private  String name;
    //建構函式
    public Person(){}
    //抽象方法
    public abstract String   Person1e();
    //非抽象方法
    public String  Personlew(){
        return null;
    }
}

抽象類的特點:

  • 抽象類不能被例項化
  • 抽象類中可以包含抽象方法和非抽象方法,繼承抽象類後的物件可以直接訪問抽象類的非抽象方法
  • 抽象類的修飾符必須是public protected 這些,因為抽象類是必須要可以被繼承的
  • 抽象類中可以有構造方法,但是不能建立物件

抽象類使用的場景:

  • 當擁有一些方法,並且有一些需要實現,有一些不需要實現的,可以使用抽象類
  • 想要捕捉一些類的通用特性的時候,可以使用抽象類

 介面

介面(英文:Interface),在JAVA程式語言中是一個抽象型別,是抽象方法的集合,介面通常以interface來宣告。一個類通過繼承介面的方式,從而來繼承介面的抽象方法。

介面中裡面包含的都是抽象方法和常量,一個類如果想要實現介面,必須實現所有的方法,否則要定義為抽象類。

/* 檔名 : Animal.java */
interface Animal {
   public void eat();
   public void travel();
}
/* 檔名 : MammalInt.java */
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

介面特性

  • 介面中每一個方法也是隱式抽象的,介面中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)。
  • 介面中可以含有變數,但是介面中的變數會被隱式的指定為 public static final 變數(並且只能是 public,用 private 修飾會報編譯錯誤)。
  • 介面中的方法是不能在介面中實現的,只能由實現介面的類來實現介面中的方法。

抽象類和介面的區別

  • 1. 抽象類中的方法可以有方法體,就是能實現方法的具體功能,但是介面中的方法不行。
  • 2. 抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是 public static final 型別的。
  • 3. 介面中不能含有靜態程式碼塊以及靜態方法(用 static 修飾的方法),而抽象類是可以有靜態程式碼塊和靜態方法。
  • 4. 一個類只能繼承一個抽象類,而一個類卻可以實現多個介面。

 

介面的繼承

一個介面能繼承另一個介面,和類之間的繼承方式比較相似。介面的繼承使用extends關鍵字,子介面繼承父介面的方法。

介面可以多繼承,public interface Hockey extends Sports, Event

 

3.2.4 物件克隆

什麼是克隆?

有一個物件A,在某一時刻A中已經包含了一些有效值,此時可能 會需要一個和A完全相同新物件B,並且此後對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的物件,但B的初始值是由A物件確定的。在 Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現clone方法是其中最簡單,也是最高效的手段。 

使用clone方法的必要條件:

  1. 克隆的類必須要實現Cloneable介面,並且重寫clone方法(實現Cloneable介面只是一種標誌,真正重寫的是在Object類中的clone方法),如果沒有實現介面,就會丟擲異常CloneNotSupportedException。
  2. 使用public訪問修飾符重新定義clone方法

深克隆與淺克隆 

淺克隆:

淺克隆就是複製一個物件的複本.若只需要複製物件的欄位值(對於基本資料型別,如:int,long,float等,則複製值;對於複合資料型別僅複製該欄位值,如陣列變數則複製地址,對於物件變數則複製物件的reference

深克隆:

深克隆就是不但複製物件的基本型別的值,也要將該物件的引用型別的值也全部克隆過來,成為完全獨立的完整的物件。

深克隆的方式有三種:序列化與反序列化、json轉換、手動賦值(推薦,效率最高)

下面是深克隆與淺克隆的例項(使用的是手動賦值的方式):

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class Teacher implements Cloneable{
    private String name;
    private double salary;
    private Date hireDay;
    private String[] range;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getHireDay() {
        return hireDay;
    }

    public void setHireDay(Date hireDay) {
        this.hireDay = hireDay;
    }

    public String[] getRange() {
        return range;
    }

    public void setRange(String[] range) {
        this.range = range;
    }

    public Teacher(String name, double salary, Date hireDay, String[] range) {
        this.name = name;
        this.salary = salary;
        this.hireDay = hireDay;
        this.range = range;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", hireDay=" + hireDay +
                ", range=" + Arrays.toString(range) +
                '}';
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Teacher teacher = null;
        teacher = (Teacher)super.clone();
        //加上這句話的話,就將陣列進行了深克隆
        //teacher.setRange(teacher.getRange().clone());
        return teacher;
    }

    public static void main(String[] args) throws Exception{
        String arr[] = {"1","2","2"};
        Teacher teacher = new Teacher("張三",1200,new Date(),arr);
        System.out.println("teacher:"+teacher.toString());
        Teacher teacher1 = (Teacher)teacher.clone();
        System.out.println("teacher1:"+teacher1.toString());
        teacher1.setHireDay(new SimpleDateFormat("YYYY-mm-dd").parse("2014-6-7"));
        //改變了陣列後,如果直接繼承父類的方法,淺克隆的是陣列地址
        teacher1.getRange()[0]="5";
        teacher1.setRange(teacher1.getRange());
        System.out.println("teacher:"+teacher.toString());
        System.out.println("teacher1:"+teacher1.toString());
    }
}

3.2.5 內部類

在 Java 中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、區域性內部類、匿名內部類和靜態內部類。

1、成員內部類

成員內部類是最普通的內部類,它的定義為位於另一個類的內部,形如下面的形式:

class Circle {
    double radius = 0;
     
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     //內部類
        public void drawSahpe() {
            System.out.println("drawshape");
        }
    }
}

成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。如果內部類與外部類的名稱相同的情況下,想要呼叫外部類的成員要使用【外部類.this.成員】。

public class Car {
        private String name;
        private String code;

        public class Type{
            private String name;
            private String g2;

            public void methid(){
                System.out.println(name);
                System.out.println(Car.this.name);
            }
        }
    }

在外部類中如果要訪問成員內部類的成員,必須先建立一個成員內部類的物件,再通過指向這個物件的引用來訪問:

class Circle {
    private double radius = 0;
 
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   //必須先建立成員內部類的物件,再進行訪問
    }
     
    private Draw getDrawInstance() {
        return new Draw();
    }
     
    class Draw {     //內部類
        public void drawSahpe() {
            System.out.println(radius);  //外部類的private成員
        }
    }
}

成員內部類是依附外部類而存在的,也就是說,如果要建立成員內部類的物件,前提是必須存在一個外部類的物件。建立成員內部類物件的一般方式如下:

建立靜態內部類物件的一般形式為: 外部類類名.內部類類名 xxx = new 外部類類名.內部類類名()

建立成員內部類物件的一般形式為: 外部類類名.內部類類名 xxx = 外部類物件名.new 內部類類名()

public class Test {
    public static void main(String[] args)  {
        //第一種方式:
        Outter outter = new Outter();
        Outter.Inner inner = outter.new Inner();  //必須通過Outter物件來建立
         
        //第二種方式:
        Outter.Inner inner1 = outter.getInnerInstance();
    }
}
 
class Outter {
    private Inner inner = null;
    public Outter() {
         
    }
     
    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    class Inner {
        public Inner() {
             
        }
    }
}

2.區域性內部類

區域性內部類是定義在一個方法或者一個作用域裡面的類,它和成員內部類的區別在於區域性內部類的訪問僅限於方法內或者該作用域內。注意: 區域性內部類就像是方法裡面的一個區域性變數一樣,是不能有 public、protected、private 以及 static 修飾符的。

class People{
    public People() {
         
    }
}
 
class Man{
    public Man(){
         
    }
     
    public People getWoman(){
        class Woman extends People{   //區域性內部類
            int age =0;
        }
        return new Woman();
    }
}

3.匿名內部類(重點!!)

匿名內部類應該是平時我們編寫程式碼時用得最多的,在編寫事件監聽的程式碼時使用匿名內部類不但方便,而且使程式碼更加容易維護。

public interface ParentInterface {
    public void method();
}
public  class Person {
    private ParentInterface parentInterface ;

    public ParentInterface getParentInterface() {
        return parentInterface;
    }

    public void setParentInterface(ParentInterface parentInterface) {
        this.parentInterface = parentInterface;
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
       
        person.setParentInterface(new ParentInterface() {
            public void method() {
                System.out.println("hh");
            }
        });

    }
}

4.靜態內部類

靜態內部類也是定義在另一個類裡面的類,只不過在類的前面多了一個關鍵字static。靜態內部類是不需要依賴於外部類的,這點和類的靜態成員屬性有點類似,並且它不能使用外部類的非static成員變數或者方法,這點很好理解,因為在沒有外部類的物件的情況下,可以建立靜態內部類的物件,如果允許訪問外部類的非static成員就會產生矛盾,因為外部類的非static成員必須依附於具體的物件。

建立靜態外部類的方式:外部類類名.內部類類名 xxx = new 外部類類名.內部類類名()

public class Car {
    private static  String name;
    private String code;
    private static void   kk(){
    }
    public void CarType(){
       new Type().methid();
    }

    public static  final class Type{
        private String name;
        private String g2;

        public void methid(){
            System.out.println(Car.name);//可以使用
         //   System.out.println(Car.this.code);//報錯,不能直接引用外部的非靜態成員。
        }
    }
}

3.3 多型

多型是同一個行為具有多個不同表現形式或形態的能力。例如同樣是動物的兔子和魚,都有著運動的行為,但是兩種動物的行為方式不同,這就是多型。

多型的優點

  • 1. 消除型別之間的耦合關係
  • 2. 可替換性
  • 3. 可擴充性
  • 4. 介面性
  • 5. 靈活性
  • 6. 簡化性

 

多型存在的三個必要條件

  • 繼承
  • 重寫
  • 父類引用指向子類物件

 多型一般分為兩種:重寫式多型和過載式多型。

過載式多型,也叫編譯時多型。也就是說這種多型再編譯時已經確定好了。過載大家都知道,方法名相同而引數列表不同的一組方法就是過載。在呼叫這種過載的方法時,通過傳入不同的引數最後得到不同的結果。

重寫式多型,也叫執行時多型。這種多型通過動態繫結(dynamic binding)技術來實現,是指在執行期間判斷所引用物件的實際型別,根據其實際的型別呼叫其相應的方法。也就是說,只有程式執行起來,你才知道呼叫的是哪個子類的方法。

向上轉型

子類引用的物件轉換為父類型別稱為向上轉型。通俗地說就是是將子類物件轉為父類物件。此處父類物件可以是介面

 

public class Animal {
    public void eat(){
        System.out.println("animal eatting...");
    }
}
 
public class Cat extends Animal{
 
    public void eat(){
 
        System.out.println("我吃魚");
    }
}
 
public class Dog extends Animal{
 
    public void eat(){
 
        System.out.println("我吃骨頭");
    }
 
    public void run(){
        System.out.println("我會跑");
    }
}
 
public class Main {
 
    public static void main(String[] args) {
 
        Animal animal = new Cat(); //向上轉型
        animal.eat();
 
        animal = new Dog();
        animal.eat();
    }
 
}
 
//結果:
//我吃魚
//我吃骨頭

轉型過程中需要注意的問題

  • 向上轉型時,子類單獨定義的方法會丟失。比如上面Dog類中定義的run方法,當animal引用指向Dog類例項時是訪問不到run方法的,animal.run()會報錯。
  • 子類引用不能指向父類物件。Cat c = (Cat)new Animal()這樣是不行的。

向上轉型的好處

  • 減少重複程式碼,使程式碼變得簡潔。
  • 提高系統擴充套件性。

向下轉型

與向上轉型相對應的就是向下轉型了。向下轉型是把父類物件轉為子類物件

向下轉型注意事項

  • 向下轉型的前提是父類物件指向的是子類物件(也就是說,在向下轉型之前,它得先向上轉型)
  • 向下轉型只能轉型為本類物件(貓是不能變成狗的)。