【基本数据类型】再看8种基础数据类型

一、前言

Java语言中内置定义了八种基本原生数据类型,基本数据类型值之间不共享状态,换句话说即便多个变量的值相同,其值对应的内存地址也不相同(第8节会详细阐述)

二、8种数据类型定义

名称字节数位数取值范围默认值描述
byte18-128~1270有符号整数
short216-32,768~327670有符号整数
int432-2^31 ~ 2^31-10有符号整数
long864-2^63 ~ 2^63-10L有符号整数
float8640x0.000002P-126f~0x1.fffffeP+127f0.0f有符号数(-2^149 ~ 2^128-1)
double8640x0.0000000000001P-1022~0x1.fffffffffffffP+10230.0d有符号数(-2^1074 ~ 2^1024-1)
boolean1位或者1~4字节1位或者1~4字节true|falsefalse此数据类型只代表0或者1(不同虚拟机有不同实现)
char2160~65535或者\u0000 ~ \uffff‘\u0000’无符号整数

虚拟机规范中定义如下:

byte: The byte data type is an 8-bit signed two's complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive). The byte data type can be useful for saving memory in large arrays, where the memory savings actually matters. They can also be used in place of int where their limits help to clarify your code; the fact that a variable's range is limited can serve as a form of documentation.

short: The short data type is a 16-bit signed two's complement integer. It has a minimum value of -32,768 and a maximum value of 32,767 (inclusive). As with byte, the same guidelines apply: you can use a short to save memory in large arrays, in situations where the memory savings actually matters.

int: By default, the int data type is a 32-bit signed two's complement integer, which has a minimum value of -231 and a maximum value of 231-1. In Java SE 8 and later, you can use the int data type to represent an unsigned 32-bit integer, which has a minimum value of 0 and a maximum value of 232-1. Use the Integer class to use int data type as an unsigned integer. See the section The Number Classes for more information. Static methods like compareUnsigned, divideUnsigned etc have been added to the Integer class to support the arithmetic operations for unsigned integers.

long: The long data type is a 64-bit two's complement integer. The signed long has a minimum value of -263 and a maximum value of 263-1. In Java SE 8 and later, you can use the long data type to represent an unsigned 64-bit long, which has a minimum value of 0 and a maximum value of 264-1. Use this data type when you need a range of values wider than those provided by int. The Long class also contains methods like compareUnsigned, divideUnsigned etc to support arithmetic operations for unsigned long.

float: The float data type is a single-precision 32-bit IEEE 754 floating point. Its range of values is beyond the scope of this discussion, but is specified in the Floating-Point Types, Formats, and Values section of the Java Language Specification. As with the recommendations for byte and short, use a float (instead of double) if you need to save memory in large arrays of floating point numbers. This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead. Numbers and Strings covers BigDecimal and other useful classes provided by the Java platform.

double: The double data type is a double-precision 64-bit IEEE 754 floating point. Its range of values is beyond the scope of this discussion, but is specified in the Floating-Point Types, Formats, and Values section of the Java Language Specification. For decimal values, this data type is generally the default choice. As mentioned above, this data type should never be used for precise values, such as currency.

boolean: The boolean data type has only two possible values: true and false. Use this data type for simple flags that track true/false conditions. This data type represents one bit of information, but its "size" isn't something that's precisely defined.

char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).

完整的Oracle官方原生数据类型定义参考文档:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

备注(boolean变量占用字节补充)

笔者使用的虚拟机为OpenJDK-14版本(自己下载源码编译版本)
在这里插入图片描述
jni.h头文件中boolean定义为无符号char,即一个字节,8位

typedef unsigned char   jboolean;

在这里插入图片描述
具体参考博文:
https://blog.csdn.net/gaoyong_stone/article/details/79538195


三、JVM源码中类型定义

Java类型Unix定义Windows定义(long为32位)
bytetypedef signed char jbytetypedef signed char jbyte
shorttypedef short jshorttypedef short jshort
inttypedef int jinttypedef long jint
longtypedef long jlong或者typedef long long jlongtypedef __int64 jlong
floattypedef float jfloattypedef float jfloat
doubletypedef double jdoubletypedef double jdouble
chartypedef unsigned short jchartypedef unsigned short jchar
booleantypedef unsigned char jbooleantypedef unsigned char jboolean

四、包装类型&自动装箱拆箱

8种基本数据类型对应8中包装类型,如下表格

基本类型包装类型父类
byteByteNumber
shortShortNumber
intIntegerNumber
longLongNumber
floatFloatNumber
doubleDoubleNumber
charCharacterObject
booleanBooleanObject

