Java - 从接口类型而不是类声明

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3383726/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 22:48:11  来源:igfitidea点击:

Java - declaring from Interface type instead of Class

javainterface

提问by jexx2345

In my quest to correctly grasp Interface best practices, I have noticed declarations such as:

在我寻求正确掌握界面最佳实践的过程中,我注意到以下声明:

List<String> myList = new ArrayList<String>();

instead of

代替

ArrayList<String> myList = new ArrayList<String>();

-To my understanding the reason is because it allows flexibility in case one day you do not want to implement an ArrayList but maybe another type of list.

-据我所知,原因是它允许灵活性,以防有一天您不想实现 ArrayList 而可能是另一种类型的列表。

With this logic, I set up an example:

用这个逻辑,我设置了一个例子:

public class InterfaceTest {

    public static void main(String[] args) {

        PetInterface p = new Cat();
        p.talk();

    }

}

interface PetInterface {                

    public void talk();

}

class Dog implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Bark!");
    }

}

class Cat implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Meow!");
    }

    public void batheSelf() {
        System.out.println("Cat bathing");
    }

}

My question is, I cannot access the batheSelf() method because it only exists for Cat. That leads me to believe that I should only declare from an Interface if I am only going to use methods declared in the Interface (and not extra methods from the subclass), otherwise I should declare from the Class directly (in this case Cat). Am I correct in this assumption?

我的问题是,我无法访问 BatheSelf() 方法,因为它只存在于 Cat 中。这让我相信,如果我只打算使用在接口中声明的方法(而不是来自子类的额外方法),我应该只从接口声明,否则我应该直接从类声明(在这种情况下为 Cat)。我在这个假设中正确吗?

采纳答案by polygenelubricants

When there is a choice between referring to an object by their interfaceor a class, the former should be preferred, but only if an appropriate type exists.

当在通过其引用对象interface或通过 a引用对象之间做出选择时class,应首选前者,但前提是存在适当的类型

Consider StringimplementsCharSequenceas an example. You should not just blindly use CharSequencein preferrence to Stringfor all cases, because that would deny you simple operations like trim(), toUpperCase(), etc.

考虑作为一个例子。你不应该只是一味地用在preferrence到所有情况,因为这会剥夺你喜欢简单的操作,等等。StringimplementsCharSequenceCharSequenceStringtrim()toUpperCase()

However, a method that takes a Stringonly to care about its sequence of charvalues shoulduse CharSequenceinstead, because that is the appropriate type in this case. This is in fact the case with replace(CharSequence target, CharSequence replacement)in the Stringclass.

但是,应该使用String只关心其char值序列的方法,因为在这种情况下这是适当的类型。其实在课堂上也是如此。CharSequencereplace(CharSequence target, CharSequence replacement)String

Another example is java.util.regex.Patternand its Matcher matcher(CharSequence)method. This lets a Matcherbe created from Patternfor not just String, but also for all other CharSequencethere are out there.

另一个例子是java.util.regex.Pattern及其Matcher matcher(CharSequence)方法。这让 a 不仅Matcher可以从Patternfor创建String,还可以为所有其他CharSequence存在创建。

A great example in the library of where an interfaceshould've been used, but unfortunately wasn't, can also be found in Matcher: its appendReplacementand appendTailmethods accept only StringBuffer. This class has largely been replaced by its faster cousin StringBuildersince 1.5.

库中interface应该使用an 的一个很好的例子,但不幸的是没有,也可以在Matcher: itsappendReplacementappendTailmethods accept only 中找到StringBufferStringBuilder自 1.5 以来,该类在很大程度上已被其更快的表兄弟所取代。

A StringBuilderis not a StringBuffer, so we can not use the former with the append…methods in Matcher. However, both of them implementsAppendable(also introduced in 1.5). Ideally Matcher's append…method should accept any Appendable, and we would then be able to use StringBuilder, as well as all other Appendableavailable!

AStringBuilder不是 a StringBuffer,因此我们不能将前者与 中的append…方法一起使用Matcher。但是,两者implementsAppendable(也在 1.5 中引入)。理想情况下Matcherappend…方法应该接受 any Appendable,然后我们就可以使用StringBuilder,以及所有其他Appendable可用的!

So we can see how when an appropriate type existsreferring to objects by their interfaces can be a powerful abstraction, but only if those types exist. If the type does not exist, then you may consider defining one of your own if it makes sense. In this Catexample, you may define interface SelfBathable, for example. Then instead of referring to a Cat, you can accept any SelfBathableobject (e.g. a Parakeet)

