0%

Java/java-抽象类和接口

抽象类

要点提示: 父类中定义了相关子类的共同行为。接口可以拥有定义类的共同行为(包括非相关的类)

抽象类

要点提示: 抽象类不可以用于创建对象。抽象类可以包含抽象方法,这些方法将在具体的子类中实现。

  • 抽象类定义: 类的设计应该确保父类包含它的子类的共同特征。有时候,一个父类设计得非常抽象,以至于它都没任何具体的实例。这样的类称为抽象类。

  • 抽象方法:
    -w1084

  • 抽象类和常规类很像,但是不能使用new操作符创建它的实例。抽象方法只有定义而没有实现。他的实现由子类实现。一个包含对象方法的类的必须声明为抽象类。

  • 抽象类的沟造方法定义为protected,它只能被子类使用。创建一个具体的子类的实例时,它的父类的构造方法被调用以初始化父类中定义的数据域。

为何要使用抽象方法

使用抽象方法以后,JVM在运行时可以根据对象的类型动态地决定调用哪一个方法。

抽象类的几点说明

  • 抽象方法不能包含在非抽象类中。如果抽象父类的子类不能实现所有的抽象方法,那么子类也必须定义为抽象的。即在抽象类扩展的非抽象子类中,必须实现所有的抽象方法。另外,抽象方法是非静态的。
  • 抽象类不能使用new关键字来初始化。但是仍然可以定义它的构造方法,这个构造方法在它的子类的构造方法中调用。
  • 抽象方法的类必须是抽象的。但是,可以定义一个不包含抽象方法的抽象类。这种情况下,不能使用new创建该类的实例。这种类是用来定义新子类的基类的。
  • 子类可以覆盖分类的方法并将它定义为abstract。
  • 即使子类的父类是具体的,这个子类也可以是抽象的。
  • 不能使用new从一个抽象类创建一个实例。但是抽象类可以用作一种数据类型。
    下面语句用来创建一个元素是GeometricObject类型的数组:
    GeometricObject[] objects = new GeometricObject[10];
    然后可以创建一个GeometricObject的实例,并将它的引用赋值给数组。
    object[0] = new Circle();// Circle是GeometricObject的子类

示例学习: 抽象的Number类

要点提示: Number类是数值包装类、BigInteger以及BigDecimal的父类。

示例学习: Calendar和GregorianCalendar

要点提示: GregorianCalendar是抽象类Calendar的一个具体子类。

一个Date的实例表示以毫秒为精度的特定时刻。Calendar是一个抽象的基类,可以提取出详细的日历信息。例如: 年、月、日、小时、分钟和秒。
GregorianCalendar是一个支持公历的类。
Calendar类中的add方法是抽象的,因为它的实现依赖于某个具体的日历系统。

可以使用new GregorianCalendar()利用当前时间构造一个默认的GregorianCalendar对象,可以使用GregorianCalendar(year, month, date)利用指定的year、month和date(日)构造一个GregorianCalendar对象。参数month是基于0的,即0代表1月

在Calendar类中定义的get(int field)方法在从Calendar类中提取日期和时间信息方面是很有用的。日期和时间域都被定义为常量。

接口

要点提示: 接口是一种与类相似的结构,只包含常量和抽象方法

接口在很多方面都与抽象类很相似,但是它的目的是指明相关或者不相关类的多个对象的共同行为。例如,使用正确的接口,可以指明这些对象是可比较的、可食用的,以及可克隆的。
定义接口:
-w975

在java中,接口被看作是一种特殊的类。就像常规类一样,每个接口都被编译为独立的字节码文件。使用接口或多或少有点像使用抽象类。即:

  • 可以使用接口作为引用变量的数据类型或类型转换的结果。
  • 不能使用new操作符创建接口的实例。

可以使用Edible接口来明确一个对象是否是可食用的。这需要使用implements关键字让对象的类实现这个接口来完成。

Comparable接口

要点提示: Comparable接口定义了compoareTo方法,用于比较对象。
接口的定义如下:
// Interface for comparing objects, defined in java.lang
package java.lang;
public interface Comparable
{ public int compareTo(E o);
}

Comparable 方法判断这个对象相对于给定对象o的顺序,并且当这个对象小于、等于或大于给定对象o时,分别返回负整数、0或正整数。

Comparable接口是一个泛型接口。在实现该接口时,泛型类型E被替换成一种具体的类型。

由于所有Comparable对象都有compareTo方法,如果对象是Comparable接口类型的实例的话,Java API中的java.util.Arrays.sort(Object[])方法就可以使用compareTo方法对数组中的对象进行比较和排序。

Cloneable接口

要点提示: Cloneable接口给出了一个可克隆的对象。

经常会出现需要创建一个对象拷贝的情况。为了实现这个目的,需要使用clone方法并理解Cloneable接口。

接口通常包括常量和抽象方法,但是Cloneable接口是一个特殊情况。在java.lang包中的Cloneable接口的定义如下:
package java.lang;
public interface Cloneable {
}

也就是说,这个接口是空的。
一个带空体的接口被称为标记接口(maker interface).一个标记接口及不包括常量也不包括方法。它用来表示一个类拥有某种特定的属性。实现Cloneable接口的类标记为可克隆的,而且它的对象可以使用在Object类中定义的clone()类方法。

