接口與內部類


接口

  接口是一種與類相似的結構,只包含常量和抽象方法。它的目的是指明相關或者不相關的多個對象的共同行為。例如,使用正確的接口,可以指明這些對象是可比較的、可食用的以及可克隆的。接口是對類的一組需求描述,這些類要遵從接口描述的統一格式進行定義。

  可以使用Edible接口來明確一個對象是否是可食用的。這需要使用implements關鍵字讓對象的類實現這個接口來完成

package edu.uestc.avatar;

/**
 * 可食用接口
 * 在jdk1.8以前,接口中只能包含常量和抽象方法。
 * 定義的方法默認就是public abstract
 *
 */
public interface Ediable {
    /**
     * 所有可食用的都具有怎么吃共同的行為
     * 接口中的所有方法自動地屬於public。因此public可以省略
     */
    String howToEat();
}

  Animal類定義了sound方法。這是個抽象方法,將被具體的動物類所實現

package edu.uestc.avatar;

public abstract class Animal {
    public abstract String sound();
}

  Chicken類實現了Edible接口表明小雞是可食用的。當一個類實現一個接口時,該類用同樣的簽名和返回值類型實現定義在接口中的所有方法。小雞類也繼承Animal類並實現sound方法。

package edu.uestc.avatar;

public class Chicken extends Animal implements Ediable{

    @Override
    public String sound() {
        return "老雞罵小雞,你是個壞東西,教你咯咯咯,你偏嘰嘰嘰。。。。";
    }

    @Override
    public String howToEat() {
        return "用啤酒炸雞......";
    }

}

  Tiger是動物但不可食用。繼承自Animal類。

package edu.uestc.avatar;

public class Tiger extends Animal{
    @Override
    public String sound() {
        return "兩只老虎跑的快,跑得快";
    }
}

  Fruit類實現Edible。因為它不實現howToEat方法,所以Fruit類必須為abstract的,Fruit的子類必須實現howToEat方法(Apple類和Orange類)

package edu.uestc.avatar;

/**
 * 實現接口:implements,某個類實現了某個接口,就代表這個是滿足這個接口規范的這一類事物
 * 要求實現該接口所有的抽象方法
 */
public abstract class Fruit implements Ediable{
}

package edu.uestc.avatar;

public class Orange extends Fruit{

    @Override
    public String howToEat() {
    return "將orange榨汁";
    }
}

package edu.uestc.avatar;

//這兒將Apple換為Banana,道理一樣
public class Banana extends Fruit{
    @Override
    public String howToEat() {
        return "apple...";
    }
}
    

測試類

package edu.uestc.avatar;

public class EidableTest {
    public static void main(String[] args) {
        Object[] instances = {new Tiger(),new Chicken(),new Orange(),new Banana()};
        for(Object obj : instances) {
            if(obj instanceof Ediable) 
                System.out.println(((Ediable)obj).howToEat());
            
            if(obj instanceof Animal)
                System.out.println(((Animal)obj).sound());
        }
    }
}

Comparable接口

  現在假設希望使用Arrays類的sort方法對Circle對象數組進行排序,Circle類就必須實現Comparable接口,需要實現里面的compareTo方法。假設希望根據圓的面積進行比較。如果第一個圓的面積小於第二個圓的面積就返回-1,如果相等就返回0,否則返回-1.

package edu.uestc.avatar.demo;

public class Circle implements Comparable<Circle>,Cloneable{
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
    
    public double getArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public String toString() {
        return "Circle [radius=" + radius + ",area: " +getArea()+ "]";
    }

    /**
     * 比較規則:如果當前對象比circle小,返回小於0的整數,如果相等,返回0,如果大於circle,返回一個大於0的整數
     */
    @Override
    public int compareTo(Circle circle) {
        return this.radius == circle.radius ? 0 :
            this.radius < circle.radius ? -1 : 1;
    }

