文章

Java包装类与泛型

本文介绍了Java中包装类的概念及其重要性,解释了为何需要包装类,包括支持泛型、统一基本类型与对象的关系以及节省内存等方面。此外,还探讨了包装类与基本类型之间的区别。

Java包装类与泛型

文章信息

  • 原文链接:https://jiayq.blog.csdn.net/article/details/89003751
  • 发布时间:2019-04-03 20:05:09
  • 阅读量:858
  • 分类:java专栏收录该内容, 订阅专栏
  • 标签:#泛型, #包装类, #自动装箱, #自动拆箱, #Java内存

摘要

文章浏览阅读858次。本文介绍了Java中包装类的概念及其重要性,解释了为何需要包装类,包括支持泛型、统一基本类型与对象的关系以及节省内存等方面。此外,还探讨了包装类与基本类型之间的区别。


Java包装类与泛型

  • 1.什么是包装类
  • 2.为什么需要包装类

1.什么是包装类

首先应该知道Java有基本类型与非基本类型
基本类型包含四类八种:
整型:
int
long
short
byte
浮点型:
double
float
布尔型:
boolean
字符型:
char
基本类型在Java语言中的地位很高,是关键字差不多一级的。
每一个基本类型都有自己的包装类:
Integer
Long
Short
Byte
Double
Float
Boolean
Character

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
	@Test
	public void method(){
		System.out.println(int.class.getName());
		System.out.println(Integer.class.getName());
		System.out.println(boolean.class.getName());
		System.out.println(Boolean.class.getName());
		System.out.println(double.class.getName());
		System.out.println(Double.class.getName());
		System.out.println(float.class.getName());
		System.out.println(Float.class.getName());
		System.out.println(long.class.getName());
		System.out.println(Long.class.getName());
		System.out.println(byte.class.getName());
		System.out.println(Byte.class.getName());
		System.out.println(short.class.getName());
		System.out.println(Short.class.getName());
		System.out.println(char.class.getName());
		System.out.println(Character.class.getName());
	}



int
java.lang.Integer
boolean
java.lang.Boolean
double
java.lang.Double
float
java.lang.Float
long
java.lang.Long
byte
java.lang.Byte
short
java.lang.Short
char
java.lang.Character

2.为什么需要包装类

在Java中,面向对象编程的思想中,一个方法应该尽可能的抽象。
比如我写一个方法,只能处理int类型的数据。
另一个方法能够处理所有整形的数据。
还有一个方法能够处理任意类型的数据。
那么第三种方法的抽象程度就比较高,使用的范围就大,使用的地方就会多。
那么如何表示任意类型呢?
有人说可以用Object类型。
是,所有的类都是Object类的子类。
但是,如果是基本类型呢?基本类型与Object可不是同一类。
所以,为了解决这一个问题,jdk就封装了所有的包装类,每一个基本类型对应一个包装类。
同时因为包装类支持对自己对应的基本类型的自动装箱,所以实际在使用的时候基本上无法对int与Integer的自动装箱做出感知。
例子:

1
2
3
4
5
	@Test
	public void method1(){
		int i = 3;
		System.out.println((Object)i);
	}

编译为class文件后,使用反编译软件进行查看:

1
2
3
4
5
  @Test
  public void method1() {
    int i = 3;
    System.out.println(Integer.valueOf(i));
  }

可以看到Integer对int做了自动的装箱。
有自动装箱就有自动拆箱

1
2
3
4
5
	@Test
	public void method2(){
		Integer mInteger = 3;
		System.out.println((int) mInteger);
	}

编译后class文件:

1
2
3
4
5
  @Test
  public void method2() {
    Integer mInteger = Integer.valueOf(3);
    System.out.println(mInteger.intValue());
  }

可以看到自动装箱是通过包装类的valueOf方法实现,自动拆箱通过intValue实现。
所以,使用上述方法可以实现手动装箱与拆箱。

