Java编程思想笔记——接口

抽象类和抽象方法

抽象方法:abstract void f();包含抽象方法的类叫做抽象类。如果一个类包含抽象方法,该类必须内限定为抽象的。抽象类创建对象是不安全的,编译器不允许其实例化。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这么做(可以选择不做),那么导出类便也是抽象类,且编译器将会强制我们使用abstract关键字来限定这个类。
也可以创建没有抽线方法的抽象类(一个类,让其包含任何abstract方法都显得没有实际意义,而且也想要阻止产生这个类的任何对象)。

创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎么来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易的公共方法沿着继承层次结构向上移动。

接口

interface产生了一个完全抽象的类,根本没有提供任何具体实现,允许确定方法名、参数列表、返回类型,但没有任何方法体。接口只提供了形式,而未提供任何具体实现。
所有实现了该特定接口的类看起来都像这样。接口被用来建立类与类之间的协议。

接口也包含域,但是这些域隐式地是static和final的。
可以选择在接口中显式的将方法声明为public的,但即使你不这么做,他们也是public的。因此,当要实现一个接口时,在接口中被定义的方法必须被定义为public。否则,它们将只能得到默认的包访问权限,这样在方法被继承过程中,其可访问权限被降低了,这是Java编译器所不允许的。

完全解耦

只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要这个方法应用于不在此继承结构中的某个类,就不行。接口可以在很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。

public class Processor {
    public String name(){
        return getClass().getSimpleName();
    }
    Object process(Object input){return input;}
}
public class Upcase extends Processor{
    @Override
    String process(Object input){
        return ((String)input).toUpperCase();
    }
}
public class Downcase extends Processor{
    @Override
    String process(Object input){
        return ((String)input).toLowerCase();
    }
}
public class Splitter extends Processor{
    @Override
    String process(Object input){
        return Arrays.toString(((String)input).split(" "));
    }
}
public class Apply {
    public static void process(Processor p, Object s){
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }

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

输出:
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]

Apply.process()方法可以接受任何参数的Processor,并将其应用到一个Object对象上,然后打印结果。像本例这样,创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而策略包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里,Processor对象就是一个策略。

假设一组电子滤波器:

public class Filter {
    public String name(){
        return getClass().getSimpleName();
    }
    public Waveform process(Waveform input){
        return input;
    }
}
public class LowPass extends Filter{
    double cutoff;
    public LowPass(double cutoff){
        this.cutoff = cutoff;
    }
    @Override
    public Waveform process(Waveform input){
        return input;
    }
}
public class HighPass extends Filter{
    double cutoff;
    public HighPass(double cutoff){
        this.cutoff = cutoff;
    }
    @Override
    public Waveform process(Waveform input){
        return input;
    }
}
public class BandPass extends Filter{
    double lowCutoff,highCutoff;
    public BandPass(double lowCut,double highCut){
        lowCutoff = lowCut;
        highCutoff = highCut;
    }
    @Override
    public Waveform process(Waveform input){
        return input;
    }
}

Filter与Processor具有相同的接口元素,但因为它并非继承自Processor,因此不能将Filter用于Apply.process方法,即便这样可以正常运行。主要是因为Apply.process方法和Processor之间耦合过紧,已经超出了所需要的程度,这就使得应该复用Apply.process的代码时,复用被禁止了。另外还需要注意是它们的输入和输出都是Waveform。

但如果Processor是一个接口,那么这些限制就会变得松动,使得你可以复用结构该接口的Apply.process:

public interface Processor {
    String name();
    Object process(Object input);
}
public class Apply {
    public static void process(Processor p, Object s){
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }
}
public abstract class StringProcessor implements Processor{
    @Override
    public String name() {
        return getClass().getSimpleName();
    }

    @Override
    public abstract Object process(Object input);
    public static String s =
            "If she weighs the same as a duck, she's made of wood";

    public static void main(String[] args) {
        Apply.process(new Upcase(),s);
        Apply.process(new Downcase(),s);
        Apply.process(new Splitter(),s);
    }
}
public class Upcase extends StringProcessor{
    @Override
    public String process(Object input){
        return ((String)input).toUpperCase();
    }
}
public class Downcase extends StringProcessor{
    @Override
    public String process(Object input){
        return ((String)input).toLowerCase();
    }
}
public class Splitter extends StringProcessor{
    @Override
    public String process(Object input){
        return Arrays.toString(((String)input).split(" "));
    }
}

输出:
Using Processor Upcase
IF SHE WEIGHS THE SAME AS A DUCK, SHE’S MADE OF WOOD
Using Processor Downcase
if she weighs the same as a duck, she’s made of wood
Using Processor Splitter
[If, she, weighs, the, same, as, a, duck,, she’s, made, of, wood]