Java库中的很多类(例如,Date、Calendar和ArrayList)实现Cloneable。这样这些类的实例可以被克隆。

为了定义一个自定义类来实现Cloneable接口,这个类必须覆盖Object类中的clone()方法。

浅复制、 深复制

意思是:
Object类中的clone方法将原始对象的每个数据域赋值给目标对象。如果一个数据域是基本类型,复制的就是它的值,例如,area(double 类型)的值从house1复制到house2.如果一个数据域是对象,复制的就只是该域的引用。例如,域whenBuilt是Date类,所以,它的引用被复制给house2。
因此,尽管,house1==house2为假,但是house1.whenBuilt==house2.whenBuilt为真。这称为浅复制(shallow copy)而不是深复制(deep copy),这意味着如果数据域是对象类型,那么复制的是对象的引用,而不是它的内容。

接口与抽象类

要点提示:一个类可以实现多个接口,但是只能继承一个父类。
接口的使用和抽象类的使用基本相似,但是,定义一个接口与定义一个抽象类有所不同。
-w1096

利用关键字extands,接口可以继承其他接口。这样的接口称为子接口(subinterface)。

接口可以扩展其他接口而不是类。一个类可以扩展它的父类同时实现多个接口。

所有的类共享一个根类Object,但是接口没有共同的根。与此类似,接口也可以定义一种类型。一个接口类型的变量可以引用实现该接口的类的实例。如果一个类实现了一个接口,那么这个接口就类似于该类的一个父类。可以将接口当作一种数据类型使用,将接口类型的变量转换为它的子类,反过来也可以。

注意:类名是一个名词。接口名可以是形容词或者名词。

设计指南:
-w1131

通常,推荐使用接口而非抽象类,因为接口可以定义非相关类共有的父类型。接口比类更灵活。

实例学习: Rational类

设计一个Rational类,用于表示和处理有理数。

类的设计原则

要点提示: 类的设计原则有助于设计出合理的类

内聚性

类应该描述一个单一的实体,而所有的类操作应该在逻辑上互相配合,只吃一个一致的目的。

如果一个实体担负太多的职责,就应该按各自的职责分成几个类

一致性

遵循标准Java程序设计风格和命名习惯。为类、数据域和方法选取具有信息的名字。通常的风格是将数据声明至于构造方法之前,并且将构造方法置于方法之前。

选择名字要保持一致。给类似的操作选择不同的名字并非良好的实践。

一般来说,应该具有一致性地提供一个公共无参构造方法,用于构建默认实例。如果一个类不支持无参的构造方法,要用文档写出原因。如果没有显式定义构造方法,即假定有一个空方法体的公共默认无参构造方法。

如果不想用户创建类的对象,可以在类中声明一个私有的构造方法,Match类就是如此

封装性

一个类应该使用private修饰符隐藏其数据,以免用户直接访问它。这使类更易于维护。

只在希望数据域可读的情况下,才提供get方法;也只希望数据域可更新的情况下,才提供set方法。

清晰性

类应该有一个很清晰的合约,从而易于解释和理解。

用户可以以各种不同组合、顺序,以及在各种环境中结合使用多个类。因此,在设计一个类时,这个类不应该限制用户如何以及何时使用该类;以一种方式设计属性,以容许用户按值的任何顺序和任何组合来设置;设计方法应该使得实现的功能与它们出现的顺序无关。

不应该声明一个来自其他数据域的数据域。
例如:
-w1122

完整性

类是为许多不同用户的使用而设计的。为了能在一个广泛的应用中使用,一个类应该通过属性和方法提供多种方案以适应用户的不同需求。

实例和静态

依赖于类的具体实例的变量或方法必须是一个实例变量或方法。

如果一个变量被类的所有实例所共享,那就应该将它声明为静态的。

如果方法 不依赖于某个具体的实例,那就应该将它声明为静态方法。

应该总是使用类名(而不是引用变量)引用静态变量和方法,以增强可读性并避免错误。

不要从构造方法中传入参数初始化静态数据。最好使用set方法改变静态数据域。

实例和静态是面向对象程序设计不可或缺的部分。数据域或方法要么是实例的,要么是静态的。不要错误地忽视了静态数据域或方法。

构造方法永远都是实例方法,因为它是用来创建具体实例的。一个静态变量或方法可以从实例中调用,但是不能从静态方法中调用实例变量或方法。

继承和聚合

继承和聚合之间的差异,就是is-a(是一种)和has-a(具有)之间的关系。
这里解释一下后者:
人具有名字,因此,可以使用聚合来对Person类和Name类之间的关系建模。

接口和抽象类

接口和抽象类都可以用于为对象指定共同的行为。

如何决定是采用接口还是抽象类?
同常,比较强的is-a关系清晰地描述了父子关系,应该采用类的继承关系来建模。弱的is-a关系,也称为is-kind-of(是一类)关系,表明一个对象拥有某种属性。弱的is-a关系可以使用接口来建模。

接口比抽象类更灵活,因为一个子类只能继承一个父类,但是却可以实现任意个数的接口。然而,接口不能具有具体的方法。可以结合接口和抽象类的优点,创建一个接口,使用一个抽象类来实现它。可以视其方便使用接口或抽象类。