重新认识普通类、抽象类、接口

简介

1.1来由

偶然间在公众号里看到别人整理好的面试题,问:接口和抽象类的区别是什么?自己心里默默说出了答案,但是总觉得不是很完整。在网上搜索了一大圈,几乎都是不尽人意。

1.2抽象类

1.21举个小栗子(JDK7)

出行的交通工具各式各样,有飞机、动车、大巴等,那么我们在设计阶段分析会发现,所有人都有一个共性名称分类,那么so easy,妈妈再也不用担心我的代码。

// 交通工具
class Vehicle {
	public void name(){
		System.out.println("交通工具");
	}
}
// 飞机
class AirPlaneextends Vehicle{
	@Override
	public void name(){
		System.out.println("I am airplane");
	}
}
// 地铁
class Subway extends Vehicle{
	@Override
	public void name(){
		System.out.println("I am subway");
	}
}
...

看起来一切都是完美的,功能实现没毛病,但是如果其他人写代码的时候直接写了个new Vehicle().name(); 上线以后卧槽,这是什么鬼,还能有一个交通工具叫交通工具,于是在茫茫码海中你觉得无比蛋疼,这bug我去哪找去?
Vehicle是一个概念性、抽象化的,它的name方法指的是所有交通工具的分类,但是它不应该具体出来这个方式,而是应该交给每一个个体对象,也就是我们new出来的交通工具(飞机、地铁、大巴车)。因此Vehicle不应该可以被new出来(new Vehicle())。那么这就引出来了抽象类。

1.22抽象类与抽象方法(JDK7)
类是对某一类事物的共性的抽象概念,而对象描述的是一个具体的产物。而抽象类是一部分类的共性的抽象。

抽象类

  1. abstract修饰的类是抽象类
  2. 抽象类中可以定义抽象方法,可以定义普通方法,可以定义变量
  3. 抽象类不可以被实例化
  4. final不能修饰抽象类,因为final修饰的抽象类不能被继承

抽象方法

  1. abstract修饰的方法是抽象方法没有方法体,即没有具体实现
  2. 抽象方法必须定义在抽象方法中,换句话说一个类中有抽象方法,它必然是一个抽象类。
  3. 子类必须重写父类所有抽象方法,除非子类也是个抽象类
  4. 抽象类中可以存在普通方法也可以存在抽象方法,都是非必须存在的。
  5. private、static、final不能修饰抽象方法,因为抽象方法必须被重写后才能使用

于是乎,我们的代码可以进一步优化重构了

// 交通工具
abstract class Vehicle{
	public abstract void name();
}
// 飞机
class AirPlane extends Vehicle{
	@Override
	public void name(){
		System.out.println("I am airplane");
	}
}
// 地铁
class Subway extends Vehicle{
	@Override
	public void name(){
		System.out.println("I am subway");
	}
}
...

这样,其他人小伙伴就不会不小心再new Vehicle()了。抽象类可以理解为代码重构出来的,抽出共性,如果脱离了继承那么,抽象类基本上也就失去了意义。

1.3接口(JDK7)

  • 定义:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
  • Java接口,Java语言中存在的结构,有特定的语法和结构;
  • Java中类不可以多继承,而接口可以多实现,恰好就弥补了这个缺陷
  • java中接口默认为public,其实它也只允许public,显式声明为private等编译会报错。
  • 接口中可以声明变量,但是它默认会将该变量转换为public static final常量,通过接口.xxx访问
  • 一个类实现了某个接口,那么它要实现该接口的所有抽象方法,接口中不存在具体方法
  • 因为接口是允许多实现的,因此一个类可能会实现多个接口,但是接口中方法名要避免相同。
  • 接口不能实例化,但是可以声明一个接口变量接收对象,例如List<String> list = new ArrayList<String>();
  • JDK8 中可以有defalut修饰具体方法实现,可以有static修饰的具体方法实现,可以有main方法。default方法可以不用重写由实例对象直接调用;static方法可以直接由接口.xx()调用;

2.接口与抽象类的区别

2.1 设计的理念不同

抽象类与子类直接是父子继承关系,本质是相同的,子类去具体化实现父类的方法。
接口则不然,接口与接口的实现者之前并没有什么关联,只需要遵照约定的协议进行实现即可。
我们仍旧使用交通工具为例,交通工具都具有运输的能力都需要动力来源,我们可以定义abstract class 和 interface来定义。