但是,你经常碰到的情况是你无法修改你想要使用的类。例如,在电子滤波器的例子中,类库是被发现而未被创建的。在这种情况下,可以使用适配器设计模式。适配器中的代码将接受你所拥有的接口,并产生你所需要的接口:

public class FilterAdapter implements Processor{
    Filter filter;
    public FilterAdapter(Filter filter){
        this.filter = filter;
    }
    @Override
    public String name() {
        return filter.name();
    }

    @Override
    public Object process(Object input) {
        return filter.process((Waveform)input);
    }
}
public class FilterProcessor {
    public static void main(String[] args) {
        Waveform w = new Waveform();
        Apply.process(new FilterAdapter(new LowPass(1.0)),w);
        Apply.process(new FilterAdapter(new HighPass(2.0)),w);
        Apply.process(new FilterAdapter(new BandPass(3.0,4.0)),w);
    }
}

输出:
Using Processor LowPass
Waveform 0
Using Processor HighPass
Waveform 0
Using Processor BandPass
Waveform 0
在这种使用适配器的方式中,FilterAdapter的构造器接受你所拥有的接口Filter,然后生成具有你所需要的Processor接口的对象。在FilterAdapter类中用到了代理。
将接口从具体是实现中解耦使得接口可以应用于多种不同的具体实现,因此代码也就更具可复用性。

Java中的多重继承

组合多个类的接口的行为被称作多重继承
在Java中,导出类不强制要求必须有一个是抽象的或者“具体的”(没有任何抽象方法)基类。如果要从一个非接口的类继承,那么只能从一个类去继承。其他的基元素都必须是接口。可以继承任意多个接口,并可以向上转型为每一个接口,因为每一个接口都是一个独立类型。

public interface CanFight {
    void fight();
}
public interface CanSwim {
    void swim();
}
public interface CanFly {
    void fly();
}
public class ActionCharacter {
    public void fight(){}
}
public class Hero extends  ActionCharacter implements CanFight, CanSwim, CanFly{
    @Override
    public void fly() {

    }

    @Override
    public void swim() {

    }
}
public class Adventure {
    public static void t(CanFight x){x.fight();}
    public static void u(CanSwim x){x.swim();}
    public static void v(CanFly x){x.fly();}
    public static void w(ActionCharacter x){x.fight();}

    public static void main(String[] args) {
        Hero h = new Hero();
        t(h);
        u(h);
        v(h);
        w(h);
    }
}
使用接口的核心原因

1.为了能够向上转型为多个基类型
2.防止客户端程序员创建该类的对象

通过继承来扩展接口

通过继承,可以很容易的在接口中添加新的方法声明,还可以通过继承在新的接口中组合数个接口。这两种情况都可以获得新的接口。

public interface Monster {
    void menace();
}
public interface DangerousMonster extends Monster{
    void destroy();
}
public interface Lethal {
    void kill();
}
public class DragonZilla implements DangerousMonster{
    @Override
    public void menace() {

    }

    @Override
    public void destroy() {

    }
}
public interface Vampire extends DangerousMonster, Lethal{
    void drinkBlood();
}
public class VerBadVampire implements Vampire{
    @Override
    public void kill() {

    }

    @Override
    public void menace() {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void drinkBlood() {

    }
}
public class HorrorShow {
    static void u(Monster b){b.menace();}
    static void v(DangerousMonster d){
        d.menace();
        d.destroy();
    }
    static void w(Lethal l){l.kill();}

    public static void main(String[] args) {
        DangerousMonster barney = new DragonZilla();
        u(barney);
        v(barney);
        Vampire vlad = new VerBadVampire();
        u(vlad);
        v(vlad);
        w(vlad);
    }
}
组合接口时的名字冲突

上面例子中,CanFight和ActionCharacter都有一个void fight方法,二者方法相同,相同的方法不会出现问题。如果他们的签名或返回类型不同,就会出现错误。在打算组合的不同接口中使用的方法名通常会造成代码可读性的混乱,应避免。

适配接口

接口最吸引人的原因之一就是允许同一接口具有多个不同的具体实现。因此,接口的一种常见用法就是前面提到的策略设计模式
方法接受一个指定的接口:可以用任何对象来调用方法,只要对象遵循接口。
例如:Scanner类的构造器接受的就是一个Readable接口。