所以,包装类的存在的第一个原因就是为了统一基本类型与Object的关系,通过包装类弱化基本类型的特殊性。

对于抽象的方法,如果使用Object对象作为参数,那么实际方法实现的时候需要使用instanceof来判定实例对象所属的类,然后使用强类型转换转换为需要的类。因为对将要处理的类的不确定性,此方式无法适应所有的类,只能兼容有限的类,而且对于开闭原则非常不友好,所以jdk在1.5版本中新增了泛型。
泛型的本质就是对任意一个类的实例进行如下处理:
1.声明方法的参数类型为Object,
2.使用时参数为任意类型,因为基本类型的自动装箱的存在,所有的类型都是Object的子类,
3.编译器自动进行类型转换。
所以,泛型就是对类型的擦除。

所以包装类存在的第二个原因就是为了支持泛型。

泛型与上界限定符有一定的相似之处:

<? super Object>这个上界限定符的范围就是所有的类包括Object在内的所有类。 其实最重要的一个原因就是基本类型与对象类型的处理方式: Java内存对于变量有两个存储区域: 常量表和堆 相对来说常量表比较小,且长度固定。 而堆的大小比较大,且长度不固定。 举一个非常简单明显的例子: 常量表不会有null异常。 堆中的变量可以赋值null. 常量表中存储的是常量以及基本类型: 基本类型+final修饰的变量。 其实我们都知道,基本类型有默认值,不会报null异常。 final类型除非赋值null,否则一般情况下也不会发生null异常。 因为在修改final类型的变量的值的时候编译就会报异常。 堆里面存储的就是对象类型。 那么堆里面的变量如何进行访问的? java @Test public void method4(){ System.out.println(new Object()); } java.lang.Object@7daf6ecc @Test public void method4(){ System.out.println(new Object().toString()); } public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } @Test public void method4(){ System.out.println(new Integer(0).toString()); } public static String toString(int i) { if (i == Integer.MIN_VALUE) return "-2147483648"; int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); char[] buf = new char[size]; getChars(i, size, buf); return new String(buf, true); } 首先堆里面的变量都是对象,都继承于Object,因为Object类实现了toString方法,所以,如果你直接打印一个变量,得到的是类名+@+hashCode 但是如果打印包装类,因为包装类重写了toString方法,所以让你完全无法感知Integer和int在自动装箱和拆箱的区别。 但是基本类型和包装类的区别还是有的: 基本类型在长量表中存储的是值,其变量就直接指示到长量表中的数据: 举个例子: 有一个int类型的i,首先i变量可以指示到常量表中的某一个,然后常量表中存储的就是值。 包装类型因为是对象类型,存储位置在堆,所以通过变量指示到常量表中的一个,常量表中存储的是堆里面的地址,然后通过访问堆地址找到对象中的值。 因为包装类型中在常量表中存储的是地址,那么地址的长度其实是确定的,比如32位系统,那么内存的范围就是2的32次方,是4G,2的64次方是2的10次方是k,2的20次方是m,2的30次方是G,2的40次方是T,2的50次方是P,2的60次方是E,所以64位系统可以支持的内存是16E即4G的4G,现在还没有那么大的内存,所以可以认为64位系统不受内存的大小限制。 一个对象一般占用16字节,一个整形是4字节。 有关内存地址是一个比较恶心的地方。 C语言与C++有指针这个概念,说的就是变量存储的是地址不是值。 因为Java没有指针概念,所以Java的难度远远的小于C与C++。(因为指针可以嵌套,所以指针非常的复杂) 同样的汇编比c与c++还要恶心,并不是指针方面,而是汇编要完全参与内存管理,汇编需要声明内存才能使用,更加恶心。 回到正题: 包装类存在的第三个原因就是节省内存,因为Java需要使用Java虚拟机解析字节码,虚拟机会占用一定的内存,为了优化内存,包装类与基本类型映射就是为了节省内存。 所以包装类的意义重大。

本文由作者按照 CC BY 4.0 进行授权