抽象类和抽象方法
- 抽象方法只有声明没有方法体,其语法如下: abstract void f()
- 含有抽象方法的类叫抽象类,要被abstract修饰
- 抽象类中可以没有抽象方法
- 抽象类不能新建对象
- 如果继承了抽象类并想要新建新类的对象,必须实现基本类的所有抽象方法(完成方法体)。如果没有实现,则衍生类也是抽象类,且编译器会强迫在类名前加abstract关键字
见下图:
见下面抽象类和抽象方法的例子:
//: Music4.java
// Abstract classes and methods
import java.util.*;
abstract class Instrument4 {
int i; // storage allocated for each
public abstract void play();
public String what() {
return "Instrument4";
}
public abstract void adjust();
}
class Wind4 extends Instrument4 { //继承抽象类
public void play() {
System.out.println("Wind4.play()");
}
public String what() { return "Wind4"; }
public void adjust() {}
}
class Percussion4 extends Instrument4 {
public void play() {
System.out.println("Percussion4.play()");
}
public String what() { return "Percussion4"; }
public void adjust() {}
}
class Stringed4 extends Instrument4 {
public void play() {
System.out.println("Stringed4.play()");
}
public String what() { return "Stringed4"; }
public void adjust() {}
}
class Brass4 extends Wind4 {
public void play() {
System.out.println("Brass4.play()");
}
public void adjust() {
System.out.println("Brass4.adjust()");
}
}
class Woodwind4 extends Wind4 {
public void play() {
System.out.println("Woodwind4.play()");
}
public String what() { return "Woodwind4"; }
}
public class Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument4 i) {
// ...
i.play();
}
static void tuneAll(Instrument4[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument4[] orchestra = new Instrument4[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind4();
orchestra[i++] = new Percussion4();
orchestra[i++] = new Stringed4();
orchestra[i++] = new Brass4();
orchestra[i++] = new Woodwind4();
tuneAll(orchestra);
}
} ///:~
创建抽象类和抽象方法明确地将类抽象化,并向用户和编译器指明该如何使用抽象类
抽象类也是很有用的重构工具(refactoring tolls),因为可以通过抽象类将公共方法(common method)向继承层次结构上移动
接口(interfaces)
- 关键字interface将抽象的概念更深一步。使用关键字interface可创建一个完全的抽象类,类中允许定义方法名,参数表和返回类型,但没有方法体。接口只提供形式,不做实现
- 接口这样描述自己:“对于所有实现了这个特定接口的类,都应该像这个接口一样”。因此,实现了特定接口的所有代码都知道针对这个接口可能会调用什么方法。这便是接口的全部含义。所以常把接口用于建立类和类之间的一个“协议”(protocol)。有些面向对象的程序设计语言采用了一个名为“protocol”(协议)的关键字,它做的便是与接口相同的事情
- 与继承不同的是,一个类可以实现多个接口,因为可数上溯造型多个基本类
- 使用关键字interface创建抽象类,interface前可以有用权限修饰符修饰,如public或无(“friendly”友好的)
- 接口内也可以包含成员变量,但是默认为static和final
- 使用关键字implements来实现接口
见下图:
从上图可见,实现了一个接口,就获得了一个普通类,就可以正常地继承。
可以将接口中的方法显式声明为“public”。但接口中的方法默认为public。所以在实现接口的时候,来自接口的方法必须定义成public。否则会默认为“友好的”这会限制继承过程中对方法的访问——Java编译器不允许这样做。
//: Music5.java
// Interfaces
import java.util.*;
interface Instrument5 {
// Compile-time constant:
int i = 5; // static & final
// Cannot have method definitions:
void play(); // Automatically public
String what();
void adjust();
}
class Wind5 implements Instrument5 {
public void play() {
System.out.println("Wind5.play()");
}
public String what() { return "Wind5"; }
public void adjust() {}
}
class Percussion5 implements Instrument5 {
public void play() {
System.out.println("Percussion5.play()");
}
public String what() { return "Percussion5"; }
public void adjust() {}
}
class Stringed5 implements Instrument5 {
public void play() {
System.out.println("Stringed5.play()");
}
public String what() { return "Stringed5"; }
public void adjust() {}
}
class Brass5 extends Wind5 {
public void play() {
System.out.println("Brass5.play()");
}
public void adjust() {
System.out.println("Brass5.adjust()");
}
}
class Woodwind5 extends Wind5 {
public void play() {
System.out.println("Woodwind5.play()");
}
public String what() { return "Woodwind5"; }
}
public class Music5 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument5 i) {
// ...
i.play();
}
static void tuneAll(Instrument5[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument5[] orchestra = new Instrument5[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind5();
orchestra[i++] = new Percussion5();
orchestra[i++] = new Stringed5();
orchestra[i++] = new Brass5();
orchestra[i++] = new Woodwind5();
tuneAll(orchestra);
}
} ///:~
注意:可以自由上溯造型到一个类,不管这个类是通常类,抽象类还是接口。都是可行的。实际上,在tune()方法中也没有任何证据显示Instrument5到底是个“普通”类、抽象类还是一个接口。
完全解耦合
接口可以将方法应用于不属于该层次结构中的类。允许编写更多可重用的代码。
见下例:
//: interfaces/classprocessor/Apply.java
package interfaces.classprocessor;
import java.util.*;
import static net.mindview.util.Print.*;
class Processor {
public String name() { return getClass().getSimpleName(); }
Object process(Object input) { return input; }
}
class Upcase extends Processor {
String process(Object input) { // Covariant return
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(" "));
}
}
public class Apply {
public static void process(Processor p, Object s) {
print("Using Processor " + p.name());
print(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);
}
}
/* Output:
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]
*///:~
创建一个根据传递参数对象的不同而执行不同行为的方法,叫做策略设计模式(Strategy design pattern)。方法包含要执行算法的固定部分,策略包含要改变的部分。策略是你传入的对象,它包含要执行的代码。在本例中,Processor对象是策略,在main()中, 可见,有三种不同的策略应用到String s
但是Apply.process()和Processor之间耦合太强,有时想重用Apply.process()变得不可能
如果Processor是接口,那只要实现Processor接口的类就能重用Apply.process(),下 面是改版的Processor和Apply
//:interfaces/interfaceprocessor/Processor.java
package interfaces.interfaceprocessor;
public interface Processor {
String name();
Object process(Object input);
} ///:~
//: interfaces/interfaceprocessor/Apply.java
package interfaces.interfaceprocessor;
import static net.mindview.util.Print.*;
public class Apply {
public static void process(Processor p, Object s) {
print("Using Processor " + p.name());
print(p.process(s));
}
} ///:~
首先如果客户程序员将他们的类实现接口就能复用代码,如:
//: interfaces/interfaceprocessor/StringProcessor.java
package interfaces.interfaceprocessor;
import java.util.*;
public abstract class StringProcessor implements Processor{
public String name() { return getClass().getSimpleName(); }
public abstract String 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);
}
}
class Upcase extends StringProcessor {
public String process(Object input) {
// Covariant return
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(" "));
}
} /* Output:
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]
*///:~
但是,经常出现的情况是,不能更改想要使用的类。在这种情况下,可以使用适配器设计模式(Adapter design pattern)。在该模式下,要生成代码实现有的接口,然后创建你需要的接口。见下例:
//: interfaces/interfaceprocessor/FilterProcessor.java
package interfaces.interfaceprocessor;
import interfaces.filters.*;
class FilterAdapter implements Processor {
Filter filter;
public FilterAdapter(Filter filter) { this.filter = filter; }
public String name() { return filter.name(); } //黄标部分为delegation
public Waveform 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);
}
}
/* Output:
Using Processor LowPass
Waveform 0
Using Processor HighPass
Waveform 0
Using Processor BandPass
Waveform 0
*///:~
在本例中,FilterAdapter构造方法取已有的接口Filter,然后产出有Processor接口的类
Java中的“多重继承”(multiple inheritance)
因为接口没有进行任何实现,所以没有内存分配给接口,从而可以将接口和接口结合起 来。这种组合多个类接口的方法叫做多重继承(multiple inheritance)。在Java中,多重继承里只有其中一个类可以进行具体实现。
在衍生类中,并没有强制基类一定要是abstract或具体的(没有抽象方法)。接口可以继承多个,放在implements关键字后用逗号隔开。可以上溯造型到接口。下面的例子实现多个接口构建了一个新类:
//: interfaces/Adventure.java
// Multiple interfaces.
interface CanFight { void fight(); }
interface CanSwim { void swim(); }
interface CanFly { void fly(); }
class ActionCharacter {
public void fight() {}
}
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
}
} ///:~
注意:将具体类和接口这样组合时,具体类在前(extends),接口在后(interface)。
要记住使用接口的主要两个原因:
- 使用接口可以上溯造型到多个基本类型(base type)
- 防止用户程序员新建这个类的对象(接口和抽象类都不能新建对象)
那么是用接口还是抽象类?
如果创建的基类没有方法声明和成员变量,则优先使用接口。
使用继承的方法扩展接口
可以通过继承很轻松地给接口增加新方法定义,也可以结合其他接口形成一个新的接口,
见下例:
//: interfaces/HorrorShow.java
// Extending an interface with inheritance.
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 menace() {}
public void destroy() {}
public void kill() {}
public void drinkBlood() {}
}
public class HorrorShow {
static voidu(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);
}
} ///:~
注意:在使用继承构建新的接口时,extends后可以接多个接口,不同接口之间用逗号隔开。如:interface Vampire extends DangerousMonster, Lethal 其中DangerousMonster 和Lethal都是接口
组合接口时发生名字冲突
如果在实现接口时,遇到方法名字相同,而方法的签名(方法签名就由方法名+形参列
表构成)和返回类型不同怎么办?
//: interfaces/InterfaceCollision.java
package interfaces;
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 void f() {}
public int f(int i) { return 1; } // overloaded 重载
}
class C3 extends C implements I2 {
public int f(int i) { return 1; } // overloaded 重载
}
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 {} ///:~
困难的地方在于将重载,实现和重写混在一起。另外重载方法不能仅靠返回类型区分。
所以会报错。
在组合不同接口时,如果不同接口中含有相同名字的方法,则会造成混乱影响代码的可
读性。所以要尽量避免这种情况。
接口的字段
接口里的字段都自动修饰为static和final,所以接口用来创建常量组(groups of constant values)很方便。如:
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;
} ///:~
接口字段的初始化
接口的字段不能为空final,但是可以用常量表达式初始化,如:
//: interfaces/RandVals.java
// Initializing interface fields with
// non-constant initializers.
import java.util.*;
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;
} ///:~
嵌套接口
接口可以嵌套在接口里,见下例:
//: interfaces/nesting/NestingInterfaces.java
package interfaces.nesting;
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 interface D { //将接口private
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();
}
}
interface E {
interface G {
void f();
} //接口里面有接口
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface: //接口里面不能用private
//! 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,private类和public类都可以实现private接口,那private的嵌套接口有什么好处?实现private接口是强制在该接口中定义方法且不添加任何类型信息(即不允许上溯造型)
另外,要想接口嵌套接口,则接口不能是private
问题:Java中,一个类实现某个接口,必须重写接口中的所有方法吗?
回答:“Java中,一个类实现了某接口,则必须实现该接口中的所有方法么?”这句话其实是不准确的,因为我们还没有考虑到抽象类。
抽象类实现某个接口,可以不实现所有接口的方法,可以由它的子类实现。而普通类即非抽象类则必须实现接口里的全部方法。
总结:
设计时,优先类再是接口,从类开始,如果确实有必要使用接口,在重构为接口。接口是很好用,但要主要不要滥用