    /**
     * 覆蓋父類的clone方法,以便子類實例可供調用
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Cloneable接口

  當拷貝一個變量時,原始變量和拷貝變量引用同一個對象,也就是說,改變一個變量所引用的對象將會對另一個變量產生影響;如果創建一個對象的新copy,它的初始狀態和原始對象一樣,但以后將可以各自改變各自的狀態,那就需要使用clone方法

Circle copy = circle.clone();
copy.getArea();

  不過,事情並沒有這么簡單。clone方法是Object類的一個protected方法,也就是說,在用戶編寫的代碼中不能直接調用它。只有Employee類才能夠克隆Employee對象。這種限制有一定的道理。這里査看一下Object類實現的clone方法。由於這個類對具體的類對象一無所知,所以只能將各個域進行對應的拷貝。如果對象中的所有數據域都屬於數值或基本類型,這樣拷貝域沒有任何問題。但是,如果在對象中包含了子對象的引用,拷貝的結果會使得兩個域引用同一個子對象,因此原始對象與克隆對象共享這部分信息。

    默認的克隆操作是淺拷貝,它並沒有克隆包含在對象中的內部對象。

    如果進行淺拷貝會發生什么呢?這要根據具體情況而定。如果原始對象與淺克隆對象共享的子對象是不可變的,將不會產生任何問題。也確實存在這種情形。例如,子對象屬於像String類這樣的不允許改變的類,也有可能子對象在其生命周期內不會發生變化,既沒有更改它們的方法,也沒有創建對它引用的方法。

    然而,更常見的情況是子對象可變,因此必須重新定義clone方法,以便實現克隆子對象的深拷貝。在列舉的示例中,hireDay域屬於Date類,這就是一個可變的子對象,

    對於每一個類,都需要做出下列判斷:

  1. 默認的clone方法是否滿足要求。

  2. 默認的clone方法是否能夠通過調用可變子對象的clone得到修補。

  3. 是否不應該使用clone。

    實際上,選項3是默認的。如果要選擇1或2,類必須:

  1. 實現Cloneable接口。

  2. 使用public訪問修飾符重新定義clone方法。

  必須謹慎地實現子類的克隆。

接口與抽象類

接口 抽象類
不考慮java8中default方法的情況下,接口中是沒有實現代碼的實現 抽象類中可以有普通成員方法 ,並且可以定義變量
接口中的方法修飾符號 只能是public 抽象類中的抽象方法可以有public,protected,default
接口中沒有構造方法 可以有構造方法

  選擇:

  1、當我們需要一組規范的方法的時候,我們就可以用接口,在具體的業務中,來對接口進行實現,能達到以不變應對萬變,多變的需求的情況我們只需要改變對應的實現類 。
  2、如果多個實現類中有者相同可以復用的代碼 這個時候就可以在實現類和接口之間,添加一個抽象類,把公共的代碼抽出在抽象類中。然后要求不同實現過程的 子類可以重寫抽象類中的方法,來完成各自的業務。

接口與回調

     回調(callback)是一種常見的程序設計模式。在這種模式中,可以指出某個特定事件發生時應該采取的動作。如在java.swing包中有一個Timer類,可以使用它在給定的事件間隔時發出通告。

如程序中有一個時鍾,請求每秒鍾獲得一個通告,以便更新時鍾的畫面。定時器需要直到調用哪一個方法,並要求傳遞的對象實現了java.awt.ActionListner接口.

public interface ActionListener extends EventListener {
   //回調方法,ActionEvent提供了事件的相關信息 
    public void actionPerformed(ActionEvent e); 
}

  當到達指定時間間隔時,定時器就調用actionPerformed方法。

案例:每10秒鍾打印一條信息”At the tone,the time is ...“

package edu.uestc.avatar.beep;

import javax.swing.JOptionPane;
import javax.swing.Timer;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;

public class BeepDemo {
    public static void main(String[] args) {
        /**
         * 創建一個定時器
         * 每10000毫秒觸發定時器,定時器就會調用該事件里的回調方法
         */
        Timer timer = new Timer(1000, new BeepActionListner());
        //啟動定時器
        timer.start();
        JOptionPane.showMessageDialog(null, "退出定時器");
        System.exit(0);
    }
    
}

class BeepActionListner implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone,the time is " + LocalDateTime.now());
        Toolkit.getDefaultToolkit().beep();
    }
}

內部類

  內部類就是在類的內部定義的類,為什么需要使用內部類:

  1.一個內部類的對象能夠訪問創建它的對象的實現,包括私有數據。

  2. 對於同一個包中的其他類來說,內部類能夠隱藏起來。

  3.匿名內部類可以很方便的定義回調。

  4.使用內部類可以非常方便的編寫事件驅動程序

