接口是否比使用正确的方法更多

时间:2009-02-02 21:07:36

标签: java oop language-features interface

所以我想说我有这个界面:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

我有一个实现它的类:

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

如果我想使用IBox接口,我实际上无法创建它的实例:

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

正确?所以我实际上必须这样做:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

如果这是真的,那么接口的唯一目的是确保实现接口的类如接口所描述的那样在其中获得了正确的方法?或者是否有其他任何接口用途?

17 个答案:

答案 0 :(得分:142)

接口是一种使代码更加灵活的方法。你做的是这个:

Ibox myBox=new Rectangle();

然后,如果你决定要使用不同类型的盒子(也许还有另一个库,有更好的盒子),你可以将代码切换到:

Ibox myBox=new OtherKindOfBox();

一旦你习惯了它,你会发现它是一种很棒的(实际上必不可少的)工作方式。

另一个原因是,例如,如果要创建一个框列表并对每个框执行某些操作,但您希望列表包含不同类型的框。在每个盒子上你可以做到:

myBox.close()

(假设IBox有一个close()方法),即使myBox的实际类根据你在迭代中所处的框而改变。

答案 1 :(得分:120)

接口有用的是这样的事实:“你可以改变主意,稍后使用不同的实现,只需要改变创建对象的地方”。这不是问题。

真正的观点已经在名称中:它们定义了一个接口,任何人都可以实现它来使用在该接口上运行的所有代码。最好的示例是java.util.Collections,它提供了专门在接口上运行的各种有用方法,例如sort()reverse() List。这里的重点是,此代码现在可用于排序或反转实现List接口的任何类 - 不仅仅是ArrayListLinkedList,还包括你自己写的课程,可以用写java.util.Collections的人从未想象过的方式实现。

以同样的方式,您可以编写在众所周知的界面或您定义的界面上运行的代码,而其他人可以使用您的代码而无需要求您支持他们的类。

接口的另一个常见用途是Callbacks。例如,java.swing.table.TableCellRenderer,它允许您影响Swing表如何显示某列中的数据。您实现该接口,将实例传递给JTable,并且在呈现表的某个时刻,您的代码将被调用以执行其操作。

答案 2 :(得分:118)

我读过的众多用途之一就是在Java中没有多继承使用接口的情况下很难:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

现在,想象一下:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

但是,

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

更好的设计将是:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Animal没有chew()方法,而是将其放在接口中:

interface Chewable {
void chew();
}

让Reptile类实现这个而不是Birds(因为Birds无法咀嚼):

class Reptile extends Animal implements Chewable { } 

和鸟类只是:

class Bird extends Animal { }

答案 3 :(得分:46)

接口的目的是多态,a.k.a。类型替换。例如,给定以下方法:

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

调用scale方法时,您可以提供实现IBox接口的任何类型的值。换句话说,如果RectangleSquare都实现IBox,那么只要Rectangle预期SquareIBox,就可以提供{{1}}或{{1}}

答案 4 :(得分:33)

