Java支持的数据类型有哪些?什么是自动拆装箱?

Python034

Java支持的数据类型有哪些?什么是自动拆装箱?,第1张

java支持的数据类型有两大类:  一类是基本数据类型分为8种 (数据)   byte——(单字节)   sort——(双字节)   int——(4字节)    long——(8字节)   float——(4字节)    double——(8字节)    char——(双字节)    boolean——(双字节) 前面四种为整数类型,5和6为浮点数类型,7为字符类型,8为布尔类型。  一类是引用类型3种(除了数据还有地址编号) Object——(类类型)  Array——(数组类型)  Interface——(接口类型)  JVM在解释java变量的时候是把它们按照数据类型区分的,基本数据类型的变量会存储在栈空间中,而引用类型在栈空间存放的是地址编号,它们的数据是存储在堆空间中的。查找引用类型变量的时候现在栈空间拿到该变量 在堆空间存储数据的地址编号,然后再到堆空间按照拿到的地址编号取数据。

一、Java简介

Java是一种面向对象的编程语言。Java语言有两个特点:功能强大和使用方便。Java具有简单的、面向对象的和分布式的特点。它可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序。

二、java的工作原理

在编辑和运行一个Java程序时,这四个方面需要同时涉及:(1)Java编程语言;(2)Java类文件格式;(3)Java虚拟机;(4)Java应用程序接口。使用文本编辑软件(如写字板、记事本、UltraEdit等)或集成开发环境(MyEclipse、Eclipse等)。在Java源文件中定义不同的类,通过调用类中的方法访问资源系统(这些类实现Java API),编译源文件生成二进制中间代码,并存储在类文件中。然后通过运行操作系统平台环境对应的Java虚拟机运行类文件,执行编译生成的字节码,调用类文件中实现的方法,满足程序的Java API调用。

三、Java的贡献及影响力

Java语言是一种与时俱进的计算机语言程序。它深刻地展现了编程的本质,其简洁严谨的结构和简洁的语法为其未来的开发和维护提供了保障。因为它提供了网络应用和多媒体访问的支持,所以它将促进互联网和企业网络的应用。此外,为了保持Java的增长,促进Java社区的参与,Sun在Java One开发者大会上宣布开放Java核心源代码,鼓励更多人参与Java社区活动。在Java社区和IBM等全球技术合作伙伴的支持下,Java技术继续在创新和社会进步中发挥着强大而重要的作用,并且随着其编程难度的降低,更多的专业人士专注于Java语言的编写和框架结构的设计。

把一个基本类型包装成一个类,一个是可以使这个类型具有很多可以调用的方法。二个是Java向面像对象语言的靠近。其实Java还不算是很纯的面向对象的语言。真正的面向对象,是没有基本数据类型的。它只有一种类型,就是对象。三个是在泛型中,基本类型是不可以做泛型参数的。如:List <int>list = new ArrayList<int>()这是不合法的。你只能这个样写List<Integer>list = new ArrayList<Integer>()也就是要用int型的包装类类型来解决基本类型不可以做泛型参数的问题 。

一名话,包装类有更多的方法和用途, 而这是基本类型没有的!!!

详解Java的自动装箱与拆箱(Autoboxing and unboxing)

一、什么是自动装箱拆箱 

很简单,下面两句代码就可以看到装箱和拆箱过程

//自动装箱

Integer total = 99

//自定拆箱

int totalprim = total

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

下面我们来看看需要装箱拆箱的类型有哪些:

这个过程是自动执行的,那么我们需要看看它的执行过程:

public class Main {

    public static void main(String[] args) {

    //自动装箱

    Integer total = 99

    //自定拆箱

    int totalprim = total

    }

}

反编译class文件之后得到如下内容:

javap -c StringTest

Integer total = 99 

执行上面那句代码的时候,系统为我们执行了: 

Integer total = Integer.valueOf(99)

int totalprim = total 

执行上面那句代码的时候,系统为我们执行了: 

int totalprim = total.intValue()

我们现在就以Integer为例,来分析一下它的源码: 

1、首先来看看Integer.valueOf函数

public static Integer valueOf(int i) {

    return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128]

}

它会首先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。

首先我们来看看Integer的构造函数:

private final int value

public Integer(int value) {

    this.value = value

}

public Integer(String string) throws NumberFormatException {

    this(parseInt(string))

}

它里面定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。

下面看看SMALL_VALUES[i + 128]是什么东西:

private static final Integer[] SMALL_VALUES = new Integer[256]

它是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象。

所以我们这里可以总结一点:装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能。

2、接着看看intValue函数

@Override

public int intValue() {

    return value

}

这个很简单,直接返回value值即可。

二、相关问题 

上面我们看到在Integer的构造函数中,它分两种情况: 

1、i >= 128 || i <-128 =====>new Integer(i) 

2、i <128 &&i >= -128 =====>SMALL_VALUES[i + 128]

private static final Integer[] SMALL_VALUES = new Integer[256]

SMALL_VALUES本来已经被创建好,也就是说在i >= 128 || i <-128是会创建不同的对象,在i <128 &&i >= -128会根据i的值返回已经创建好的指定的对象。

说这些可能还不是很明白,下面我们来举个例子吧:

public class Main {    public static void main(String[] args) {

        Integer i1 = 100

        Integer i2 = 100

        Integer i3 = 200

        Integer i4 = 200

        System.out.println(i1==i2)  //true

        System.out.println(i3==i4)  //false

    }

}

代码的后面,我们可以看到它们的执行结果是不一样的,为什么,在看看我们上面的说明。 

1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。

2、i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

下面我们来看看另外一个例子:

public class Main {    public static void main(String[] args) {

        Double i1 = 100.0

        Double i2 = 100.0

        Double i3 = 200.0

        Double i4 = 200.0

        System.out.println(i1==i2) //false

        System.out.println(i3==i4) //false

    }

}

看看上面的执行结果,跟Integer不一样,这样也不必奇怪,因为它们的valueOf实现不一样,结果肯定不一样,那为什么它们不统一一下呢? 

这个很好理解,因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。

但是对于Double类型来说,我们就不能这样做,因为它在这个范围内个数是无限的。 

总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

所以在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。

public static Double valueOf(double d) {

    return new Double(d)

}

下面我们进行一个归类: 

Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 

Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。

下面对Integer派别进行一个总结,如下图:

下面我们来看看另外一种情况:

public class Main {    public static void main(String[] args) {

        Boolean i1 = false

        Boolean i2 = false

        Boolean i3 = true

        Boolean i4 = true

        System.out.println(i1==i2)//true

        System.out.println(i3==i4)//true

    }

}

可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。

public static Boolean valueOf(boolean b) {

    return b ? Boolean.TRUE : Boolean.FALSE

}

可以看到它并没有创建对象,因为在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。

public static final Boolean TRUE = new Boolean(true)

public static final Boolean FALSE = new Boolean(false)

上面把几种情况都介绍到了,下面来进一步讨论其他情况。

Integer num1 = 400  

int num2 = 400  

System.out.println(num1 == num2) //true

说明num1 == num2进行了拆箱操作

Integer num1 = 100  

int num2 = 100  

System.out.println(num1.equals(num2))  //true

我们先来看看equals源码:

@Override

public boolean equals(Object o) {

    return (o instanceof Integer) && (((Integer) o).value == value)

}

我们指定equal比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。

Integer num1 = 100  

int num2 = 100  

Long num3 = 200l  

System.out.println(num1 + num2)  //200

System.out.println(num3 == (num1 + num2))  //true

System.out.println(num3.equals(num1 + num2))  //false

1、当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。 

2、对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:

@Override

public boolean equals(Object o) {

    return (o instanceof Long) && (((Long) o).value == value)

}

它必须满足两个条件才为true: 

1、类型相同 

2、内容相同 

上面返回false的原因就是类型不同。

Integer num1 = 100

Ingeger num2 = 200

Long num3 = 300l

System.out.println(num3 == (num1 + num2)) //true

我们来反编译一些这个class文件:javap -c StringTest 

可以看到运算的时候首先对num3进行拆箱(执行num3的longValue得到基础类型为long的值300),然后对num1和mum2进行拆箱(分别执行了num1和num2的intValue得到基础类型为int的值100和200),然后进行相关的基础运算。

我们来对基础类型进行一个测试:

int num1 = 100

int num2 = 200

long mum3 = 300

System.out.println(num3 == (num1 + num2)) //true

上面就说明了为什么最上面会返回true.

所以,当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

通过上面的分析我们需要知道两点: 

1、什么时候会引发装箱和拆箱 

2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

我有一个微信公众号,经常会分享一些Java技术相关的干货文章,还有一些学习资源。

如果你需要的话,可以用微信搜索“Java团长”或者“javatuanzhang”关注。