资讯专栏INFORMATION COLUMN

(Thinking in Java)第9章 接口

CoorChice / 2039人阅读

摘要:但如果导出类还有抽象方法,那这个类还应该加上声明为抽象类。并且接口具有继承的一系列特点,如向上转型等等。接口中的方法是自动是的。

Thinking in Java 好书全是干货 一、抽象类和抽象方法

抽象方法:这种方法只有声明而没有方法体,下面是抽象方法生命所采用的语法

abstract void f();

包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的,并且编译器不会允许直接创建一个抽象类对象,正确的做法是从这个抽象类继承并为抽象方法完成定义,这样就可以创建这个类的对象。但如果导出类还有抽象方法,那这个类还应该加上abstract声明为抽象类。简单的使用如下

package tij.interfacedemo;

public class Test {
    public static void main(String[] args) {
        new Wind().play(Note.MIDDLE_A);
    }
}
enum Note{
    MIDDLE_A,MIDDLE_B,MIDDLE_C;
}
abstract class Instrument{//抽象父类
    private int i;//
    public abstract void play(Note n);
    public String what(){
        return "Instrument";
    }
    public abstract void adjust();
}
class Wind extends Instrument{
    public void play(Note n){
        System.out.println("Wind.play() "+n);
    }
    public String what(){
        return "wind";
    }
    public void adjust(){
        
    }
}

其实可以发现和普通继承没什么区别。

二、接口

接口(interface)是一个完全抽象的类,没有任何具体实现方法,允许创建者创建方法的方法名、参数列表以及返回类型,但没有任何方法体。接口体现的思想是:“实现了这个接口的类看起来都像这样”。并且接口具有继承的一系列特点,如向上转型等等。
接口可以加public关键字(如果要加也只能加public,不能加private和protected),不添加就是默认访问权限(包访问权限)。接口中的方法是自动是public abstract的。接口也可以包含域(即引用变量或基本变量),并且自动是static final的,并且也不能被private和protected修饰。如下例:

package tij.interfacedemo;

public class Test {
    public static void main(String[] args) {
        new Wind().play(Note.MIDDLE_A);
    }
}
enum Note{
    MIDDLE_A,MIDDLE_B,MIDDLE_C;
}
interface Instrument{//
    int i=5;//
    void play(Note n);
    String what();
    void adjust();
}
class Wind implements Instrument{
    public void play(Note n){
        System.out.println("Wind.play() "+n);
    }
    public String what(){
        return "wind";
    }
    public void adjust(){
        
    }
}
三、完全解耦

这是接口的一个很好的功能
在继承中,如果一个方法接受一个类的实例作为参数,那么你可以用这个类或子类的实例当做传入参数,如下例:

package tij.interfacedemo;

import java.util.Arrays;

public class Test {
    static void process(Processor p, Object s) {
        System.out.println("Using Processor:" + p.name());
        System.out.println(p.process(s));
    }

    static String s = "Disagreement with beliefs is by definition incorrect";

    public static void main(String[] args) {
        process(new Upcase(), s);
        process(new Downcase(), s);
        process(new Splitter(), s);
    }
}

class Processor {
    public String name() {
        return getClass().getSimpleName();
    }

    Object process(Object input) {
        return input;
    }
}

class Upcase extends Processor {
    String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

class Downcase extends Processor {
    String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

class Splitter extends Processor {
    String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}

在本例中,Test.process方法可以接受一个Processor以及其子类的实例对象,然后对一个Object的对象s进行操作,根据传入的Processor不同,进行的操作也不同,这种方法体现了策略设计模式,这类方法包含要执行的固定部分(s),策略包含变化的部分(p)。但是在这个例子中要注意两个与本章不相关的事儿:1.应该想想为什么子类明明没有重写name方法,但输出却还是像重写了一样。2.子类重写了process方法,但返回值不是Object而是String,重写方法必须要是与类方法的返回类型的相同或者是其子类。

但是假如现在我们发现了一系列滤波器类,如下:

class Waveform{//代表波形
    private static long counter;
    private final long id=counter++;
    public String toString(){
        return "Waveform"+id;
    }
}

class Filter{//滤波器
    public String name(){
        return getClass().getSimpleName();
    }
    public Waveform process(Waveform input){
        return input;
    }
}

class LowPass extends Filter{
    double cutoff;//设定低通滤波器的滤波上限
    public LowPass(double cutoff){
        this.cutoff=cutoff;
    }
    public Waveform process(Waveform input){
        return input;
    }
}

class HighPass extends Filter{
    double cutoff;//设置高通滤波器的滤波下限
    public HighPass(double cutoff){
        this.cutoff=cutoff;
    }
    public Waveform process(Waveform input){
        return input;
    }
}
class BandPass extends Filter{
    double lowCutoff,highCutoff;//设置带通滤波器的滤波上下限
    public BandPass(double lowCut,double highCut){
        this.lowCutoff=lowCut;
        this.highCutoff=highCut;
    }
    public Waveform process(Waveform input){
        return input;
    }
}

那么如果想把各种滤波器传给Test.process方法,那这会被编译器阻止,因为process方法只接受processor类以及子类,那如果希望运用了策略设计模式的Test.process方法仍能接受滤波器,那么首先就需要把processor改变成接口:

interface Processor {
    String name() ;