接口允许静态类型语言支持多态。面向对象的纯粹主义者会坚持认为语言应该提供继承,封装,模块化和多态性,以便成为一个功能齐全的面向对象语言。在动态类型 - 或鸭类型 - 语言(如Smalltalk)中,多态性是微不足道的;然而,在静态类型语言(如Java或C#)中,多态性远非微不足道(事实上,从表面上看,它似乎与强类型的概念不一致。)

让我演示一下:

在动态类型(或鸭子类型)语言(如Smalltalk)中,所有变量都是对象的引用(没有更多,仅此而已。)因此,在Smalltalk中,我可以这样做:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

那段代码:

  1. 声明一个名为anAnimal的局部变量(请注意,我们不指定变量的TYPE - 所有变量都是对象的引用,不多也不少。)
  2. 创建名为“Pig”
  3. 的类的新实例
  4. 将新的Pig实例分配给变量anAnimal。
  5. 将信息makeNoise发送给猪。
  6. 使用奶牛重复整个事情,但将其分配给与Pig相同的确切变量。
  7. 相同的Java代码看起来像这样(假设Duck和Cow是Animal的子类:

    Animal anAnimal = new Pig();
    duck.makeNoise();
    
    anAnimal = new Cow();
    cow.makeNoise();
    

    这一切都很好,直到我们介绍类蔬菜。蔬菜与动物有一些相同的行为,但不是全部。例如,动物和蔬菜都可能生长,但显然蔬菜不会产生噪音,动物也无法收获。

    在Smalltalk中,我们可以这样写:

    |aFarmObject|
    aFarmObject := Cow new.
    aFarmObject grow.
    aFarmObject makeNoise.
    
    aFarmObject := Corn new.
    aFarmObject grow.
    aFarmObject harvest.
    

    这在Smalltalk中运行得非常好,因为它是鸭子型的(如果它像鸭子一样走路,像鸭子一样呱呱叫 - 它是一只鸭子。)在这种情况下,当一条消息被发送到一个对象时,一个查找在接收者的方法列表上执行,如果找到匹配的方法,则调用它。如果没有,则抛出某种NoSuchMethodError异常 - 但它都是在运行时完成的。

    但是在Java中,一种静态类型语言,我们可以为变量分配什么类型?玉米需要从蔬菜中继承,以支持生长,但不能从动物身上继承,因为它不会产生噪音。 Cow需要继承Animal以支持makeNoise,但不能继承VEG,因为它不应该实现收获。看起来我们需要多重继承 - 从多个类继承的能力。但事实证明这是一个非常困难的语言特性,因为弹出的所有边缘情况(当多个并行超类实现相同的方法时会发生什么?等等。)

    接下来的接口......

    如果我们制作动物和蔬菜类,每个实施Growable,我们都可以声明我们的牛是动物而我们的玉米是蔬菜。我们还可以宣称动物和蔬菜都是可以生长的。这让我们写这个来增长一切:

    List<Growable> list = new ArrayList<Growable>();
    list.add(new Cow());
    list.add(new Corn());
    list.add(new Pig());
    
    for(Growable g : list) {
       g.grow();
    }
    

    它让我们这样做,发出动物的声音:

    List<Animal> list = new ArrayList<Animal>();
    list.add(new Cow());
    list.add(new Pig());
    for(Animal a : list) {
      a.makeNoise();
    }
    

    duck-typed语言的优点是你可以获得非常好的多态性:所有类都必须提供行为才能提供方法。只要每个人都玩得很好,并且只发送符合定义方法的消息,一切都很好。缺点是下面的错误类型直到运行时才被捕获:

    |aFarmObject|
    aFarmObject := Corn new.
    aFarmObject makeNoise. // No compiler error - not checked until runtime.
    

    静态类型语言提供了更好的“按合同编程”,因为它们将在编译时捕获下面的两种错误:

    // Compiler error: Corn cannot be cast to Animal.
    Animal farmObject = new Corn();  
    farmObject makeNoise();
    

    -

    // Compiler error: Animal doesn't have the harvest message.
    Animal farmObject = new Cow();
    farmObject.harvest(); 
    

    所以....总结一下:

    1. 接口实现允许您指定对象可以执行的操作(交互),而类继承允许您指定应该如何完成(实现)。

    2. 接口为我们提供了许多“真正的”多态性的好处,而不会牺牲编译器类型检查。

答案 5 :(得分:9)

通常接口定义你应该使用的接口(如名称所示;-))。样品


public void foo(List l) {
   ... do something
}

现在,您的功能foo接受ArrayList s,LinkedList s,......不仅仅是一种类型。

Java中最重要的是你可以实现多个接口,但你只能扩展一个类!样品:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
可能但是

class Test extends Foo, Bar, Buz {
...
}
不是!

您的上述代码也可以是:IBox myBox = new Rectangle();。现在重要的是,myBox仅包含来自IBox的方法/字段,而不包含Rectangle中的(可能存在的)其他方法。

答案 6 :(得分:6)

为什么接口??????

首先是一只狗。特别是,哈巴狗

哈巴狗有各种各样的行为:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

你有一个拉布拉多犬,他也有一套行为。

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

我们可以制作一些哈巴狗和实验室:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

我们可以调用他们的行为:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

假设我经营一只狗窝,我需要跟踪我所居住的所有狗。我需要将我的哈巴狗和拉布拉多存放在不同的数组中

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

但这显然不是最佳选择。如果我想要一些贵宾犬,我也必须更改我的狗窝定义以添加一系列贵宾犬。事实上,我需要为每种狗提供一个单独的数组。

洞察力:哈巴狗和拉布拉多犬(和贵宾犬)都是狗的类型,他们有相同的行为。也就是说,我们可以说(为了这个例子的目的)所有的狗都可以吠叫,有一个名字,并且可能有也可能没有卷曲的尾巴。我们可以使用界面来定义所有狗可以做什么,但是将它留给特定类型的狗来实现这些特定的行为。界面上写着“这里是所有狗都可以做的事情”,但没有说明每种行为是如何完成的。

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

然后我稍微改变了Pug和Lab类以实现Dog行为。我们可以说帕格是狗,实验室是狗。

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

我仍然可以像以前那样实例化Pugs和Labs,但现在我也有了新的方法:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

这说d1不仅是一只狗,它还特别是一只哈巴狗。 d2也是狗,特别是实验室。 我们可以调用这些行为,它们像以前一样工作:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

这是所有额外工作得到回报的地方。养犬班变得更加简单。我只需要一个数组和一个addDog方法。两者都适用于任何狗的物体;也就是说,实现Dog接口的对象。

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

以下是如何使用它:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

最后一句话会显示: 现货Fido

通过接口,您可以指定一组行为,实现该接口的所有类将共享。因此,我们可以定义变量和集合(例如数组),这些变量和集合不必事先知道它们将保留哪种特定对象,只是它们将保存实现接口的对象。

答案 7 :(得分:6)

你可以做到

Ibox myBox = new Rectangle();

通过这种方式,您将此对象用作Ibox,并且您并不关心它真的Rectangle

答案 8 :(得分:6)

我认为你了解接口所做的一切,但你还没有想到接口有用的情况。

如果您在一个狭窄的范围内实例化,使用和释放对象(例如,在一个方法调用中),则接口实际上不会添加任何内容。就像你提到的那样,具体的类是已知的。

接口有用的地方是需要在一个地方创建一个对象并返回给可能不关心实现细节的调用者。让我们将您的IBox示例更改为Shape。现在我们可以实现Shape的实现,例如Rectangle,Circle,Triangle等。对于每个具体的类,getArea()和getSize()方法的实现将完全不同。

现在你可以使用一个带有各种createShape(params)方法的工厂,这些方法将根据传入的参数返回一个合适的Shape。显然,工厂会知道正在创建什么类型的Shape,但是调用者赢了不管是圆形还是方形,都要关心它。

现在,假设您必须对形状执行各种操作。也许您需要按区域对它们进行排序,将它们全部设置为新大小,然后在UI中显示它们。形状都由工厂创建,然后可以非常容易地传递给Sorter,Sizer和Display类。如果您需要在将来的某个时间添加六角形类,除了工厂之外,您不必更改任何内容。没有界面,添加另一个形状会变得非常混乱。

答案 9 :(得分:3)

这就是Factory Patterns和其他创作模式在Java中如此受欢迎的原因。你没有它们是正确的Java没有提供开箱即用的机制来轻松抽象实例化。尽管如此,在在您的方法中创建对象的任何地方都可以获得抽象,这应该是您的大部分代码。

顺便说一句,我通常鼓励人们不要遵循“IRealname”机制来命名接口。这是一个Windows / COM的东西,它将一只脚置于匈牙利符号的坟墓中并且实际上没有必要(Java已经是强类型的,并且具有接口的全部意义在于它们尽可能地与类类型无法区分)。

答案 10 :(得分:3)

  

接口的唯一目的是确保实现接口的类在接口描述中获得了正确的方法?或者是否有其他任何接口用途?

我正在使用 java 8 版本引入的界面新功能更新答案。

来自summary of interface上的oracle文档页面:

接口声明可以包含

  1. 方法签名
  2. 默认方法
  3. 静态方法
  4. 不变的定义。
  5. 实现的唯一方法是默认和静态方法。

    界面的使用

    1. 定义合同
    2. 链接不相关的类并具有功能(例如,实现Serializable接口的类可能有也可能没有任何关系,除了实现该接口
    3. 提供可互换实施e.g. strategy pattern
    4. 默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性
    5. 使用静态方法在库中组织帮助器方法(您可以在同一个接口中保留特定于接口的静态方法,而不是在单独的类中)
    6. 关于抽象类接口之间差异的一些相关SE问题以及带有工作示例的用例:

      What is the difference between an interface and abstract class?

      How should I have explained the difference between an Interface and an Abstract class?

      查看documentation页面,了解java 8中添加的新功能:默认方法和静态方法

答案 11 :(得分:3)

不要忘记,以后您可以使用现有的类,并使其实现IBox,然后它将可用于您所有的框感知代码。

如果接口名为 -able ,则会更清楚一些。 e.g。

public interface Saveable {
....

public interface Printable {
....

等。 (命名方案并不总是有效,例如我不确定Boxable是否合适)

答案 12 :(得分:3)

如何使用接口的一个很好的例子是Collections框架。如果您编写的函数需要List,那么用户是否传入VectorArrayListHashList或其他任何内容都无关紧要。您也可以将List传递给任何需要CollectionIterable接口的功能。

无论Collections.sort(List list)如何实现,都可以使List等函数成为可能。

答案 13 :(得分:2)

接口的目的是抽象,或者与实现脱钩。

如果在程序中引入抽象,则不关心可能的实现。您感兴趣的是它可以做什么而不是如何,并且您使用interface在Java中表达它。

答案 14 :(得分:1)

将java添加到java以允许多重继承的接口。 Java的开发人员虽然/意识到拥有多个继承是一个“危险”的功能,这就是为什么想出了一个接口的想法。

多重继承很危险,因为您可能有类似以下的类:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

当我们使用

时应该调用的方法

   FunckyFigure.GetArea(); 

所有问题都通过接口来解决,因为你知道你可以扩展接口并且他们不会有分类方法...当然编译器很好并告诉你如果你没有实现方法,但我喜欢认为这是一个更有趣的想法的副作用。

答案 15 :(得分:1)

如果您有CardboardBox和HtmlBox(两者都实现了IBox),您可以将它们都传递给任何接受IBox的方法。尽管它们都非常不同并且不能完全互换,但是不关心“打开”或“调整大小”的方法仍然可以使用您的类(可能因为它们关心在屏幕上显示某些内容需要多少像素)。

答案 16 :(得分:0)

这是我对界面优势的理解。如果我错了,请纠正我。 想象我们正在开发OS,而其他团队正在为某些设备开发驱动程序。 因此,我们开发了一个接口StorageDevice。我们有其他开发人员团队提供的两种实现方式(FDD和HDD)。

然后我们有一个OperatingSystem类,只需传递实现了StorageDevice接口的类的实例即可调用接口方法,例如saveData。

这里的优点是我们不在乎接口的实现。另一个团队将通过实现StorageDevice接口来完成这项工作。

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}