资讯专栏INFORMATION COLUMN

接口

xiyang / 888人阅读

摘要:恰当的原则是优先选择类而不是接口。接口是一种重要的工具,但是它们容易被滥用。

接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法

抽象类和抽象方法

抽象类可以理解为是一种不够纯粹的接口,它是普通类与接口之间的一种中庸之道。

再论初始化

首先看看下面的实例代码:


class Glyph { void draw() { print("Glyph.draw()"); } Glyph() { print("Glyph() before draw()"); draw(); print("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; print("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { print("RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 *///:~

根据Thinking in Java的描述:
1)在其它任何事物发生之前,将分配给对象的存储空间初始化为成二进制的零(基本类型按照其类型初始化,引用类型为null)
2)调用基类的构造器(如果基类中实例属性或者静态属性有初始化过程则先执行初始化过程),此时,调用被覆盖后的draw(),由于步骤1的缘故,
我们会发现radius的值为0.
3)按照声明的顺序调用成员的初始化方法
4)调用导出类的构造器主体
之所以出现上面的这种问题,是因为初始化的顺序,以及在构造器中调用了可以被子类重写的方法造成的。这种错误往往难以发现。如何避免这种
错误呢?在编写构造器时有一个重要的准则:“用尽可能简单的方法使对象进入正常状态:如果可以的话,避免调用其他方法”
在构造器中唯一能够安全调用的那些方法是基类中的final方法()(也适用于private方法,因为private方法自动属于final方法),

接口为Java提供了多重继承

使用接口的核心原因:
为了能够向上转型为多个基类型(以及由此带来的灵活性).看看下面的示例代码:


package c07.po; interface CanFight { void fight(); } interface CanClimb { void canClimb(); } interface CanSwim { void swim(); } interface CanFly { void fly(); } class ActionCharacter { public void fight() { } } class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly,CanClimb { @Override public void fly() { } @Override public void swim() { } @Override public void canClimb() { } } 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 c(CanClimb c) { c.canClimb(); } 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 c(h); // Treat it as a CanClimb } }

接口带给Java多重继承的特性,可以使一个对象C既可以被认作是A对象,也可以被认作是B对象,并且在被认作是A或者B对象的时候可以利用多态实
现不同对象的同一方法的不同的行为。这样就提高了设计的灵活性。并且在对象C作为方法参数时,最好使用它的基类型作为参数类型,这样对象c即
适用于以类型A作为参数类型的方法,也可以适用于类型B作为参数类型的方法,提高了灵活性。
可以这么说:接口是保证系统可扩展可插拔的基础(关键因素),一个设计良好的系统必须留有足够的接口。

接口是允许多继承的

通过继承,可以很容易的在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口,可以参看下面的
示例代码:

package c07.po;

interface Monster {
    void menace();
}

interface DangerousMonster extends Monster {
    void destroy();
}

interface Lethal {
    void kill();
}

class DragonZilla implements DangerousMonster {
    public void menace() {
    }

    public void destroy() {
    }
}

interface Vampire extends DangerousMonster, Lethal {
    void drinkBlood();
}

class VeryBadVampire implements Vampire {
    public void menace() {
    }

    public void destroy() {
    }

    public void kill() {
    }

    public void drinkBlood() {
    }
}

public class HorrorShow {
    static void u(Monster b) {
        b.menace();
    }

    static void v(DangerousMonster d) {
        d.menace();
        d.destroy();
    }

    static void w(Lethal l) {
        l.kill();
    }