    Object process(Object input) ;
}

abstract class StringProcessor implements Processor{
    public String name(){
        return getClass().getSimpleName();
    }
}

class Upcase extends StringProcessor {
    public String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

class Downcase extends StringProcessor {
    public String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

class Splitter extends StringProcessor {
    public String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}


class Waveform{//代表波形
    private static long counter;
    private final long id=counter++;
    public String toString(){
        return "Waveform"+id;
    }
}

但接下来我们发现,滤波器这个类是我们找到的,我们并不能对这么个类内的代码进行修改,如何让新的Test.process还能接受滤波器呢,于是我们可以采用适配器设计模式,代码如下:

class FilterAdapter implements Processor {
    Filter filter;
    public FilterAdapter(Filter filter) {
        this.filter = filter;
    }
    public String name() {
        return filter.name();
    }
    public Waveform process(Object input) {
        return filter.process((Waveform) input);
    }
}

这样传进去这个FilterAdapter适配器就OK了

四、Java中的多重继承

接口的另一个重要的功能就是能实现多重继承

package tij.interfacedemo;

public class Test {
    public static void main(String[] args) {
        Hero superman=new Hero();
        superman.fight();
        superman.fly();
        superman.swim();
    }
}

class ActionCharacter {
    public void fight() {
        System.out.println("fight");
    }
}
interface CanFight {
    void fight();
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}

class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
    public void fly() {
        System.out.println("fly");
    }
    public void swim() {
        System.out.println("swim");
    }

}
五、通过继承来拓展接口

通过继承可以生成一个新的接口,以此来对原来的接口进行拓展;还可以通过继承在新接口中组合多个接口(玩的真花= =)。如下:

interface Monster {
    void menace();
}
interface DangerousMonster extends Monster {
    void destroy();
}
interface Lethal {
    void kill();
}
class DragonZilla implements DangerousMonster {
    public void menace() {}
    public void destroy() {}
}
interface Vampire extends DangerousMonster, Lethal {
    void drinkBlood();
}
class VeryBadVampire implements Vampire {
    public void destroy() {}
    public void menace() {}
    public void kill() {}
    public void drinkBlood() {}
}

接口之间可以继承,可以多继承,可以相互拓展

1.组合接口时的名字冲突

在实现多重继承时,如果不同接口有相同方法怎么办

interface I1 {
    void f();
}
interface I2 {
    int f(int i);
}
interface I3 {
    int f();
}
class C {
    public int f() {
        return 1;
    }
}
class C2 implements I1, I2 {
    public int f(int i) {
        return 1;
    }
    public void f() {}//重载
}
class C3 extends C implements I2{
    public int f(int i) {//重载
        return 0;
    }
}
class C4 extends C implements I3{
    //可以不必重写int f()方法,因为从C那里继承过来了,但C那里的f()必须是public的
}
//class C5 extends C implements I1{
//    //错误
//}
//interface I4 extends I1,I3{
//    //错误
//}

因此在设计接口的时候请尽量避免这点

六、适配接口

接口的一种常用的方法就是之前提到的策略设计模式,如编写一个进行某些操作的方法,而这个方法接收一些接口,就是说如果你的对象遵循我的接口那就能用。
下面的例子中使用了scanner,这个类需要接收一个readable对象,其中arg用来存储要输入的字符串的

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Random;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner s = new Scanner(new RandomWords(10));
        while (s.hasNext()) {
            System.out.println(s.next());
        }
    }
}
class RandomWords implements Readable {
    private static Random rand = new Random(47);
    private static final char[] capitals = "ABCDEFGHIGKLMNOPQRST".toCharArray();
    private static final char[] lowers = "ABCDEFGHIGKLMNOPQRST".toLowerCase()
            .toCharArray();
    private static final char[] vowels = "aeiou".toCharArray();
    private int count;
    public RandomWords(int count) {
        this.count = count;
    }
    public int read(CharBuffer arg) throws IOException {
        if (count-- == 0) {
            return -1;
        }
        arg.append(capitals[rand.nextInt(capitals.length)]);
        for (int i = 0; i < 4; i++) {
            arg.append(vowels[rand.nextInt(vowels.length)]);
            arg.append(lowers[rand.nextInt(lowers.length)]);
        }
        arg.append(" ");
        return 10;// Number of characters appended
    }

}

接口的还有一个功能就是之前我们提到过的适配器设计模式,在这里再举另一个与scanner相关的例子。
首先假如我们现在有一个如下的类:

class RandomDoubles{
    private static Random rand =new Random(47);
    public double next(){
        return rand.nextDouble();
    }
}

希望让他作为一个readable传入给scanner,来生成随机的double类型数,这需要装饰设计模式

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Random;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner s=new Scanner(new AdaptedRandomDoubles(7));
        while(s.hasNext()){
            System.out.println(s.next());
        }
    }
}
class AdaptedRandomDoubles extends RandomDoubles implements Readable {
    private int count;
    public AdaptedRandomDoubles(int count){
        this.count=count;
    }
    public int read(CharBuffer cb) throws IOException {
        if(count--==0){
            return -1;
        }
        String result=Double.toString(this.next());
        cb.append(result);
        return result.length();
    }

}
七、接口中的域

