首页 > 编程语言 >《Effective Java》阅读笔记-第六章

《Effective Java》阅读笔记-第六章

时间:2023-12-18 15:59:41浏览次数:30  
标签:Java Effective double 接口 枚举 apply 第六章 return public

Effective Java 阅读笔记

第六章 枚举和注解

第 34 条 用 enum 代替 int 常量

int 类型常量或者 String 类型常量作为参数的可读性和可维护性都比较差,甚至 IDE 都不好提示。

Java 中的枚举是完全单例,并且可以有字段、方法,以及实现接口(因为编译之后就是个类,并且自动继承了java.lang.Enum类)。

给枚举实现方法时,如果会根据枚举类型进行不同的处理,不要使用 switch 或者 if 进行判断:

这是一个反例:

枚举反例
public enum BadOperation {
    PLUS,
    MINUS,
    TIMES,
    DIVIDE,
    ;

    public double apply(double x, double y) {
        switch (this) {
            case PLUS : {
                return x + y;
            }
            case MINUS : {
                return x - y;
            }
            case TIMES : {
                return x * y;
            }
            case DIVIDE : {
                return x / y;
            }
        }
        throw new UnsupportedOperationException("unknown op: " + this);
    }
}

如果添加了新的操作方法枚举,很容易在switch时漏掉,这种情况可以在枚举上声明抽象方法,然后针对每个枚举实例去实现抽象方法:

枚举抽象方法

public enum GoodOperation {
    PLUS {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        public double apply(double x, double y) {
            return x / y;
        }
    },
    ;

    public abstract double apply(double x, double y);
}

在后续添加新字段/方法的时候就不会忘记处理(不处理就编译不过):

枚举添加新字段
public enum GoodOperation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    },
    ;

    private final String symbol;

    GoodOperation(String symbol) {
        this.symbol = symbol;
    }

    public abstract double apply(double x, double y);

    @Override
    public String toString() {
        return "GoodOperation{" +
                "symbol='" + symbol + '\'' +
                '}';
    }
}

如果枚举中只有部分是特定计算方法,比如周一到周五工资计算是工作日工资计算方式,周六和周日是加班工资计算方式,
那么在定义星期的时候,可以吧计算方式通过构造函数穿进去,计算方式通过内部枚举进行定义,这样虽然啰嗦一些,但是更安全,可以避免添加新的枚举常量时漏掉switch中的定义。

第 35 条 用字段代替枚举序号

每个枚举类的实例都有方法ordinal(),可以获取枚举的序号,从0开始。但是这个序号是根据位置获得的,不要用这个序号作为业务中使用的序号,使用字段来代替这个东西,不然非常不好维护。永远不要根据序号获取值,如果有需要就保存到字段上。

字段代替序号
public enum Ensemble {
    SOLO(1),
    DUET(2),
    // ...
    ;

    private final int numberOfMusicians;

    Ensemble(int numberOfMusicians) {
        this.numberOfMusicians = numberOfMusicians;
    }

    public int getNumberOfMusicians() {
        return numberOfMusicians;
    }
}

第 36 条 用 EnumSet 代替位域

位域:位域(Bit fields)通常是指将一个或多个相关的布尔标志(或状态)打包到单个整数类型中的特定位上。这允许有效地使用内存,因为它避免了使用多个独立的布尔变量,而是将它们存储在一个整数类型中的不同位上。使用位域通常涉及到位操作,例如位与(&)、位或(|)、位取反(~)和位移(<<、>>)

位域例子
public class FlagsExample {
    // 定义一些标志的位置
    private static final int FLAG1 = 1;  // 0001
    private static final int FLAG2 = 2;  // 0010
    private static final int FLAG3 = 4;  // 0100
    private static final int FLAG4 = 8;  // 1000

    // 用一个整数类型的变量来表示一组标志
    private int flags;

    // 设置标志的方法
    public void setFlag1(boolean value) {
        if (value) {
            flags |= FLAG1;
        } else {
            flags &= ~FLAG1;
        }
    }

    public void setFlag2(boolean value) {
        if (value) {
            flags |= FLAG2;
        } else {
            flags &= ~FLAG2;
        }
    }

    // 其他标志的设置方法类似...

    // 检查标志的方法
    public boolean isFlag1Set() {
        return (flags & FLAG1) != 0;
    }

    public boolean isFlag2Set() {
        return (flags & FLAG2) != 0;
    }

    // 其他标志的检查方法类似...
}

使用位域不仅可读性不好,容易出错,而且需要提前计算flag数量,确定使用int还是lang,而且超过64个之后也不再支持(int为32个)。

EnumSet可以很好的解决,例子:

Bad Example
public class Text {
    public static final int STYLE_BLOD      = 1 << 0; // 1
    public static final int STYLE_ITALIC    = 1 << 1; // 2
    public static final int STYLE_UNDERLINE = 1 << 2; // 4
    // ...

    // 根据静态常量进行位运算获取最终style
    // 比如 text.applyStytle(STYLE_BLOD | STYLE_ITALIC)
    public void applyStytle(int styles) {
        // ...
    }
}

位运算非常容易出现错误,并且不美观,唯一的优点就是内存占用少。

Good Example
public class Text {
    // 使用枚举代替
    public static enum Style {BLOD, ITALIC, UNDERLINE;}

    public void applyStytle(Set<Stytle> styles) {
        // ...
    }
}

修改成枚举之后可以通过EnumSet.of(Style.BLOD, Style.ITALIC)来快速创建枚举 set 集合。

第 37 条 用 EnumMap 代替序号索引

道理和上一条类似,如非必要,永远不要使用ordinal()

如果要表达的关系是多维的就用EnumMap<..., EnumMap<...>>表示。

第 38 条 用接口模拟可扩展的枚举

枚举不方便继承,如果需要扩展枚举,就需要通过接口来实现。

比如上面例子中的Operation枚举,把抽象方法改为接口,可以更方便的进行扩展:

// 将抽象方法转移到接口中
public interface Operation {
    double apply(double x, double y);
}

// 然后枚举类实现接口
public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    },
    ;

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return "BasicOperation{" +
                "symbol='" + symbol + '\'' +
                '}';
    }
}

这样扩展的枚举只需要实现Operation接口就行。

接受枚举的地方也需要修改,第一种方式是通过传递 Class 对象,就可以获取该枚举下的所有示例:

public <T extends Enum<T> & Operation> void useEnumMethod(Class<T> enumClass, double x, double y) {
    for (T enumConstant : enumClass.getEnumConstants()) {
        enumConstant.apply(x, y);
    }
}

或者仅传输一个实例:

public <T extends Enum<T> & Operation> void useEnumMethod(T opation, double x, double y) {
    opation.apply(x, y);
}

泛型<T extends Enum<T> & Operation>限定了只能是实现了 Operation 接口的枚举类。

第二种方式是传递 Operation 集合:

public void useEnumMethodByCollection(Collection<? extends Operation> operations, double x, double y) {
    for (Operation operation : operations) {
        operation.apply(x, y);
    }
}

这种方式相对更灵活一些。

第 39 条 注解优先于命名模式

命名模式(naming pattern)就是根据名称进行处理,一般是工具或者框架需要进行特殊操作时使用。比如 Java 4 发行之前,JUnit 要求 test 作为方法名的开头。

这种方式的限制很多,注解显然是更好的解决方法,用注解进行操作可比用名字好多了。

第 40 条 坚持使用 Override 注解

重写父类方法时使用 Override 注解可以在编译期就发现很多错误,而且现在重写方法IDE中直接用快捷键就会自动加上,为什么不用呢?

第 41 条 用标记接口定义类型

标记接口(marker interface)就是不包含任何方法的接口,仅作为一个标记,比如Serializable接口。
同事还有标记注解,比如弃用注解@Deprecated

标记接口相对标记注解而言优点:

  1. 接口的类型实例就是被标记的类型,枚举没有具体的实例类型,因此可以在编译期就发现错误,而不是推迟到运行时。
  2. 被接口标记的类型可以更精准的指定类型。因为接口可以继承,所以可以为特定接口进行标记,而注解如果要标记类型,那么所有的类和接口都允许被标记。

标记注解最大的优点就是:不仅可以标记类和接口,还可以标记方法、参数等。而且可以标记一次或多次,并且逐渐添加丰富的信息。

  • 注解可以标记一次

标记接口和标记注解的选择:

  • 如果标记类、接口、枚举,那就用接口
  • 如果是方法、字段等非类、非接口的地方,就用注解

这本书的中文版真是狗屎翻译

标签:Java,Effective,double,接口,枚举,apply,第六章,return,public
From: https://www.cnblogs.com/aliveneko/p/17911402.html

相关文章

  • java常考面试题1-20
    Java程序员面试题集(1-70)一、Java基础部分1、面向对象的特征有哪些方面?答:面向对象的特征主要有以下几个方面:1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。2)继承:继承是......
  • Java 数组和ArrayList排序
    数组排序1.数组排序(从小到大排序)importjava.util.Arrays;publicclassTest01{publicstaticvoidmain(String[]args){//数组(从小到大排序)//1.第一种方法Integer[]arr1={21,11,41,31,51};Arrays.sort(arr1);Sy......
  • Java五种设计模式实现奶茶订单生成系统小DEMO
    前言这是大学时候上设计模式这门课写的程序,当时课程任务是要求结合五个设计模式写一个系统,最近偶然翻到,把系统分享一下。成品预览主界面​功能介绍订单管理系统,实现了对订单的增删改查。且实现了将订单内容写入文件,每次增删改查都会做保存,下次重启程序时......
  • JavaWeb - Day09 - Mybatis - 基础操作、XML映射文件、动态SQL
    01.Mybatis-基础操作-环境准备需求需求说明:根据资料中提供的《tlias智能学习辅助系统》页面原型及需求,完成员工管理的需求开发。通过分析以上的页面原型和需求,我们确定了功能列表:查询根据主键ID查询条件查询新增更新删除根据主键ID删除根据......
  • Java Spring Boot 集成 Swagger 生成 API文档(SpringDoc)
    在我们进行项目开发的时候,有些文档是必不可少的,或者也有利于自己查阅,比如API接口文档。在SpringBoot中通常有有个选择:springfoxspringdoc因为springfox和SpringBoot版本适配问题是个坑,这里我们选用更加友好的springDoc,而且用起来更顺手,很丝滑。以下是环境:Spring......
  • 无涯教程-Java - BitSet 类函数
    BitSet类创建一种特殊的数组,其中包含位值,BitSet数组可以根据需要增加大小,这使其类似于位向量,这是一个旧类,但已在Java2版本1.4中进行了重新设计。BitSet定义以下两个构造函数。Sr.No.Constructor&Remark1BitSet()该构造函数创建一个默认对象。2BitSet(intsize)......
  • Java设计模式之七大设计原则
    七大设计原则设计原则概述单一职责原则定义一个类仅有一个引起它变化的原因分析模拟场景访客用户普通用户VIP用户代码实现/***视频用户接口*/publicinterfaceIVideoUserService{​  voiddefinition();​  voidadvertisement();}​/***......
  • Java工具库中字符串判空方法知多少?各有什么区别?
    SpringFramework库在SpringBoot项目中,通常情况下,你不需要额外引入依赖来使用SpringFramework库中的常用工具方法,这是因为SpringBoot的起步依赖已经包含了SpringFramework的核心库,其中包括org.springframework.util包。这个用起来就很方便。在这里面有两个常用的方法:StringUtil......
  • 无涯教程-Java - Enumeration 枚举接口函数
    Enumeration接口定义了可以枚举对象集合中的元素的方法。下表总结了Enumeration声明的方法-Sr.No.Method&Remark1booleanhasMoreElements()当实现时,必须在提取更多元素时返回true,而在列举所有元素时返回false。2ObjectnextElement()这将返回枚举中的下一个对象......
  • GCGP:Global Context and Geometric Priors for Effective Non-Local Self-Attention加
    GlobalContextandGeometricPriorsforEffectiveNon-LocalSelf-Attention*Authors:[[WooS]]初读印象comment::(GCGP)提出了一个新的关系推理模块,它包含了一个上下文化的对角矩阵和二维相对位置表示。动机普通注意力的缺点:单独处理输入图像中的每个特征,并在整个输......