Java编程思想学习笔记(9)

2019-12-29  本文已影响0人  Cool_Pomelo

Java编程思想学习笔记(9)

抽象类与抽象方法

首先先看例子,之前的一系列乐器的例子,把基类Instrument声明为抽象类。

UML图:

1.PNG
abstract class Instrument {

    private int i; // Storage allocated for each

    //抽象方法,不能有“{}”
    public abstract void play(Note n);
    public String what() { return "Instrument"; }

    public abstract void adjust();

}


public class Brass extends Instrument{

    public void play(Note n) {
        print("Brass.play() " + n);
    }
    public void adjust() { print("Brass.adjust()"); }


}

public class Percussion extends Instrument{

    public void play(Note n) {
        print("Percussion.play() " + n);
    }
    public String what() { return "Percussion"; }
    public void adjust() {}


}

public class Stringed extends Instrument{

    public void play(Note n) {
        print("Stringed.play() " + n);
    }
    public String what() { return "Stringed"; }
    public void adjust() {}


}


public class Woodwind extends Wind{

    public void play(Note n) {
        print("Woodwind.play() " + n);
    }
    public String what() { return "Woodwind"; }


}

public class Music4 {

    // Doesn’t care about type, so new types
// added to the system still work right:
    static void tune(Instrument i) {
// ...
        i.play(Note.MIDDLE_C);
    }
    static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    public static void main(String[] args) {
// Upcasting during addition to the array:
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind()
        };
        tuneAll(orchestra);
    }

}


抽象:从具体事物抽出、概括出它们共同的方面、本质属性与关系等,而将个别的、非本质的方面、属性与关系舍弃,这种思维过程,称为抽象。

这句话概括了抽象的概念,而在Java中,你可以只给出方法的定义不去实现方法的具体事物,由子类去根据具体需求来具体实现。

这种只给出方法定义而不具体实现的方法被称为抽象方法,抽象方法是没有方法体的,在代码的表达上就是没有“{}”。

包含一个或多个抽象方法的类也必须被声明为抽象类。

抽象类除了包含抽象方法外,还可以包含具体的变量和具体的方法。类即使不包含抽象方法,也可以被声明为抽象类,防止被实例化。

抽象类不能被实例化,也就是不能使用new关键字来得到一个抽象类的实例,抽象方法必须在子类中被实现。

抽象类总结规定:

  1. 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

  2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

  3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

  4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

  5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

接口

interface关键字使得抽象的概念更加向前迈进了一步,abstract关键字允许人们在类中创建一个或多个没有任何定义的方法---提供了接口部分。但是没有提供任何相应的具体实现,这些实现是由此类的继承者实现的。

interface关键字产生一个完全抽象的类,根本没有提供任何具体实现,它允许创建者确定方法名,参数列表和返回类型。但是没有任何方法体,接口只是提供了形式,没有提供任何具体实现。

继续把之前乐器的例子来进行改造:

UML图:

2.PNG
public interface Instrument {

    // Compile-time constant:
    int VALUE = 5; // static & final
    // Cannot have method definitions:
    void play(Note n); // Automatically public
    void adjust();


}

public class Percussion implements Instrument{

    public void play(Note n) {
        print(this + ".play() " + n);
    }
    public String toString() { return "Percussion"; }
    public void adjust() { print(this + ".adjust()"); }
}



public class Stringed implements Instrument{

    public void play(Note n) {
        print(this + ".play() " + n);
    }
    public String toString() { return "Stringed"; }
    public void adjust() { print(this + ".adjust()"); }


}

public class Brass extends Wind{

    public String toString() { return "Brass"; }


}



public class Woodwind extends Wind{

    public String toString() { return "Woodwind"; }


}

public class Music5 {

    // Doesn’t care about type, so new types
// added to the system still work right:
    static void tune(Instrument i) {
// ...
        i.play(Note.MIDDLE_C);
    }
    static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    public static void main(String[] args) {
// Upcasting during addition to the array:
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind()
        };
        tuneAll(orchestra);
    }
}

在抽象类中,可以包含一个或多个抽象方法;但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”。

接口使用 interface 关键字来声明,可以看做是一种特殊的抽象类,可以指定一个类必须做什么,而不是规定它如何去做。

与抽象类相比,接口有其自身的一些特性:

Java中的多重继承

接口不仅仅是一种更加纯粹的抽象类,它的目标比这更高。因为接口中根本没有任何具体实现,所以没有任何与接口相关的存储,因此也就无法阻止多个接口的组合。在C++中,组合多个类的接口的行为叫做多重继承,但这可能会带来很多副作用,因为每个类都有一个具体实现。在Java中,可以执行一样的行为,但是只有一个类可以有具体实现,所以通过组合多个接口,C++的问题不会在Java中发生。

