抽象类和抽象方法
- 抽象方法表示形式
abstract void method()
,仅仅只有声明没有方法体。 - 包含抽象方法的类叫做抽象类,
class
前面添加关键字abstract
,如public abstract class People
- 一个类如果需要继承抽象类,必须要实现全部的抽象方法。
接口
- 接口和类相比,所有方法只有声明,没有实现,关键字不是
class
而是interface
。一个类可以通过关键字implements
关键字。可以理解成接口一份房屋设计图,具体房屋怎么盖,用什么材料,都在类中具体实现。 - 接口中方法默认都是
public
的,定义的变量,默认都是final static
,接口不能被实例化。
完全解耦
- 如果有一个方法操作的是一个类,那这个方法只能用于这个类或者该类的子类。如果你想把这个方法应用在不在此继承结构的某个类,那肯定是无法使用的。接口可以很大程度放宽这种限制,因此可以编写复用性更高的代码。
使用类的耦合性
- 有个
Bird
类作为基类,老鹰Eagle
和燕子Swallow
都继承自Bird
类,并且重载fly()
方法,毕竟每种鸟类飞行方式不一样,Apply
让这些鸟飞起来,调用对应的fly()
方法。
public class Bird {
public void fly() {
System.out.println("Bird can fly");
}
}
class Eagle extends Bird {
@Override
public void fly() {
System.out.println("Eagle can fly");
}
}
class Swallow extends Bird {
@Override
public void fly() {
System.out.println("Swallow can fly");
}
}
class Apply {
public static void fly(Bird bird) {
bird.fly();
}
}
- 下面是
AirPlane
类也具备飞行的能力,看起来是不是和上面的Bird
差不多,但是我这时候也想复用Apply
的代码,显然是不可能的,因为Apply
中的fly
方法已经牢牢和Bird
绑定了,我如果想复用除非AirPlane
也继承Bird
,这在逻辑上显然是行不通的,飞机怎么能继承鸟类呢。
public class AirPlane {
public void fly() {
System.out.println("AirPlane can fly");
}
}
class Helicopter extends AirPlane {
@Override
public void fly() {
System.out.println("Helicopter can fly");
}
}
class Fighter extends AirPlane {
@Override
public void fly() {
System.out.println("Fighter can fly");
}
}
使用接口解耦
- 这时候我们抽象出一个飞行接口就可以解决上面的问题,然后让
Bird
和AirPlane
都实现飞行接口,将Apply
的传递参数改为Fly
接口类型,这样就实现了Apply
和Bird
的解耦,如果以后再新建其他具有飞行能力的类都可以复用。
public interface Fly {
void fly();
}
public class Bird implements Fly {
@Override
public void fly() {
System.out.println("Bird can fly");
}
}
public class AirPlane implements Fly {
@Override
public void fly() {
System.out.println("AirPlane can fly");
}
}
public class Apply {
public static void fly(Fly fly) {
fly.fly();
}
}
适配接口
- 接口将声明和实现分开,同一个接口可以有多种实现方式。你只要编写一个方法用于接受接口,只要对象实现这个接口,就能被这个方法所使用,如上面的
Apply
中的Fly()
方法。 - 在现实开发中可能会遇到
AirPlane
是第三方jar包提供的,我们无法修改内部代码,我们可以使用适配器模式,在适配器中实现接口,充当一个转换器,从而可以复用代码。这种方式是针对在无法修改源码的情况,如果我们可以直接使用接口,就不需要用这种方式了。
public class AirPlaneAdapter implements Fly {
private AirPlane airPlane;
public AirPlaneAdapter(AirPlane airPlane) {
this.airPlane = airPlane;
}
@Override
public void fly() {
airPlane.fly();
}
}
Apply.fly(new AirPlaneAdapter(new AirPlane()));
- 通过上面发现,我们可以在任意一个类上面添加接口,这意味着任何一个类都可以通过这种方式来适配方法。
困惑
- 刚开始看书的时候,我觉得创建接口这么麻烦,为什么不直接在
Apply
再添加一个方法用来接受AirPlane
类型。 这种做法从功能实现的角度来看也可以,毕竟我这样做也实现了功能嘛。 - 从设计角度来看,不可取。结合实际项目想一下,如果这时候项目经理跟你说添加了3种具备飞行能力的类,原来的那个
Bird
类不用了,如果按照上面那种做法,我又得在Apply
类中添加3个方法用于接受新建的类,再删除接收Bird
类的方法。 - 由于
Apply
和这些类具有耦合性,这些类的改变也带动了Apply
内部的改变。但是如果我使用了接口,Apply
中的代码完全不需要修改,外部不管怎么变,我自岿然不动,这就是解耦。
实现多个接口
- 在Java中只能继承单个类,但是可以实现多个接口,将所有的接口名都置于
implements
关键字之后,用逗号隔开,并且可以向上转型为每个接口,因为每一个接口都是一个独立类型。 - 如代码所示,一个超级英雄要具备打坏蛋,能上天也能入海的能力,可以实现多个接口,将这些能组合在一起,并且可以依次向上转型为任意一个接口被调用。
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class SuperHero implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
public void fight(){}
}
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
}
}
类和接口
- 类有属性,有行为,可以是对行为具体实现,接口只能对行为声明,无法具体实现,没有属性。
- 类对事物的抽象,或者说是某个群体的抽象,比如人类、鸟类、飞机类等等。类有属性、有行为。继承一个类表示属于这个群体,是一种是不是的关系,男人是人,喜鹊是鸟。他们具有某些相同的能力和属性,比如大部分鸟都会飞,都有翅膀,有羽毛。
- 接口是对行为的抽象,比如飞机和鸟都具备飞行能力,可以抽象出一个飞行的行为接口。实现接口表示具备这个行为,一种有没有或者具备不具备的关系,鸟具备飞的能力,飞机具备飞的能力。
- 为什么类只有单继承,现实生活中很少有事物是属于同一层次的不同种类,即使有也会被单独划分为一个类,比如骡,它虽然是马和驴的杂交,但没有说驴既是马类也是驴类,而是被单独抽象成骡类。还有注意是同一层次,你说小明既是男人,也是人。从继承角度来说,男人肯定是继承自人,这样就不是同一层次了,这么说才是同一层次小明既是男人,也是女人,一听显然逻辑不对。
- 为什么接口可以多种实现,因为事物可以具备多种行为,超级英雄既能游泳,也能跑步,还能打架,都可以单独抽象出各自的行为接口。
- 从上面看接口的抽象程度高于类,那是不是我们都用实现接口的方式而不用继承呢。其实不然,类可以实现具体的行为,继承基类可以理解成一种模板设计,可以把相同的行为实现写在基类中,比如下面的
Man
和Women
吃的行为逻辑一样,那么就直接在基类中实现逻辑,Man
和Women
继承即可。想一下如果不用继承,那Man
和Woman
中都要实现相同的行为逻辑,写了一样的代码。
public class People implements Eat {
@Override
public void eat() {
System.out.println("I can eat rice");
}
}
public class Woman extends People {
}
public class Man extends People {
}
interface Eat {
void eat();
}
通过继承扩展接口
接口之间也可以继承,并且可以多继承,也就是说一个接口可以继承多个其他接口,扩展原来的接口。比如原来已经有Swim
和Fight
接口,这时候我还想增加一个Help
接口也具备游泳和打坏蛋的行为,就可以继承上面两个接口,当SuperHero
实现Help
接口的时候就需要实现上述3个方法。
interface Swim {
void canSwim();
}
interface Fight {
void canFight();
}
interface Help extends Swim, Fight {
void canHelp();
}
public class SuperHero implements Help {
@Override
public void canSwim() {
}
@Override
public void canFight() {
}
@Override
public void canHelp() {
}
}
接口与工厂模式
- 上面已经说了接口是把声明和实现分开,在工厂模式中这是需要这样的思想,不同工厂可以生产出对应产品,产品的功能因为设计图也会不一样。如代码所示一个汽车因为设计的不同会导致不同的速度,汽车生产商也会生产出不同的汽车。
interface Car {
void running();
}
class Jeep implements Car {
@Override
public void running() {
System.out.println("Jeep's speed is 80km/h");
}
}
class Track implements Car {
@Override
public void running() {
System.out.println("Track's speed is 60km/h");
}
}
interface CarFactory {
Car getCar();
}
class JeepFactory implements CarFactory {
@Override
public Car getCar() {
return new Jeep();
}
}
class TrackFactory implements CarFactory {
@Override
public Car getCar() {
return new Track();
}
}
public class User {
private void drive(CarFactory carFactory) {
carFactory.getCar().running();
}
}