    public static void main(String[] args) {
        DangerousMonster barney = new DragonZilla();
        u(barney);
        v(barney);
        Vampire vlad = new VeryBadVampire();
        u(vlad);
        v(vlad);
        w(vlad);
    }
} // /:~


组合接口时的命名冲突

实现多重继承时,可能会遇到一个小麻烦。两个接口的包含方法签名和返回类型都完全一致的方法当然没有什么问题,但是下面的例子就有些麻烦了
先考虑一件事情:编译器是无法分别一个方法名和方法参数都相同仅有返回值不同的方法的。两个方法签名完全一致的方法会导致编译器报错。
关于方法签名可以这么简单的理解:方法的名字,方法的参数列表(包括参数类型和参数的顺序)组成方法的方法签名。
看看下面的示例代码:


public interface F1 { void f(); String g(); } public interface F2 { void f(int i ); int g(int g); } public interface F3 { int f(); } public class F1F2 implements F1, F2 { /** * 接口F1的方法void f() 与接口F2的方法void f(int i ) 名称相同但是参数列表不同,所以可以形成重载overload * 这是可行的,也就是不同接口两个方法同名但是参数列表不同 */ @Override public void f(int i) { } @Override public void f() { } /** * 接口F1的String g()和接口F2的int g(int g)只有方法名称相同,参数和返回值都不同,不形成重载 */ @Override public int g(int g) { return 0; } @Override public String g() { return null; } } public class F1F3 implements F1,F3 {//The type F1F3 must implement the inherited abstract method F3.f() /**ERROR * The return type is incompatible with F3.f() 接口F1的void f()方法与接口F3的方法int f()参数列表和名称完全相同,只有返回值不同,不能形成重载,但是也无法通过 编译,编译器无法分别两个同名同参的方法 所以类F1F3是无法同时兼容接口F1和F3的 当然这种蛋疼的事情还是很少遇见的。 @Override public void f() { // TODO Auto-generated method stub } */ @Override public String g() { return null; } }
方法签名

摘抄自一段提问
What is method signature in java?

The term "method signature" has an exact meaning in Java and is explicitly defined in the Java Language Specification.
The signature of a method consists of the method name and the parameter list (type and number). It DOES NOT include the return type or modifiers such as access modifiers (public, protected, , private), abstract, static, etc.

For example, you cannot have two methods with the same name and parameter list that differs only in that one is static and the other is not, or that differ in that one returns void and the other returns a non-void value.

The use of generics resulted in the addition of the term subsignature, which is documented in the latest Java Language Specification

原文链接:https://answers.yahoo.com/question/index?qid=20070306205943AAAsAnx

Java给出的官方文档的解释是这样的:

8.4.2. Method Signature

Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.

The signature of a method m1 is a subsignature of the signature of a method m2 if either:

m2 has the same signature as m1, or

the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.

It is a compile-time error to declare two methods with override-equivalent signatures in a class.


class Point { int x, y; abstract void move(int dx, int dy); void move(int dx, int dy) { x += dx; y += dy; } } /** This program causes a compile-time error because it declares two move methods with the same (and hence, override-equivalent) signature. This is an error even though one of the declarations is abstract. **/

The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.

class CollectionConverter {
    List toList(Collection c) {...}
}
class Overrider extends CollectionConverter {
    List toList(Collection c) {...}
}

/**
Now, assume this code was written before the introduction of generics, and now the author of class CollectionConverter decides to generify the code, thus:
**/

class CollectionConverter {
     List toList(Collection c) {...}
}

/**
Without special dispensation, Overrider.toList would no longer override CollectionConverter.toList. Instead, the code would be illegal. This would significantly inhibit the use of generics, since library writers would hesitate to migrate existing code.
**/
接口中的域

放入接口中的任何域都都自动是static 和final的,所以接口是一种便捷的用来创建常量的工具。在JavaSE5之前,这是产生enum类型的唯一途径
接口中的域不可以是“空final的”,但是可以被非常量表达式初始化。
接口的静态常量的初始化只有在接口加载的时候才会进行初始化,比如直接调用接口常量可以引起接口常量的初始化,再比如子类调用实现接口的方法
会引起接口常量的初始化.实例代码如下:


public class Value { private String str; public Value(String str){ this.str = str; System.out.println(this.str); } } public interface InterFaceA { public Value v1 = new Value("v1"); Value v2 = new Value("v2"); void printValue(); } public class TC { public static Value v1TC = new Value("v1TC"); public static Value v2TC= new Value("v2TC"); } public class ImplA extends TC implements InterFaceA{ public ImplA(){ System.out.println("ImplA构造函数来啦"); } @Override public void printValue() { System.out.println(v1); System.out.println(v2); } } package c07.ta; import static org.junit.Assert.*; import org.junit.Test; public class TestClient { /** * 直接访问接口常量,会引起接口常量的初始化(全部常量) */ @Test public void test1() throws Exception { Value v1 = ImplA.v1; //输出 : v1 v2 } /** * 只初始化接口子类,只会先初始化父类的静态常量,再初始化子类 * 并不会进行接口的初始化,也不会有接口静态常量的初始化 */ @Test public void test2() throws Exception { ImplA implA = new ImplA(); /*输出: * v1TC v2TC ImplA构造函数来啦 */ } /** *初始化接口子类,并且调用子类实现接口的方法,会引起接口的初始化,那么接口的静态常量也就初始化了 */ @Test public void test3() throws Exception { ImplA implA = new ImplA(); implA.printValue(); /*输出: v1TC v2TC ImplA构造函数来啦 v1 v2 c07.ta.Value@18fe7c3 c07.ta.Value@b8df17 */ } }
不要总是使用接口进行设计

“确定接口是理想的选择,因而应该总是选择接口而不是具体的类”这其实是一种诱惑。这种逻辑看起来是这样的:因为需要使用不同的具体实现,因此总应该添加这种抽象性
。这句话本身并没有错,但是却成为草率设计滥用接口的指导。
首先需要明确的一点是:任何抽象性都应该是由真正的需求而产生的。当必须时,你应该重构接口而不是到处添加额外级别的间接性,并由此带来额外的复杂性。
如果过度的对接口抽象,那么带来的后果则是类膨胀和复杂性的增加,因此既要保证系统的灵活性和可扩展性(利用多态特性,实现接口进行抽象),也要保证系统的复杂性不过分提高(慎用接口)。
恰当的原则是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确,那么就进行重构。接口是一种重要的工具,但是它们容易被滥用。
所以我认为合适的设计过程应该是先从划分类开始,先划分类的功能,再对类进行重新审视和设计,对类进行重构并且提炼出抽象类,在这基础上再进行接口的抽象,接口抽象后再进行审视,对接口进行重构,之后再审视接口与类,这样完成从底到上,在从顶到下的设计一个系统(子系统,模块,三五个类组成的组件)。

author zhaob
time : 2014-09-14 16:57

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64137.html

相关文章

