Java编程思想学习笔记(9)
抽象类与抽象方法
首先先看例子,之前的一系列乐器的例子,把基类Instrument声明为抽象类。
UML图:
1.PNGabstract 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关键字来得到一个抽象类的实例,抽象方法必须在子类中被实现。
抽象类总结规定:
-
抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
-
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
-
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
-
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
-
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
接口
interface关键字使得抽象的概念更加向前迈进了一步,abstract关键字允许人们在类中创建一个或多个没有任何定义的方法---提供了接口部分。但是没有提供任何相应的具体实现,这些实现是由此类的继承者实现的。
interface关键字产生一个完全抽象的类,根本没有提供任何具体实现,它允许创建者确定方法名,参数列表和返回类型。但是没有任何方法体,接口只是提供了形式,没有提供任何具体实现。
继续把之前乐器的例子来进行改造:
UML图:
2.PNGpublic 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 关键字来声明,可以看做是一种特殊的抽象类,可以指定一个类必须做什么,而不是规定它如何去做。
与抽象类相比,接口有其自身的一些特性:
-
接口中只能定义抽象方法,这些方法默认为 public abstract 的,因而在声明方法时可以省略这些修饰符。试图在接口中定义实例变量、非抽象的实例方法及静态方法,都是非法的
-
接口中没有构造方法,不能被实例化
- 一个接口不实现另一个接口,但可以继承多个其他接口。接口的多继承特点弥补了类的单继承
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。
网友评论