Java编程思想读书笔记——第九章:接口

第九章 接口

  • 接口和实现类
  • 抽象类是介于普通的类和接口之间的中庸之道,抽象类也是一种重要的工具,你不可能总是使用纯接口

9.1 抽象类和抽象方法

  • 抽象方法声明的语法:
abstract void f();
  • 如果一个类有一个或者多个抽象方法,该类必须限定为抽象的,否则就报错
  • abstract关键字最重要的作用就是,告诉开发者,哪些方法是必须重新定义的,也就是重写
  • 也可以创建一个没有任何抽象方法的抽象类,比如阻止创建这个类的对象
  • 直接看练习

练习1、修改第八章练习9中的Rodent,使其成为一个抽象类

// 声明抽象类,继承此抽象类,然后实现类去实现抽象方法
abstract class Rodent {   
    public abstract void hop();   
    public abstract void scurry();   
    public abstract void reproduce(); 
} 
 
class Mouse extends Rodent {   
    public void hop() { print("Mouse hopping"); }   
    public void scurry() { print("Mouse scurrying"); }   
    public void reproduce() { print("Making more Mice"); }   
    public String toString() { return "Mouse"; } 
} 

练习2、创建一个不包含任何抽象方法的抽象类,并验证我们不能为该类创建任何对象

abstract class NoAbstractMethods {   
    void f() { System.out.println("f()"); } 
} 
 
public class E02_Abstract { 
    public static void main(String args[]) {     
        new NoAbstractMethods();   
    } 
}
// 编译器报错,无法直接为抽象类创建对象

练习3、创建一个基类,让它包含抽象方法print(),并在导出类中覆盖该方法,覆盖后的方法版本可以打印导出类中定义的某个整型变量的值。在定义该变量之处,赋予它非零值。在基类的构造器中调用这个方法。现在,在main方法中,创建一个导出类的对象,然后调用它的print()方法。请解释发生的情形

abstract class BaseWithPrint { 
  
    public BaseWithPrint() { 
        print(); 
    }   

    public abstract void print(); 
} 
 
class DerivedWithPrint extends BaseWithPrint {   
    int i = 47;   
    public void print() {     
        System.out.println("i = " + i);   
    } 
} 
 
public class E03_Initialization {   
    public static void main(String args[]) {     
        DerivedWithPrint dp = new DerivedWithPrint();     
        dp.print();   
    } 
}
// 运行结果
i = 0 
i = 47

// 在执行父类构造器的时候,i值并没有被赋值,所以输出了0

练习4、创建一个不包含任何方法的抽象类,从它那里导出一个类,并添加一个方法,创建一个静态方法,它可以接受指向基类的引用,将其向下转型到导出类,然后再调用该静态方法。在main()中,展现它的运行情况。然后,为基类中的方法加上abstract声明,这样就不再需要进行向下转型

abstract class NoMethods {} 
 
class Extended1 extends NoMethods {   
    public void f() {     
        System.out.println("Extended1.f");   
    } 
} 
 
abstract class WithMethods {   
    abstract public void f(); 
} 
 
class Extended2 extends WithMethods {   
    public void f() {     
        System.out.println("Extended2.f");   
    } 
} 
 
public class E04_AbstractBase {   
    public static void test1(NoMethods nm) {     
        // Must downcast to access f():  
        ((Extended1)nm).f();   
    }   
    public static void test2(WithMethods wm) {     
        // No downcast necessary:     
        wm.f();   
    }   
    public static void main(String args[]) {     
        NoMethods nm = new Extended1();     
        test1(nm);     
        WithMethods wm = new Extended2();     
        test2(wm);   
    } 
} 
// 运行结果
Extended1.f 
Extended2.f

// 声明了抽象方法,那么就必须去重写
// 声明了抽象方法,就不必向下转型了
// 这也好理解,因为我们声明的是基类的引用,但是基类中并没有此方法,所以想要调用f()方法,必须向下转型

9.2 接口

  • interface提供一个完全抽象的类,接口只提供形式,未提供具体实现,接口用来建立类与类之间的协议
  • 之前总认为接口中是不能定义变量的,其实接口可以包含域,但是是隐式的static final

练习5、在某个包内创建一个接口,内含三个方法,然后在另一个包中实现接口

// 在不同包下可以实现接口

练习6、证明接口内修改的所有的方法都是public的

// 接口中的方法默认是public的
// 接口中的方法不需要我们再去加public关键字

练习7、修改第8章中的练习9,使Rodent成为一个接口

