第6章:枚举和注解

第34条:用enum代替int常量

34.1 int枚举模式
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
34.1.1 缺点
  1. 不具有类型安全性
    1. 例如将apple传到想要orange的方法中,编译器不报错
    2. ==、-、+等操作符对apple和orange进行比较,编译器不报错
  2. 当两个int枚举组具有相同名称的常量时,必须加前缀来区分
//例如apple和orange都有HAN这个品种,那么必须加前缀,即APPLE_HAN和ORANGE_HAN这两个变量来区分
  1. int枚举值一旦改变,必须重新编译客户端代码
//因为int枚举为编译时常量
  1. 打印int枚举值时只会打印出数字,同时没有方法可以遍历所有的int枚举值常量,也没有方法能够获取int枚举值的个数
34.2 String枚举模式
  1. 提供了打印上的方便
  2. 性能没有int枚举模式好,因为依赖字符串的比较
  3. 初级程序员可能用错,而编译时发现不了,运行时报错
public static final String APPLE_FUJI = "FUJI";
public static void main(String[] args) {
   
	//没使用常量的属性名APPLE_FUJI,而是将字符串常量硬编码到客户端代码
	//如果写错了,编译时不报错,运行时可能会有问题
	System.out.println("FUJI1");
}
34.3 枚举类型
  1. Java枚举本质是int值
  2. 枚举值默认被public static final修饰,不允许人为添加
  3. 枚举的构造器默认使用private修饰,因此不能被继承
  4. 客户端无法创建其实例,即枚举类型是实例受控的
  5. 单例模式本质上是单个元素的枚举类型
34.3.1 优点
  1. 类型安全:将Apple的枚举值传给Orange的变量会报错
  2. 不同枚举类型的同名常量可以共处,因为每个枚举类型都有自己的命名空间
  3. 增加、重新排序枚举类型中的常量,无需重新编译客户端代码
//因为不像int枚举模式一样,将常量值编译到了客户端代码中
  1. 可以通过调用枚举值的toString方法,将其打印
  2. 枚举类中可以添加属性和方法
34.3.2 示例
//7. 如果一个枚举类具有普遍适用性,可以将它设计为一个顶层类,例如java.math.RoundingMode,表示十进制小数的舍入模式(四舍五入还是什么),它被用于BigDecimal类,但该API的设计者,还希望程序员你重用这个枚举类,增强自己设计的API与他们设计的API的一致性,因此设计为顶层类
//8. 如果枚举类只被用在特定的顶层类中,那就应该把该枚举类设计为这个顶层类的一个成员类(内部的枚举类)
public enum Planet {
   
	//5. 但删除一个枚举值时,客户端用到这个枚举值的地方重新编译时会失败,如果不重新编译,执行时也会报错,不会像int枚举模式那样,不报错,但给出错误的结果
	MERCURY(3.302e+23, 2.439e6), VENUS(4.869e+24, 6.052e6), EARTH(5.975e+24, 6.378e6), MARS(6.419e+23, 3.393e6),
	JUPITER(1.899e+27, 7.149e7), SATURN(5.685e+26, 6.027e7), URANUS(8.683e+25, 2.556e7), NEPTUNE(1.024e+26, 2.477e7);
	
	//1. 设计枚举类的初衷,就是希望它是不可变的类,为了实现这一点,应该将成员变量都设置为final
	//2. 最好private修饰,并提供getter方法
	private final double mass;
	private final double radius;
	private final double surfaceGravity;
	private static final double G = 6.67300E-11;

	Planet(double mass, double radius) {
   
		this.mass = mass;
		this.radius = radius;
		surfaceGravity = G * mass / (radius * radius);
	}

	public double mass() {
   
		return mass;
	}

	public double radius() {
   
		return radius;
	}

	public double surfaceGravity() {
   
		return surfaceGravity;
	}

	public double surfaceWeight(double mass) {
   
		return mass * surfaceGravity;
	}
	//6. 如果方法只用在枚举类,或其所在的包中,最好用private或default修饰,除非客户端需要调用该方法,可以使用public、protected
	private void wusihan() {
   
		
	}

