Java 数据类型

Java 提供了丰富的基本数据类型。

一、数据类型分类

1. Java 是强类型的

Java 是强类型语言,强类型包含两方面含义:

  • 所有的变量都必须先声明后使用
  • 变量只能接受类型匹配的值

2. Java 中的类型

在 Java 中,定义变量至少需要指定变量类型和变量名。其中,变量类型分为基本类型引用类型两大部分。

3. 基本类型

(1) 数值类型

  • 整数:byte、short、int、long
  • 浮点:float、double
  • 字符:char

(2) 布尔类型

  • boolean

4. 引用类型

引用类型包括类、接口、数组和特殊的 null 类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。

实际上,引用类型变量就是一个指针,只是 Java 中不再有引用指针这个说法。

二、基本数据类型

1. 整型

  • byte: -27 ~ 27 - 1
  • short: -215 ~ 215 - 1
  • int: -231 ~ 231 - 1
  • long: -263 ~ 263 - 1

2. 字符型

  • 字符应该用单引号括起来

  • char 类型的值可以作为整型值使用,其数值范围为 0 ~ 65535

  • Java 中,用两个字节代表一个字符

    C 语言中,用一个字节代表一个字符

3. 浮点型

  • 有 double 和 float 两种类型,模式使用 double 类型

  • 浮点数有两种表示方法:

    • 十进制数形式

      例如: 5.621

    • 科学计数法形式:

      例如:5.621e1

  • Java 提供了三种特殊浮点数值,分别表示正无穷大、负无穷大、非数(不存在的数字,例如负数开方)

    • 正无穷大:通过 Double/Float 中的 POSITIVE_INFINITY 表示
    • 负无穷大:通过 Double/Float 中的 NEGATIVE_INFINITY 表示
    • 非数:通过 Double/Float 中的 NaN 表示

4. 下划线分隔数值

当程序中使用的数值位数特别多时,不方便程序员辨认。程序员可以在数值中使用下划线,无论是整形还是浮点,从而更直观地辨认数值。

例如:

1
2
double pi = 3.14_15_92_65_36;
long bignum = 9_876_543_321;

5. 布尔型

boolean 类型,用于表示逻辑上的 “真” 或 “假”。在 Java 中,boolean 的数值只能是 true 或 false,不能用 1 或 0 替代,也不能由其它数值转换。

6. var

  • var 相当于一个动态类型,使用 var 定义的局部变量的类型由编译器自动推断,即定义变量时给了什么类型的初始值,就给变量赋予什么类型

  • 使用 var 定义局部变量时,必须在定义局部变量的同时指定初始值

  • 使用 var 定义的变量不仅可以是基本类型,也可以是字符串等引用类型

  • var 的使用会降低代码的可读性

三、包装类

具体请看:

Java 基础类库 - 基本类型包装类

四、类型转换

1. 自动类型转换

(1) 数值的自动类型转换

当把一个数值范围小的值赋给另一个数值范围更大的变量时,系统可以进行自动类型转换,否则就需要强制类型转换。

(2) 字符串的自动类型转换

当把任何基本类型的值和字符串值进行连接运算时,基本类型会自动转化为字符串,并与字符串进行连接。因此,如果希望把基本类型的值转换为对应的字符串,可以把基本类型的值和一个空字符串进行连接。

代码:

1
2
3
4
String str = 3.14 + "";
System.out.println(str);
System.out.println("Hello" + 1 + 2);
System.out.println(1 + 2 + "Hello");

运行结果:

1
2
3
3.14
hello12
3hello

解释:

3.14 + "" ,此代码发生了一次自动类型转换,3.14 从 double 类型转换为字符串类型,并与空字符串进行连接;

"Hello" + 1 + 2 ,此代码发生了两次自动类型转换, “hello” 首先与转换为字符串的 “1” 相连,“hello1” 再与转换为字符串的 “2” 相连;

1 + 2 + "Hello" ,此代码发生了一次自动类型转换,1 和 2 首先做数值运算,结果为 3 ,3 转换为字符串后再与 “hello” 做连接。

2. 强制类型转换

(1) 一般格式

1
(目标类型)值

可以强制地转换数值类型,但这个操作可能会引起溢出,从而造成数据丢失。

(2) 基本类型和引用类型

  • 基本类型的转换只能在数值类型之间进行

  • 引用类型的转换只能在具有继承关系的类之间进行转换,并且

    • 子类可以转为父类

    • 如果希望父类转子类,则父类应该是子类实例

      (即编译时类型为父类类型,运行时类型为子类类型)

(3) instanceof

适用于引用类型,用于判断对象是否属于类,并返回布尔值,其格式为:

1
对象 instanceof

如果需要转换引用类型,为保证程序的健壮性,应该先使用 instanceof 进行判断,具体做法为:

1
2
3
if (对象 instanceof 类) {
···(类)对象···
}

3. 表达式类型的自动提升

当一个算数表达式中包含多个基本类型的值时,整个算数表达式的数据类型将会发生自动提升。具体规则如下:

  • 所有的 byte 、short 、char 都将会被提升到 int
  • 整个算术表达式的数据类型将会自动提升到与表达式中最高等级操作数相同的类型

五、字面量

字面量,值在程序中通过源代码直接给出的量,有基本类型、字符串类型和 null 类型。