// 这个也没什么可以写的
// 创建一个接口,有三个实现类去实现这个接口,然后调用它们

练习8、在polymorphism.Sandwich.java中,创建接口FastFood并添加合适的方法,然后修改Sandwich以实现FastFood接口

// 还是创建一个接口,去实现它的方法

练习9、重构Music5.java,将在Wind、Percussion和Stringed中的公共方法移入一个抽象类中

// 将三个类中的公共方法都放到一个抽象类中
// 而非公共的方法变为抽象方法,让子类去实现

练习10、修改Musci5.java,添加Playable接口,将play()的声明移到Playable中,通过将Playable包括在implements列表中,把Playable添加到导出类中,修改tune(),使它接口Playable

// Playable最重要的作用是消除代码重复

9.3 完全解耦

  • 接口可以在很大程度上使得我们可以编写可复用性更好的代码
  • 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式
  • 接口和继承是一样的,都有多态
  • 想想mvp模式中,只要接口了,就可以声明接口去创建对象,并且在某个方法中传参也能传此接口的引用,最后作用于实现类
  • 整个9.3告诉我们最重要的一点:使用接口可能会更加灵活,比如在方法中传参使用的时候,使用接口可以复用某些方法

// 书中的例子是这样的:

// 一个processor(处理器)基类,两个方法:name()、process()
// 三个子类去继承它,Upcase、Downcase、Splitter
// 在Apply中创建一个process方法:传入processor类型的对象
publc void process(Processor p, Object s)

// 那么现在又有一个Filter(滤波器)基类,也是和上面一样的两个方法
// 三个子类去继承它,LowPass、HighPass、BandPass
// 那么要想复用Apply中的process方法,是不允许传入Filter对象的

// 但是如果改为接口,这个问题就迎刃而解了

练习11、创建一个类,它有一个方法用于接受一个String类型的参数,生成结果是将该参数中每一对字符进行互换,对该类进行适配,使得它可以用于interfaceprocessor.Apply.process();

扫描二维码关注公众号,回复: 3617287 查看本文章
// 书中例子进行修改后的关键代码
public interface Processor {
    String name();
    Object process(Object input);
}

public class Apply {
    public static void process(Processor p, Object s) {
        print("Using Processor" + p.name());
        print(p.process(s));
    }
}
// 此练习的答案
class CharacterPairSwapper {   
    static String swap(String s) {     
        StringBuilder sb = new StringBuilder(s);     
        for(int i = 0; i < sb.length() - 1; i += 2) {       
        char c1 = sb.charAt(i);       
        char c2 = sb.charAt(i + 1);       
        sb.setCharAt(i, c2);       
        sb.setCharAt(i + 1, c1);     
    }     
    return sb.toString();   
    } 
} 
 
class SwapperAdapter implements Processor {   
    public String name() {     
        return CharacterPairSwapper.class.getSimpleName();   
    }   
    public String process(Object input) {     
        return CharacterPairSwapper.swap((String)input);   
    } 
} 
 
public class E11_Swapper {   
    public static void main(String[] args) {     
        Apply.process(new SwapperAdapter(), "1234");     
        Apply.process(new SwapperAdapter(), "abcde");   
    } 
} 
// 运行结果
Using Processor CharacterPairSwapper 
2143 
Using Processor CharacterPairSwapper 
badce 


// 主要还是讲了适配器模式的应用,此例子中也使用到了代理模式

9.4 Java中的多重继承

  • 接口不仅仅只是一种更纯粹形式的抽象类,它的目标比这要高。接口根本没有任何具体实现,没有任何与几口相关的存储
  • Java中不允许多重继承,但是可以实现多个接口
  • 有一个例外,那就是Java允许多个接口继承,以前的我还不知道,练习13是一个很好的诠释

练习12、在Adventure.java中,按照其他接口的样式,增加一个CanClimb接口

interface CanClimb {   
    void climb(); 
} 
 
class Hero2 extends ActionCharacter implements CanFight, CanSwim, CanFly, CanClimb {   
    public void swim() {}   
    public void fly() {}   
    public void climb() {} 
}
// 添加一个方法,然后在Main函数中调用此方法就可以了
void z(CanClimb x) {
    x.climb();
}

练习13、创建一个接口,并从该接口继承两个接口,然后从后面两个接口多重继承第三个接口

interface BaseInterface {   void f(); } 
 
interface IntermediateInterface1 extends BaseInterface {   void f(); } 
 
interface IntermediateInterface2 extends BaseInterface {   void f(); } 
 