	public static void main(String[] args) {
   
		//3. 打印一个在地球上1234g的物体,在各个星球上的重量
		double earthWeight = Double.parseDouble("1234");
		double mass = earthWeight / Planet.EARTH.surfaceGravity();
		for (Planet p : Planet.values())
			//4. 打印枚举类对象p时,默认调用其toString方法,而枚举类的toString方法默认返回其枚举值的字符串,也可以覆盖toString方法, 进行修改
			System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
	}
}
34.4 在枚举类中定义根据不同枚举值有不同表现的方法
34.4.1 switch
  1. 代码
public enum Operation {
   
	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 AssertionError("Unknown op: " + this);
	}
}
  1. 缺点:
    1. throw语句肯定执行不到,但代码必须存在,否则编译报错。因为该句话不存在,系统认为你如果传入的枚举值不在PLUS、MINUS、TIMES、DIVIDE中,就没有返回值,因此编译不通过
    2. 当加入新的枚举值,却没给swtich增加相应的条件,编译通过,但执行报错
34.4.2 特定于常量(枚举值)的方法实现:constant-specific method implementation
  1. 在枚举类中声明一个抽象的apply方法,每个枚举值,用不同的方式覆盖该方法
public enum Operation {
   
	//1. 该括号后的内容,书中叫做特定于常量的类主体(constant-specific class body),其实constant-specific翻译应该是独特的常量,强调每个常量中的方法实现不同
	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);
}
34.4.3 特定于常量的方法实现与特定于常量的数据结合,从而方便打印
  1. 代码
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public enum 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;
		}
	};
	// 1. 当枚举值不同时,其属性symbol的值也不同,因此叫做特定于常量的数据
	private final String symbol;

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

	@Override
	public String toString() {
   
		return symbol;
	}

	public abstract double apply(double x, double y);
	//3. Stream 就如同一个高级版本的 迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返
	//4. 以Operation的toString作为key,Operation对象作为value的map
	private static final Map<String, Operation> stringToEnum = Stream.of(values())
			.collect(Collectors.toMap(Object::toString, e -> e));

	//2. fromString方法,可以通过传入枚举值的toString打印的结果,得到该枚举值
	public static Optional<Operation> fromString(String symbol) {
   
		return Optional.ofNullable(stringToEnum.get(symbol));
	}

	public static void main(String[] args) {
   
		double x = Double.parseDouble(args[0]);
		double y = Double.parseDouble(args[1]);
		for (Operation op : Operation.values())
			System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
	}
}
  1. 无法将枚举值,通过自己的构造器,将自身放入映射
//我觉得这个操作是可以的,怀疑作者的本意是想表达,static的这种map,枚举值做不到通过构造器将自身放入,因为枚举构造器压根无法访问枚举的静态域
//新成员,定义一个映射
public Map map = new HashMap();
//构造器
Operation(String symbol) {
   
	this.symbol = symbol;
	//将自身放入映射,编译不会报错
	map.put("/",this);
}
  1. 枚举构造器不可以访问枚举的静态域,除非该静态域是编译时常量
private static final String name = "handidiao";
private static final String name1 = new String("handidiao");
Operation(String symbol) {
   
	this.symbol = symbol;
	//这是因为在枚举类中,枚举值必须写在最前面,而枚举值默认由public static final修饰,属于静态域,而静态域的初始化,是按顺序的,也就是说,构造器被调用时,实际上你想使用的其他静态域还没被初始化,因此不允许使用
	//而如果该静态域为编译时常量,由于编译后,该值被直接替换为一个常量,因此可以使用
	System.out.println(name);
	//System.out.println(name1);
	//1. 这导致构造器无法将自身放入 静态的映射中
	//stringToEnum.put("123", DIVIDE);
	//2. 这导致构造器中无法访问其他枚举值
	//System.out.println(TIMES);
}
34.5 策略枚举

实际上这个方案的本质,是为枚举值,传入一个实例(该实例可以是另一个枚举类的枚举值),然后枚举值的方法中,转为调用该实例的方法,可以减少样板代码

  1. 用于解决特定于常量的方法实现,所造成的样板代码的增加
  2. 利用switch语句:新增枚举值,但不维护switch中代码,会造成编译通过,但和自己想要的行为不同
enum PayrollDay {
   
	MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
	private static final int MINS_PER_SHIFT = 8 * 60;

	int pay(int minutesWorked, int payRate) {
   
		int basePay = minutesWorked * payRate;
		int overtimePay;
		switch (this) {
   
		case SATURDAY:
		case SUNDAY: 
			overtimePay = basePay / 2;
			break;
		default: 
			overtimePay = minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领,每个领都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值