例子:

public interface CanFly {

    void fly();


}

public interface CanFight {

    void fight();


}

public interface CanSwim {

    void swim();

}

public class ActionCharacter {

    public void fight() {}


}

public class Hero extends ActionCharacter implements CanFight,CanSwim,CanFly{
    public void swim() {}
    public void fly() {}


}


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); // Treat it as a CanFight
        u(h); // Treat it as a CanSwim
        v(h); // Treat it as a CanFly
        w(h); // Treat it as an ActionCharacter
    }



}



上面例子,Hero组合了具体类ActionCharacter与多个接口,当通过这种方式将一个具体类和多个接口组合到一起时,这个具体类必须放在前面,后面跟着的才是接口。

细节:

CanFight接口与ActionCharacter类中的fight()方法的特征签名一样,而且Hero中没有提供fight()的定义。可以扩展接口,但是得到的却是另一个接口。即时Hero中没有显式的提供fight()的定义,但是其定义也随之ActionCharacter而来。

通过继承来扩展接口

例子:

public interface Monster {

    void menace();

}


public interface DangerousMonster extends Monster{
    void destroy();


}

public interface Lethal {

    void kill();

}


public interface Vampire extends DangerousMonster,Lethal{
    void drinkBlood();


}

public class DragonZilla implements DangerousMonster{

    public void menace() {}
    public void destroy() {}


}


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 VeryBadVampire();
        u(vlad);
        v(vlad);
        w(vlad);
    }


}




在Vampire中使用的语法仅仅适用于接口继承,一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口,只需要用逗号将接口名字一一分隔开就行了。

组合接口时的名字冲突问题

例子:

public interface I1 {
    void f();
}
public interface I2 {

    int f(int i);

}

public interface I3 {
    int f();


}

public class C {

    public int f() { return 1; }

}

public class C2 implements I1,I2{

    public void f() {}
    public int f(int i) { return 1; } // overloaded

}

public class C3 extends C implements I2{

    public int f(int i) { return 1; } // overloaded

}

public class C4 extends C implements I3{

    // Identical, no problem:
    public int f() { return 1; }


    // Methods differ only by return type:
//! class C5 extends C implements I1 {}
//! interface I4 extends I1, I3 {} ///:~
}


注释掉的语句会引起编译器报错,因为覆盖,实现和重载全部搅乱在了一起,而且重载方法仅仅根据返回类型是区分不了的。

接口中的域

初始化接口中的域

在接口中定义的域不能是“空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.nextLong() * 10;
    double RANDOM_DOUBLE = RAND.nextDouble() * 10;


}


public class TestRandVals {

    public static void main(String[] args) {
        print(RandVals.RANDOM_INT);
        print(RandVals.RANDOM_LONG);
        print(RandVals.RANDOM_FLOAT);
        print(RandVals.RANDOM_DOUBLE);
    }


}


既然域是static的,那么它们就可以在类第一次被加载时初始化。

嵌套接口

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 class CImp2 implements C {
        public void f() {}
    }

//    修饰为private
    private interface D {
        void f();
    }
    private class DImp implements D {
        public void f() {}
    }
    public class DImp2 implements D {
        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();
    }
    // Redundant "public":
    public interface H {
        void f();
    }
    void g();
// Cannot be private within an interface:
//! private interface I {}

}

public class NestingInterfaces {

    public class BImp implements A.B {
        public void f() {}
    }
    class CImp implements A.C {
        public void f() {}
    }
    // Cannot implement a private interface except
// within that interface’s defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
    class EImp implements E {
        public void g() {}
    }
    class EGImp implements E.G {
        public void f() {}
    }
    class EImp2 implements E {
        public void g() {}
        class EG implements E.G {
            public void f() {}
        }
    }
    public static void main(String[] args) {
        A a = new A();
// Can’t access A.D:
//! A.D ad = a.getD();
// Doesn’t return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
        A a2 = new A();
        a2.receiveD(a.getD());
    }

}

在类中嵌套接口的语法是相当显而易见的。

作为一种新添加的方式,接口也是被实现为private。

接口E说明接口彼此之间可以嵌套。但是作用域接口的各种规则,特别是所有的接口元素都必须是为public,在此处都会严格执行。因此嵌套在另一个接口中的接口自动就是public的,而不能声明为private。

上一篇下一篇

猜你喜欢

热点阅读