interface CombinedInterface extends IntermediateInterface1,IntermediateInterface2 {   
    void f(); 
} 
 
class CombinedImpl implements CombinedInterface {   
    public void f() {     
        System.out.println("CombinedImpl.f()");   
    }    
} 
 
public class E13_Diamond {   
    public static void main(String[] args) {     
        new CombinedImpl().f();   
    } 
}
// 运行结果
CombinedImpl.f() 

9.5 通过继承来扩展接口

// 再次强调一次,这种写法只适用于接口继承
interface Vampire extends DangerousMonster, Lethal {

}

练习14、创建三个接口,每个接口都包含两个方法。继承出一个接口,它组合了这三个接口并添加了一个新方法。创建一个实现了该接口并且继承了某个具体类的类。现在编写四个方法,每一个都接受这四个接口之一作为参数。在main()方法中,创建这个类的对象,并将其传递给这四个方法

interface Interface1 {   void f1();   void g1(); } 
 
interface Interface2 {   void f2();   void g2(); } 
 
interface Interface3 {   void f3();   void g3(); } 
 
interface Multiple extends Interface1, Interface2, Interface3 {   void h(); } 
 
class Concrete {   
    String s;   
    public Concrete(String s) { 
        this.s = s; 
    } 
} 
 
class All extends Concrete implements Multiple { 
    All() { super("All"); }   
    public void h() { print("All.h"); }   
    public void f1() { print("All.f1"); }   
    public void g1() { print("All.g1"); }   
    public void f2() { print("All.f2"); }   
    public void g2() { print("All.g2"); }   
    public void f3() { print("All.f3"); }   
    public void g3() { print("All.g3"); } } 
 
public class E14_InterfaceInheritance {   
    static void takes1(Interface1 i) {     
        i.f1();     
        i.g1();   
    }   
    static void takes2(Interface2 i) {     
        i.f2();     
        i.g2();   
    }   
    static void takes3(Interface3 i) {     
        i.f3();     
        i.g3();   
    }   
    static void takesAll(All a) {     
        a.f1();     
        a.g1();     
        a.f2();     
        a.g2();     
        a.f3();     
        a.g3();     
        a.h();   
    }   
    public static void main(String args[]) {     
        All a = new All();     
        takes1(a);     
        takes2(a);     
        takes3(a);     
        takesAll(a);   
    } 
} 
// 运行结果
All.f1 
All.g1 
All.f2 
All.g2 
All.f3 
All.g3 
All.f1 
All.g1 
All.f2 
All.g2 
All.f3 
All.g3 
All.h 

// 熟练的掌握接口和继承的用法

练习15、将前一个练习修改为:创建一个抽象类,并将其继承到一个导出类当中

abstract class Abstract {   
    String s;   
    public Abstract(String s) { this.s = s; }   
    abstract void af(); 
} 
 
class All2 extends Abstract implements Multiple {   
    All2() { super("All2"); }   
    void af() { print("All.af"); }   
    public void f1() { print("All.f1"); }   
    public void g1() { print("All.g1"); }   
    public void f2() { print("All.f2"); }   
    public void g2() { print("All.g2"); }   
    public void f3() { print("All.f3"); }   
    public void g3() { print("All.g3"); }   
    public void h() { print("All2.h"); } 
}

// 下面的Main类代码就不贴了,大同小异,new一个All2的对象传入调用的方法就行

9.5.1 组合接口时的名字冲突

在打算组合的不同接口中使用相同的方法名通常会造成代码可读性的混乱,尽量避免

// 书中的例子是这样的,这两种写法都将报错:

C中的int f()和I1中的void f()是相同名字的方法,尽管它们的返回类型不同
class C5 extends C implements I1 {}
// 或
I1中的void f()和I3中的int f()是相同名字的方法,尽管它们的返回类型不同
interface I4 extends I1, I3 {}

9.6 适配接口

  • 接口最吸引人的地方就是允许同一个接口有多个不同的具体实现

练习16、创建一个类,它将生成一个char序列,适配这个类,使其可以成为Scanner对象的一种输入

// 此练习的目的是让我们懂得去适配接口
// Scanner类的构造器中接受的是一个Readable接口
// 只要我们写一个类去实现Readable接口并且重写接口中的read方法
// 那Scanner就会自动调用重写后的方法,那么就可以实现输出

9.7 接口中的域

  • 因为放入接口中的任何域都自动是static final的,所以用接口来创建常量组是很便捷的

练习17、证明在接口中的域隐式地是static和final的

