Fork me on GitHub

Java的艺术-数据类型详解

此处输入图片的描述

一文道尽Java数据类型怎么用。

一、什么是数据类型

  数据类型在计算机语言里面,是对内存位置的一个抽象表达方式,可以理解为针对内存的一种抽象的表达方式。
  Java是强类型语言,所以Java对于数据类型的规范会相对严格。数据类型是语言的抽象原子概念,可以说是语言中最基本的单元定义,在Java里面,本质上讲将数据类型分为两种:基本类型和引用数据类型。

基本类型:简单数据类型是不能简化的、内置的数据类型、由编程语言本身定义,它表示了真实的数字、字符和整数。

引用数据类型:Java语言本身不支持C++中的结构(struct)或联合(union)数据类型,它的复合数据类型一般都是通过类或接口进行构造,类提供了捆绑数据和方法的方式,同时可以针对程序外部进行信息隐藏。

  不同数据类型的变量在内存中分配的字节数不同,同时存储方式也是不同的。所以Java给变量赋值前需要先确定变量的类型,确定了变量的类型,即确定了数据需分配内存空间的大小,数据在内存的存储方式。

二、Java基本类型和对应的包装类型

为了方便对基本类型进行操作,Java为每个基本类型提供了对应的包装类型(引用类型)。

基本数据类型

byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0

short:短整型,在内存中占16位,即2个字节,取值范围-32768~32717,默认值0

int:整型,用于存储整数,在内在中占32位,即4个字节,取值范围-2147483648~2147483647,默认值0

long:长整型,在内存中占64位,即8个字节-2^63~2^63-1,默认值0L

float:浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位),默认值0

double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0

char:字符型,用于存储单个字符,占16位,即2个字节,取值范围0~65535,默认值为空

boolean:布尔类型,占1位,1个字节或者四个字节(存在不同解释角度),用于判断真或假(仅有两个值,即true、false),默认值false。

Java中基本类型的存储是固定字节的,具体大小与对应的包装类型如下:

基本类型 存储位数 包装类型
boolean 1,8,32 Boolean
byte 8 Byte
char 16 Character
short 16 Short
int 32 Integer
long 64 Long
float 32 Float
double 64 Double
X X BigInteger
X X BigDecimal

封装类型特征

每个基本类型都有一个包装类,这些包装类包含在java.lang包中。继承关系如下:

  • Object
    • Boolean
    • Number
      • Byte
      • Short
      • Integer
      • Long
      • Float
      • Double
      • BigInteger
      • BigDecimal
    • Character

BigInteger、BigDecimal没有相对应的基本类型,主要应用于高精度的运算,BigInteger 支持任意精度的整数,BigDecimal支持任意精度带小数点的运算。

当整数类型的数据使用字面量赋值的时候,默认值为int类型,就是直接使用0或者其他数字的时候,默认值的类型为int类型,所以当使用 long a = 0这种赋值方式的时候,JVM内部存在数据转换。

浮点类型的数据使用字面量赋值的时候,默认值为double类型,就是当小数字面量出现的时候,JVM会使用double类型的数据类型。

从JDK 5.0开始,Java包含自动拆箱装箱的特性.调用valueOf()方法。

缓存池

基本类型对应的缓冲池如下:

  • -boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

三、在内存中的存储方式

基本数据类型:所有的简单数据类型不存在“引用”的概念,基本数据类型都是直接存储在内存中的堆、栈上;

引用类型:引用类型继承于Object类(也是引用类型)都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”是存储在有序的内存栈上的,而对象本身的值存储在内存堆上的;

关于基本数据类型存放的位置

取决于基本类型声明的位置:

在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。
(1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中
(2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的。

在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量。
(1)当声明的是基本类型的变量其变量名及其值放在堆内存中的
(2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

基本类型和封装类型异同

基本类型不是对象

声明方式不同:包装类型需要用new。

存储方式和存储位置不同

初始值不同:boolean初始为false,int为0,而Boolean和Integer都为null。

使用方式和场景不同:基本类型数据存储相对简单,运算效率比较高。使用包装类型会牺牲一些转换效率,但可以避免持久化数据时产生的一些异常。同时,集合的元素必须是对象类型,满足了java一切皆是对象的思想。

销毁效率不同:基本类型定义的变量创建和销毁很快,而类定义的变量还需要JVM去销毁。

四、类型转换

基本类型之自动类型转换

注意,只能低精度转向高精度。

1
2
3
4
5
char char1 = 'a';
char char2 = 'b';
int int1 = char1 + 1;//这里即自动类型转换。
int int2 = char1 +char2;//这里也完成了自动类型转换。
//char2 = int1;//出错

基本类型之强制类型转换

1
2
3
4
5
char char1 = 'a';
char char2 = 'b';
int int1 = 5;
char char3 =(char)int1;
char char4 = (char)(char1 + char2);//这里也完成了强制类型转换。

基本类型→包装类型

这主要是包括构造方法和装箱操作。
还有valauOf()方法。

(1)拆箱与装箱

编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。

1
2
3
Integer a = 8;
int b = 8;
System.out.println(a == b);//true

注意区分拆箱和装箱,以及对象比较:

1
2
3
4
5
6
7
int int1 = 9;
Integer a = 9;
Integer c = new Integer(9);

System.out.println(int1 == c);//true,比较值
System.out.println(a == int1);//true,比较值
System.out.println(a == c);//false,比较引用

(2)构造方法

1
2
//char
Character(char value)
1
2
3
4
5
//boolean
public Boolean(boolean value)
public Boolean(String s)
//注意,这个构造方法是输入的字符串是忽略大小写,相见源码:
return ((s != null) && s.equalsIgnoreCase("true"));

其余的Byte、Int、Double、Long、Short都包含两个构造方法,一个传入本身的基本类型,一个传入一个String类型。

注意,Float包含三个构造函数,除了上述两个,还可以传入double基本类型的值,源码如下:

1
2
3
public Float(double value) {
this.value = (float)value;
}

(3)valueOf():使用上等价于构造方法

valueOf()方法等价于构造方法,能用构造方法得到的包装对象也可以用同样的方法通过valueOf()得到。
valueOf() 方法的实现是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。

(4)String →基本类型的其他方法

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//Integer to String
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/

if (s == null) {
throw new NumberFormatException("null");
}

if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}

if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}

int result = 0;
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;

if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);

if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}

其他类包括了parseByte(),parseDouble()等。

包装类型→基本类型

(1)包装类型到对应基本类型

根据继承关系,继承自java.lang.Number的包装类型都包含了Number类的如下公共无参方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract int intValue();

public abstract long longValue();

public abstract float floatValue();


public abstract double doubleValue();


public byte byteValue() {
return (byte)intValue();
}


public short shortValue() {
return (short)intValue();
}

类似的,对于Charcter类:

1
2
3
public char charValue() {
return value;
}

对于Boolean类:

1
2
3
public boolean booleanValue() {
return value;
}

(2)包装类型→String类型

除此外,所有的包装类型都可以用toString()方法得到字符串类型的对象。

1
2
3
4
5
//Character
public String toString() {
char buf[] = {value};
return String.valueOf(buf);//实际上调用String类的valueOf()方法。
}
1
2
3
4
//Boolean
public String toString() {
return value ? "true" : "false";
}
1
2
3
4
5
6
7
//Float
public String toString() {
return Float.toString(value);
}
public static String toString(float f) {
return FloatingDecimal.toJavaFormatString(f);
}
1
2
3
4
5
6
7
//Double
public String toString() {
return toString(value);
}
public static String toString(double d) {
return FloatingDecimal.toJavaFormatString(d);
}
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
39
40
41
42
43
44
45
46
//Integer
public String toString() {
return toString(value);
}
//注意看这里,java是如何把int类型的数转换成String的
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);//得到i的字符数
char[] buf = new char[size];//buf存储字符
getChars(i, size, buf);
return new String(buf, true);
}
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;

if (i < 0) {
sign = '-';
i = -i;
}

// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}

// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//Long,与Integer类似的实现
public String toString() {
return toString(value);
}
public static String toString(long i) {
if (i == Long.MIN_VALUE)
return "-9223372036854775808";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//Short
public String toString() {
return Integer.toString((int)value);
}
//所以也调用了Integer的这个静态方法
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);//得到i的字符数
char[] buf = new char[size];//buf存储字符
getChars(i, size, buf);
return new String(buf, true);
}
1
2
3
4
//Byte
public String toString() {
return Integer.toString((int)value);
}

总结

常见的类型转换,以Integer为例:

int → String

(1)int后直接+””
(2)s = String.valueOf(i);
(3)Integer.toString(i);

String→int

(1) i = Integer.parseInt(s);
(2) i = Integer.valueOf(s).intValue();
(3) i = new Integer(s).intValue();

五、枚举类型

Java 5.0版本之后引入的除泛型之外的另外一个强大特性:枚举(Enums)。性能远高于静态类。解决一些固定常量集合的问题上枚举成为唯一首选和推荐的一种方式。
枚举类解析

enum 不能使用 extends 关键字继承其他类,因为 enum 已经继承了 java.lang.Enum(java是单一继承)。

-------------本文结束感谢阅读-------------