因此,我们可以看到,当存在通过接口引用对象的适当类型时,这是一种强大的抽象,但前提是这些类型存在。如果该类型不存在,那么您可以考虑定义自己的类型(如果有意义)。Cat例如,在此示例中,您可以定义interface SelfBathable。然后,而不是引用 a Cat,您可以接受任何SelfBathable对象(例如 a Parakeet

If it does not make sense to create a new type, then by all means you can refer to it by its class.

如果创建新类型没有意义,那么您可以通过它的class.

See also

也可以看看

  • Effective Java 2nd Edition, Item 52: Refer to objects by their interfaces

    If appropriate interface types exist, then parameters, return values, and fields should all be declared using interface types. If you get into the habit of using interface types, your program will be much more flexible. It is entirely appropriate to refer to an object by a class if no appropriate interface exists.

  • Effective Java 2nd Edition,Item 52:通过接口引用对象

    如果存在合适的接口类型,则参数、返回值和字段都应使用接口类型声明。如果你养成使用接口类型的习惯,你的程序就会灵活得多。如果不存在适当的接口,则通过类引用对象是完全合适的。

Related links

相关链接

回答by Borealid

Yes, you are correct. You should declare as the most general type providing the methods you use.

是的,你是对的。您应该声明为最通用的类​​型,提供您使用的方法。

This is the concept of polymorphism.

这就是多态的概念。

回答by Rolf

Yes, you are correct. By having Cat implent "PetInterface" you can use it in the example above and easily add more kinds of pets. If you really need to be Cat-specific you need to access the Cat class.

是的,你是对的。通过让 Cat 实现“PetInterface”,您可以在上面的示例中使用它并轻松添加更多种类的宠物。如果您确实需要特定于 Cat,则需要访问 Cat 类。

回答by Paulo Guedes

Your are correct, but you can cast from the interface to the desired pet if you need. For example:

您是对的,但如果需要,您可以从界面投射到所需的宠物。例如:

PetInterface p = new Cat();
((Cat)p).batheSelf();

Of course if you try to cast your pet to a dog you cannot call the batheSelf() method. It would not even compile. So, to avoid problems, you could have a method like this:

当然,如果您尝试将您的宠物投给狗,则不能调用 BatheSelf() 方法。它甚至不会编译。因此,为了避免出现问题,您可以使用这样的方法:

public void bathe(PetInterface p){
    if (p instanceof Cat) {
        Cat c = (Cat) p;
        c.batheSelf();
    }
}

When using instanceof, you make sure you will not try to make a dog bathe himself during runtime. Which would throw an error.

使用 时instanceof,请确保不会在运行时尝试让狗自己洗澡。这会引发错误。

回答by javaguy

You can call method batheSelffrom talkin Cat.

您可以batheSelftalkCat 中调用方法。

回答by severoon

Generally, you should prefer interfaces to concrete classes. Along those lines, if you can avoid using the new operator (which always requires a concrete type as in your new ArrayList example), even better.

通常,与具体类相比,您应该更喜欢接口。沿着这些思路,如果您可以避免使用 new 运算符(它总是需要一个具体的类型,就像在您的新 ArrayList 示例中一样),那就更好了。

This all has to do with managing dependencies in your code. It's best to depend only on highly abstract things (like interfaces) because they also tend to be very stable (see http://objectmentor.com/resources/articles/stability.pdf). Because they have no code, they only must be changed when the API changes...in other words, when you want that interface to present a different behavior to the world, i.e., a design change.

这一切都与管理代码中的依赖项有关。最好只依赖高度抽象的事物(如接口),因为它们也往往非常稳定(请参阅http://objectmentor.com/resources/articles/stability.pdf)。因为它们没有代码,所以只有在 API 更改时才必须更改它们……换句话说,当您希望该接口向世界呈现不同的行为时,即设计更改时。

Classes, on the other hand, change all the time. Code that depends upon a class doesn't care how it does what it does, as long as the inputs and the outputs of the API don't change, callers shouldn't care.

另一方面,课程一直在变化。依赖于类的代码并不关心它如何做,只要 API 的输入和输出不改变,调用者就不应该关心。

You should strive to nail down the behavior of your classes according to the Open-Closed Principle (see http://objectmentor.com/resources/articles/ocp.pdf), that way existing interfaces need not change even when you add functionality, you can just specify a new subinterface.

您应该努力根据开闭原则(参见http://objectmentor.com/resources/articles/ocp.pdf)确定类的行为,这样即使添加功能,现有接口也不需要更改,您可以指定一个新的子接口。

The old way of avoiding the new operator was by using the Abstract Factory pattern, but that comes with its own set of problems. Better is to use a tool like Guice that does dependency injection, and prefer constructor injection. Make sure you understand the Dependency Inversion Principle (see http://objectmentor.com/resources/articles/dip.pdf) before you start using dependency injection. I've seen a lot of people inject inappropriate dependencies and then later complain that the tool isn't helping them...it won't make you a great programmer, you still have to use it appropriately.

避免使用 new 运算符的旧方法是使用抽象工厂模式,但这有其自身的一系列问题。更好的是使用像 Guice 这样的工具进行依赖注入,并且更喜欢构造函数注入。在开始使用依赖注入之前,请确保您了解依赖倒置原则(请参阅http://objectmentor.com/resources/articles/dip.pdf)。我见过很多人注入不适当的依赖项,然后抱怨该工具对他们没有帮助……它不会使您成为一名出色的程序员,您仍然必须适当地使用它。

Example: you are writing a program that helps students learn physics. In this program, students can put a ball in various physical scenarios and watch how it behaves: shoot it out of a cannon off a cliff, put it underwater, in deep space, etc. Question: you want to include something about the heaviness of the ball in the Ball API...should you include a getMass() method or a getWeight() method?

示例:您正在编写一个帮助学生学习物理的程序。在这个程序中,学生可以将一个球放在各种物理场景中并观察它的行为:从悬崖上的大炮中射出它,把它放在水下,在深空等等。 问题:你想包括一些关于球的重量的东西Ball API 中的球……您应该包含 getMass() 方法还是 getWeight() 方法?

Weight depends upon the environment the ball happens to be in. It might be convenient for callers to be able to call one method and get the weight of the ball wherever it happens to be, but how do you write this method? Each ball instance must constantly keep track of where it is and what the current gravitational constant is. So you should prefer getMass(), because mass is an intrinsic property of the ball and doesn't depend on its environment.

重量取决于球碰巧所处的环境。调用者可以方便地调用一个方法并在任何地方获取球的重量,但是如何编写这个方法呢?每个球实例必须不断跟踪它的位置以及当前的重力常数是多少。所以你应该更喜欢 getMass(),因为质量是球的固有属性,不依赖于它的环境。

Wait, what if you just use getWeight(Environment) instead? This way, the ball instance can just get its current g out of the environment and proceed...better yet, you can use Guice to inject the Environment in the Ball's constructor! This is the type of misuse I often see, and people end up blaming Guice for not being able to handle dependency injection as seamlessly as they would've hoped.

等等,如果你只使用 getWeight(Environment) 呢?这样,球实例就可以从环境中取出当前的 g 并继续……更好的是,您可以使用 Guice 在球的构造函数中注入环境!这是我经常看到的滥用类型,人们最终指责 Guice 无法像他们希望的那样无缝地处理依赖注入。

The problem is not Guice here, it's the Ball API design. Weight is not an intrinsic property of the ball, so it's not a property that should be accessible from the ball. Instead, Ball should implement the MassiveObject interface with a getMass() method, and Environment should have a method called getWeightOf(MassiveObject). Intrinsic to the Environment is its own gravitational constant, so this is much better. And Environment only depends upon a simple interface now, MassiveObject...but it's job is to contain objects, so this is as it should be.

这里的问题不是 Guice,而是 Ball API 设计。重量不是球的固有属性,因此它不是应该可以从球获得的属性。相反,Ball 应该使用 getMass() 方法实现 MassiveObject 接口,而 Environment 应该有一个名为 getWeightOf(MassiveObject) 的方法。环境的内在是它自己的万有引力常数,所以这要好得多。而 Environment 现在只依赖于一个简单的接口,MassiveObject……但它的工作是包含对象,所以这应该是。

回答by tranquil

Why not simply do this!

为什么不简单地这样做!

Cat c = new Cat();
PetInterface p = (PetInterface)c;
p.talk();
c.batheSelf();

Now we have a single object, which can be manipulated using 2 references.
The reference p can be used to call functions defined in interface and c can be used to call functions defined in class(or superclass) only.

现在我们只有一个对象,可以使用 2 个引用来操作它。
引用 p 可用于调用接口中定义的函数,而 c 仅可用于调用类(或超类)中定义的函数。