public interface Vehicle {

	// 运输
	void transport();

	// 动力来源
	void powerSource();

}

public abstract class Vehicle {

	// 运输
	public abstract void transport();

	// 动力来源
	public abstract void powerSource();

}

所有交通工具都可以通过集成abstract class Transport 或实现interface Transport来定义。照这么看,接口跟抽象类也没啥区别嘛,反正功能都实现了,也没啥毛病。
但是现在有了一个新的需求,飞机上要安装雷达。解决方案如下:

  1. 直接添加新方法,问题是解决了,但是却违背了ISP接口隔离原则(客户端不应该依赖它不需要的接口或类间的依赖关系应该建立在最小的接口上);雷达导航预警并不是每个交通工具都必须的固有行为,将它们混合在一起会导致交通工具的所有子类必须实现该方法,然后自行车也具备了雷达功能,这显然是不合理的。
public interface Vehicle {

	// 运输
	void transport();

	// 动力来源
	void powerSource();
	// 雷达预警
	void radar();
}

public abstract class Vehicle {

	// 运输
	public abstract void transport();

	// 动力来源
	public abstract void powerSource();
	// 雷达预警
	void radar();
}
  1. 根据ISP原则,将雷达拆出来,生成一个新的接口或抽象类。那么
    (1) 两个抽象类描述
    (2) 两个接口描述
    (3) 一个抽象类一个接口描述
    java中抽象类是单继承的,(1)排除;其次两个接口看着是没有问题,但是交通工具本质上的定义就是运输,通过能源提供动力,而雷达与交通工具并没有什么直接关联。用两个接口并不能体现出设计的意图;Vehicle 本质定义就是用于运输,我们使用抽象类来定义,运输和动力是所有交通工具的共性,雷达只是完成导航预警的一个行为并不是所有交通工具都要具备的,因此用接口定义,这样所有交通工具都继承Vehicle ,飞机可以选择实现radar接口。
public abstract class Vehicle {

  // 运输
  public abstract void transport();

  // 动力来源
  public abstract void powerSource();

}

// 雷达
public interface Radar{
  // 雷达预警
  void radar();
}

// 飞机
public class AirPlane extends Vehicle implements Radar {
  	// 运输
  public abstract void transport(){}

  // 动力来源
  public abstract void powerSource(){}
  
  // 雷达预警
  void radar(){}
}

3抽象类和接口总结

抽象类和接口写了一大堆,但是到底我们应该怎么使用呢?设什么时候选择抽象类什么时候选择接口呢?
其实个人认为抽象类是代码重构出来的而接口是设计出来的,为什么这么说呢?因为抽象类是为了把共性东西抽出来,例如人都会移动、吃东西等等,但是有的人会开车,有的人会多国语言,车子种类不同,语言种类也不同。
我们可以抽象出共性为抽象类,一些技能的东西抽取为接口,谁需要谁实现

// 人
public abstract class Person{

 // 吃东西
 public abstract void eat();

 // 移动
 public abstract void move();
}

// 开车
public interface Drive {
 // 开车
 void drive ();
}	
// 语言
public interface Language{
 // 掌握语言
 void language();
}	

个人认为这是一种比较合理的实现方式。随着JDK的升级,接口与抽象类之间的区别也越来越小,从理论上说两者混用其实也没有什么大不了的。于是仁者见仁,智者见智咯!

4总结

  • 接口和抽象类都不能实例化
  • 一个类只能继承一个抽象类但可以实现多个接口
  • 接口中只能有方法声明(默认并强制为public)和变量定义(默认为public static final),抽象类中可以有抽象方法、具体方法、变量、main方法等(JDK7及之前),方法都不能用private修饰。
  • 在JDK8中,接口中可以有default 修饰的具体方法、main方法、静态方法,实现接口的类可以选择是否重写defaul方法
  • 继承抽象类或实现接口的类必须实现所有抽象方法,除非该子类是抽象类
  • 抽象类中可以有抽象方法也可以没有,但是一个类中有抽象方法,那么该类必须是抽象类
  • 抽象类和接口都不能被final和static修饰,fianl修饰的类不能被继承,修饰的方法不能被重写。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值