    private Scanner(Readable source, Pattern pattern) {
        assert source != null : "source should not be null";
        assert pattern != null : "pattern should not be null";
        this.source = source;
        delimPattern = pattern;
        buf = CharBuffer.allocate(BUFFER_SIZE);
        buf.limit(0);
        matcher = delimPattern.matcher(buf);
        matcher.useTransparentBounds(true);
        matcher.useAnchoringBounds(false);
        useLocale(Locale.getDefault(Locale.Category.FORMAT));
    }

Readable没有用作Java标准类库中其他任何方法的参数,它是单独为Scanner创建的,以使得Scanner不必将其参数限制为某一个特定类。通过这种方式,Scanner可以作用于更多的类型。如果你创建了一个新的类,并想让Scanner作用于它,那就让它成为Readable:

public class RandomWords implements Readable{
    private static Random rand = new Random(47);
    private static final char[] capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final char[] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray();
    private static final char[] vowels = "aeiou".toCharArray();
    private int count;
    public RandomWords(int count){
        this.count = count;
    }
    @Override
    public int read(CharBuffer cb) throws IOException {
        if(count-- == 0){
            return -1;
        }
        cb.append(capitals[rand.nextInt(capitals.length)]);
        for(int i = 0; i < 4; i++){
            cb.append(vowels[rand.nextInt(vowels.length)]);
            cb.append(lowers[rand.nextInt(lowers.length)]);
        }
        cb.append(" ");
        return 10;
    }

    public static void main(String[] args) {
        Scanner s = new Scanner(new RandomWords(10));
        while (s.hasNext()){
            System.out.println(s.next());
        }
    }
}/* Output:
Yazeruyac
Fowenucor
Goeazimom
Raeuuacio
Nuoadesiw
Hageaikux
Ruqicibui
Numasetih
Kuuuuozog
Waqizeyoy
*/

假设有一个为实现Readable的类,怎么才能让Scanner作用于它?:

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

    public static void main(String[] args) {
        RandomDoubles rd = new RandomDoubles();
        for (int i = 0; i < 7; i ++){
            System.out.println(rd.next() + " ");
        }
    }
}/*
0.7271157860730044
0.5309454508634242
0.16020656493302599
0.18847866977771732
0.5166020801268457
0.2678662084200585
0.2613610344283964
*/

再次使用了适配器模式,但在本例中,被适配的类可以通过继承和实现Readable接口来创建。因此,通过使用interface关键字提供的伪多重继承机制,我们可以生成既是RandomDoubles又是Readable的新类:

public class AdaptedRandomDoubles extends RandomDoubles implements Readable{
    private int count;

    public AdaptedRandomDoubles(int count) {
        this.count = count;
    }

    @Override
    public int read(CharBuffer cb) throws IOException {
        if(count-- == 0){
            return -1;
        }
        String result = Double.toString(next()) + " ";
        cb.append(result);
        return result.length();
    }

    public static void main(String[] args) {
        Scanner s = new Scanner(new AdaptedRandomDoubles(7));
        while (s.hasNextDouble()){
            System.out.println(s.nextDouble() + " ");
        }
    }
}/*Output:
0.7271157860730044 
0.5309454508634242 
0.16020656493302599 
0.18847866977771732 
0.5166020801268457 
0.2678662084200585 
0.2613610344283964 
*/

因为在这种方式中,我们可以在任何现有类之上添加新的接口,所以这意味着让方法接受接口类型,是一种让任何类都可以对该方法进行适配的方式。这就是使用接口而不是类的强大之处。

接口中的域

接口中的任何域都自动是static和final的,所以接口就成为了一种很便捷的用来创建常量组的工具。在Java SE5之前,这是产生与C或C++的enum(枚举类型)具有相同效果的类型的唯一途径。

public interface Months {
    int JANUARY = 1, FEBRUARY = 2, MARCH = 3,
    APRIL= 4, MAY= 5, JUNE= 6, JULY= 7,
    AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
    NOVEMBER = 11, DECEMBER = 12;
}

接口中的域自动是public的,所以没有显式地指明这一点。

初始化接口中的域

在接口中定义的域不能说空final,但是可以被非常量表达式初始化。

public interface RandVals {
    Random RAND = new Random(47);
    int RANDOM_INT = RAND.nextInt(10);
    long RANDOM_LONG = RAND.nextLong() * 10;
    float RANDOM_FLOAT = RAND.nextFloat() * 10;
    double RANDOM_DOUBLE = RAND.nextDouble() * 10;
}

既然域是staic的,他们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时。

public class Test {
    public static void main(String[] args) {
        System.out.println(RandVals.RANDOM_INT);
        System.out.println(RandVals.RANDOM_LONG);
        System.out.println(RandVals.RANDOM_FLOAT);
        System.out.println(RandVals.RANDOM_DOUBLE);
    }
}/*
8
-32032247016559954
-8.5939291E18
5.779976127815049
*/

