目录
学习笔记
当你可以使用抽象类和接口进行设计的时候,那么基本上表示面向对象的概念理解了。这一步需要大量的程序累积的。
接口的基本定义
抽象类与普通类最大的优势在于:可以实现子类的覆写方法的控制。抽象类中依然会保留一些普通方法,而普通方法里面可能会涉及到一些安全或者隐私的操作问题,那么在进行开发的过程之中,如果要想对外部隐藏全部的实现细节,则可以通过接口进行描述。
接口可以理解为一个纯粹的抽象类(最原始定义接口之中只包含有抽象方法与全局常量的),但是JDK1.8开始由于引入了lambda表达式的概念,接口的定义也得到了加强,除了抽象方法与全局常量之外,还可以定义普通方法和静态方法。如果从设计本身的角度来讲,接口的组成还是以抽象方法和全局常量为主。
在Java中主要使用interface关键字来进行定义。
范例:定义一个接口
//由于类名称与接口的定义要求吸纳同,所以为了区分,在接口名称前往往加“I”
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
但是现在很明显的问题出现了,此时的接口肯定无法产生实例化对象。所以对于接口的使用原则如下:
- 接口需要被子类实现(implements),一个子类可以实现多个父接口;
- 子类(如果不是抽象类)那么一定要覆写接口之中的全部抽象方法;
- 接口对象可以利用子类对象的向上转型进行实例化。
范例:定义接口子类
//由于类名称与接口的定义要求吸纳同,所以为了区分,在接口名称前往往加“I”
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
class MessageImpl implements IMessage{ // 实现了接口
public String getInfo(){
return "得到一个消息" ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl() ;
System.out.println(msg.getInfo()) ;
System.out.println(IMessage.INFO) ; // 直接输出全局常量
}
}
以上是一个接口使用,但是在Java中,一个子类可以使用多个接口,利用接口可以实现多继承的概念。
范例:观察子类实现多个父接口
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
interface IChannel{
public abstract boolean connect() ; // 定义一个抽象的方法
}
class MessageImpl implements IMessage, IChannel{ // 实现了多个接口继承
public String getInfo(){
if (this.connect()){
return "得到一个消息" ;
}
return "通道创建失败,无法获取消息" ;
}
public boolean connect(){
System.out.println("消息发送通道已经成功建立") ;
return true ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl() ;
System.out.println(msg.getInfo()) ;
}
}
但是这个时候,就需要考虑一个实际的情况了,关于对象的转型问题。向谁转型呢?
此时MessageImpl子类的对象可以任意的实现父接口的转换。
范例:观察转换
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
interface IChannel{
public abstract boolean connect() ; // 定义一个抽象的方法
}
class MessageImpl implements IMessage, IChannel{ // 实现了多个接口继承
public String getInfo(){
if (this.connect()){
return "得到一个消息" ;
}
return "通道创建失败,无法获取消息" ;
}
public boolean connect(){
System.out.println("消息发送通道已经成功建立") ;
return true ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl() ;
IChannel chl = (IChannel) msg ;
System.out.println(chl.connect()) ;
}
}
由于MessageImpl子类实现了IMessage与IChnanel两个接口,所以这个子类可以是这两个接口,任意一个接口的实例,那么就表示这两个接口实例之间是可以转换的。
在Java的程序中接口是不允许去继承父类的,所以接口绝对不会是object的子类,但是根据之前的分析可以方法MessageImpl是Object的子类,所以接口一定可以通过object接收。
范例:观察Object与接口转换
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl() ;
Object obj = msg ; //向上转型
IChannel chan = (IChannel) obj ;
System.out.println(chan.connect()) ;
}
}
Object类对象可以接收所有数据类型,包括基本数据类型、类对象、接口对象、数组。
由于接口塑造的是一个公共的定义标准,所以在接口之中所有的抽象方法的组成访问权限都为public,也就是说写与不写都是一样的。例如:下面的两个接口本质上完全一样的。
完整定义
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
简化定义
interface IMessage{ // 定义了一个接口
String INFO = "测试" ; // 全局常量
String getInfo() ; // 抽象方法
}
注意:接口中不写访问权限也是public而不是default,所以覆写的时候只能够使用public。abstract、final加与不加都是抽象、常量。
接口虽然已经成功的进行了定义,但是千万不要忽略,实现接口的有可能是一个抽象类,一个抽象类可以实现多个接口,而一个普通类只能够继承一个抽象类并且可以实现多个父接口,但是要求先继承后实现。
范例:子类继承抽象类并且实现接口
interface IMessage{ // 定义了一个接口
public static final String INFO = "测试" ; // 全局常量
public abstract String getInfo() ; // 抽象方法
}
interface IChannel{
public abstract boolean connect() ; // 定义一个抽象的方法
}
abstract class DatabaseAbstract{ // 定义一个抽象类
// 接口中的abstract可以省略,抽象方法中的不可以省略
public abstract boolean getDatabaseConnection() ;
}
class MessageImpl extends DatabaseAbstract implements IMessage, IChannel{
public String getInfo(){
if (this.connect()){
if(this.getDatabaseConnection()){
return "数据库中得到一个消息" ;
}else{
return "数据库消息无法访问" ;
}
}
return "通道创建失败,无法获取消息" ;
}
public boolean connect(){
System.out.println("消息发送通道已经成功建立") ;
return true ;
}
public boolean getDatabaseConnection(){
return true ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl() ;
System.out.println(msg.getInfo()) ;
}
}
虽然一个接口无法继承一个父类,但是一个接口可以通过extends继承若干个父接口,此时称为接口的多继承。
范例:接口多继承
interface IMessage{
public abstract String getInfo() ;
}
interface IChannel{
public boolean connect() ; // 省略了abstract
}
// extends在类继承上只能继承一个父类,在接口上可以继承多个
interface IService extends IMessage,IChannel{ // 接口多继承
public String Service() ;
}
class MessageService implements IService{
public String getInfo(){
return "测试" ;
}
public boolean connect(){
return true ;
}
public String Service(){
return "获取消息服务" ;
}
}
public class StringDemo{
public static void main(String[] args){
}
}
在实际的开发之中,接口的使用往往有三种形式:
- 进行标准的设置;
- 表示一个操作的能力;
- 暴露远程方法视图,这个一般都在RPC分布式开发中使用。
接口定义加强
接口的主要特点最早是由全局常量和抽象方法组成,但是如果你的项目设计不当,就会有非常大的问题。如下:
一直在强调该操作是设计不当的结果,那么在最初的时候任何人都不敢保证你的结构设计得足够完善,在这样得一种情况下,为了方便子类得修改,往往不会让子类直接实现接口,而是在中间追加一个过度得抽象类。如下:
但是从JDK1.8之后开始,为了解决设计得缺陷,所以在接口之中允许开发者定义普通方法。
范例:观察普通方法定义
interface IMessage{
public String message();
public default boolean connect(){ // 方法是公共方法都具备
System.out.println("建立连接通道") ;
return true ;
}
}
class MessageImpl implements IMessage{
public String message(){
return "测试" ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl();
if(msg.connect()){
System.out.println(msg.message()) ;
}
}
}
接口中的普通方法需要追加default说明,需要注意: 该操作是补救操作,非必要不使用,先考虑过度抽象类。
除了可以追加普通方法之外,接口也可以定义static方法,而static方法可以通过接口直接调用。
范例:在接口中定义satatic方法
interface IMessage{
public String message();
public default boolean connect(){ // 方法是公共方法都具备
System.out.println("建立连接通道") ;
return true ;
}
public static IMessage getInstance(){
return new MessageImpl() ; // 获得子类对象
}
}
class MessageImpl implements IMessage{
public String message(){
if (this.connect()){
return "测试" ;
}
return "没有消息发送" ;
}
}
public class StringDemo{
public static void main(String[] args){
IMessage msg = IMessage.getInstance() ;
System.out.println(msg.message()) ;
}
}
如果可以在接口里面定义普通方法和static方法,这个时候就可以却带抽象类了,但是不应该将这两个组成作为接口的主要设计原则。所以写的代码里面应该奉行:接口就是抽象方法。
使用接口的定义标准
对于接口而言在开发中最为重要的应用就是制定标准,实际上在日常的生活中也会听见许多关于接口的名词,例如:USB接口、鼠标接口等。这些都是属于标准的应用。
以USB的程序为例,电脑上可以插入各种USB的设备,所以电脑上只是认得USB的标准,而不关心这个标准的具体实现类。
interface IUSB{ //定义USB标准
public boolean check() ; //检查通过可以工作
public void work() ;
}
class Computer{
public void plugin(IUSB usb){
if (usb.check()){
usb.work() ; //开始工作
}else{
System.out.println("硬件设备安装出现问题,无法使用") ;
}
}
}
class Keyboard implements IUSB{
public boolean check(){
return true ;
}
public void work(){
System.out.println("开始进行码字任务") ;
}
}
class Print implements IUSB{
public boolean check(){
return false ;
}
public void work(){
System.out.println("开始进行照片打印") ;
}
}
public class StringDemo{
public static void main(String[] args){
Computer computer = new Computer() ;
computer.plugin(new Keyboard()) ; // 插入键盘 // 向上转型 )
computer.plugin(new Print()) ;
}
}
而在现实的开发之中,对于标准的概念无处不在。
工厂设计模式(Factory)
对于接口而言,已经可以明确的清楚必须拥有子类,并且子类可以通过对象的向上转型类获取接口的实例化对象。但是在进行对象实例化的过程之中,也可能存在设计问题。
范例:观察如下一个程序
interface IFood{ // 定义一个食物的标准
public void eat() ;
}
class Bread implements IFood{ // 定义一种食物
public void eat(){
System.out.println("吃面包") ;
}
}
public class JavaDemo{
public static void main(String[] args){
IFood food = new Bread() ;
food.eat() ; // 吃面包
}
}
在本程序之中,根据接口进行子类的定义,并且利用对象的向上转型进行接口对象实例化的一个处理,而此时程序的结构设计如下:
客服端需要明确的知道具体的那一个子类,如果说现在面包吃腻了,需要牛奶了,那么客服端就要做出修改。
范例:扩展一类食物
interface IFood{ // 定义一个食物的标准
public void eat() ;
}
class Bread implements IFood{ // 定义一种食物
public void eat(){
System.out.println("吃面包") ;
}
}
class Milk implements IFood{
public void eat(){
System.out.println("喝牛奶") ;
}
}
public class JavaDemo{
public static void main(String[] args){
Bread food = new Milk() ;
food.eat() ;
}
}
此时的程序就表示出现有耦合问题,而造成耦合最直接的元凶:关键字new。以JVM的设计为例,Java实现可移植性的关键在于:JVM,而JVM的核心原理:利用一个虚拟机来运行Java程序,所有的程序并不与具体的操作系统有任何的关联,而是由JVM进行匹配,所以良好的设计应该避免耦合。
范例:工厂设计实现
interface IFood{ // 定义一个食物的标准
public void eat() ;
}
class Bread implements IFood{ // 定义一种食物
public void eat(){
System.out.println("吃面包") ;
}
}
class Milk implements IFood{
public void eat(){
System.out.println("喝牛奶") ;
}
}
class Factory{
public static IFood getInstance(String className){
if ("bread".equals(className)){
return new Bread() ;
}else if("milk".equals(className)){
return new Milk() ;
}else{
return null ;
}
}
}
public class JavaDemo{
public static void main(String[] args){
IFood food = Factory.getInstance(args[0]) ;
food.eat() ;
}
}
在本程序中,客户端程序类与IFood接口的子类没有任何的关联,所有的关联都是通过Factory类完成的,而在程序运行的时候可以通过初始化参数进行要使用的子类定义:
- java JavaDemo bread
- java JavaDemo milk
如果在日后进行子类扩充的时候,只需要修改Factory程序类即可实现。
代理设计模式
代理设计的主要功能是可以帮助用户将所有开发的注意力集中在和兴业务处理上,例如:肚子饿,思考的是如何吃到东西。
范例:实现代理设计
interface IEat{
public abstract void get() ;
}
class EatReal implements IEat{
public void get(){
System.out.println("【真实主题】得到一份食物,吃饭") ;
}
}
class EatProxy implements IEat{ // 代理服务
private IEat eat ; // 为吃而服务
public EatProxy(IEat eat){ // 一定要有一个代理的项
this.eat = eat ;
}
public void get(){
this.prepare() ;
this.eat.get() ;
this.clear() ;
}
public void prepare(){ // 准备过程
System.out.println("【代理主题】1、准备食材 ") ;
System.out.println("【代理主题】2、加工食材") ;
}
public void clear(){
System.out.println("【代理主题】3、收拾碗筷") ;
}
}
public class JavaDemo{
public static void main(String[] args){
IEat eat = new EatProxy(new EatReal()) ;
eat.get() ;
}
}
代理设计模式的主要特点有:一个接口提供有两个子类,其中一个子类是真实业务操作类,另外一个主题是代理业务操作类,没有代理业务操作真实业务无法进行。
抽象类与接口的区别
在实际大开发之中,抽象类与接口的定义非常的相似,这一点从JDK1.8开始就特别的明显。在JDK1.8里面接口也可以定义default和static方法。但是这两者依然有明显区别。
抽象类 | 接口 | |
定义 | abstract class 抽象类的名称 { } | interface 接口名称 { } |
组成 | 构造方法、抽象方法、普通方法、static方法、全局常量、成员 | 抽象方法、defaullt + 普通方法、static方法、全局常量 |
权限 | 可以使用各种权限定义 | 只能够使用public |
子类使用 | 子类可以通过extends关键字继承一个抽象类 | 子类使用implements关键字可以实现多个接口 |
两者关系 | 抽象类可以实现若干个接口 | 接口不许继承抽象类,但是允许继承多个父接口 |
使用 | 1、抽象类和接口必须定义子类 2、子类一定要覆写抽象类和接口中的抽象方法 3、通过子类的向上是实现抽象类或接口的对象实例化 |
当抽象类和接口都可以使用的情况下,优先考虑接口,因为就可可以避免单继承局限。从正常的设计角度,也需要从接口是实现项目的整体设计。