Java中存在2种数据类型,下面我们来详解一下:
- 基本数据类型;
- 引用数据类型;
可以用一张表来记录:
基本数据类型
整型
- byte:1个字节8位,取值范围为:[-128, 127],直接写值:(byte) 1;
- short:2个字节16位,取值范围为:[-32768, 32768],直接写值:(short) 1;
- int:4个字节32位,取值范围为:[-2147483648, 2147483648],直接写值:1;
- long:8个字节64位,取值范围为:[-9233372036854477808, 9233372036854477808],直接写值:1L;
- char:2个字节16位,取值范围为:[0, 65535],直接写值:(char) 1或'a';
(注意Java中没有无符号类型,需要使用无符号类型需要自行扩展,可以参考我的扩展类库)
浮点型
- float:4个字节32位,直接写值:1.0F;
- double:8个字节64位,直接写值:1.0D;
布尔型
- boolean:1个字节8位,取值范围为:true或false;
引用数据类型
- String:字符串类型,无长度限制,直接写值:"abc";
- java.math.BigInteger:基本数据中的整型不能满足需求时可以使用该类来表示任意大小的整型数据;
- java.math.BigDecimal:基本数据中的浮点型不能满足需求时可以使用该类来表示任意精度的浮点型数据;
- 其他类型:类、接口、数组等其他类型;
栈和堆
栈内存
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
堆内存
堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的。
另外,栈内存的内容会在作用域结束时自动移除,而堆内存的释放要借助Java的垃圾回收机制来完成。
包装类
不同于C#的万物皆对象,Java中,基本数据类型仅仅是数据而非对象,所以为了能将基本类型视为对象进行处理,并能连接相关的方法,java为每个基本类型都提供了包装类。以便将基本数据类型作为一个对象来处理。
C#中一切皆对象,所以将一个数字转换为16进制可以这么写:
254.ToString("x");//输出FE
但是在Java中,整型数据并不是对象,所以需要这么写:
new Integer(254).toString();//输出字符串"254"
包装类功能
- 提供数据的最大值与最小值;
- 提供转换为其他数据类型的功能;
int i = 5;//直接在栈中分配空间 Integer i = new Integr(5);//对象是在堆内存中,而i(引用变量)是在栈内存中
当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装类。
- 原始类型 包装类
- boolean Boolean
- char Character
- byte Byte
- short Short
- int Integer
- long Long
- float Float
- double Double
装箱和拆箱
装箱:把基本类型用它们相应的引用类型包装起来,使其具有对象的性质。int包装成Integer、float包装成Float;
拆箱:和装箱相反,将引用类型的对象简化成值类型的数据;
Integer a = Integer.valueOf(100); int b = a.intValue();
Java1.5之后引入了自动装箱和自动拆箱,如下:
Integer a = 100;//编译后实际是Integer a = Integer.valueOf(100); int b = a;//编译后实际是int b = a.intValue();
需要注意的地方
需要注意每个包装类的valueOf方法的实现:
Integer
1 public static Integer valueOf(int i) { 2 if(i >= -128 && i <= IntegerCache.high) 3 return IntegerCache.cache[i + 128]; 4 else 5 return new Integer(i); 6 }
当数值在[-128,127]之间使用实现缓存好的对象,否则创建新的Integer对象,这就不难解释下面的代码运行了:
1 public class Main { 2 public static void main(String[] args) { 3 Integer i1 = 100; 4 Integer i2 = 100; 5 Integer i3 = 200; 6 Integer i4 = 200; 7 System.out.println(i1==i2);//true 8 System.out.println(i3==i4);//false 9 } 10 }
Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double
1 public static Double valueOf(double d) { 2 return new Double(d); 3 }
为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
看下面的代码:
1 public class Main { 2 public static void main(String[] args) { 3 Double i1 = 100.0; 4 Double i2 = 100.0; 5 Double i3 = 200.0; 6 Double i4 = 200.0; 7 System.out.println(i1==i2);//false 8 System.out.println(i3==i4);//false 9 } 10 }
Double、Float的valueOf方法的实现是类似的。
Boolean
1 public static Boolean valueOf(boolean b) { 2 return (b ? TRUE : FALSE); 3 }
布尔是直接返回一个常量,而该常量是提前创建好的两个Boolean对象。
1 public class Main { 2 public static void main(String[] args) { 3 Boolean i1 = false; 4 Boolean i2 = false; 5 Boolean i3 = true; 6 Boolean i4 = true; 7 System.out.println(i1==i2);//true 8 System.out.println(i3==i4);//true 9 } 10 }
变量
在Java中定义变量的写法如下:
int a = 0; int b = 1, c = 2;
作用域
在栈内存中定义的变量是存在作用域的:
- 使用大括号来定义作用域;
- 作用域可以嵌套,子作用域可以访问父级的变量,父作用域不能访问子级的变量;
- 同一个作用域中的变量名不能重复;
- 离开作用域变量会被JVM自动回收;
下面我们看几个例子:
1 public static void main(String[] args) 2 { 3 int a = 0; 4 { 5 int b = 1; 6 a = 10; 7 } 8 a = b;//这行编译报错, 变量 b 离开自身作用域后就被回收了, 不能访问 9 }
1 public static void main(String[] args) 2 { 3 int a = 0; 4 { 5 int b = 1; 6 a = 10; 7 } 8 int b;//这个 b 是当前作用域创建的新变量, 值为默认的 0 而不是 1 9 a = b;//不报错, b 可以访问到 10 }
1 public static void main(String[] args) 2 { 3 int a = 0; 4 { 5 int a = 100;//报错, 子作用域不能使用父级已经存在的变量名 6 } 7 }
类型转换
数据类型之间常常会需要相互转换,下面我们来看看;
小数据向大数据转换
由JVM自动完成,不需要进行额外处理,称为隐式类型转换,如:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 byte a = 16; 6 short b = a; 7 int c = b; 8 long d = c; 9 10 float e = 16.66f; 11 double f = e; 12 13 test(a); 14 test(e); 15 } 16 17 private static void test(int i) 18 { 19 } 20 21 private static void test(double i) 22 { 23 } 24 }
整数转换为浮点数时会出现精度丢失的情况。
大数据向小数据转换
需要强制转换,称为显式类型转换,如:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 long a = 1L; 6 int b = (int) a; 7 short c = (short) b; 8 byte d = (byte) c; 9 10 double e = 16.66f; 11 float f = (float) e; 12 13 test((byte) a); 14 test2((float) e); 15 } 16 17 private static void test(byte i) 18 { 19 } 20 21 private static void test2(float i) 22 { 23 } 24 }
转换时要注意数据越界的问题。
运算时要注意的点
有一个要注意的地方,请看下面的代码:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 System.out.println(5 / 3);//1 6 7 System.out.println(5.0 / 3);//1.6666666666666667 8 System.out.println(5 / 3.0);//1.6666666666666667 9 System.out.println(5d / 3);//1.6666666666666667 10 System.out.println((double)5 / 3);//1.6666666666666667 11 12 System.out.println((double)(5 / 3));//1.0 13 14 System.out.println(5f / 3);//1.6666666 15 System.out.println(5 / 3f);//1.6666666 16 System.out.println(5 / (float)3);//1.6666666 17 18 System.out.println((float)(5 / 3));//1.0 19 } 20 }
根据输出,我看可以得到下面的结论:
- 两个整形进行运算得到的仍然是整形;
- 其中一个是浮点型则另一个也转换为浮点型,并且结果也是浮点型;
- 其中一个是双精度浮点型则另一个也转换为双精度浮点型,并且结果也是双精度浮点型;
- 默认的小数不带“f”则表示为一个双精度浮点数;
- 整形相除之后再转换为浮点数,小数部分仍然丢失;