枚举类 在Java中,我们可以通过static final来定义常量,无论是int常量还是String常量,使用这些常量来表示一组枚举值的时候,有一个严重的问题就是,编译器无法检查每个值的合理性。为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类。 public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day == Weekday.SAT || day == Weekday.SUN) { System.out.println("Work at home!"); } else { System.out.println("Work at office!"); } } } enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; } 定义枚举类是通过关键字enum实现的,只需依次列出枚举的常量名。 和int定义的常量相比,enum常量本身带有类型信息,即Weekday.SUN类型是Weekday,编译器会自动检查出类型错误,而且不同类型的枚举不能互相比较或者赋值,也不可能引用到非枚举的值。 enum的比较 使用enum定义的枚举类是一种引用类型。通常引用类型比较,要使用equals()方法,如果使用==比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。这是因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较。 if (day == Weekday.FRI) { // ok! } if (day.equals(Weekday.SUN)) { // ok, but more code! } enum类型 通过enum定义的枚举类,和其他的class没有任何区别。enum定义的类型就是class,只不过它有以下几个特点: 定义的enum类型总是继承自java.lang.Enum,且无法被继承; 只能定义出enum的实例,而无法通过new操作符创建enum的实例; 定义的每个实例都是引用类型的唯一实例; 可以将enum类型用于switch语句。 enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法: //返回常量名 String s = Weekday.SUN.name(); // "SUN" //返回定义的常量的顺序,从0开始计数 int n = Weekday.MON.ordinal(); // 1 //改变枚举常量定义的顺序就会导致ordinal()返回值发生变化 如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的。而enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段: public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("Work at home!"); } else { System.out.println("Work at office!"); } } } enum Weekday { MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0); public final int dayValue; //枚举类的字段也可以是非final类型,即可以在运行期修改,但是不推荐这样做! private Weekday(int dayValue) { this.dayValue = dayValue; } } //这样就无需担心顺序的变化,新增枚举常量时,也需要指定一个int值 默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。 public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("Today is " + day + ". Work at home!"); } else { System.out.println("Today is " + day + ". Work at office!"); } } }//Today is 星期日. Work at home! enum Weekday { MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日"); public final int dayValue; private final String chinese; private Weekday(int dayValue, String chinese) { this.dayValue = dayValue; this.chinese = chinese; } @Override public String toString() { return this.chinese; } } 注意:覆写toString()的目的是在输出时更有可读性。判断枚举常量的名字,要始终使用name()方法,绝不能调用toString()! 枚举类可以应用在switch语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比int、String类型更适合用在switch语句中: public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; switch(day) { case MON: case TUE: case WED: case THU: case FRI: System.out.println("Today is " + day + ". Work at office!"); break; case SAT: case SUN: System.out.println("Today is " + day + ". Work at home!"); break; default: throw new RuntimeException("cannot process " + day); } } } enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN; } //加上default语句,可以在漏写某个枚举常量时自动报错,从而及时发现错误 BigInteger 在Java中,由CPU原生提供的整型最大范围是64位long型整数。当整数范围超过了long型,这个时候,就只能用软件来模拟一个大整数。java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数: BigInteger bi = new BigInteger("1234567890"); System.out.println(bi.pow(5)); //bi 的 5次方 // 2867971860299718107233761438093672048294900000 对BigInteger做运算的时候,只能使用实例方法,例如,加法运算: BigInteger i1 = new BigInteger("1234567890"); BigInteger i2 = new BigInteger("12345678901234567890"); BigInteger sum = i1.add(i2); // 12345678902469135780 和long型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。 也可以把BigInteger转换成long型: BigInteger i = new BigInteger("123456789000"); System.out.println(i.longValue()); // 123456789000 System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range 注意:使用longValueExact()方法时,如果超出了long型的范围,会抛出ArithmeticException。 BigInteger和Integer、Long一样,也是不可变类,并且也继承自Number类。因为Number定义了转换为基本类型的几个方法: 转换为byte:byteValue() 转换为short:shortValue() 转换为int:intValue() 转换为long:longValue() 转换为float:floatValue() 转换为double:doubleValue() 如果BigInteger表示的范围超过了基本类型的范围,转换时将丢失高位信息,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用intValueExact()、longValueExact()等方法,在转换时如果超出范围,将直接抛出ArithmeticException异常。 BigDecimal 和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数,BigDecimal也是从Number继承的,也是不可变对象: BigDecimal bd = new BigDecimal("123.4567"); System.out.println(bd.multiply(bd)); // 15241.55677489 BigDecimal用scale()表示小数位数: BigDecimal d1 = new BigDecimal("123.45"); BigDecimal d2 = new BigDecimal("123.4500"); BigDecimal d3 = new BigDecimal("1234500"); System.out.println(d1.scale()); // 2,两位小数 System.out.println(d2.scale()); // 4 System.out.println(d3.scale()); // 0 通过BigDecimal的stripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal,如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0: BigDecimal d1 = new BigDecimal("123.4500"); BigDecimal d2 = d1.stripTrailingZeros(); System.out.println(d1.scale()); // 4 System.out.println(d2.scale()); // 2,因为去掉了00 BigDecimal d3 = new BigDecimal("1234500"); BigDecimal d4 = d1.stripTrailingZeros(); System.out.println(d3.scale()); // 0 System.out.println(d4.scale()); // -2 以对一个BigDecimal设置它的scale,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断: import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal d1 = new BigDecimal("123.456789"); BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568 BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567 System.out.println(d2); System.out.println(d3); } } 对BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及截断 BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("23.456789"); BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入 比较两个BigDecimal的值是否相等时,必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于 BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("123.45600"); System.out.println(d1.equals(d2)); // false,因为scale不同 System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2 System.out.println(d1.compareTo(d2)); // 0 //使用equals()方法要求它们的scale()相等