Effective Java读书笔记二:枚举和注解

本文探讨了在Java编程中如何高效地使用枚举和接口,包括使用enum代替int常量、实例域代替序数、EnumSet代替位域、EnumMap代替序数索引等内容,并介绍了如何利用接口实现可伸缩的枚举。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第30条:用enum代替int常量

当需要一组固定常量的时候,应该使用enum代替int常量,除了对于手机登资源有限的设备应该酌情考虑enum的性能弱势之外。

第31条:用实例域代替序数

枚举的ordinal()方法会返回枚举常量在类型中的数字位置, 但是尽量不要使用它,因为当重新排序后,会对客户端造成破坏。 正确的做法是,将他保存在一个实例域中。

应该给enum添加int域,而不是使用ordinal方法来导出与枚举关联的序数值。(几乎不应使用ordinal方法,除非在编写像EnumMap这样的基于枚举的通用数据结构)

//WRONG
public enum Fruit{
    APPLE, PEAR, ORANGE;
    public int numberOfFruit(){
        return ordinal() + 1;
   }
}

//RIGHT
public enum Fruit{
    APPLE(1), PEAR(2), ORANGE(3);
    private final int number;
    Fruit(int num) {number  = num;}
    public int numberOfFruit(){
        return number;
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

第32条:用EnumSet代替位域

每个EnumSet的内容都表示为位矢量。如若底层的枚举类型个数小于64个,则整个EnumSet就用单个long来表示,因此性能上比的上位域。

//WRONG
public class Text{
    private static final int STYLE_BOLD                  = 1 << 0;
    private static final int STYLE_ITALIC                 = 1 << 1;
    private static final int STYLE_UNDERLINE         = 1 << 2;

    public void applyStyles(int styles) {...}
}
//use
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

//RIGHT
public class Text{
    public enum Style{STYLE_BOLD, STYLE_ITALIC, STYLE_UNDERLINE}

    public void applyStyles(Set<Style> styles) {...} //这里不使用EnumSet<Style>参数是因为考虑到某些客户端可能会传递一些其他的Set实现
}
//use
text.applyStyles(EnumSet.of(STYLE_BOLD, STYLE_ITALIC));
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. EnumSet 实现了 Set 接口,提供了丰富的功能,类型安全.可以从其他任何Set中得到互换性.
  2. 整个 EnumSet 就是用 单个 long 来表示的,性能上比得上 位运算的性能.
  3. 总而言之因为枚举类型要用在集合(Set)中,所以没有理由用位域来表示.

第33条:用EnumMap代替序数索引

序数索引是指依赖于枚举成员在枚举中的序数来进行数组索引,如:

//定义了植物类,其中植物又分为水果,蔬菜,树木三种
public class Plant{
    public enum Type { Fruit, Vegetables, Tree}
    private final String name;
    private final Type type;

    Plant(String name, Type type){
        this.name = name;
        this.type = type;
    }
}

Set<Plant>[] plants = (Set<Plant>[]) new Set[Plant.Type.valuse().lenght]; 
//根据植物的类型,分别把所有的植物放入三个set中
for(int i = 0; i < plant.lenght; i++){
   plant[i] = new HashSet<Plant>();
}

for(Plant p : garden){  //garden里放了所有的植物
    plant[p.type.ordinal()].add(p)  //反面教材:利用了枚举的序数来得到想要的数组索引,用户在其他地方可以不使用ordinal函数,而直接使用int值来访问,就可能出错
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

应该使用EnumMap来实现,EnumMap内部是采用数组实现的,具有Map的丰富功能和类型安全以及数组 的效率:

Map<Plant.Type, Set<Plant>> plants = new EnumMap<Plant.Type, Set<Plant>>(Plant.Type.class);  //构造函数需要 键 类型的Class对象
//根据植物的类型,分别把所有的植物放入三个set中
for(Plant.Type type : Plant.Type.valuse()){
   plant.put(type, new HashSet<Plant>);
}

for(Plant p : garden){  //garden里放了所有的植物
    plant.get(p.type).add(p)  //用户必须使用正确的键值来访问,即Type类型
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当需要多维关系时,可以使用EnumMap<…, EnumMap<…>>

第34条:用接口模拟可以伸缩的枚举

1、如果让一个 枚举类型 去扩展另一个 枚举类型,利用语言的特性,几乎是不可能的 
2、枚举的可扩展性,到最后都证明不是一个好点子.

由于在Java中enum不是可扩展的,在某些情况下,可能需要对枚举进行扩展,比如操作类型(+-*/等),就可以考虑: 
1.定义一个接口,比如public interface Operation{…}; 
2.使枚举继承接口:比如public enum BasicOperation implements Operation{…} 
3.使用时的API写成接口(比如,T extends Enum & Operation),而不是实现(比如BasicOperation )

private static <T extends Enum<T> & Operation> void function(T t,..); //表示T即表示枚举又是Operation的子类型
 
  • 1
  • 1

4.当需要扩展BasicOperation枚举时,就可以另写一个枚举,且implements接口Operation

第35条:注解优先于命名模式

优先使用注解来表面针对某些程序元素的特定信息。 
注解的优势:

  1. @Retention : 限定保留时期
  2. @Target: 限定其应用的程序元素
  3. 还有很多注解,如 @IntDef,@ViewDebug…
  4. 注解接收的参数如果是数组,为其赋值一个单独的元素也是合法的

第36条:坚持使用Override注解

在想要覆盖的方法上使用Override注解,编译器就可以帮助发现一些错误。可以不写Override的特例:在具体类中不必标注你确信覆盖了抽象方法声明的方法(虽然这么做也没有什么坏处)。

第37条:用标记接口实现类型

标记分为标记接口和标记注解。

标记接口:没有包含方法声明的接口,只是指明某个类实现了具有某种属性的接口。比如Serializable接口,通过实现这个接口,类表明它的实例可以被写到ObjectOutputStream。。

标记接口与标记注解的最终要的区别在于: 
标记接口可以在编译时就检查到相应的类型问题,而标记注解则要到运行时。

标记接口优势:

  1. 标记接口定义的类型是由 被标记的类的实例 实现的,标记注解 则没有定义这样的类型
  2. 他们可以被更加精确的进行锁定,比如 
    如果注解类型利用 @Target(ElementType.TYPE) 标记,则它可以被应用到任何类或者接口上

标记注解优势:

  1. 它可以 通过默认的方式 添加 一个或者多个注解类型元素,给 已被使用的注解类型添加更多的信息.随着时间的推移,简单的标记注解类型可以演变成更加丰富的注解类型.
  2. 他们是更大的注解机制的一部分.

使用:

  1. 如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解. 因为只有 类和接口可以用来实现或者扩展接口
  2. 如果标记只应用给类和接口,就应该 优先使用标记接口而非注解

《Effective Java中文版 第2版》PDF版下载: 
http://download.youkuaiyun.com/detail/xunzaosiyecao/9745699

作者:jiankunking 出处:http://blog.youkuaiyun.com/jiankunking

from: http://blog.youkuaiyun.com/jiankunking/article/details/54844842

内容概要:本文针对国内加密货币市场预测研究较少的现状,采用BP神经网络构建了CCi30指数预测模型。研究选取2018年3月1日至2019年3月26日共391天的数据作为样本,通过“试凑法”确定最优隐结点数目,建立三层BP神经网络模型对CCi30指数收盘价进行预测。论文详细介绍了数据预处理、模型构建、训练及评估过程,包括数据归一化、特征工程、模型架构设计(如输入层、隐藏层、输出层)、模型编译与训练、模型评估(如RMSE、MAE计算)以及结果可视化。研究表明,该模型在短期内能较准确地预测指数变化趋势。此外,文章还讨论了隐层节点数的优化方法及其对预测性能的影响,并提出了若干改进建议,如引入更多技术指标、优化模型架构、尝试其他时序模型等。 适合人群:对加密货币市场预测感兴趣的研究人员、投资者及具备一定编程基础的数据分析师。 使用场景及目标:①为加密货币市场投资者提供一种新的预测工具方法;②帮助研究人员理解BP神经网络在时间序列预测中的应用;③为后续研究提供改进方向,如数据增强、模型优化、特征工程等。 其他说明:尽管该模型在短期内表现出良好的预测性能,但仍存在一定局限性,如样本量较小、未考虑外部因素影响等。因此,在实际应用中需谨慎对待模型预测结果,并结合其他分析工具共同决策。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值