interface StaticFinalTest {   
    String RED = "Red"; 
}

// main方法中直接调用是OK的啦
System.out.println("StaticFinalTest.RED = "StaticFinalTest.Red);

9.7.1 初始化接口中的域

  • 接口中定义的域不能是空final,但是可以被非 常量表达式初始化,比如:
public interface RandVals {
    Random RAND = new Random(47);
    int RANDOM_INT = RAND.newInt(10);
}

这些域并不是接口的一部分,它们的值被存储在该接口的静态存储区域内

9.8 嵌套接口

  • 接口可以嵌套在其他类或其他接口中,也可以拥有相应的访问权限
  • 当实现某个接口时,并不需要实现嵌套在其内部的任何接口

9.9 接口与工厂

  • 生成遵循某个某个接口的对象的典型方法就是工厂方法设计模式:
// 简单来说就是一个工厂类的getXXX方法,去new一个产品的实例
// 然后创建一个方法将这个工厂对象传入,对该产品做一系列操作
  • 比如下国际象棋和西洋跳棋,那么如果国际象棋工厂和西洋跳棋共产都继承了游戏工厂,那么它们就可以直接被方法调用,说来说去还是一个多态的应用,这种方式就允许你在不同类型中进行复用代码
void palyGame(GameFactory factory) {
...
}

练习18、创建一个Cycle接口及其Unicycle、Bicycle和Tricycle实现。对每种类型的Cycle都创建相应的工厂,然后编写代码使用这些工厂

interface Cycle {   
    int wheels(); 
} 
 
interface CycleFactory {   
    Cycle getCycle(); 
} 
 
class Unicycle implements Cycle {   
    public int wheels() { 
        return 1; 
    } 
} 
 
class UnicycleFactory implements CycleFactory {   
    public Unicycle getCycle() { 
        return new Unicycle(); 
    } 
} 
 
class Bicycle implements Cycle {   
    public int wheels() { 
        return 2; 
    } 
} 
 
class BicycleFactory implements CycleFactory {   
    public Bicycle getCycle() { 
        return new Bicycle(); 
    } 
} 
 
class Tricycle implements Cycle {   
    public int wheels() { 
        return 3; 
    } 
} 
 
class TricycleFactory implements CycleFactory {   
    public Tricycle getCycle() { 
        return new Tricycle(); 
    } 
} 
 
public class E18_CycleFactories {   

    public static void ride(CycleFactory fact) {      
        Cycle c = fact.getCycle();      
        System.out.println("Num. of wheels: " + c.wheels());   
    }
   
    public static void main(String[] args) {     
        ride(new UnicycleFactory());     
        ride(new BicycleFactory ());     
        ride(new TricycleFactory ());   
    } 
} 

// 运行结果
Num. of wheels: 1 
Num. of wheels: 2 
Num. of wheels: 3

练习19、使用工厂方法来创建一个框架,它可以执行抛硬币和掷骰子功能

interface Tossing { 
    boolean event(); 
} 
 
interface TossingFactory { 
    Tossing getTossing(); 
}

class CoinTossing implements Tossing {  
 
    private int events;   
    private static final int EVENTS = 2;   
    public boolean event() {     
        System.out.println("Coin tossing event " + events);     
        return ++events != EVENTS;   
    } 
} 
 
class CoinTossingFactory implements TossingFactory {  
 
    public CoinTossing getTossing() {     
        return new CoinTossing();   
    } 
}

class DiceTossing implements Tossing {   

    private int events;   
    private static final int EVENTS = 6;   
    public boolean event() {     
        System.out.println("Dice tossing event " + events);     
        return ++events != EVENTS;   
    } 
} 
 
class DiceTossingFactory implements TossingFactory { 
  
    public DiceTossing getTossing() {     
        return new DiceTossing();   
    } 
}  

public class E19_TossingFramework {
   
    public static void simulate(TossingFactory fact) {     
        Tossing t = fact.getTossing();     
        while(t.event())       
            ;   
    }
   
    public static void main(String[] args) {     
        simulate(new CoinTossingFactory());     
        simulate(new DiceTossingFactory());   
    } 
}

9.10 总结

  • 确定接口是理想的选择,因而总该选择接口而不是具体的类,这是一种引诱,陷阱,很多人都掉进了陷阱,只要有可能就去创建接口和工厂,这是一种草率的设计优化

正确的做法:优先选择类而不是接口,如果接口的必须行变得非常明确,那么就进行重构。

猜你喜欢

转载自blog.csdn.net/pengbo6665631/article/details/82670339