package edu.uestc.avatar;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;

import javax.swing.Timer;

public class Talking {
    private boolean beep;
    private int interval = 1000;
    
    public Talking(boolean beep, int interval) {
        this.beep = beep;
        this.interval = interval;
    }
    
    public void start() {
        TimerPrintActionListener listener = new TimerPrintActionListener();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }
    /**
     * TimerPrintActionListner位於Talking內部----內部類
     */
    public class TimerPrintActionListener implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("注意,當前時間:" + LocalDateTime.now());
            //內部類可以直接訪問外部類的數據
            if(beep) Toolkit.getDefaultToolkit().beep();
        }
    }
}

  內部類的特殊語法規則

  內部類有一個外圍類的引用,使用外圍類的引用語法:OuterClass.this

public void actionPerformed(ActionEvent e) {
    System.out.println("注意,當前時間:" + LocalDateTime.now());
    if(Talking.this.beep) Toolkit.getDefaultToolkit().beep();
}

  反過來,可以采用下列語法更加明確地編寫內部類對象的構造器:outerObject.new InnerClass(costruction params)

1)成員內部類:定義在類的內部,方法的外部,

       A.特點:a.作為類的一個成員,有4個權限修飾符:public (default) protected private

                b.作為一個類,可以用abstract、final修飾,也有構造器,也可以在類里定義屬性、方法

       B.成員內部類的注意事項:

       a.非靜態成員內部類:

              1)創建對象的方式:先有外部類的對象,再通過外部類對象調用內部類的構造器,格式:外部類對象.new 內部類()

              2)調內部類的屬性,可以用"this."來指明;

              調外部類的不同名屬性,直接調用即可;

              調外部類的同名屬性:外部類的類名.this.同名屬性:表示外部類的當前對象的屬性

              3)不能有靜態的屬性和方法

       b.靜態成員內部類:

              1)創建對象的方式:調用構造器的方式:外部類類名.內部類()

              2)可以有非靜態的屬性和方法

              3)靜態內部類只能調用外部類的靜態屬性、方法,不能調用外部類的非靜態屬性、方法

package edu.uestc.avatar;
/**
 * 只是為了把一個類隱藏在另外一個類的內部,並不需要內部類引用外圍對象,可以把內部類聲明為static的,以便取消產生的引用
 * 查找數字中的最大值和最小值
 * @author Adan
 *
 */
public class ArrayAlg {
    public static Pair getMinAndMax(int[] list) {
        int min = list[0], max = list[0];
        for(int i = 1; i < list.length; i++) {
            if(min > list[i]) min = list[i];
            if(max < list[i])max = list[i];
        }
        return new Pair(min, max);
    }
    
    public static class Pair{
        private int min;
        private int max;
        public Pair(int min,int max) {
            this.min = min;
            this.max = max;
        }
        public int getMin() {
            return min;
        }
        
        public int getMax() {
            return max;
        }
    
    }
}

       C.成員內部類的優勢:成員內部類作為外部類的成員,可以直接訪問外部類的私有屬性。

2)局部內部類:定義在方法的內部,對於局部內部類我們常常使用一個方法,得到一個接口實現類的對象。局部內部類的優勢:通過方法非常方便的得到一個接口實現類的對象。

package edu.uestc.avatar;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;

import javax.swing.Timer;

import edu.uestc.avatar.Talking.TimerPrintActionListener;

/**
 * 局部內部類
 *         發現:前面的TimerPrintActionListener類只是在Talking類的start()方法內部使用,可以使用局部內部類
 *
 */
public class LocalInnerClassTalking {
    private boolean beep;
    private int interval = 1000;
    
    public LocalInnerClassTalking(boolean beep, int interval) {
        this.beep = beep;
        this.interval = interval;
    }
    
    public void start() {
        class TimerPrintActionListener implements ActionListener{
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("注意,當前時間:" + LocalDateTime.now());
                //內部類可以直接訪問外部類的數據
                if(beep) Toolkit.getDefaultToolkit().beep();
            }
        }
        TimerPrintActionListener listener = new TimerPrintActionListener();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }
}

       注意:匿名內部類通過使用"new 接口(){}"的方式用其隱含實現一個接口或抽象類,實現的部分寫在大括號內。

package edu.uestc.avatar;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;

