Java 数据类型
Java 数据类型
1. 基本数据类型
基本数据类型是 Java 语言操作数据的基础,包括 boolean、char、byte、short、int、long、float 和 double,共 8 种。
2. 引用数据类型
除了基本数据类型以外的类型,都是所谓的引用类型。常见的有数组、class(也就是类),以及接口(指向的是实现接口的类的对象)。
变量可以分为局部变量、成员变量、静态变量。
当变量是局部变量的时候,必须得先初始化,否则编译器不允许你使用它。
当变量是成员变量或者静态变量时,可以不进行初始化,它们会有一个默认值,仍然以 int 为例,来看代码:
public class LocalVar { private int a; static int b;
public static void main(String[] args) { LocalVar lv = new LocalVar(); System.out.println(lv.a); System.out.println(b); }}来看输出结果:
00瞧见没,int 作为成员变量时或者静态变量时的默认值是 0。那不同的基本数据类型,是有不同的默认值和占用大小的,来个表格感受下。
| 数据类型 | 默认值 | 大小 |
|---|---|---|
| boolean | false | 不确定 |
| char | ‘\u0000’ | 2 字节 |
| byte | 0 | 1 字节 |
| short | 0 | 2 字节 |
| int | 0 | 4 字节 |
| long | 0L | 8 字节 |
| float | 0.0f | 4 字节 |
| double | 0.0 | 8 字节 |
比特和字节
1. bit(比特)
比特 作为信息技术的最基本存储单位,非常小,它的简写为小写字母“b”。
计算机是以二进制存储数据的,二进制的一位,就是 1 比特,也就是说,比特要么为 0 要么为 1。
1. Byte(字节)
通常来说,一个英文字符是一个字节,一个中文字符是两个字节。字节与比特的换算关系是:1 字节 = 8 比特。
在往上的单位就是 KB,并不是 1000 字节,因为计算机只认识二进制,因此是 2 的 10 次方,也就是 1024 个字节。
基本数据类型
布尔
布尔(boolean)仅用于存储两个值:true 和 false,也就是真和假,通常用于条件的判断。代码示例:
boolean hasMoney = true;boolean hasGirlFriend = false;根据 Java 语言规范,boolean 类型只有两个值 true 和 false,但在语言层面,Java 没有明确规定 boolean 类型的大小。
byte
一个字节可以表示 2^8 = 256 个不同的值。由于 byte 是有符号的,它的值可以是负数或正数,其取值范围是 -128 到 127(包括 -128 和 127)。
在网络传输、大文件读写时,为了节省空间,常用字节来作为数据的传输方式。代码示例:
byte b; // 声明一个 byte 类型变量b = 10; // 将值 10 赋给变量 bbyte c = -100; // 声明并初始化一个 byte 类型变量 c,赋值为 -100short
short 的取值范围在 -32,768 和 32,767 之间,包含 32,767。代码示例:
short s; // 声明一个 short 类型变量s = 1000; // 将值 1000 赋给变量 sshort t = -2000; // 声明并初始化一个 short 类型变量 t,赋值为 -2000实际开发中,short 比较少用,整型用 int 就 OK。
int
nt 的取值范围在 -2,147,483,648(-2 ^ 31)和 2,147,483,647(2 ^ 31 -1)(含)之间。如果没有特殊需求,整型数据就用 int。代码示例:
int i; // 声明一个 int 类型变量i = 1000000; // 将值 1000000 赋给变量 iint j = -2000000; // 声明并初始化一个 int 类型变量 j,赋值为 -2000000为什么 32 位的有符号整数的取值范围是从 -2^31 到 2^31 - 1 呢?
这是因为其中一位用于表示符号(正或负),剩下的 31 位用于表示数值,这意味着其范围是 -2,147,483,648(即 -2^31)到 2,147,483,647(即 2^31 - 1)。
在二进制系统中,每个位(bit)可以表示两个状态,通常是 0 和 1。对于 32 位得正二进制数,除去符号位,从右到左的每一位分别代表 2^0, 2^1, 2^2, …, 2^30,这个二进制数转换为十进制就是 2^0 + 2^1 + 2^2 + … + 2^30,也就是 2,147,483,647。
long
long 的取值范围在 -9,223,372,036,854,775,808(-2^63) 和 9,223,372,036,854,775,807(2^63 -1)(含)之间。如果 int 存储不下,就用 long。代码示例:
long l; // 声明一个 long 类型变量l = 100000000000L; // 将值 100000000000L 赋给变量 l(注意要加上 L 后缀)long m = -20000000000L; // 声明并初始化一个 long 类型变量 m,赋值为 -20000000000L为了和 int 作区分,long 型变量在声明的时候,末尾要带上大写的“L”。不用小写的“l”,是因为小写的“l”容易和数字“1”混淆。
float
float 是单精度的浮点数(单精度浮点数的有效数字大约为 6 到 7 位),32 位(4 字节),遵循 IEEE 754(二进制浮点数算术标准),取值范围为 1.4E-45 到 3.4E+38。float 不适合用于精确的数值,比如说金额。代码示例:
float f; // 声明一个 float 类型变量f = 3.14159f; // 将值 3.14159f 赋给变量 f(注意要加上 f 后缀)float g = -2.71828f; // 声明并初始化一个 float 类型变量 g,赋值为 -2.71828f为了和 double 作区分,float 型变量在声明的时候,末尾要带上小写的“f”。不需要使用大写的“F”,是因为小写的“f”很容易辨别。
double
double 是双精度浮点数(双精度浮点数的有效数字大约为 15 到 17 位),占 64 位(8 字节),也遵循 IEEE 754 标准,取值范围大约 ±4.9E-324 到 ±1.7976931348623157E308。double 同样不适合用于精确的数值,比如说金额。
double myDouble = 3.141592653589793;在进行金融计算或需要精确小数计算的场景中,可以使用 BigDecimal 类来避免浮点数舍入误差。BigDecimal 可以表示一个任意大小且精度完全准确的浮点数。
在实际开发中,如果不是特别大的金额(精确到 0.01 元,也就是一分钱),一般建议乘以 100 转成整型进行处理。
char
char 用于表示 Unicode 字符,占 16 位(2 字节)的存储空间,取值范围为 0 到 65,535。
代码示例:
char letterA = 'A'; // 用英文的单引号包裹住。注意,字符字面量应该用单引号(‘’)包围,而不是双引号(“”),因为双引号表示字符串字面量。
int 和 char 类型互转
int 和 char 之间比较特殊,可以互转。
- 可以通过强制类型转换将整型 int 转换为字符 char。
int value_int = 65;char value_char = (char) value_int;System.out.println(value_char);输出 A(其 ASCII 值可以通过整数 65 来表示)。
- 可以使用 Character.forDigit() 方法将整型 int 转换为字符 char,参数 radix 为基数,十进制为 10,十六进制为 16。
int radix = 10;int value_int = 6;char value_char = Character.forDigit(value_int , radix);System.out.println(value_char );- 可以使用 int 的包装器类型 Integer 的 toString() 方法+String 的 charAt() 方法转成 char。
int value_int = 1;char value_char = Integer.toString(value_int).charAt(0);System.out.println(value_char );- char 转 int
如果只是简单的 char 转 int,因为发生了自动类型转换,直接赋值就可以了。
int a = 'a';不过,如果字符本身就是数字,这种方法就行不通了。
int a = '1';这样的话,a 的值是 49,而不是 1。因为字符 ‘1’ 的 ASCII 码是 49。
那么,怎么才能把字符 ‘1’ 转成数字 1 呢?
可以使用 Character.getNumericValue() 方法。
int a = Character.getNumericValue('1');这样的话,a 的值就是 1 了。
除此之外,还可以使用 Character.digit() 方法。
int a = Character.digit('1', 10);这样的话,a 的值也是 1。
那还有一种更直观的方法,就是 - ‘0’ 方法。
int a = '1' - '0';这样的话,a 的值也是 1。这是因为在 ASCII 编码和 Unicode 编码(Java 使用 Unicode 编码)中,数字字符 ‘0’ 到 ‘9’ 是连续排列的,并且它们的编码值是顺序递增的。
字符 ‘0’ 的编码值是 48,字符 ‘1’ 的编码值是 49,依此类推,字符 ‘9’ 的编码值是 57。
当从一个字符的编码值中减去字符 ‘0’ 的编码值(即 48),结果就是该字符所表示的数字值。例如,对于字符 ‘5’,其编码值是 53。计算 53 - 48 得到 5,这就是字符 ‘5’ 所表示的数字值。
包装器类型
包装器类型(Wrapper Types)是 Java 中的一种特殊类型,用于将基本数据类型(如 int、float、char 等)转换为对应的对象类型。
Java 提供了以下包装器类型,与基本数据类型一一对应:
- Byte(对应 byte)
- Short(对应 short)
- Integer(对应 int)
- Long(对应 long)
- Float(对应 float)
- Double(对应 double)
- Character(对应 char)
- Boolean(对应 boolean)
包装器类型允许我们使用基本数据类型提供的各种实用方法,并兼容需要对象类型的场景。例如,我们可以使用 Integer 类的 parseInt 方法将字符串转换为整数,或使用 Character 类的 isDigit 方法检查字符是否为数字,还有前面提到的 Character.forDigit() 方法。
下面是一个简单的示例,演示了如何使用包装器类型:
// 使用 Integer 包装器类型Integer integerValue = new Integer(42);System.out.println("整数值: " + integerValue);
// 将字符串转换为整数String numberString = "123";int parsedNumber = Integer.parseInt(numberString);System.out.println("整数值: " + parsedNumber);
// 使用 Character 包装器类型Character charValue = new Character('A');System.out.println("字符: " + charValue);
// 检查字符是否为数字char testChar = '9';if (Character.isDigit(testChar)) {System.out.println("字符是个数字.");}引用数据类型
基本数据类型在作为成员变量和静态变量的时候有默认值,引用数据类型也有的。
String 是最典型的引用数据类型,所以我们就拿 String 类举例,看下面这段代码:
public class LocalRef { private String a; static String b;
public static void main(String[] args) { LocalRef lv = new LocalRef(); System.out.println(lv.a); System.out.println(b); }}输出结果如下所示:
nullnullnull 在 Java 中是一个很神奇的存在,在你以后的程序生涯中,见它的次数不会少,尤其是伴随着令人烦恼的“空指针异常”,也就是所谓的 NullPointerException。
也就是说,引用数据类型的默认值为 null,包括数组和接口。
那你是不是很好奇,为什么数组和接口也是引用数据类型啊?
先来看数组:
int [] arrays = {1,2,3};System.out.println(arrays);arrays 是一个 int 类型的数组,对吧?打印结果如下所示:
[I@2d209079[I 表示数组是 int 类型的,@ 后面是十六进制的 hashCode——这样的打印结果太“人性化”了,一般人表示看不懂!为什么会这样显示呢?查看一下 java.lang.Object 类的 toString() 方法就明白了。
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hasCode())}数组虽然没有显式定义成一个类,但它的确是一个对象,继承了祖先类 Object 的所有方法。
来看一下基本数据类型和引用数据类型之间最大的差别。
基本数据类型:
- 1、变量名指向具体的数值。
- 2、基本数据类型存储在栈上。
引用数据类型:
- 1、变量名指向的是存储对象的内存地址,在栈上。
- 2、内存地址指向的对象存储在堆上。
基本数据类型的转换
数据类型转换的目的是确保在表达式求值时,不同类型的数据能够相互兼容。
自动类型转换
自动类型转换(自动类型提升)是 Java 编译器在不需要显式转换的情况下,将一种基本数据类型自动转换为另一种基本数据类型的过程。这种转换通常发生在表达式求值期间,当不同类型的数据需要相互兼容时。自动类型转换遵循以下规则:
- 如果任一操作数是 double 类型,其他操作数将被转换为 double 类型。
- 否则,如果任一操作数是 float 类型,其他操作数将被转换为 float 类型。
- 否则,如果任一操作数是 long 类型,其他操作数将被转换为 long 类型。
- 否则,所有操作数将被转换为 int 类型。
需要注意的是,自动类型转换只发生在兼容类型之间。例如,从较小的数据类型(如 int)到较大的数据类型(如 long 或 double)的转换是安全的,因为较大的数据类型可以容纳较小数据类型的所有可能值。
byte -> short -> int -> long -> float -> doublechar -> int -> long -> float -> double下面是一个简单的示例,演示了自动类型转换:
int intValue = 5;double doubleValue = 2.5;
// 自动类型转换:intValue 被转换为 double 类型double result = intValue * doubleValue;System.out.println("结果: " + result); // 输出:结果: 12.5在这个示例中,我们有一个 int 类型的变量 intValue 和一个 double 类型的变量 doubleValue。当我们将它们相乘时,根据自动类型转换的规则,intValue 将被转换为 double 类型,以便将两个 double 类型的操作数相乘。最终结果将是一个 double 类型的值:12.5。
再来举个例子,顾客到超市购物,购买牙膏 2 盒,面巾纸 4 盒。其中牙膏的价格是 10.9 元,面巾纸的价格是 5.8 元,求商品总价格。实现代码如下:
float price1 = 10.9f; // 定义牙膏的价格,单精度浮点型floatdouble price2 = 5.8; // 定义面巾纸的价格,双精度浮点型doubleint num1 = 2; // 定义牙膏的数量,整型 intint num2 = 4; // 定义面巾纸的数量double res = price1 * num1 + price2 * num2; // 计算总价System.out.println("一共付给收银员" + res + "元"); // 输出总价上述代码中首先定义了一个 float 类型的变量存储牙膏的价格,然后定义了一个 double 类型的变量存储面巾纸的价格,再定义两个 int 类型的变量存储物品的数量,最后进行了乘运算以及和运算之后,将结果储存在一个 double 类型的变量中进行输出。 从执行结果看出,float、int 和 double 三种数据类型参与运算,最后输出的结果为 double 类型的数据。这种转换一般称为“表达式中类型的自动提升”。
自动类型提升有好处,但它也会引起令人疑惑的编译错误。例如,下面看起来正确的程序却会引起问题:
byte b = 50;
b = b * 2; // Type mismatch: cannot convert from int to byte如上所示,第二行会报“类型不匹配:无法从int转换为byte”错误。
该程序试图将一个完全合法的 byte 型的值 50*2 存储给一个 byte 型的变量。但是当表达式求值的时候,操作数被自动的提升为 int 型,计算结果也被提升为 int 型。这样表达式的结果现在是 int 型,不强制转换它就不能被赋为 byte 型。
所以应该使用一个显示的强制类型转换,例如:
byte b = 50;b = (byte)(b*2);这样就能产生正确的值 100。
但如果是下面这样的代码,就不会报错:
byte b = 50;b *= 2;这是因为 b = 2 等价于 b = (byte)(b2),编译器会自动进行强制类型转换。
强制类型转换
强制类型转换是 Java 中将一种数据类型显式转换为另一种数据类型的过程。与自动类型转换不同,强制类型转换需要程序员显式地指定要执行的转换。强制类型转换在以下情况中可能需要:
- 将较大的数据类型转换为较小的数据类型。
- 将浮点数转换为整数。
- 将字符类型转换为数值类型。
需要注意的是,强制类型转换可能会导致数据丢失或精度降低,因为目标类型可能无法容纳原始类型的所有可能值。因此,在进行强制类型转换时,需要确保转换后的值仍然在目标类型的范围内。
double -> float -> long -> int -> char -> short -> byte以下是一个简单的示例,演示了强制类型转换:
double doubleValue = 42.8;
// 强制类型转换:将 double 类型转换为 int 类型int intValue = (int) doubleValue;System.out.println("整数值: " + intValue); // 输出:整数值: 42在这个示例中,我们有一个 double 类型的变量 doubleValue。我们希望将其转换为 int 类型的变量 intValue。为此,我们使用强制类型转换语法,即在要转换的变量之前加上目标类型的括号(如 (int))。
需要注意的是,将 doubleValue 转换为 int 类型时,小数部分将被截断。因此,输出结果将是:Integer value: 42。在这种情况下,精度丢失是可以接受的,但在其他情况下,我们可能需要更加小心地处理类型转换以避免数据丢失。
顾客到超市购物,购买牙膏 2 盒,面巾纸 4 盒。其中牙膏的价格是 10.9 元,面巾纸的价格是 5.8 元,求商品总价格,在计算总价时采用 int 类型的数据进行存储。实现代码如下:
float price1 = 10.9f;double price2 = 5.8;int num1 = 2;int num2 = 4;int res2 = (int) (price1 * num1 + price2 * num2);System.out.println("一共付给收银员" + res2 + "元");在上述实例中,有 double 类型、float 类型和 int 类型的数据参与运算,其运算结果默认为 double 类型,题目要求的结果为 int 类型,因为 int 类型的取值范围要小于 double 类型的取值范围,所以需要进行强制类型转换。
基本数据类型缓存池
在 Java 中,针对一些基本数据类型(如 Integer、Long、Boolean 等),Java 会在程序启动时创建一些常用的对象并缓存在内存中,以提高程序的性能和节省内存开销。这些常用对象被缓存在一个固定的范围内,超出这个范围的值会被重新创建新的对象。
使用数据类型缓存池可以有效提高程序的性能和节省内存开销,但需要注意的是,在特定的业务场景下,缓存池可能会带来一些问题,例如缓存池中的对象被不同的线程同时修改,导致数据错误等问题。因此,在实际开发中,需要根据具体的业务需求来决定是否使用数据类型缓存池。
来看下面这段代码:
Integer x = new Integer(18);Integer y = new Integer(18);System.out.println(x == y);
Integer z = Integer.valueOf(18);Integer k = Integer.valueOf(18);System.out.println(z == k);
Integer m = Integer.valueOf(300);Integer p = Integer.valueOf(300);System.out.println(m == p);来看一下输出结果吧:
falsetruefalse第一个 false,因为 new 出来的是不同的对象,地址不同。 第二个和第三个我认为都应该是 true 啊,为什么第三个会输出 false 呢?
其实原因也很简单。
基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。
- Byte:-128~127,也就是所有的 byte 值
- Short:-128~127
- Long:-128~127
- Character:\u0000 - \u007F
- Boolean:true 和 false
拿 Integer 来举例子,Integer 类内部中内置了 256 个 Integer 类型的缓存数据,当使用的数据范围在 -128~127 之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。
18 在 -128~127 之间,300 不在。
来看一下 valueOf 方法的源码吧。
public static Integer valueOf(int i) { if (i >=IntegerCache.low && i <=IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}来看一下 IntegerCache 这个静态内部类的源码吧
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[];
static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h;
cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7) assert Integer.IntegerCache.high >= 127; }
private IntegerCache() {}}详细解释下:当我们通过 Integer.valueOf() 方法获取整数对象时,会先检查该整数是否在 IntegerCache 中,如果在,则返回缓存中的对象,否则创建一个新的对象并缓存起来。
需要注意的是,如果使用 new Integer() 创建对象,即使值在 -128 到 127 范围内,也不会被缓存,每次都会创建新的对象。因此,推荐使用 Integer.valueOf() 方法获取整数对象。