0
点赞
收藏
分享

微信扫一扫

java中的包装类型


1、包装类型引入

在实际程序使用中,程序界面上用户输入的数据都是以字符串类型进行存储的。

而程序开发中,我们需要把字符串数据,根据需求转换成指定的基本数据类型,如年龄需要转换成int类型,考试成绩需要转换成double类型等。

那么,想实现字符串与基本数据之间转换怎么办呢?Java中提供了相应的对象来解决该问题,基本数据类型对象包装类:java将基本数据类型值封装成了对象。

封装成对象有什么好处?可以提供更多的操作基本数值的功能。

我们已经知道,Java的数据类型分两种:

  • 基本类型:​​byte​​,​​short​​,​​int​​,​​long​​,​​boolean​​,​​float​​,​​double​​,​​char​
  • 引用类型:所有​​class​​和​​interface​​类型

引用类型可以赋值为​​null​​​,表示空,但基本类型不能赋值为​​null​​:

String s = null;
int n = null; // compile error!

那么,如何把一个基本类型视为对象(引用类型)?

比如,想要把​​int​​​基本类型变成一个引用类型,我们可以定义一个​​Integer​​​类,它只包含一个实例字段​​int​​​,这样,​​Integer​​​类就可以视为​​int​​的包装类

public class Integer {
private int value;

public Integer(int value) {
this.value = value;
}

public int intValue() {
return this.value;
}
}

定义好了​​Integer​​​类,我们就可以把​​int​​​和​​Integer​​互相转换:

Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();

实际上,因为包装类型非常有用,Java核心库为每种基本类型都提供了对应的包装类型:

基本类型

对应的引用类型

boolean

java.lang.Boolean

byte

java.lang.Byte

short

java.lang.Short

int

java.lang.Integer

long

java.lang.Long

float

java.lang.Float

double

java.lang.Double

char

java.lang.Character

注意int对应的是Integer,char对应的Character,其他6个都是基本类型首字母大写 

我们可以直接使用,并不需要自己去定义:

2、装箱操作和拆箱操作

因为​​int​​​和​​Integer​​可以互相转换:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

所以,Java编译器可以帮助我们自动在​​int​​​和​​Integer​​之间转型:

Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

这种直接把​​int​​​变为​​Integer​​​的赋值写法,称为自动装箱(Auto Boxing),反过来,把​​Integer​​​变为​​int​​的赋值写法,称为自动拆箱(Auto Unboxing)。

注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。

装箱和拆箱会影响代码的执行效率,因为编译后的​​class​​​代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报​​NullPointerException​

3、不变类

所有的包装类型都是不变类。我们查看​​Integer​​的源码可知,它的核心代码如下:

public final class Integer {
private final int value;
}

因此,一旦创建了​​Integer​​对象,该对象就是不变的。

对两个​​Integer​​​实例进行比较要特别注意:绝对不能用​​==​​​比较,因为​​Integer​​​是引用类型,必须使用​​equals()​​比较:

public class Main {
public static void main(String[] args) {
Integer x = 127;
Integer y = 127;
Integer m = 99999;
Integer n = 99999;
System.out.println("x == y: " + (x==y)); // true
System.out.println("m == n: " + (m==n)); // false
System.out.println("x.equals(y): " + x.equals(y)); // true
System.out.println("m.equals(n): " + m.equals(n)); // true
}
}

仔细观察结果的童鞋可以发现,​​==​​​比较,较小的两个相同的​​Integer​​​返回​​true​​​,较大的两个相同的​​Integer​​​返回​​false​​​,这是因为​​Integer​​​是不变类,编译器把​​Integer x = 127;​​​自动变为​​Integer x = Integer.valueOf(127);​​​,为了节省内存,​​Integer.valueOf()​​​对于较小的数,始终返回相同的实例,因此,​​==​​​比较“恰好”为​​true​​,但我们绝不能因为Java标准库的​​Integer​​​内部有缓存优化就用​​==​​​比较,必须用​​equals()​​​方法比较两个​​Integer​​。 

总结:比较值的内容,除了基本类型只能使用==外,其他类型都需要使用equals

因为​​Integer.valueOf()​​​可能始终返回同一个​​Integer​​​实例,因此,在我们自己创建​​Integer​​的时候,以下两种方法:

  • 方法1:​​Integer n = new Integer(100);​
  • 方法2:​​Integer n = Integer.valueOf(100);​

方法2更好,因为方法1总是创建新的​​Integer​​​实例,方法2把内部优化留给​​Integer​​的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。

我们把能创建“新”对象的静态方法称为静态工厂方法。​​Integer.valueOf()​​就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。

 创建新对象时,优先选用静态工厂方法而不是new操作符。

如果我们考察​​Byte.valueOf()​​​方法的源码,可以看到,标准库返回的​​Byte​​实例全部是缓存实例,但调用者并不关心静态工厂方法以何种方式创建新实例还是直接返回缓存的实例。

4、进制转换

​Integer​​​类本身还提供了大量方法,例如,最常用的静态方法​​parseInt()​​可以把字符串解析成一个整数:

int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

​Integer​​还可以把整数格式化为指定进制的字符串:

public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
}
}

注意:上述方法的输出都是​​String​​​,在计算机内存中,只用二进制表示,不存在十进制或十六进制的表示方法。​​int n = 100​​在内存中总是以4字节的二进制表示:

┌────────┬────────┬────────┬────────┐
│00000000│00000000│00000000│01100100│
└────────┴────────┴────────┴────────┘

我们经常使用的​​System.out.println(n);​​​是依靠核心库自动把整数格式化为10进制输出并显示在屏幕上,使用​​Integer.toHexString(n)​​则通过核心库自动把整数格式化为16进制。

这里我们注意到程序设计的一个重要原则:数据的存储和显示要分离。

Java的包装类型还定义了一些有用的静态变量

// boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

最后,所有的整数和浮点数的包装类型都继承自​​Number​​,因此,可以非常方便地直接通过包装类型获取各种基本类型:

// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

5、处理无符号整型

在Java中,并没有无符号整型(Unsigned)的基本数据类型。​​byte​​​、​​short​​​、​​int​​​和​​long​​都是带符号整型,最高位是符号位。而C语言则提供了CPU支持的全部数据类型,包括无符号整型。无符号整型和有符号整型的转换在Java中就需要借助包装类型的静态方法完成。

例如,byte是有符号整型,范围是​​-128​​​~​​+127​​​,但如果把​​byte​​​看作无符号整型,它的范围就是​​0​​​~​​255​​​。我们把一个负的​​byte​​​按无符号整型转换为​​int​​:

 

public class Main {
public static void main(String[] args) {
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
}
}

因为​​byte​​​的​​-1​​​的二进制表示是​​11111111​​​,以无符号整型转换后的​​int​​​就是​​255​​。

类似的,可以把一个​​short​​​按unsigned转换为​​int​​​,把一个​​int​​​按unsigned转换为​​long​​。

小结

Java核心库提供的包装类型可以把基本类型包装为​​class​​;

自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);

装箱和拆箱会影响执行效率,且拆箱时可能发生​​NullPointerException​​;

包装类型的比较必须使用​​equals()​​;

整数和浮点数的包装类型都继承自​​Number​​;

包装类型提供了大量实用方法。

举报

相关推荐

0 条评论