java 枚举查找并不抛异常的实现
java en m是一个非常有用的功能,但很多人通常并不能充分利用,这是因为一些库不会优先择用该功能。通常我们也可以正确使用java枚举功能,但在许多代码库中往往存在着这样一个问题,于是有了这篇文章。问题很简单:我们应该如何通过名称或值获取枚举,并忽略不存在的值？ 枚举 这是我们将在示例中使用的枚举。 挑选更复杂的枚举是为了也可以通过其他字段展示查找枚举。 p blic en m cardcolor { red, black, ；}  jackson annotation to print the en m as an object instead of the defa lt name. @jsonformat (shape = jsonformat.shape.object) p blic en m cards it {  unicode s its -  spade( "spade" , string.val eof(( char ) 0x2660 ), ), heart( "heart" , string.val eof(( char ) 0x2665 ), ), diamond( "diamond" , string.val eof(( char ) 0x2666 ), ), club( "cl b" , string.val eof(( char ) 0x2663 ), ), ； private string displayname； private string symbol； private cardcolor color； private cards it (string displayname, string symbol, cardcolor color) { this .displayname = displayname； this .symbol = symbol； this .color = color； } p blic string getdisplayname  { ret rn displayname； } p blic void setdisplayname (string displayname) { this .displayname = displayname； } p blic string getsymbol  { ret rn symbol； } p blic void setsymbol (string symbol) { this .symbol = symbol； } p blic cardcolor getcolor  { ret rn color； } p blic void setcolor (cardcolor color) { this .color = color； } 问题 在你知道输入有效的时候,使用en m.val eof非常棒。但是,如果传入无效的名称,那么将抛出异常。在某些情况下,这很好。不过,在通常情况下,我们宁愿忽略异常并返回n ll。 log .deb g( "r nning val eof" )； for (string name : names) { try { log .deb g( "looking p {} fo nd {}" , name, json.serializer.tostring(cards it.val eof(name； } catch (exception ex) { log .warn( "exception thrown" , ex)； }} 在 gith b 上查看。 2017 - 02 - 22 14 : 46 : 38.556 [main] debug c .s.examples.common. en mlook p - r nning val eof 2017 - 02 - 22 14 : 46 : 38.804 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.806 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.806 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.806 [main] debug c .s.examples.common. en mlook p - looking p club fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.808 [main] warn c .s.examples.common. en mlook p - exception thrown java.lang. illegalarg mentexception : no en m constant com . st bbornjava . examples . common . en mlook p . cards it . missing at java . lang . en m . val eof ( en m . java :238) at com . st bbornjava . examples . common . en mlook p$cards it . val eof ( en mlook p . java :1) at com . st bbornjava . examples . common . en mlook p . main ( en mlook p . java :154) 拙劣的实现 很不幸的是,以下两种方法在代码库中出现得是这么的频繁。反面例子,勿学。 en m.val eof with try catch(劣) 这种拙劣的做法最常见于初学者。异常不应该用于控制流,并且这样可能会有一些性能影响。不要偷懒。你必须用正确的方式去做。 * * please don t do this！ using try  catch for * control flow is a bad practice. * p blic static cards it trycatchval eof (string name) { try { ret rn cards it.val eof(name)； } catch (exception ex) { log.warn( "exception thrown" , ex)； ret rn n ll ； }} 在 gith b 上查看。 log .deb g( "r nning trycatchval eof" )； for (string name : names) { log .deb g( "looking p {} fo nd {}" , name, json.serializer.tostring(cards it.trycatchval eof(name；} 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - r nning trycatchval eof 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p club fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.809 [main] warn c .s.examples.common. en mlook p - exception thrown java.lang. illegalarg mentexception : no en m constant com . st bbornjava . examples . common . en mlook p . cards it . missing at java . lang . en m . val eof ( en m . java :238) at com . st bbornjava . examples . common . en mlook p$cards it . val eof ( en mlook p . java :1) at com . st bbornjava . examples . common . en mlook p$cards it . trycatchval eof ( en mlook p . java :89) at com . st bbornjava . examples . common . en mlook p . main ( en mlook p . java :171)2017-02-22 14:46:38.809 [ main ] debug c . s . examples . common . en mlook p - looking p missing fo nd n ll 通过迭代查找(劣) 这种方法也很常见,但至少 程序员 知道不能用trycatch来捕获异常。那么,这种方法有什么问题呢？没错,它会迭代所有枚举,直到找到匹配的枚举或返回n ll-最坏的情况下需要n次,其中n就是枚举值的数量。有些人可能会认为这微不足道,这是过早优化了而已。但是,数据结构和算法是cs基础。使用map而不是迭代集合要省力得多。这会大大提高性能吗？不,但它是一个很好的习惯。在面试候选人时,你会对线性复杂度搜索算法感到满意吗？此时,你不应该让这样的代码审查通过。 * * please don t do this！ it is inefficient and it s * not very hard to se g ava or a static map as an index. * p blic static cards it iterationfindbyname (string name) { for (cards it s it : cards it.val es) { if (name.eq als(s ( { ret rn s it； } } ret rn n ll ；} 在 gith b 上查看。 log .deb g( "r nning iteration" )； for (string name : names) { log .deb g( "looking p {} fo nd {}" , name, json.serializer.tostring(cards it.iterationfindbyname(name；} 2017 - 02 - 22 14 : 46 : 38.808 [main] debug c .s.examples.common. en mlook p - r nning iteration 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p club fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p missing fo nd n ll 较优的实现 以下都可以通过使用map形式的索引起作用。但是,它们之间也有一些细微的差别。 静态map索引(较优) 用于快速查找固定大小的正确数据结构是什么？那就是hashmap。现在通过一些额外的样板,只要我们有很好的散列函数的话,我们就可以进行更有效的查找。稍微更冗长,但如果有办法减少样板的话,这将是极好的。 private static final map  string , cards it  nameindex = maps .newhashmapwithexpectedsize( cards it .val es.length)； static { for ( cards it s it : cards it .val es) { nameindex.p t(s , s it)； }} p blic static cards it look pbyname( string name) { ret rn nameindex. get (name)；} log .deb g( "r nning look pbyname" )； for (string name : names) { log .deb g( "looking p {} fo nd {}" , name, json.serializer.tostring(cards it.look pbyname(name；} 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - r nning look pbyname 2017 - 02 - 22 14 : 46 : 38.809 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.810 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.810 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.813 [main] debug c .s.examples.common. en mlook p - looking p club fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.813 [main] debug c .s.examples.common. en mlook p - looking p missing fo nd n ll g ava en ms.getifpresent(推荐) 这是一个常见的用例,我们在google的朋友为此提供了一个非常干净且无需样板的解决方案。探其究竟,它甚至使用了weakreferences和 weakhashmap s。基本说来,这段代码将创建一个全局的静态地图,并以en m的类名称键入,并将其用于查找。 p blic static cards it getifpresent (string name) { ret rn en ms.getifpresent(cards it. class , name).orn ll；} log .deb g( "r nning g ava getifpresent" )； for (string name : names) { log .deb g( "looking p {} fo nd {}" , name, json.serializer.tostring(cards it.getifpresent(name；} 2017 - 02 - 22 14 : 46 : 38.813 [main] debug c .s.examples.common. en mlook p - r nning g ava getifpresent 2017 - 02 - 22 14 : 46 : 38.814 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.814 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p club fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p missing fo nd n ll 通过字段的进一步索引 这个完全相同的方法可以用于枚举的其他字段。想要通过其显示的名称或其他属性来查找枚举并不少见。 通过字段索引的静态map(较优) 与上述相同的方法,但是在显示名称上而不是枚举名称上进行索引。 private static final map  string , cards it  displaynameindex = maps .newhashmapwithexpectedsize( cards it .val es.length)； static { for ( cards it s it : cards it .val es) { displaynameindex.p t(s it.getdisplayname, s it)； }} p blic static cards it look pbydisplayname( string name) { ret rn displaynameindex. get (name)；} log .deb g( "r nning look pbydisplayname" )； for (string displayname : displaynames) { log .deb g( "looking p {} fo nd {}" , displayname, json.serializer.tostring(cards it.look pbydisplayname(displayname；} 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - r nning look pbydisplayname 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.815 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p cl b fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p missing fo nd n ll 通过字段索引的静态map(较优) 我们不能在这里利用g ava,因为对于静态索引创建唯一的全局密钥将是一件困难的事。但是,这并不意味着我们没有帮手！ p blic class en mutils { p blic static t, e extends en me f nction  t , e  look pmap (classe clazz, f nctione, t mapper) { @s ppresswarnings( " nchecked" ) e[] emptyarray = (e[]) array .newinstance(clazz, 0 )； ret rn look pmap(en mset.allof(clazz).toarray(emptyarray), mapper)； } p blic static t, e extends en me f nction  t , e  look pmap (e[] val es, f nctione, t mapper) { mapt, e index = maps.newhashmapwithexpectedsize(val es.length)； for (e val e : val es) { index.p t(mapper.apply(val e), val e)； } ret rn (t key) - index.get(key)； }} 现在我们有了一个相与样板没多大关联的通用解决方案。 private static final f nction  string , cards it  f nc = en mutils . look pmap (cards it. class , e - e.getdisplayname  )； p blic static cards it look pbydisplaynameutil( string name) { ret rn f nc . apply (name) ；} log .deb g( "r nning look pbydisplaynameutil" )； for (string displayname : displaynames) { log .deb g( "looking p {} fo nd {}" , displayname, json.serializer.tostring(cards it.look pbydisplaynameutil(displayname；} 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - r nning look pbydisplaynameutil 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p spade fo nd { "displayname" : "spade" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p heart fo nd { "displayname" : "heart" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p diamond fo nd { "displayname" : "diamond" , "symbol" : "" , "color" : "red" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p cl b fo nd { "displayname" : "cl b" , "symbol" : "" , "color" : "black" } 2017 - 02 - 22 14 : 46 : 38.816 [main] debug c .s.examples.common. en mlook p - looking p missing fo nd n ll 结论 这里有若干方法都可用于解决同一个问题。有些拙劣,有些更优。