  • 持续更新免费的API,做一个API的搬运工

    摘要:为了方便广大的开发者,特此统计了网上诸多的免费,为您收集免费的接口服务,做一个的搬运工,以后会每月定时更新新的接口。将长段中文切词分开。 为了方便广大的开发者,特此统计了网上诸多的免费API,为您收集免费的接口服务,做一个api的搬运工,以后会每月定时更新新的接口。有些接口来自第三方,在第三方注册就可以成为他们的会员,免费使用他们的部分接口。 百度AccessToken:针对HTTP ...

    Shihira 评论0 收藏0
  • 不用写代码,也能做好接口测试

    摘要:接口测试形式单个接口测试包含性能测试和通过接口调用进行场景测试。充分来说就是接口测试相对容易实现自动化持续集成。 本文你将了解到 1、接口测试基本概念,包含什么是接口,什么是接口测试,为什么要做接口测试2、接口测试用例设计3、怎样不用写代码,也能快速的根据开发的API文档完成接口自动化测试脚本 注:如果你对接口基本概念和接口测试用例已熟悉,可以直接跳过,其实看一遍也无防,就当作 温故知...

    idisfkj 评论0 收藏0
  • 12.java 接口

    摘要:接口的对象可以利用子类对象的向上转型进行实例化赋值。接口文件保存在结尾的文件中,文件名使用接口名。接口相应的字节码文件必须在与包名称相匹配的目录结构中。接口不能包含成员变量,除了全局常量定义。 概念 接口,在JAVA编程语言中是一个引用类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。 接口中只能包含抽象方法和全局常量。 接...

    pinecone 评论0 收藏0
  • Java 接口(9)

    摘要:接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。 1.抽象类和抽象方法 抽象类,是普通的类与接口之间的一种中庸之道. 抽象方法:仅有声明而没有方法体. 抽象类:包含抽象方法的类.如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的. 如果从一个抽象类继承,并想创建该新类的对象,那么久必须为基类中的所...

    lncwwn 评论0 收藏0
  • Java 接口与抽象类方式实现类的扩展

    摘要:子类继承抽象类,并具体实现方法。抽象类的使用区别于具体类,抽象类无法直接创建抽象类对象,但是可以声明抽象类的变量,引用抽象类对应具体子类对象。接口优于抽象类中讨论到一条规则接口优于抽象类。接口声明能力,抽象类提供默认实现全部或部分方法。 接口 类,强调数据类型(自定义)的概念,在一些情况下,并不能反映对象以及对象操作的本质。有时我们关注的并非对象的类型,而是对象的能力。 接口声明一组功...

    neroneroffy 评论0 收藏0
  • 面向对象基本原则(1)- 单一职责原则与接口隔离原则

    摘要:面向对象基本原则单一职责原则与接口隔离原则面向对象基本原则单一职责原则与接口隔离原则面向对象基本原则里式代换原则与依赖倒置原则面向对象基本原则最少知道原则与开闭原则一单一职责原则单一职责原则简介单一职责原则的英文名称是,简称。 面向对象基本原则(1)- 单一职责原则与接口隔离原则 面向对象基本原则(1)- 单一职责原则与接口隔离原则面向对象基本原则(2)- 里式代换原则与依赖倒置原则面...

    lunaticf 评论0 收藏0

发表评论

0条评论

xiyang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<