接口与内部类

接口

  抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的"抽象类"——接口(interface),接口里不能包含普通方法,接口里所有的方法都是抽象方法。java8对接口进行了改进,允许在接口中定义默认方法及静态方法,默认方法可以提供方法实现,静态方法让接口具备一定功能。

  

  从上图可以看出,同一个类的内部状态数据、各种方法的实现细节完全相同,类是一种具体实现体。而接口定义了一种规范,接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。

  接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学

  让规范和实现分离正是接口的好处,让软件系统的各组件之间面向接口耦合,是一种松耦合的设计。例如主板上提供了PCI插槽,只要一块显卡遵守PCI接口规范,就可以插入PCI插槽内,与该主板正常通信。至于这块显卡是哪个厂家制造的,内部是如何实现的,主板无须关心。

  类似的,软件系统的各模块之间也应该采用这种面向接口的耦合,从而尽量降低各模块之间的耦合,为系统提供更好的可扩展性和可维护性

  因此,接口定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常定义一组公用方法。

接口的定义

  接口是一种与类相似的结构,只包含常量和抽象方法。它的目的是指明相关或者不相关的多个对象的共同行为。例如,使用正确的接口,可以指明这些对象是可比较的、可食用的以及可克隆的。接口是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

  可以使用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.同名属性:表示外部类的当前对象的属性

       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;
    }
    
} 

类的设计原则

  • 内聚性
  • 一致性
  • 封装性
  • 清晰性
  • 完整性
  • 实例和静态
  • 继承与聚合
  • 接口和抽象类

练习:使用接口组装电脑。

posted @ 2021-04-08 22:14  Tiger-Adan  阅读(1138)  评论(0编辑  收藏  举报