实例变量都是static final的,好了结束

八、嵌套接口

推荐先看完内部类再来看这个

1.类中的接口
class t1 implements A.C,A.B{//访问不到A.D
    public void f() {
    }
}

class A {
    interface B {
        void f();
    }
    public class BImp implements B {
        public void f() {}
    }
    private class BImp2 implements B {
        public void f() {}
    }

    public interface C {
        void f();
    }
    class CImp implements C {
        public void f() {}
    }

    private interface D {
        void f();
    }
    private class DImp implements D {
        public void f() {}
    }
    public class DImp2 implements D {
        public void f() {}
    }
}
2.接口中的接口
interface E{
    interface G{
        //默认为public
        void f();
    }
}
class t2 implements E.G{
    public void f() {
    }
}
九、接口与工厂

接口时实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式

package tij.interfacedemo;
public class Test {
    public static void main(String[] args) {
        Factories.serviceConsumer(new Implementation1Factory());
        Factories.serviceConsumer(new Implementation2Factory());
    }
}

interface Service {
    void method1();
    void method2();
}
interface ServiceFactory {
    Service getService();
}
class Implementation1 implements Service {
    Implementation1() {}
    public void method1() {System.out.println("Implementation1 method1");}
    public void method2() {System.out.println("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
    public Service getService() {
        return new Implementation1();
    }
}
class Implementation2 implements Service {
    Implementation2() {}
    public void method1() {System.out.println("Implementation2 method1");}
    public void method2() {System.out.println("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
    public Service getService() {
        return new Implementation1();
    }
}
class Factories{
    static void serviceConsumer(ServiceFactory fact){
        Service s = fact.getService();
        s.method1();
        s.method2();
    }
}

这样设计的好处就是将方法的实现实例对象的生成分离开来,而且在使用Factories.serviceConsumer的时候不需要特定指定是哪种Service.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67585.html

相关文章

  • Thinking in Java11 持有对象

    摘要:迭代器解决了这个问题。删除后于是我们可以写一个方法,接受一个类型,然后让他调用方法,这就不需要考虑这个是个还是了,也就是说,可以将遍历容器的操作与序列底层的结构分离,迭代器统一了对容器类的访问方式。十二和两种遍历的方法,与迭代器方法。 一、泛型和类型安全的容器 package tij.hoding; import java.util.ArrayList; public class ...

    v1 评论0 收藏0
  • Thinking in Java14 类型信息

    摘要:通过运行时类型信息,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。编程应该尽量面向接口编程,应该对类型信息尽量的少了解二对象看书,书上写得好静态语句块在这个类被加载的时候运行。 一、为什么需要RTTI Run-Time Type Information。通过运行时类型信息,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。编程应该尽量...

    tomorrowwu 评论0 收藏0
  • Thinking in Java10 内部类

    摘要:内部类中也可以取得这个外部类对象引用。创建成员内部类对象的时候需要外部类对象。另外在方法中的内部类不能加等权限修饰符,只能加和修饰符。可以在接口内部定义内部类,而且他们即使没有修饰,也会自动变成的。 Thinking in Java捞干货,写笔记 一、成员内部类 1.最基本使用 public class Demo { class Contents{ privat...

    Brenner 评论0 收藏0
  • Thinking in Java15 泛型

    摘要:反省发放需要将泛型参数列表之余返回值之前杠杆利用类型参数推断现在可以了,别。现在可以显式的类型说明这段代码没毛病的,可变参数与泛型方法没啥好说用于的泛型方法方法可以透明的应用于实现了泛型接口的类。但是这个却可以指向各种是的对象。 二、简单泛型 2.一个堆栈类 package tij.generic; public class Test { public static void...

    tinyq 评论0 收藏0
  • Thinking in Java13 字符串

    摘要:四上的操作看五格式化输出运用和语言很相似和是等价的哟类格式化说明符转换六正则表达式网上教程学七扫描输入新增了类。 一、不可变String String类型的对象是不可变的,所有的改变实际上都是创建了一个新的String对象,另外当String作为传入参数的时候,其实实际上传入的是这个引用的一个拷贝,这个方法结束了之后这个传入的引用也就消失了,原来的那个String不会受到方法内的影响而...

    feng409 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<