import javax.swing.Timer;

public class AnonymousInnerClassTalking {
    private int interval;
    private boolean beep;
    public AnonymousInnerClassTalking(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }
    
    public void start() {
        //匿名內部類:new的是實現了ActionListener接口類的實例,該類沒有名字
        Timer timer = new Timer(interval, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("注意,當前時間:" + LocalDateTime.now());
                //內部類可以直接訪問外部類的數據
                if(beep) Toolkit.getDefaultToolkit().beep();
            }
        });
        timer.start();
    }
}

Java8改進的接口

     在jdk1.8以前,接口里只能定義常量(public static final)和抽象方法(public abstract),試想,假設1萬個類實現了一個接口,這時候對接口進行了升級,按照jdk1.7的規則,加方法的話只能加
抽象方法,當加完抽象方法之后1萬個類瞬間編譯報錯。因為必須要重寫抽象方法,在jdk1.8提出接口更新。

  有的時候我們希望1萬個類如果有類想升級那么重寫,有類的不想升級就別重寫了。這時候默認方法方法就來了,用default修飾,默認方法可提供方法實現,而實現該接口的類可以不用實現默認方法

  接口中可以定義靜態方法,讓接口具備了功能, 讓接口來調用(通過接口名.方法名調用)

函數式接口

  接口中有且僅有一個抽象方法的接口即為函數式接口,可以使用@FunctionalInterface檢查定義的接口是否是一個函數式接口。函數式接口可以采用lambda表達式。

示例:Rational類

package edu.uestc.avatar.demo;

/**
 * 有理數:a/b,a表示為分子,b表示為分母,分母不能為0
 * @author Adan
 *
 */
public class Rational extends Number implements Comparable<Rational>{
    private static final long serialVersionUID = 1L;
    /*
     * 分子
     */
    private long numerator = 0;
    /**
     * 分母
     */
    private long denominator = 1;
    
    public Rational() {
        this(0,1);
    }
    
    public Rational(long numerator, long denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public long getNumerator() {
        return numerator;
    }

    public void setNumerator(long numerator) {
        this.numerator = numerator;
    }

    public long getDenominator() {
        return denominator;
    }

    public void setDenominator(long denominator) {
        this.denominator = denominator;
    }
    
    /**
     * 兩個有理數相加
     * @param rational 另一個有理數
     * @return 有理數
     */
    public Rational add(Rational rational) {
        long n = numerator * rational.denominator + denominator * rational.numerator;
        long d = denominator * rational.denominator;
        return new Rational(n, d);
    }
    
    /**
     * 兩個有理數相減
     */
    public Rational substract(Rational rational) {
        long n = numerator * rational.denominator - denominator * rational.numerator;
        long d = denominator * rational.denominator;
        return new Rational(n, d);
    }
    
    /**
     * 兩個有理數相乘
     */
    public Rational multiply(Rational rational) {
        long n = numerator * rational.numerator;
        long d = denominator * rational.denominator;
        
        return new Rational(n, d);
    }
    
    /**
     * 兩個有理數相除
     */
    public Rational divide(Rational rational) {
        return multiply(new Rational(denominator,rational.numerator));
    }

    @Override
    public int compareTo(Rational o) {
        if(substract(o).numerator == 0)
            return 0;
        else if(substract(o).numerator < 0)
            return -1;
        else
            return 1;
    }

    @Override
    public int intValue() {
        return (int)doubleValue();
    }

    @Override
    public long longValue() {
        return (long)doubleValue();
    }

    @Override
    public float floatValue() {
        return (float)doubleValue();
    }

    @Override
    public double doubleValue() {
        return this.numerator * 1.0 / this.denominator;
    }

    @Override
    public String toString() {
        if(denominator == 1)
            return numerator + "";
        else if(numerator == 0)
            return 0 + "";
        else if(numerator == denominator)
            return 1 + "";
        else
            return  numerator + "/" + denominator;
    }
    @Override
    public boolean equals(Object obj) {
        return substract((Rational)obj).getNumerator() == 0;
    }
    
} 

類的設計原則

  • 內聚性
  • 一致性
  • 封裝性
  • 清晰性
  • 完整性
  • 實例和靜態
  • 繼承與聚合
  • 接口和抽象類

練習:使用接口組裝電腦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM