Effective-Java-Chapter6

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md

在这里插入图片描述

准则一 用枚举代替常量

Java 枚举类型背后的基本思想很简单:它们是通过 public static final 修饰的字段为每个枚举常量导出一个实例的类。枚举类型实际上是 final 类型,因为没有可访问的构造函数。之所以强大是因为我们还可以这样使用:

public enum FourArithmeticOperations {
    ADD("+") {
        public double apply(double x, double y) {
            this.printLog();
            return x + y;
        }
    },
    SUBTRACT("-") {
        public double apply(double x, double y) {
            this.printLog();
            return x - y;
        }
    },
    MULTIPLY("*") {
        public double apply(double x, double y) {
            this.printLog();
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            this.printLog();
            return x / y;
        }
    };


    private final String desc;

    FourArithmeticOperations(String desc) {
        this.desc = desc;
    }

    abstract double apply(double x, double y);

    public String getDesc() {
        return desc;
    }
    public void printLog() {
        System.out.println(this.desc);
    }

    public static void main(String[] args) {
        System.out.println(FourArithmeticOperations.ADD.apply(1, 2));
    }
}

本质上枚举也是一个类,只不过这个类是不可变的,也就是final的,所以怎么使用枚举类全靠你的想象力。
那么再使用这些枚举类型的时候我们也有一个技巧,来看一个例子:

private static final Map<String, FourArithmeticOperations> stringToEnum = Arrays.stream(FourArithmeticOperations.values()).collect(Collectors.toMap(Objects::toString, e -> e));
public static FourArithmeticOperations fromString(String symbol) {
    return stringToEnum.get(symbol);
}

这样的话可以实现快速查找,但是我个人认为如果这个枚举被被访问的非常频繁可以这样做,因为系统中有很多枚举,我们不可能为每个枚举都生成这样的Map会浪费空间,这样做的本质其实就是空间换时间。
还提到了一个技巧:

// 如果需要对某个操作进行拓展(这里的拓展是我们针对这个操作本身拓展一定的功能,比如下面的我们进行了取反操作),我们可以采用下面这种方式非常的
// 简洁易读性非常强
public static Operation inverse(Operation op) {
    switch(op) {
        case PLUS: return Operation.MINUS;
        case MINUS: return Operation.PLUS;
        case TIMES: return Operation.DIVIDE;
        case DIVIDE: return Operation.TIMES;
        default: throw new AssertionError("Unknown op: " + op);
    }
}

准则二 使用实例字段替代序数

public enum Ensemble {
   SOLO, DUET, TRIO, QUARTET, QUINTET,SEXTET, SEPTET, OCTET, NONET, DECTET;

   public int numberOfMusicians() { return ordinal() + 1; }
}

我们可以得到每个枚举值的位置,这样做有什么不好呢?如果枚举类的使用与顺序无关则不打紧,但是一旦使用这个枚举和顺序建立关联那就比较的不好办了,因为在中间插入一个值就会影响顺序。更好的做法是把这个值存到字段里:

public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),NONET(9), DECTET(10),TRIPLE_QUARTET(12);
    private final int numberOfMusicians;
    Ensemble(int size) { this.numberOfMusicians = size; }
    public int numberOfMusicians() { return numberOfMusicians; }
}

准则三 用 EnumSet 替代位字段

public class Text {
    public static final int STYLE_BOLD = 1 << 0; // 1
    public static final int STYLE_ITALIC = 1 << 1; // 2
    public static final int STYLE_UNDERLINE = 1 << 2; // 4
    public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
    // Parameter is bitwise OR of zero or more STYLE_ constants
    public void applyStyles(int styles) { ... }
}

这种表示方式称为位字段,允许你使用位运算的 OR 操作将几个常量组合成一个 Set,也就是一个数字表示一个集合。位字段表示方式允许使用位运算高效地执行 Set 操作,如并集和交集。但是位字段具有 int 枚举常量所有缺点,甚至更多。当位字段被打印为数字时,它比简单的 int 枚举常量更难理解。没有一种简单的方法可以遍历由位字段表示的所有元素。最后,你必须预测在编写 API 时需要的最大位数,并相应地为位字段(通常是 int 或 long)选择一种类型。一旦选择了一种类型,在不更改 API 的情况下,不能超过它的宽度(32 或 64 位)。
随意推荐使用EnumSet:

EnumSet<FourArithmeticOperations> fourArithmeticOperations = EnumSet.allOf(FourArithmeticOperations.class);

这个类实现了 Set 接口,提供了所有其他 Set 实现所具有的丰富性、类型安全性和互操作性。但在内部,每个 EnumSet 都表示为一个位向量。如果底层枚举类型有 64 个或更少的元素(大多数都是),则整个 EnumSet 用一个 long 表示,因此其性能与位字段的性能相当。批量操作(如 removeAll 和 retainAll)是使用逐位算法实现的,就像手动处理位字段一样。

准则四 使用 EnumMap 替换序数索引

好来看一个例子学习这个准则:

public enum Animal {
    DOG,CAT,BIRD;
}

比如我们有一个这样的,枚举类,我们需要为每个类创建很多对象放到Set里。我们可以采用下面这种写法:

private static final Set<Animal>[] sets = new Set[Animal.values().length];
public static void init() {
    for (int i = 0; i < sets.length; i++) {
        sets[i] =  new HashSet<>();
    }
}

这样确实没有问题确实能够满足大部分时候的需求,但是也有满足不小的时候,因为数组有限制,数组使用不了泛型。
所以书中推荐我们使用:

Map<Animal, Set<Animal>> enumMap = new EnumMap(Animal.class);
Map<Animal, Set<Animal>> collect = Arrays.stream(Animal.values()).collect(Collectors.groupingBy(e -> e, Collectors.toSet()));

上面两种写法又有所区别,因为第一种类型是全的,但是第二种如果某一种类型空缺了,则不会有对应的Set。
对于嵌套的类型:

public enum Animal {
    DOG,CAT,BIRD;

    public enum Color {
        RED,BLUE,GREEN;
    }

    public static void main(String[] args) {
    	// 同样的这里 也可以换成流式表达式
        EnumMap<Animal, Set<Color>> animalSetEnumMap = new EnumMap<>(Animal.class);
    }
}

准则五 使用接口模拟可扩展枚举

一个枚举类型扩展另一个枚举类型,一般来说不需要这样操作,但是如果要拓展我们可以通过接口的方式, 枚举可以实现接口。

// Emulated extensible enum using an interface
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 symbol;
    }
}

使用接口来模拟可扩展枚举的一个小缺点是实现不能从一个枚举类型继承到另一个枚举类型。如果需要共享操作,可以在接口默认实现,然后实现这个接口。例如:

public enum LinkOption implements OpenOption, CopyOption {
    NOFOLLOW_LINKS;
}

准则六 注解优于命名模式

如果可以使用注解,那么就没有理由使用命名模式。

准则七 坚持使用@Override

要覆盖超类声明的每个方法声明上使用@Override注解。

准则八 使用标记接口定义类型

你可能听过一个说法:标记接口已经过时,更好的方式是标记注解(Item-39)。这个言论是错误的。与标记注解相比,标记接口有两个优点。首先,标记接口定义的类型由标记类的实例实现;标记注解不会。 标记接口类型的存在允许你在编译时捕获错误,如果你使用标记注解,则在运行时才能捕获这些错误。
例如序列化是个标记接口,这样如果流在写对象的时候,可以判断这个是否实现了Serializable,这个是在编译的时候就能做到的。

ObjectOutputStream#writeObject()

对比:
使用标记注解的情况
应用范围广泛:当需要标记的不仅仅是类或接口,还包括方法、字段、构造函数等其他程序元素时,只能使用标记注解。
不需要类型检查:如果标记的目的仅仅是提供元数据信息,而不打算利用其进行类型检查或强制某些行为,则标记注解更为合适。
框架集成:如果正在使用的框架或库大量依赖于注解(如 Spring 或 JPA),则使用标记注解可以更好地与这些框架集成。
元数据需求:当需要在编译时或运行时捕获额外信息(例如,用于代码生成或反射操作),但不需要在运行时强制任何行为时,标记注解是更好的选择。
使用标记接口的情况
类型检查:如果您希望利用标记来实现类型安全的编程,即在编译时就能确保某些类型具有特定的标记,则应使用标记接口。
行为约束:当标记不仅仅是为了提供元数据,而且还暗示了一定的行为或约定时,使用标记接口可以更好地表达这种意图。
API 设计:如果您计划编写一个或多个方法,这些方法只接受实现了特定标记接口的对象,那么使用标记接口可以提供更强大的类型系统支持。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Effective Java第三版》是由Joshua Bloch所著的一本Java编程指南。这本书是基于第二版的更新版本,目的是给Java程序员提供一些最佳实践和经验,以编写高效、可维护和可靠的Java代码。 这本书共分为15个章节,每个章节都讲解了一个与Java开发有关的重要主题。比如,章节一讲述了使用静态工厂方法代替构造器的优点,章节二则介绍了如何用Builder模式来构建复杂的对象。此外,书中还提及了Java对象的等价性、覆盖equals方法和hashCode方法、避免创建不必要的对象、使用泛型、枚举、lambda表达式等等。 《Effective Java第三版》通过具体的代码示例和清晰的解释来说明每个主题的关键概念,使读者能够更好地理解和应用。此外,书中还提供了一些实用的技巧和技术,例如避免使用原始类型、尽量使用接口而非类来定义类型等。 总的来说,这本书提供了很多实用的建议和技巧,可以帮助Java开发者写出高质量的代码。无论是初学者还是有经验的开发者,都可以从中受益匪浅。无论你是打算从头开始学习Java编程,还是已经有一定经验的开发者,这本书都是值得推荐的读物。 ### 回答2: 《Effective Java 第三版》是由Joshua Bloch 所著的一本Java编程指南,是Java程序员必读的经典之作。该书共包含90个条目,涵盖了各种Java编程的最佳实践和常见问题的解决方法。 本书分为多个部分,每个部分都侧重于一个特定的主题。作者探讨了Java编程中的各种问题和挑战,并提供了解决方案和建议。这些建议包括如何选择和使用合适的数据结构和算法,如何设计高效的类和接口,如何处理异常和错误,以及如何编写可读性强的代码等等。 《Effective Java 第三版》还关注了Java编程中的性能优化和安全性问题。作者强调了遵循Java语言规范、使用标准库、防范常见安全漏洞等重要原则。此外,本书还介绍了Java 8及其后续版本的新特性和用法,如Lambda表达式、流式编程和Optional类等。 这本书的特点之一是每个条目都独立于其他条目,可以单独阅读和理解。每个条目开头都有一个简洁的总结,让读者能够快速掌握主要观点。此外,书中还有大量的示例代码和解释,帮助读者更好地理解和运用所学知识。 总的来说,《Effective Java 第三版》是一本非常实用和全面的Java编程指南。它适用于各个层次的Java程序员,无论是初学者还是有经验的开发人员,都可以从中获得宝贵的经验和知识。无论是编写高质量的代码、优化性能还是确保安全性,这本书都是一本不可或缺的参考书籍。 ### 回答3: 《Effective Java 第3版(中文版)》是由 Joshua Bloch 所著的一本关于使用 Java 编程语言的指南书。该书是对 Java 语言的最佳实践的详尽描述,为中高级 Java 开发人员提供了许多实用的建议和技巧。 该书的主要内容包括Java 语言的优雅编程风格、类和接口的设计、Lambda 表达式和流的使用、泛型、异常和并发编程等方面的最佳实践。 在《Effective Java 第3版(中文版)》中,许多传统的 Java 开发中的陷阱、常见错误和不良习惯都得到了深入的剖析和解答。它不仅提供了可供开发人员参考的示例代码,还解释了为什么某种方式是有问题的,以及如何更好地进行改进。 该书的深度和广度非常适合正在努力提高 Java 编程技能的开发人员。它涵盖了多个关键领域,为读者提供了在实际项目中解决常见问题的方法和思路。 此外,《Effective Java 第3版(中文版)》还介绍了最新版本的一些特性和改进。例如,它详细说明了如何正确地使用 Java 8 中新增的 Lambda 表达式和流,以及如何充分利用 Java 9、10 和 11 中的新功能。 总之,这本书是 Java 开发人员必备的指南之一。通过深入理解和应用书中的实践建议,读者可以更加高效地编写、优化和维护 Java 代码。无论是想提升职业技能还是在项目中减少错误和问题,这本《Effective Java 第3版(中文版)》都是一本非常有帮助的参考书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值