需要注意的是,当程序第一次使用某个字符串直接量时,Java 会使用常量池来缓存该字符串直接量,如果程序后面继续用到该字符串,Java 会直接使用常量池中的字符串直接量。

六、数组

1. 数组的特点

  • 相同类型:Java 数组要求所有的元素都具有相同的数据类型

    因为 Java 的类与类之间有继承关系,因此同一个数组中可以存在不同类的元素,当这些不同类元素应该继承自同一个父类。

  • 固定大小:数组一旦初始化完成,其在内存中的空间便被固定下来

  • 定义后无法被直接使用:数组是一种引用类型的变量,定义后仅仅是一个引用变量(可以视为指针),未指向任何有效内存,无法被直接使用

    并且因为只是定义了一个引用变量,因此在定义时只需给出类型和名字,无需给出数组长度。

    1
    int[] a;

2. 定义数组

Java 支持用两种语法格式定义数组:

1
类型[] 数组名;
1
类型 数组名[];

推荐使用 类型[] 数组名 的格式,具有更好的语意和可读性。

3. 数组初始化

(1) 初始化方法

数组的初始化有两种方法:

  • 给定每个元素的初始值,由系统决定数组长度
  • 指定数组长度,系统会将初始值设置为 0

(2) 给定初始值

初始化时指定每一个数组元素额初始值,由系统决定数组长度。

语法格式
1
数组名 = new 类型[]{元素1, 元素2, ···, 元素n};
简化语法格式
1
类型[] 数组名 = {元素1, 元素2, ···, 元素n};

(3) 指定数组长度

1
数组名 = new 类型[数组大小];

系统会分配一块 数组大小 * 类型大小 的空间,并将初始值设置为 0 。

4. 访问数组

(1) 访问数组元素

1
数组名[数组下标]
  • 数组下标范围为 0 ~ length - 1
  • 当数组下标越界时,并不会在编译时报错,但会在运行时出现异常

(2) 获得数组长度

数组类自带成员变量 length ,可以通过 数组名.length 获得数组大小

(3) 循环访问数组

for
1
2
3
for (int i = 0; i < 数组名.length; i++){
··· 数组名[i] ···
}
foreach
1
2
3
for (类型 局部变量名 : 数组){
··· 局部变量名 ···
}

5. 多维数组

(1) 直接定义二维数组

1
类型[][] 数组名 = new 类型[m][n];

(2) 定义多个一维数组

1
2
3
4
类型[][] 数组名 = new 类型[m][];
for (int i = 0; i < m; i++){
数组名[i] = new 类型[n];
}

七、字符串

1. String

String 类代表字符串,Java 程序中所有字符串文字都是该类的实例。

2. 创建字符串

  • 通过构造方法创建字符串

    • ```java
      String str = new String(“Hello World!”);
      1
      2
      3
      4
      5
      6
      7

      * 每一次创建都将会申请一个内存空间

      * 直接创建字符串对象

      * ```java
      String str = "Hello World!";
    • 只要字符串相同,所有的字符串将会指向同一个字符串对象,并且字符串对象放置在常量池中

3. 访问字符串

(1) 访问字符串元素

1
str.charAt(索引)

(2) 获得字符串长度

1
str.length()

(3) 字符串比较

假设有两个字符串 str1str2

  • 若用 == 作比较,则比较的是指针指向的地址值

  • 如果希望比较字符串的是否相同,应该通过 equals 方法进行比较:

    1
    str1.equals(str2)

4. 不可变性

String 具有不可变性,一旦创建,其内容就无法被修改。

这种设计的好处是:

  • 保证安全性,防止字符串被篡改
  • 与享元模式结合,通过字符串常量池复用字符串对象,节省空间
  • 避免线程安全问题
  • String 被广泛应用中 HashMap 等类中,由于不可变性,哈希值只需要计算一次,因此可以提升性能

5. 字符串常量池

字符串常量池的使用是对享元模式的运用,能够有效减少对象的频繁销毁与创建,也能节省内存空间。

  • String hello = "hello" 将会创建 0 ~ 1 个字符串常量

    首先在常量池中查找,

    • 如果找到,则直接将其引用赋值给 hello
    • 如果没有找到,则在常量池中创建字符串常量,将字符串常量的引用赋值给 hello
  • String hello = new String("hello") 将会创建 0 ~ 1 个字符串常量、1 个字符串对象

    首先在常量池中查找,

    • 如果找到,在堆中创建一个内容等于该字符串常量的对象,将字符串对象的引用赋值给 hello
    • 如果没有找到,则在常量池中创建字符串常量,在堆中创建一个内容等于该字符串常量的字符串对象,将字符串对象的引用赋值给 hello
  • new StringBuilder("aa").append("a").toString() 将创建 1 个字符串对象,不会创建字符串常量

  • "he" + "llo" 将会被编译器优化为 "hello",只创建 0 ~ 1 个字符串常量

  • "he" + new String("llo") 将会被编译器优化为 String s = new String("llo");new StringBuilder().append("he").append(s).toString(),其中,String s = new String("llo") 创建 0 ~ 1 个字符串常量、1 个字符串对象,"he" 创建 0 ~ 1 个字符串常量,stringBuilder.toString() 创建 1 个字符串对象

  • str.intern() 方法可以将字符串对象添加至常量池中,增加 1 个字符串常量

参考