Java入门系列之包装类(四)
前言
上一节我们讲解了StringBuilder VS StringBuffer以及二者区别,本节我们来讲解包装类。
包装类
我们知道在Java中有8中基本数据类型,分为数值类型:byte、short、int、long、float、double。字符类型:char。布尔类型:bool,那么什么是包装类呢?包装类是8种基本数据类型的对象表示,而且8种包装类和字符串对象一样是不可变且final(不可通过继承或扩展破坏不可变性)的。我们通过查看int的包装类型Integer可知,如下:
如下为基本数据类型对应的包装类以及构造函数参数:
基本数据类型 | 包装类型 | 构造参数 |
---|---|---|
byte | Byte | byte or String |
short | Short | short or String |
int | Integer | int or String |
long | Long | long or String |
float | Float | float, double or String |
double | Double | double or String |
char | Character | char |
boolean | Boolean | boolean or String |
比如当我们实例化Integer包装类时,既然是对int的包装,要是我们传一个带小数位的数字,毫无疑问也就抛出如下异常了:
public class Main { public static void main(String[] args) { Integer a = new Integer("12.5"); } }
开头我们就直接抛出了包装类的概念,但是不知道您是否有和我一样的疑惑,我们为什么要用包装类呢?比如Integer是对int的包装,我们直接使用int不就完事了吗,怎么还包装一层呢?这就需要我们了解包装类的作用是什么了?
1.包装类将基本数据类型转换为对象(当我们需要在给定方法中传递参数时需要对象)。
2.包java.util只处理对象的类,所以包装类在这个包中也有其用武之地。
3.数据结构仅存储对象和基本数据类型。
4.在多线程中,我们需要对象来支持线程同步。
或者说存在即合理,在Java中将包装类和基本数据类型区分开这是明智之举,当我们以适合面向对象的方式编程时,我们使用包装类,当处理起来更加简单时,我们使用基本数据类型,一切取决于我们。
自动装箱和拆箱
在Java 5中引入了自动装箱和拆箱,这允许基本数据类型和包装类相互之间能够轻松自如的实现转换。接下来我们通过基本数据类型和包装类转换实现装箱和拆箱,装箱则是自动将基本数据型转换为包装类,拆箱反之。
char ch = 'a'; Character a = ch; ArrayList<Integer> arrayList = new ArrayList<Integer>(); arrayList.add(25); System.out.println(arrayList.get(0));
如上第一个我们将基本数据类型转换为对象以及我们初始化集合且参数类型为包装类(对象),紧接着我们在此集合中添加基本数据类型为25的int,都自动实现了装箱。反过来,如下我们将实现拆箱:
Character ch = 'a'; char a = ch; ArrayList<Integer> arrayList = new ArrayList<Integer>(); arrayList.add(24); int num = arrayList.get(0); System.out.println(num);
上述我们首先将包装类转换为基本数据类型,紧接着我们添加基本数据类型为24的int,这属于装箱,但是最后我们获取数据时赋值给为int的num,也就达到了将包装类到基本数据类型的转换,也就是拆箱。同时我们还需谨记:包装类可以为空,基本数据类型不能为空。也就是说要使其可空,我们必须使用包装类,这一点比不上C#语法高级,若基本数据类型可空,在C#中可如下定义:
int? a = null;
Console.WriteLine(a == null);
接下来我们来看如下一道题,请问:如下是自动装箱吗?如果不是体现了什么呢?
public class Main { public static void main(String[] args) { char c = 'a'; doSomething(new Character(c)); } static void doSomething(Object obj) { } }
我们知道自动装箱是将基本数据类型转换为包装类,上述我们定义了一个doSomething方法,参数为Object类型,在程序入口点,我们调用该方法且传入的参数为包装类,所以上述并不是自动装箱,它所体现的是通过包装类实现多态。所以我们需谨记:自动装箱是将基本数据类型转换为包装类,而不是将一种数据类型转换为其他数据类型,这也许就是为什么自动装箱不能将String转换为包装类的原因。
数据类型转换
我们依然以基本数据类型int为例,在int对应的包装类Integer中有intValue方法实现拆箱,比如我们想要将double转换为int,利用包装类实现则是如下形式:
double d = 135.d;
Double doubleWrapper = new Double(d); int integerValue = doubleWrapper.intValue(); System.out.println(integerValue);
我们也可以使用包装类类型通过拆箱转换成其他基本数据类型,当我们需要将基本数据类型转换为对象并使用它们来获取其他基本数据类型时,将使用这种类型的转换。通过如上转换,我们需要编写一大片代码, 然而,我们可以通过如下简单的方式来实现相同的目的:
double d = 135.d;
int integerValue = (int)d; System.out.println(integerValue);
valueOf和parseInt使用详解
到目前我所知道的有以下两种方式将String对象转换为int或Integer,我们一起来看看。
Integer.parseInt
此种方式作为将String转换为int的首先方式,简单且灵活,我们看一个例子:
int i = Integer.parseInt("123");
System.out.println("i: " + i);
如果提供的String不是正确的数字,Integer.parseInt()方法将抛出NumberFormatException异常,相同的方式同样适用于其他数据类型(如float和Double)转换为Java中的String。 Java API提供静态方法,如Float.parseFloat()和Double.parseDouble(),以执行数据类型转换。
Integer.valueOf
在8种包装类中都有这个valueOf方法,这也是一种将String转换为int的方式,我们同样来看一个示例:
int i = Integer.valueOf("000000081");
System.out.println("i: " + i);
它将忽略前导零并将字符串转换为int。如果提供的String不是正确的数字,同样也会抛出NumberFormatException异常。在Java 1.4之前没有自动装箱,但是在Java 1.5即(Java 5+)引入了自动装箱,所以推荐使用Integer.valueOf(int)而不是new Integer(int),因为Integer现在可以在-128到127之间缓存Integer对象,并且每次都可以将同一个完整的Integer(0)对象交给我们,而不是在全新的相同Integer对象上浪费对象构造。
下面我们来看两个例子来验证上述源码观点:
Integer i1 = 260;
Integer i2 = 260; if (i1 == i2) { System.out.println("i1 and i2 is equal"); } else { System.out.println("i1 and i2 is not equal "); }
接下来我们再来看一个例子,如下:
Integer i1 = 100;
Integer i2 = 100; if (i1 == i2) { System.out.println("i1 and i2 is equal"); } else { System.out.println("i1 and i2 is not equal "); }
我们看到“i1和i2相等”,因为-128到127之间的int值在大多数JVM要缓存的范围内,所以VM实际上对i1和i2使用相同的对象实例(因此也使用同一内存地址),所以打印出相等。Integer.valueOf方法还有重载,我们来看一个例子:
Integer I = Integer.valueOf("100", 2);
System.out.println(I);
Integer i = Integer.valueOf("101", 4); System.out.println(i);
第二个参数表示进制,例如上述两个通过2进制表示100,通过4进制表示101,计算方式如下:
2进制表示100:(1 * 2 ^ 2)+(0 * 2 ^ 1)+(0 * 2 ^ 0)= 4 + 0 + 0 = 4。
4进制表示101:(1 * 4 ^ 2)+(0 * 4 ^ 1)+(1 * 4 ^ 0)= 16 + 0 + 1 = 17。
总结
valueOf和parseInt方法都用于在Java中将String转换为Integer,但它们之间存在细微差别(在Java 1.5引入自动装箱后),我们通过查看valueOf()方法的源码得知,发现它在内部调用parseInt()方法将String转换为Integer,但是它还维护一个从-128到127的整数池,如果请求的整数在池中,它从池中返回对象,这也意味着使用valueOf()方法返回的两个整数对象可以通过相等运算符相同,这种对不可变对象的缓存,确实有助于减少垃圾并帮助垃圾收集器。 parseInt()和valueOf()方法之间的另一个区别是返回类型,valueOf()返回一个Integer对象,而parseInt()方法返回一个int基本数据类型。无论是使用parseInt还是valueOf将String转换为基本数据类型Int和包装类Integer,如果我们需要基本数据类型Int可以使用parseInt,由于不可变对象可以安全地缓存在池中并且得到重用,如此一来减少了垃圾收集器的负载,因此如果需要Integer对象,最好使用valueOf。
Java中的“==”或等于运算符是Java编程语言提供的二元运算符,用于比较基元和对象,在比较boolean,int,float等基本数据类型时,利用“==”工作正常,但在比较对象时,它会与Java中的equals方法产生混淆, “==”根据内存引用比较两个对象。 所以“==”运算符只有在两个对象引用比较时才返回true来表示完全相同的对象,否则“==”将返回false。在Java 5中引入自动装箱和拆箱之后,因版本的问题使用“==”来比较包装器对象可能会出现意想不到的结果。