说到这里就有两个如下的问题

  1. 为什么需要有包装类型?有原生类型不就可以了吗?

    答:原生类型是JAVA面向对象语言中唯一不是对象类型的类型,在很多场景下无法发挥面向对象的特点,比如泛型集合,你可以定义一个List<Integer>确不能申明一个List<int>,再比如我想快速知道一个byte的最小值和最大值,你可能会一下就给出结论-128~127,但是short或者int类型呢?总不能每次都去查阅资料吧,JDK8之后Short类提供了MIN_VALUEMAX_VALUE两个场景,方便快捷的将short相关封装到一起

  2. 什么是装箱,什么是拆箱,有什么用?

    答:装箱顾名思义即把基本数据类型装起来变成一个对象类型,反之拆箱是将对象类型变成基本类型,可让你编写更整洁、更易读的代码,引用oracle官网原话

    Autoboxing and unboxing lets developers write cleaner code, making it easier to read. The following table lists the primitive types and their corresponding wrapper classes, which are used by the Java compiler for autoboxing and unboxing:

    官方资料参考:
    https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

这里我们举个例子说明,代码如下(方法1)

    /**
     * 自动装箱测试
     */
    @Test
    public void autoBoxing(){
        List<Integer> list=new ArrayList<>();
        list.add(0);
        list.stream().forEach(i->System.out.println(i));
    }

这段代码逻辑很简答,定义一个list,然后往list里面写入一个0,最后打印出来
在这里插入图片描述

细心的同学可能已经发现list集合接收一个Integer类型数字,但第二行确传入了一个int类型的变量,编译运行确没有问题,我们看看编译后的字节码
在这里插入图片描述

如图上可知,编译器聪明的识别到了0并调用Integer.valueOf将0转成了一个Integer对象,Integer.valueOf源码如下

    @HotSpotIntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

那问题来了,我们为什么不干脆这样写(方法2)

 @Test
    public void autoBoxingManual(){
        List<Integer> list=new ArrayList<>();
        list.add(Integer.valueOf(0));
        list.stream().forEach(i->System.out.println(i));
    }

或者(方法3)

    @Test
    public void autoBoxingManualWithNew(){
        List<Integer> list=new ArrayList<>();
        list.add(new Integer(0));
        list.stream().forEach(i->System.out.println(i));
    }

方法1、2、3都可以顺利编译并执行,但对比之下会发现方法1更简单明了,不推荐方法3的写法,每次都需要new一个实例出来,自动装箱最终会调用Integer.valueOf,这里JDK实现时做了一个优化,在虚拟机启动是会将-128到127的值记载到IntegerCache缓存中,默认缓存大小为256,保存-128到127(包括)的数值缓存,valueOf参数在[-128,127]范围内时从缓存中查找,否则new一个实例出来,这样可提高性能,不用每次都new一个对象出来。

Byte、Short、Integer、Long这四个整形包装类都有对应的缓存,范围都是从[-128,127](左闭右闭),Float和Double没有缓存

Integer更为特殊,可使用如下两种方式设置最大值

  1. -XX:AutoBoxCacheMax=1000
  2. -D java.lang.Integer.IntegerCache.hight=1000

我们再看看自动拆箱(方法4)

    @Test
    public void autoUnboxing(){
        Integer base=Integer.valueOf(10);
        int sum=0;
        sum+=base;
        System.out.println(sum);
    }

可以看到sum是一个int类型变量,base为一个Integer对象,编译器进行了自动拆箱,我们看看编译后的字节码,实际调用了Integer.intValue方法
在这里插入图片描述

Integer.intValue源码如下

	/**
     * Returns the value of this {@code Integer} as an
     * {@code int}.
     */
    public int intValue() {
        return value;
    }

这里直接返回了value值

五、编码规范

  1. 定义变量时能使用原生类型尽量使用而不要用包装类型,可减少内存占用,避免NPE问题
  2. 包装类型的数据比较一律使用equals方法而不要使用== 内存缓存问题,默认情况下在[-128,127]区间 成立,但超过此区间不成立)
  3. 包装类型如果为null,则在自动拆箱时会报NPE异常
  4. 包装类型初始值为null,但基本类型初始化为0、’\u0000’或者false,编码时要根据实际情况注意默认值可能带来的坑(比如if条件)
  5. 尽量避免出现装箱、拆箱操作,编译器会自动增加额外方法调用
  6. 多线程场景下包装类型为非线程安全,请使用java.util.concurrent包下对应的线程安全类型,如AtomicInteger

编码规范参考: 阿里巴巴编码规范

六、隐示转换

