前言

C#和Java关于类、抽象类、接口使用方式基本相似,只是对应关键字使用不同罢了,本节呢,我们只是对照C#和Java中关于这三个概念在具体使用时,看看有哪些不一样的地方。

类继承

C#和Java在定义类方式上是一致的,这点没有什么太多要讲解的,我们直接进入到类继承上,在Java中实现继承通过extends关键字,而在C#中则是以冒号(:)来继承,非常优雅而简洁,Java如下:

class  Animal{}

class  Tiger extends  Animal{}

在C#中如下:

class Animal { }

class Tiger : Animal { }

既然讲解到了继承,必然也就涉及到方法重写了,无论Java还是C#对于重写的概念一致:方法重写意味着在子类中定义一个方法,该子类已经在父类中定义,具有相同的方法签名 - 相同的名称,参数和返回类型。Java中对于重写如下:

class Animal {
void Run() {
System.out.println("动物可能会跑");
}
} class Tiger extends Animal {
void Run() {
System.out.println(this.getClass().getSimpleName() + "会跑");
}
}
 Tiger tiger = new Tiger();
tiger.Run();

在C#中我们也可以如上代码进行,但是会有警告,如下:

    class Animal
{
public void Run()
{
Console.WriteLine("动物可能会跑");
}
}
class Tiger : Animal
{
public void Run()
{
Console.WriteLine($"{GetType().Name}会跑");
}
}

我们通过如上写了之后,我们会发现编译器提示如下警告,为何呢?,不难想象,因为我们子类继承了父类,这就相当于父类的方法就在子类中一样(我们这里说的是相当于,因为如果在一个类中要是有两个一模一样的方法也就是方法名称、签名都一样,肯定就报错了,要区分开重载和重写的概念),所以会进行警告提示。

实际上在C#中的重写是如下这样的,父类方法若根据业务来看后续存在被重写的可能则通过virtual关键字修饰,在子类中重写父类中的方法时通过override关键字修饰

    class Animal
{
public virtual void Run()
{
Console.WriteLine("动物可能会跑");
}
}
class Tiger : Animal
{
public override void Run()
{
Console.WriteLine($"{GetType().Name}会跑");
}
}

接口

C#和Java中对于抽象类使用基本无差异,都是可定义抽象方法和非抽象方法,而抽象方法只能在抽象类和接口中,有的人就说了,为何不能在类中定义,这就涉及到学习方法了,因为都是面向对象的语言,所以我们就要以人的思维方式去思考和举例(不要每学一门语言就感觉是全新的概念,很多都是相通的),因为类实例化后就是一个具体的对象,既然是具体的对象,那么在对象中的变量和方法必须是完全实现了的,这么讲想必我们就恍然大悟、豁然开朗了。抽象方法在接口中的定义唯一一点的小区别则是在idea编译器中会提示abstract完全没必要,因为接口就是抽象的类型,而在vs编译中不会进行提示。那么在Java中接口的定义是什么呢?接口是一种抽象类型,包含方法和常量变量的集合, 它是Java中的核心概念之一,用于实现抽象,多态和多重继承。接下来我们定义一个电子产品接口,如下:

interface Electronic {

    //常量
String LED = "LED"; //抽象方法
int getElectricityUse(); //静态方法
static boolean isEnergyEfficient(String electronicType) {
if (electronicType.equals(LED)) {
return true;
}
return false;
} //默认方法
default void printDescription() {
System.out.println("Electronic Description");
}
}

接下来我们来通过具体的电子产品来实现上述接口,通过implements关键字来实现接口。

class Computer implements Electronic {

    public int getElectricityUse() {
return 1000;
}
}

接下来我们进行如下调用:

 System.out.println(Computer.LED);
Computer computer = new Computer();
System.out.println(computer.getElectricityUse());

那么问题来了,定义一个接口时,在接口中我们可以定义哪些内容呢?常量变量、抽象方法、静态方法、默认方法。对于静态方法和默认方法是在Java8中才出现的新特性,常量变量必须是以public、static、final修饰,这点好理解,我们来通过类访问新特性出现的静态方法,结果如下访问不到,这是啥情况?