当然,这些域不是接口的一部分,他们的值被存储在该接口的静态存储区域内。

嵌套接口

接口可以嵌套在类或者其他接口中。

public class A {
    interface  B{
        void f();
    }
    public class BImp implements B{
        @Override
        public void f() {

        }
    }
    public class BImp2 implements B{
        @Override
        public void f() {

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

        }
    }
    public class CImp2 implements C{
        @Override
        public void f() {

        }
    }
    private interface D {
        void f();
    }
    public class DImp implements D{
        @Override
        public void f() {

        }
    }
    public class DImp2 implements D{
        @Override
        public void f() {

        }
    }
    public D getD(){return new DImp2();}
    private D dRef;
    public void receiveD(D d){
        dRef = d;
        dRef.f();
    }
}
public interface E {
    interface G{
        void f();
    }
    public interface H{
        void f();
    }
    void g();
    //不能建立一个私有的接口
    //private interface I{}
}
public class NestingInterfaces {
    public class BImp implements A.B{
        @Override
        public void f() {

        }
    }
    class CImp implements A.C{
        @Override
        public void f() {

        }
    }
    //不能实现一个私有的接口
    /*class EImp implements A.D{
        @Override
        public void f() {

        }
    }*/
    class EImp implements E {
        @Override
        public void g() {

        }
    }
    class EGImp implements E.G {

        @Override
        public void f() {

        }
    }
    class EImp2 implements E{
        @Override
        public void g() {

        }
        class EG implements E.G {

            @Override
            public void f() {
            }
        }
    }

    public static void main(String[] args) {
        A a = new A();
        //不能访问A.D
        //!A.D ad = a.getD();
        //只能返回A.D
        //!A.DImp2 di2 = a.getD();
        //无法访问接口的成员
        //!a.getD().f();
        //只有另一个A才能使用getD()做任何事(getD()是一个返回private接口引用的public方法,能使用它做些什么?将返回值交给有权使用他的对象,是另一个A通过receiveD()方法来实现)
        A a2 = new A();
        a2.receiveD(a.getD());
    }
}

嵌套接口可以拥有public和包访问两种可视性,做为一种新添加的方式,接口也可以被实现为private的,就像A.D。
private嵌套接口好处:可以强制该接口中的方法定义不要添加任何类型的信息,也就是说不允许向上转型。

接口和工厂

接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口实现分离,这就使得我们可以透明地将某个实现替换为另一个实现。

public interface Service {
    void method1();
    void method2();
}
public interface ServiceFactory {
    Service getService();
}
public class Implementation1 implements Service{
    Implementation1(){}

    @Override
    public void method1() {
        System.out.println("Implementation1 method1");
    }

    @Override
    public void method2() {
        System.out.println("Implementation1 method2");
    }
}
public class Implementation1Factory implements ServiceFactory{
    @Override
    public Service getService() {
        return new Implementation1();
    }
}
public class Implementation2 implements Service{
    @Override
    public void method1() {
        System.out.println("Implementation2 method1");
    }

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

    public static void main(String[] args) {
        serviceConsumer(new Implementation1Factory());
        serviceConsumer(new Implementation2Factory());
    }
}/*
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method1
*/

如果不是用工厂方法,你的代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器。

public interface Game {
    boolean move();
}
public interface GameFactory {
    Game getGame();
}
public class Checkers implements Game{
    private int moves = 0;
    private static final int MOVES = 3;
    @Override
    public boolean move() {
        System.out.println("Checkers move " + moves);
        return ++moves != MOVES;
    }
}
public class CheckersFactory implements GameFactory{
    @Override
    public Game getGame() {
        return new Checkers();
    }
}
public class Chess implements Game{
    private int moves = 0;
    private static final int MOVES = 4;
    @Override
    public boolean move() {
        System.out.println("Chess move " + moves);
        return ++moves != MOVES;
    }
}
public class ChessFactory implements GameFactory{
    @Override
    public Game getGame() {
        return new Chess();
    }
}
public class Games {
    public static void playGame(GameFactory factory){
        Game s = factory.getGame();
        while (s.move()) {

        }
    }

    public static void main(String[] args) {
        playGame(new CheckersFactory());
        playGame(new ChessFactory());
    }
}/*
Checkers move 0
Checkers move 1
Checkers move 2
Chess move 0
Chess move 1
Chess move 2
Chess move 3
*/

如果Games类表示一段复杂的代码,那么这种方式就允许你在不同类型的游戏中复用这段代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值