java 多个 If-else 或 enum - 哪个更可取,为什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6126242/
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
Multiple If-else or enum - which one is preferable and why?
提问by dhblah
Here is the original code:
这是原始代码:
public class FruitGrower {
public void growAFruit(String type) {
if ("wtrmln".equals(type)) {
//do watermelon growing stuff
} else if ("ppl".equals(type)) {
//do apple growing stuff
} else if ("pnppl".equals(type)) {
//do pineapple growing stuff
} else if ("rng".equals(type)) {
//do orange growing stuff
} else {
// do other fruit growing stuff
}
}
}
This is how I changed it:
我是这样改的:
public class FruitGrower {
enum Fruits {
WATERMELON {
@Override
void growAFruit() {
//do watermelon growing stuff
}
},
APPLE {
@Override
void growAFruit() {
//do apple growing stuff
}
},
PINEAPPLE {
@Override
void growAFruit() {
//do pineapple growing stuff
}
},
ORANGE {
@Override
void growAFruit() {
//do orange growing stuff
}
},
OTHER {
@Override
void growAFruit() {
// do other fruit growing stuff
}
};
static void grow(String type) {
if ("wtrmln".equals(type)) {
WATERMELON.growAFruit();
} else if ("ppl".equals(type)) {
APPLE.growAFruit();
} else if ("pnppl".equals(type)) {
PINEAPPLE.growAFruit();
} else if ("rng".equals(type)) {
ORANGE.growAFruit();
} else {
OTHER.growAFruit();
}
};
abstract void growAFruit();
}
public void growAFruit(String type) {
Fruits.grow(type);
}
}
I see that enums
code is longer and may be not as clear as if-else
code, but I believe it's better, could someone tell me, why I'm wrong (or maybe I'm not)?
我看到enums
代码更长,可能不如if-else
代码清晰,但我相信它更好,有人能告诉我,为什么我错了(或者我错了)?
UPD - changed source code to be more problem-specific. I'll rephrase the question: are there any concerns about using enum instead of if-else?
UPD - 将源代码更改为更针对特定问题。我将重新表述这个问题:是否对使用枚举而不是 if-else 有任何顾虑?
回答by Brad Mace
You've already got good answers about improving your use of Enums. As for why they're better than string constants:
您已经得到了关于改进 Enums 使用的很好的答案。至于为什么它们比字符串常量更好:
I think the biggest benefit is compile-time error checking. If I were to call growAFruit("watermelon")
, the compiler would have no idea anything was wrong. And since I've spelled it correctly, it's not going to stand out as a mistake when you're viewing the code. But if I were to WATERMELEN.growAFruit()
, the compiler can tell you immediately that I've misspelled it.
我认为最大的好处是编译时错误检查。如果我调用growAFruit("watermelon")
,编译器将不知道有什么问题。而且由于我拼写正确,当您查看代码时,它不会被视为错误。但是,如果我这样做WATERMELEN.growAFruit()
,编译器会立即告诉您我拼错了。
You also get to define growAFruit
as a bunch of simple, easy-to-read methods, instead of a big block of if
-then
-else
s. This becomes even more apparent when you have a few dozen fruits, or when you start adding harvestAFruit()
, packageAFruit()
, sellAFruit()
, etc. Without enums you'd be copying your big if-else block all over, and if you forgot to add a case it would fall through to the default case or do nothing, while with enums the compiler can tell you that the method hasn't been implemented.
您还可以定义growAFruit
为一堆简单易读的方法,而不是一大块if
- then
- else
s。当您有几十个水果时,或者当您开始添加harvestAFruit()
, packageAFruit()
,sellAFruit()
等时,这一点变得更加明显。如果没有枚举,您将完全复制您的大 if-else 块,如果您忘记添加一个案例,它就会落下通过默认情况或什么都不做,而使用枚举编译器可以告诉您该方法尚未实现。
Even more compiler-checking goodness: If you also have a growVegetable
method and the related string constants, there's nothing stopping you from calling growVegetable("pineapple")
or growFruit("peas")
. If you've got a "tomato"
constant, the only way to know if you consider it a fruit or a vegetable is to read the source code of the relevant methods. Once again, with enums the compiler can tell you right away if you've done something wrong.
更多的编译器检查优点:如果您还有一个growVegetable
方法和相关的字符串常量,则没有什么可以阻止您调用growVegetable("pineapple")
或growFruit("peas")
。如果您有一个"tomato"
常量,那么了解它是水果还是蔬菜的唯一方法就是阅读相关方法的源代码。再一次,如果你做错了什么,编译器可以立即告诉你枚举。
Another benefit is that it groups related constants together and gives them a proper home. The alternative being a bunch of public static final
fields thrown in some class that happens to use them, or stuck a interface of constants. The interface full of constants doesn't even make sense because if that's all you need defining the enum is much easierthan writing out the interface. Also, in classes or interfaces there is the possibility to accidentally use the same value for more than one constant.
另一个好处是它将相关的常量组合在一起,并为它们提供了一个合适的位置。另一种选择是public static final
在某个类中抛出一堆字段,碰巧使用它们,或者卡住一个常量接口。充满常量的接口甚至没有任何意义,因为如果仅此而已,那么定义枚举比写出接口要容易得多。此外,在类或接口中,可能会意外地为多个常量使用相同的值。
They're also iterable. To get all the values of an enum you can just call Fruit.values()
, whereas with constants you'd have to create and populate your own array. Or if just using literals as in your example, there is no authoritive listof valid values.
它们也是可迭代的。要获取枚举的所有值,您只需调用Fruit.values()
,而对于常量,您必须创建并填充您自己的数组。或者,如果只是像您的示例中那样使用文字,则没有有效值的权威列表。
Bonus Round: IDE Support
奖励回合:IDE 支持
- With an enum, you can use your IDE's auto-completion feature and automated refactorings
- You can use things like "Find References" in Eclipse with enum values, while you'd have to do a plain text search to find string literals, which will usually also return a lot of false-positives (event if you use static final constants, someone could have used the string literal somewhere)
- 通过枚举,您可以使用 IDE 的自动完成功能和自动重构
- 您可以在 Eclipse 中使用带有枚举值的“查找引用”之类的东西,而您必须进行纯文本搜索才能找到字符串文字,这通常也会返回很多误报(如果您使用静态最终常量,则为事件,有人可以在某处使用字符串文字)
The main reason notto use an enum would be if you don't know all the possible values at compile time (i.e. you need to add more values while the program is running). In that case you might want to define them as a class heirarchy. Also, don't throw a bunch of unrelated constants into an enum and call it a day. There should be some sort of common thread connecting the values. You can always make multiple enums if that's more appropriate.
不使用枚举的主要原因是如果您在编译时不知道所有可能的值(即您需要在程序运行时添加更多值)。在这种情况下,您可能希望将它们定义为类层次结构。另外,不要将一堆不相关的常量扔到枚举中,然后就收工了。应该有某种共同的线程连接这些值。如果更合适,您始终可以进行多个枚举。
回答by Sean Patrick Floyd
Enums are the way to go, but you can dramatically improve your code like this:
枚举是可行的方法,但您可以像这样显着改进您的代码:
public static String grow(String type) {
return Fruits.valueOf(type.toUpperCase()).gimmeFruit();
};
Oh, you need a default case, that makes it a bit tougher. Of course you can do this:
哦,你需要一个默认情况,这让它变得有点困难。当然你可以这样做:
public static String grow(String type) {
try{
return Fruits.valueOf(type.toUpperCase()).gimmeFruit();
}catch(IllegalArgumentException e){
return Fruits.OTHER.gimmeFruit();
}
};
But that's pretty ugly. I guess I'd to something like this:
但这太丑了。我想我会这样:
public static String grow(String type) {
Fruits /*btw enums should be singular */ fruit = Fruits.OTHER;
for(Fruits candidate : Fruits.values()){
if(candidate.name().equalsIgnoreCase(type)){
fruit = candidate;
break;
}
}
return fruit.gimmeFruit();
};
Also, if all your enum methods do is return a value, you should refactor your design so that you initialize the values in a constructor and return them in a method defined in the Enum class, not the individual items:
此外,如果您的所有 enum 方法都返回一个值,您应该重构您的设计,以便您在构造函数中初始化这些值并在 Enum 类中定义的方法中返回它们,而不是单个项目:
public enum Fruit{
WATERMELON("watermelon fruit"),
APPLE("apple fruit")
// etc.
;
private final String innerName;
private Fruit(String innerName){ this.innerName = innerName; }
public String getInnerName(){ return this.innerName; }
}
回答by JB Nizet
You have only made half of the changes to be cleaner. The grow method should be changed like this:
为了更干净,您只做了一半的更改。增长方法应该像这样改变:
static String grow(Fruits type) {
return type.gimmeFruit();
}
And Fruits
should be renamed to Fruit
: an apple is a fruit, not a fruits.
并且Fruits
应该重命名为Fruit
:一个苹果是一种水果,而不是一种水果。
If you really need to keep your string types, then define a method (in the enum class itself, for example) returning the Fruit associated to each type. But most of the code should use Fruit instead of String.
如果您确实需要保留字符串类型,则定义一个方法(例如,在枚举类本身中)返回与每种类型关联的 Fruit。但是大部分代码应该使用 Fruit 而不是 String。
回答by Pa?lo Ebermann
I think you want a Map<String, Fruit>
(or <String, FruitGrower>
).
我想你想要一个Map<String, Fruit>
(或<String, FruitGrower>
)。
This map could be filled automatically by the enum's constructors, or by a static initializer. (It could even map multiple names on the same enum constant, if some fruits have alias names.)
该映射可以由枚举的构造函数或静态初始值设定项自动填充。(如果某些水果有别名,它甚至可以在同一个枚举常量上映射多个名称。)
Your grow
method then looks like this:
您的grow
方法如下所示:
static void grow(String type) {
Fruit f = map.get(type);
if (f == null) {
OTHER.growFruit();
}
else {
f.growFruit();
}
}
Of course, do you really need the string here? Shouldn't you always use the enum object?
当然,你真的需要这里的字符串吗?你不应该总是使用枚举对象吗?
回答by Kjartan
I'm not sure I'd use Enums here. I may be missing something here (?), but I think my solution would look something like this, with separate classes for each type of fruit, all based on one genera fruit-class:
我不确定我会在这里使用枚举。我可能在这里遗漏了一些东西(?),但我认为我的解决方案看起来像这样,每种水果都有单独的类,所有类都基于一个属水果类:
// Note: Added in response to comment below
public enum FruitType {
WATERMELON,
WHATEVERYOUWANT,
....
}
public class FruitFactory {
public Fruit getFruitToGrow(FruitType type) {
Fruit fruitToGrow = null;
switch(type){
case WATERMELON:
fruitToGrow = new Watermelon();
break;
case WHATEVERYOUWANT:
...
default:
fruitToGrow = new Fruit();
}
return fruitToGrow;
}
}
public class Fruit(){
public void grow() {
// Do other fruit growing stuff
}
}
// Add a separate class for each specific fruit:
public class Watermelon extends Fruit(){
@override
public void grow(){
// Do more specific stuff...
}
}
回答by sfussenegger
I think you're on the right track. I'd go and throw in some extra bytes for a HashMap to get rid of the string switching block. This gives you both, cleaner looks, less code and most likely a little extra performance.
我认为你在正确的轨道上。我会为 HashMap 添加一些额外的字节以摆脱字符串切换块。这为您提供了更简洁的外观、更少的代码和最有可能的一点额外性能。
public enum Fruit {
APPLE("ppl") {
public void grow() {
// TODO
}
},
WATERMELON("wtrmln") {
public void grow() {
// TODO
}
},
// SNIP extra vitamins go here
OTHER(null) {
public void grow() {
// TODO
}
};
private static Map<String, Fruit> CODE_LOOKUP;
static {
// populate code lookup map with all fruits but other
Map<String, Fruit> map = new HashMap<String, Fruit>();
for (Fruit v : values()) {
if (v != OTHER) {
map.put(v.getCode(), v);
}
}
CODE_LOOKUP = Collections.unmodifiableMap(map);
}
public static Fruit getByCode(String code) {
Fruit f = CODE_LOOKUP.get(code);
return f == null ? OTHER : f;
}
private final String _code;
private Fruit(String code) {
_code = code;
}
public String getCode() {
return _code;
}
public abstract void grow();
}
And that's how you use it:
这就是你如何使用它:
Fruit.getByCode("wtrmln").grow();
Simple, no need for a FruitGrower, but go for it if you think it's necessary.
简单,不需要 FruitGrower,但如果您认为有必要,就去吧。
回答by Kevin K
The public API is exactly the same. You still have the same if-else block, it's just in the enum now. So I think it's no better. If anything it's worse, due to added complexity.
公共 API 完全相同。你仍然有相同的 if-else 块,它现在只是在枚举中。所以我认为没有更好的。如果有什么更糟糕的,由于增加了复杂性。
What does it mean to 'do fruit growing stuff'? Are you talking about stuff the fruit grower does (till the soil, plant seeds, etc), or stuff the fruit itself does (germinate, sprout, blossom, etc)? In the original example, the actions are defined by the FruitGrower
, but in your modifications they are defined by the Fruit
. This makes a big difference when you consider subclassing. For example I might want to define a MasterFruitGrower
who uses different processes to grow fruit better. Having the grow()
operation defined in Fruit
makes this harder to reason about.
“种植水果”是什么意思?您是在谈论水果种植者所做的事情(直到土壤、植物种子等),还是水果本身所做的事情(发芽、发芽、开花等)?在原始示例中,操作由 定义FruitGrower
,但在您的修改中,它们由 定义Fruit
。当您考虑子类化时,这会产生很大的不同。例如,我可能想定义一个MasterFruitGrower
谁使用不同的过程来更好地种植水果。grow()
定义的操作Fruit
使得这更难推理。
How complex are the fruit growing operations? If you're concerned about the line length of the if-else block, I think a better idea is to define separate fruit growing methods (growWatermelon()
, growOrange()
, ...) or define a FruitGrowingProcedure
interface, implementing subclasses for each fruit type, and store them in a map or set under FruitGrower
.
水果种植操作有多复杂?如果您担心 if-else 块的行长,我认为更好的主意是定义单独的水果种植方法(growWatermelon()
, growOrange()
, ...)或定义一个FruitGrowingProcedure
接口,为每种水果类型实现子类,并存储它们在地图或设置下FruitGrower
。
回答by ruhsuzbaykus
The answer to your question is using enums, or better yet, factories and polymorphism, as mentioned above. However, if you want to get rid of switches (which is what your if-else statement is really doing) completely , a good way, if not the best, to do it is using inversion of control. Thus, I suggest using spring, as follows:
您的问题的答案是使用枚举,或者更好的是,工厂和多态性,如上所述。但是,如果你想完全摆脱开关(这就是你的 if-else 语句真正在做的),一个好方法,如果不是最好的,那就是使用控制反转。因此,我建议使用spring,如下:
public interface Fruit {
public void grow();
}
public class Watermelon implements Fruit {
@Override
public void grow()
{
//Add Business Logic Here
}
}
Now, create the fruit locator interface, as follows:
现在,创建水果定位器界面,如下:
public interface FruitLocator {
Fruit getFruit(String type);
}
Let main class have a reference to a FruitLocator object, a setter for it, and just call the getFruit command:
让主类引用一个 FruitLocator 对象,它的一个 setter,然后调用 getFruit 命令:
private FruitLocator fruitLocator;
public void setFruitLocator (FruitLocator fruitLocator)
{
this.fruitLocator = fruitLocator;
}
public void growAFruit(String type) {
fruitLocator.getFruit(type).grow();
}
Now comes the tricky part. Define your FruitGrower class as a spring bean, as well as your FruitLocator and Fruits:
现在是棘手的部分。将您的 FruitGrower 类定义为 spring bean,以及您的 FruitLocator 和 Fruits:
<bean id="fruitGrower" class="yourpackage.FruitGrower">
<property name="fruitLocator" ref="fruitLocator" />
</bean>
<bean id="fruitLocator"
class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface"
value="yourpackage.FruitLocator" />
<property name="serviceMappings" ref="locatorProperties" />
</bean>
<bean id="locatorProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:fruits.properties" />
</bean>
<bean id="waterMelon" class="yourpackage.WaterMelon">
</bean>
Only thing left to do is create a fruits.properties file in your classpath and add the type-bean mapping, as follows:
剩下要做的就是在类路径中创建一个fruits.properties 文件并添加类型bean 映射,如下所示:
wtrmln=waterMelon
Now, you can add as many fruits as you wish, you only need to create a new fruit class, define it as a bean and add a mapping to your properties file. Much much more scalable than hunting for if-else logic in the code.
现在,您可以添加任意数量的水果,您只需创建一个新的水果类,将其定义为 bean 并将映射添加到您的属性文件中。比在代码中寻找 if-else 逻辑更具可扩展性。
I know this seems complex at first, but I think the topic would be incomplete without mentioning Inversion of Control.
我知道这起初看起来很复杂,但我认为如果不提及控制反转,这个主题将是不完整的。
回答by Fabian Barney
I often implement a method in enums parsing a given String and gives back the corresponding enum constant. I always name this method parse(String)
.
我经常在解析给定字符串的枚举中实现一个方法,并返回相应的枚举常量。我总是命名这个方法parse(String)
。
Sometimes I overload this method in order to parse enum constant by another given input type, too.
有时我会重载这个方法,以便通过另一个给定的输入类型解析枚举常量。
It's implementation is always the same:
Iterate over all enum values() and return when you hit one. Finally do a return as fallthrough - often a specific enum constant or null
. In most cases I prefer null.
它的实现总是相同的:迭代所有 enum values() 并在您点击一个时返回。最后作为失败返回 - 通常是特定的枚举常量或null
. 在大多数情况下,我更喜欢 null。
public class FruitGrower {
enum Fruit {
WATERMELON("wtrmln") {
@Override
void grow() {
//do watermelon growing stuff
}
},
APPLE("ppl") {
@Override
void grow() {
//do apple growing stuff
}
},
PINEAPPLE("pnppl") {
@Override
void grow() {
//do pineapple growing stuff
}
},
ORANGE("rng") {
@Override
void grow() {
//do orange growing stuff
}
},
OTHER("") {
@Override
void grow() {
// do other fruit growing stuff
}
};
private String name;
private Fruit(String name) {
this.name = name;
}
abstract void grow();
public static Fruit parse(String name) {
for(Fruit f : values()) {
if(f.name.equals(name)){
return f;
}
}
return OTHER; //fallthrough value (null or specific enum constant like here.)
}
}
public void growAFruit(String name) {
Fruit.parse(name).grow();
}
}
If you do not really need this Fruit.OTHER
then delete it. Or how a "Other-fruit" grows? oO
Return null
then in parse(String)
method as fallthrough value and do null-check before calling grow()
in growAFruit(String)
.
如果您真的不需要它,请Fruit.OTHER
删除它。或者“其他水果”是如何生长的?oO 返回null
then inparse(String)
方法作为 fallthrough 值并在调用grow()
in之前进行空检查growAFruit(String)
。
It is a good idea to add @CheckForNull
annotation to the parse(String)
method then.
然后@CheckForNull
向parse(String)
方法添加注释是个好主意。
回答by Waldheinz
I second Sean Patrick Floyd on that enums are the way to go, but would like to add that you can shorten your code event more dramatically by using a scheme like this:
我第二次提到了 Sean Patrick Floyd,认为枚举是可行的方法,但我想补充一点,您可以通过使用这样的方案来更显着地缩短代码事件:
enum Fruits {
WATERMELON("watermelon fruit"),
APPLE("apple fruit"); //...
private final String gimme;
private Fruits(String gimme) {
this.gimme = gimme;
}
String gimmeFruit() { return this.gimme; }
}
Also, the "grow" method is suspicious. Shouldn't it be something like
此外,“增长”方法是可疑的。不应该是这样的
public static String grow(Fruits f) {
return f.gimmeFruit();
}