Java8新特性对于接口添加了静态方法,既然是静态方法我们通过实现接口,通过类访问静态方法居然访问不到,那接口中的静态方法还有存在的意义?是不是脑子有坑呢?是不是有点开始怀疑人生了呢?莫慌,我们要学会分析问题:因为类可以实现多个接口,若一个类实现了多个接口,而且多个接口中定义了相同的静态方法, 此时类都将继承多个接口中相同的静态方法,此时会出现编译器不知道要调用哪个接口中的静态方法的问题。所以才出现了我们实现了接口却无法访问接口中的静态方法,这是Java8中对于接口中定义静态方法的限制即:接口中的静态方法不能由实现它的类所继承,只能通过其定义的接口访问静态方法。如下:

 System.out.println(Electronic.isEnergyEfficient("LED"));

那么问题又来了,要是我们如下在实现接口中的类中也定义接口中的静态方法,会不会出现重写的情况呢(为了观察是否重写,将判断条件取非)?

class Computer implements Electronic {

    public int getElectricityUse() {
return 1000;
} static boolean isEnergyEfficient(String electronicType) {
if (!electronicType.equals(LED)) {
return true;
}
return false;
}
}
System.out.println(Computer.isEnergyEfficient("LED"));
System.out.println(Electronic.isEnergyEfficient("LED"));

由上我们知道其接口的实现类具有相同名称的静态方法,并且都不会重写。那么在接口中定义静态方法的意义是什么呢?在我看来:静态方法本属于类级别,在java8中将静态方法扩展到接口,相当于我们可以将接口作为类来使用。那么问题又来了,在java8新特性中出现了静态方法,也出现了默认方法,那么静态方法和默认方法有何区别呢?我们在其接口实现类中添加和接口中默认方法同名的方法,如下:

class Computer implements Electronic {

    public int getElectricityUse() {
return 1000;
} static boolean isEnergyEfficient(String electronicType) {
if (!electronicType.equals(LED)) {
return true;
}
return false;
} public void printDescription() {
System.out.println("Computer Electronic");
}
}

接下来我们通过类实例和匿名类如下来调用上述方法:

        //实例类
Electronic computer = new Computer();
computer.printDescription(); //匿名类
Electronic e = new Electronic() {
@Override
public int getElectricityUse() {
return 50;
} @Override
public void printDescription() {
System.out.println("Anonymous Electronic Description");
}
};
e.printDescription();

我们看到类实例和匿名类都可重写默认方法,由上我们可得出结论:接口静态方法只能由定义的接口所调用,而接口默认方法可由接口实现类实例或接口匿名类所重写,进一步阐述了在java8中接口被扩展到当做类使用。而接口默认方法的作用则是提供常用功能,一来扩展接口,二来不用破坏接口实现类。接口静态方法的作用则是提供公共帮助方法而无需额外再创建类。在C#接口我们可以定义属性,但是在Java中则不行,同时在C# 8.0上针对接口我们也可以定义默认方法,同时对于默认方法没有任何限制,也就是说我们既可以定义普通方法,也可以定义静态方法,这点是Java不可比拟的,比如如下:

    public interface Electronic
{
string Color { get; set; } bool isEnergyEfficient(string electronicType)
{
return true;
}
}

抽象类和接口

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?《转载:https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/index.html

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP( Open-Closed Principle),抽象类是其中的关键所在。

从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:

使用interface的方式定义Demo抽象类的方式如下:

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

从编程层面看abstract class和interface

从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。

从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

使用interface方式定义Door:

其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

那么具有报警功能的AlarmDoor的定义方式如下:

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。

总结

本节我们详解讲解了类继承、抽象类、接口以及在java8中出现的新特性,同时转载了一篇虽说文章比较久远但是思想没变,个人认为写的非常好的文章关于抽象类和接口的区别所在,希望带给如我一样的初学者更深层次的思考。