byte、short、int、long这四个整形类型在执行运算过程中存在隐示转型,转型策略如下

byte->short->int->long

反之从long->int->short->byte无法隐示转型,因为会丢失精度,只能手动显示转型

举例如下

short x=100;
x+=1;//编译成功,相当于x=(short)(x+1)
x=x+1;//编译失败,1为int,不能隐示转为short

七、常见面试题

  1. 题目1,如下代码的输出是什么?(考点:装箱、拆箱、比较)

    @Test
        public void questionOne(){
            int x=100;
            Integer y=100;
            Integer z=new Integer(100);
            Integer w=Integer.valueOf(100);
    
            Integer a=Integer.valueOf(128);
            Integer b=128;
            Integer c=new Integer(128);
    
    
            System.out.println("x==y?"+ (x==y)); //true,左边为int,右边为Integer,右边被自动拆箱,转换为Integer.intValue()
            System.out.println("x==z?"+(x==z));  //true,包装类型自动拆箱
            
            System.out.println("z==x?"+(z==x));  //true,包装类型自动拆箱,转换为Integer.intValue()==x
            System.out.println("z==y"+(z==y));   //false,z为new的对象,即便在[-128,127]缓存范围内
            
            System.out.println("y==z?"+(y==z));  //false,y和z都为对象
     		System.out.println("y==w?"+(y==w));  //true,y定义被转换为Integer y=Integer.valueOf(100),同w定义一样
     		
            System.out.println("x==w?"+(x==w));  //true 
            System.out.println("w==x?"+(w==x));  //true,自动拆箱
            
            System.out.println("a==b?"+(a==b)); //false,128超过[-128,127]范围
            System.out.println("a.equals(b)?"+(a.equals(b))); //true,值相同
            
            System.out.println("b==c?"+(b==c)); //false
            System.out.println("b.equals(c)?"+(b.equals(c)));//true
        }
    
  2. 题目2,不同类型基础变量+=,-=操作

        @Test
        public void questionTwo(){
            short a=100;
            int b=200;
            a=a+b; //编译错误,b为int,需要一个short类型,隐示转型不能从高类型向低类型转型
            a+=b;  //编译正确,类似于a=(short)(a+b)
        }
    
  3. 题目3,什么情况下使得如下代码比较结果为true

    @Test
    public void questionThree(){
        Integer x=200;
        Integer y=200;
        System.out.println(y==x); //结果为false,
    }
    

    该题目考察是否对Integer的cache机制理解透彻,其实很简单,只需要将设置
    -D java.lang.Integer.IntegerCache.hight=200即可
    在这里插入图片描述
    设置好启动参数后,表达式为true

    在这里插入图片描述


八、如何理解前言中的基本数据类型值之间不共享状态?

从上面我们可以知道八种基本类型变量在虚拟机实现时是如何定义的,换句话说当我们在java里面声明了int类型的变量时,最终是通过C语言中的变量来表达的,那么不同数据之间不共享状态怎么理解?

摘自stackoverflow网友的回答:

This means that each value of primitive type occupies its own space in memory, representing a state which cannot be shared with other values. In other words, you cannot change the state of a variable or a field of a primitive type in any way other than assigning it, directly or through a compound assignment operator.

This is in contrast with reference types, which may or may not share state by “pointing” to the same object. You can change a reference object by manipulating it through a different variable.

翻译过来就是每个基本类型的变量都有自己的内存空间,不同类型值之间相互隔离不共享,你除了重新对一个基本变量赋值外不能通过其他方式改变变量值,但是指针类型的变量确可以共享内存地址,为了验证,我们来简单写一个C代码来看看

typedef int jint;
typedef unsigned char jboolean;
class jobject{
public:
    int flag;
};
int main() {
    jint a=100;
    jint b=a;
    printf("变量a内存地址为%d\n",&a);
    printf("变量b内存地址为%d\n",&b);
    jobject* oa=new jobject();
    oa->flag=100;
    jobject* ob=oa;
    printf("对象变量oa的内存地址为%d\n",oa);
    printf("对象变量ob的内存地址为%d\n",ob);
    return 0;
}

运行结果如下:

可以看到a和b的内存地址相差4个字节,但是oa和ob的内存地址一模一样
在这里插入图片描述

九、字节码操作中类型定义

某些情况下需要使用动态字节码操作来增强类,典型的场景比如hibernate查询结果到对象的映射,又比如某些情况下为提高性能,动态给类型添加getter/setter方法,避免使用反射导致的性能损失,这里列举字节码层面8中基本类型的表达方式,如下图(图选自ASM使用手册)

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值