24-特殊数据类型

不问归期 提交于 2019-11-28 17:50:45
枚举类
在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()相等

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!