Java入门系列之类继承、抽象类、接口(五)的更多相关文章

  1. Java入门系列-18-抽象类和接口

    抽象类 在第16节继承中,有父类 People People people=new People(); people.sayHi(); 实例化People是没有意义的,因为"人"是 ...

  2. 面向对象 继承 抽象类 接口 static 权限修饰符

    Day01 面向对象 继承 抽象类 接口 static 1.匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量. 2.类的继承是指在一个现有类的基础上去构建一个新的类,构建出 ...

  3. Java基础系列4:抽象类与接口的前世今生

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 1.抽象类: 当编写 ...

  4. Java入门系列(三)面向对象三大特性之封装、继承、多态

    面向对象综述 封装 封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫接口. 有了封装,就可以明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者:而外部调用者也可以知道 ...

  5. Java的修饰、继承、接口、抽象类

     1.private 修饰属性或者方法,只能在本类中被访问,定义后需要加get()set()方法,这样提高数据的安全性 私有属性虽然不能直接访问,但是其对象 或者 子类对象可以通过公有方法进行设值和获 ...

  6. java基础知识总结--继承和接口

    什么是继承?什么是接口?他们之间的区别和联系是什么? 什么是继承? 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能.多个类中存在相同属性和行 ...

  7. 【Java入门提高篇】Day2 接口

    上一篇讲完了抽象类,这一篇主要讲解比抽象类更加抽象的内容--接口. 什么是接口呢?先来看个栗子: /** * @author Frank * @create 2017/11/22 * @descrip ...

  8. JAVA常用基础知识点[继承,抽象,接口,静态,枚举,反射,泛型,多线程...]

    类的继承 Java只支持单继承,不允许多重继承- 一个子类只能有一个父类- 一个父类可以派生出多个子类这里写图片描述子类继承了父类,就继承了父类的方法和属性.在子类中,可以使用父类中定义的方法和属性, ...

  9. Java入门系列-26-JDBC

    认识 JDBC JDBC (Java DataBase Connectivity) 是 Java 数据库连接技术的简称,用于连接常用数据库. Sun 公司提供了 JDBC API ,供程序员调用接口和 ...

随机推荐

  1. 记Linux下一次乱码事件

    近来需要对着教程敲代码,但是之前在Windows上的压缩包在Linux解压后发生了乱码,主要是文件内乱码,文件名还是正常的.搜索“Linux rar解压乱码“试了一圈也没解决.不过到是发现了winra ...

  2. Web基础了解版04-XML-Tomcat-Http

    XML 什么是XML - Tomcat - Http XML:eXtensible Markup Language (可扩展标记语言). XML 是一种标记语言,很类似 HTML. XML 的设计宗旨 ...

  3. Fuchsia文章汇总

    今日,windows时代的十年已经过去,android/ios时代的十年也行将结束,下一个十年是谁的十年? 操作系统做为软件的基石,做为基础服务的基础,因为各层应用框架的层层封装,正在变的越来越透明, ...

  4. C#获取字符串的拼音和首字母

    在C#中我们想要获取字符串的拼音并不是那么困难的,在网上看到很多都是特别笨的方式来实现,其实各有各的好处吧,如果使用了下方法方式,它不知道多音字,这就是一个问题. /// <summary> ...

  5. python同步IO编程——基本概念和文件的读写

    IO——Input/Output,即输入输出.对于计算机来说,程序运行时候数据是在内存中的,涉及到数据交换的地方,通常是磁盘.网络等.比如通过浏览器访问一个网站,浏览器首先把请求数据发送给网站服务器, ...

  6. python通过人脸识别全面分析好友,一起看透你的“朋友圈”

    微信:一个提供即时通讯服务的应用程序,更是一种生活方式,超过数十亿的使用者,越来越多的人选择使用它来沟通交流. 不知从何时起,我们的生活离不开微信,每天睁开眼的第一件事就是打开微信,关注着朋友圈里好友 ...

  7. Zabbix Server 3.2

    软件环境 Centos7.3 LAMP Zabbix 3.2  1. Installing repository configuration package Install the repositor ...

  8. Please ensure the argon2 header and library are installed

    在CentOS上安装libargon2和libargon2-devel即可 yum install -y libargon2 libargon2-devel

  9. GetPrivateProfileString() 当 key 包含空格时,需要进行转义

    使用 GetPrivateProfileString() 方法可以方便的读取 ini 格式文件中的内容,如: [section] tommy = worker 使用 C# 读取如下: 1. 先引入 G ...

  10. WinCC的电子签名与审计追踪

    如何写入审计追踪记录 用脚本向Audit中添加记录有两种方法,一种方法是用InserAuditEntryNew函数写入,另一种方法是生成属于“操作员输入消息”类型的报警消息,该报警消息会记录到Audi ...