大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 004 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。

上一篇文章中,我们讨论了 Java 程序的基本结构,包括变量、方法、运算符与注释。本篇文章我们将深入了解 Java 的数据类型,包括 Java 语言的 8 种基本数据类型、字符串与数组。这些数据类型是 Java 程序设计的基础,也是编写高效、可靠代码的关键。

最后在前言的末尾我补充一下,如果这篇文章,对大家有所帮助或收获一定的乐趣和想法,那么非常欢迎大家能够,点赞、评论、收藏、订阅。这些也将是我持续更新的最大动力。



1、数据类型

Java 是一种强类型语言,这意味着每个变量都必须声明其数据类型。Java 提供了 8 种基本数据类型,包括 4 种整型(byte、short、int、long)、2 种浮点型(float、double)、1 种字符类型(char,用于表示 Unicode 字符)和 1 种布尔类型(boolean,用于表示真假值)。

1.1、整型

Java 提供了四种整型数据类型,分别是 int、short、long 和 byte,它们的存储需求、默认值和取值范围如下:

image-20240121170244917

在大多数情况下,我们会使用 int 类型。但如果需要表示非常大的数(如地球人口数量),则需要使用 long 类型。byte 和 short 类型主要用于特定的场景,如底层文件处理或大数组。

Java 中的整型范围与运行 Java 代码的机器无关,这使得 Java 程序在不同平台上的移植更为容易。

Java 还支持多进制的表示,如十六进制(0x 或 0X 开头)、八进制(0 开头)和二进制(0b 或 0B 开头)。从 Java 7 开始,还可以在数字中加入下划线以提高可读性,如 1_000_000 表示一百万。

1.2、浮点型

Java 提供了两种浮点类型,分别是 float 和 double,它们的存储需求、默认值和取值范围如下:

image-20240121170354843

float 类型的数值有一个后缀 F 或 f,如 3.14F。没有 F 后缀的浮点数值(如 3.14)默认为 double 类型。也可以在浮点数值后面添加后缀 D 或 d,如 3.14D。

double 类型的精度是 float 类型的两倍,因此在大多数情况下,我们会使用 double 类型。只有在特定的场景下,如需要单精度数据的库或需要存储大量数据,我们才会使用 float 类型。

所有的浮点数值计算都遵循 IEEE 754 规范。在这个规范中,定义了一些特殊的浮点数值,如正无穷大(Infinity)、负无穷大(-Infinity)和非数(NaN)。

需要注意的是,由于浮点数值采用二进制系统表示,因此在进行浮点数值计算时可能会出现舍入误差。如果需要进行精确的数值计算,应该使用 BigDecimal 类。

1.3、字符型

char 类型原本用于表示单个字符。不过,现在情况已经有所变化。如今,有些 Unicode 字符可以用一个 char 值描述,另外一些 Unicode 字符则需要两个 char 值。

char 类型的字面量值需要用单引号括起来,如 'A' 是编码值为 65 所对应的字符常量。它与 "A" 不同,"A" 是包含一个字符 A 的字符串。char 类型的值可以表示为十六进制值,其范围从 \u0000\uffff

除了 \u 转义序列,还有一些用于表示特殊字符的转义序列,如 \b\t\n\r\"\'\\

image-20240121170517625

需要注意的是,在 Java 中,Unicode 转义序列会在解析代码之前得到处理。例如,"\u0022+\u0022" 并不是一个由引号 (\u0022) 包围加号构成的字符串,而是一个空字符串:

public class CharDemo {
    public static void main(String[] args) {
				// 初看会输出 "+",实际输出 "" 空字符串
        System.out.println("\u0022+\u0022"); 
        // 因为 Unicode 转义序列会在解析代码之前得到处理
        // 所以上面这行代码实际在解析后相当于
        System.out.println(""+""); 
    }
}
1.4、布尔型

Java 中的 boolean 类型有两个值:false 和 true,主要用于逻辑判断。

在 Java 中,整型和布尔型是不能直接进行转换的。这一点与一些其他编程语言(如 C 和 C++)是不同的,在那些语言中,整型值 0 表示 false,非 0 值表示 true。但在 Java 中,必须使用 true 或 false 来表示布尔值。


2、数值类型之间的转换

2.1、数值类型之间的转换

我们经常需要将一种数值转换为另一种数值类型,下图给出了数值类型之间的合法转换。

image-20240122151309685

在图中: 有 6 个实心箭头,表示无信息丢失的转换;有 3 个虚箭头,表示可能有精度损失的转换。例如,123_456_789 是一个大整数,它所包含的位数比 float 类型所能够表达的位数多。当将这个整型数值转换为 float 类型时,将会得到同样大小的结果,但却失去了一定的精度。

当使用两个数值进行二元操作时(例如 n + f, n 是整数,f 是浮点数),先要将两个操作数转换为同一种类型,然后再进行计算。

2.2、强制类型转换

在必要的时候,int 类型的值将会自动地转换为 double 类型。但另一方面,有时也需要将 double 转换成 int。在 Java 中,允许进行这种数值之间的类型转换。当然,有可能会丢失一些信息。在这种情况下,需要通过强制类型转换(cast)实现这个操作。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。

例如:

double d = 100.123;
// 强制类型转换,double 强制转换为 int,小数部分会被丢弃
int i = (int) d; 

如果想对浮点数进行舍入运算,以便得到最接近的整数(在很多情况下,这种操作更有用),那就需要使用 Math.round 方法:

double d = 100.123;
// 强制类型转换,double 强制转换为 int,小数部分会被丢弃
int i = (int) Math.round(d); 

Ps: 如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。


3、大数值

如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math 包中的两个很有用的类: BigInteger 和 BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。

使用静态的 valueOf 方法可以将普通的数值转换为大数值:

BigInteger a = BigInteger.valueOf(100);

遗憾的是,大数运算不能使用人们熟悉的算术运算符处理大数。而需要使用大数类中的 addmultiply 方法:

public class Main {
    public static void main(String[] args) {
        BigInteger a = BigInteger.valueOf(100);
      	// b = a + 200
        BigInteger b = a.add(BigInteger.valueOf(200));
      	// c = b * (b + 300)
        BigInteger c = b.multiply(b.add(BigInteger.valueOf(300)));
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

4、字符串

从概念上讲,Java 字符串实际上是 Unicode 字符序列。例如,字符串 “Java\u2122” 由五个 Unicode 字符 ‘J’、‘a’、‘v’、‘a’ 和 ‘™’ 组成。Java 并没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类,叫做 String。每个用双引号括起来的字符串都是 String 类的一个实例。

4.1、String 常见方法

Java 中的 String 类包含了 50 多个方法。令人惊讶的是绝大多数都很有用,可以设想使用的频繁非常高。下面的 API 注释汇总了一部分最常用的方法:

|                  方法名                  |                      方法说明							  	      
| --------------------------------------- |----------------------------------------------|
|                length()                 |                  返回字符串的长度             	
|            charAt(int index)            |               返回指定索引位置的字符               
|            equals(String s)             |          比较此字符串与指定的对象是否相等           
|       equalsIgnoreCase(String s)        |   将此 String 与另一个 String 比较,不考虑大小写    
|            indexOf(String s)            |     返回字串第一次出现的位置,没出现则返回 -1     
|          lastIndexOf(String s)          |     返回字串最后一次出现的位置,没出现返回 -1     
|        starstWith(String prefix)        |          测试此字符串是否以指定的前缀开始          
|         endsWith(String suffix)         |          测试此字符串是否以指定的后缀结束           
|              toLowerCase()              |                返回字符串的小写形式                 
|              toUpperCase()              |                返回字符串的大写形式                 
| substring(int startindex,int endindex)  |   返回一个新的字符串,它是此字符串的一个子字符串    
|           contains(String s)            | 当且仅当此字符串包含指定的 char 值序列时,返回 true 
| replaceAll(String oldSrt,String newSrt) |          替换原有字符串中的字串为目标字串           
|            concat(String s)             |          将指定字符串连接到此字符串的结尾           
|           split(String split)           |        根据给定正则表达式的匹配拆分此字符串         
|                 tirm()                  |      返回字符串的副本,忽略前导空白和尾部空白       
|               getBytes()                |                返回字符串的字节数组               
|                isEmpty()                |                 判断字符串是否为空         
|              tocharArray()              |          将此字符串转换为一个新的字符数组     
|               hashCode()                |                 返回字符串的哈希值       			  |
4.2、String 不可变

在 Java 中,String 是不可变的,String 类的源码:

public final class String{
   //用字符数组来存数值
   private final char value[];
 
}

虽然 String 是 final 类型的类,且 value 也是 final 类型的数组,但这不是 String 不可变的根本原因,String 不可变是因为 value 是 private,且并没有提供对外的 get 和 set。

由于 String 每次修改都会创建新的对象,这在频繁修改时会导致效率低下和内存占用大。而 StringBufferStringBuilder 是可变的,可以进行高效的字符串修改。

  • StringBufferStringBuilder 都是 AbstractStringBuilder 的子类,使用数组存储字符串内容,可以进行字符串内容的修改,且不会因为修改而创建新的对象;

  • StringBuffer 的方法使用了 synchronized 关键词,因此它是线程安全的,但效率略低于 StringBuilder

  • StringBuilder 的方法没有使用 synchronized 关键词,因此它在单线程环境下效率更高,但在多线程环境下不能保证线程安全。

因此,在需要频繁修改字符串的情况下,应优先考虑使用 StringBufferStringBuilder。在多线程环境下,应优先使用 StringBuffer 以保证线程安全。在单线程环境下,可以使用 StringBuilder 以获得更高的效率。


5、数组

在 Java 中,数组是一种数据结构,用来存储同一类型值的集合。

5.1、数组声明与初始化

Java 数组需要在正确的声明和初始化后才能正常使用,其声明的格式为: 数组类型[] 数组变量名称。而数组初始化是指,在定义数组时只指定数组的长度,由系统自动为元素赋初值的方式称作动态初始化。

在 Java 中,数组的初始化有以下几种方式:

// 动态初始化:只指定数组的长度,由系统自动为元素赋初值
// 在这个例子中,arr 是一个长度为 4 的整型数组,数组的元素会被自动初始化为 0
int[] arr = new int[4];
// 静态初始化:在定义数组时就指定数组的元素值
int[] arr = new int[]{1, 2, 3, 4};
// 或者
int[] arr = {1, 2, 3, 4}
5.2、数组元素访问与赋值

通过一个整型下标可以访问数组中的每一个值,其访问的格式为: 数组变量名称[下标整数]。例如,如果 a 是一个整型数组,我们想要访问 a 的下标为 i 的元素,那就是: a[i]

同样的,当我们想要为数组下某元素进行赋值的时候,也是通过指定整数下标完成的,其格式为: 数组变量名称[下标整数] = 值

此外,Java 数组还提供了一个 length 字段,用以记录数组中的元素个数:

import java.util.Arrays;

public class Solution {
    public static strictfp void main(String[] args) {
        int[] nums1 = {1, 2, 5, 6};
        // 输出结果为 4
        System.out.println(nums1.length);
    }
}
5.3、数组的常用方法

在 Java 中,数组作为 Java 中的一种引用类型,是有父类的,java.lang.Object 就是数组的父类,这是根据 Java 语言规范中的定义而来的(根据规范,所有数组类型都直接继承自 Object 类这是因为 Object 类是 Java 中所有类的根类,所以所有的类,包括数组类型,都隐式地继承自 Object 类)。

数组的继承关系并不意味着数组类型具有与其他类相同的特性或方法。数组是一种特殊的数据结构,其操作和行为在很大程度上由 Java 语言本身定义。虽然数组继承了 Object 类的一些方法,但它们也有一些特定于数组的行为和限制。

我们可以使用 java.util.Arrays 类对数组进行一些基本的操作,Arrays 类中的方法都是静态方法,可以直接使用类名进行调用。

5.3.1、Arrays.aslist()方法

Arrays.asList() 是一个 Java 的静态方法,它可以把一个数组或者多个参数转换成一个 java.util.List 集合。这个方法可以作为数组和集合之间的桥梁,方便我们使用集合的一些方法和特性。

Arrays.asList() 的语法格式如下(以 int 类型为例):

    /**
     * 将数组转换为 List 集合
     *
     * @param srcArray 原数组
     * @return 指定类型的 List 集合
     */
    public static <T> List<T> asList(T... srcArray) {...}

这个方法接受一个泛型参数 T,表示数组或者参数的类型。T 必须是一个引用类型,不能是一个基本类型(例如 int、double、char 等)。

Arrays.asList() 返回的 List 是一个 Arrays 类的内部类,它持有一个对原始数组的引用。这意味着对 List 的修改会反映到数组上,反之亦然。但是,这个 List 的大小是固定的,不能进行增加或者删除的操作,否则会抛出 java.lang.UnsupportedOperationException 异常。

Arrays.asList() 返回的 List 是可序列化的,并且实现了 java.util.RandomAccess 接口,表示它支持随机访问。

import java.util.Arrays;
import java.util.List;

public class Solution {
    public static void main(String[] args) {
        Integer[] nums1 = {1, 2, 3, 4};
        List<Integer> list = Arrays.asList(nums1);
        // 输出结果为 "[1, 2, 3, 4]"
        System.out.println(list);

        list.set(0, 0);
        // 输出结果为 "[0, 2, 3, 4]"
        System.out.println(Arrays.toString(nums1));

        nums1[1] = 0;
        // 输出结果为 "[0, 0, 3, 4]"
        System.out.println(list);

        // 抛出 java.lang.UnsupportedOperationException 异常
        list.add(5);
    }
}
5.3.2、Arrays.binarySearch()方法

Arrays.binarySearch() 是一个 Java 的静态方法,它可以在数组中通过二分查找的方式,找到指定元素的在数组中的下标,如果没找到就返回 -1。

Arrays.binarySearch() 的格式定义如下(以 int 类型为例):

    /**
     * 查找元素
     * @param srcArray 源数组
     * @param key      指定查询元素
     * @return 查询元素所在的下标,没有则返回 -1
     */
    public static int binarySearch(int[] srcArray, int key){...}
    
     /**
     * 在指定范围内查找元素
     * @param srcArray  源数组
     * @param fromIndex 查找起始索引位置
     * @param toIndex   查找终止索引位置(不包括索引值本身)
     * @param key       查询元素
     * @return 查询元素所在的下标,没有则返回 -1
     */
    public static int binarySearch(int[] srcArray, int fromIndex, int toIndex, int key) {...}
5.3.3、Arrays.copyOf()方法

Arrays.copyOf() 是一个 Java 的静态方法,它将返回一个新的数组对象。

Arrays.copyOf() 的格式定义如下(以 int 类型为例):

    /**
     * 数组复制
     * @param srcArray  源数组
     * @param newLength 新数组长度
     * @return 新数组
     */
    public static <T> T[] copyOf(T[] srcArray, int newLength) {...}

使用这种方法复制数组时,默认从原数组的第一个元素(索引值为 0)开始复制,目标数组的长度将为 newLength。如果 newLength 大于 srcArray.length,则目标数组中采用默认值填充;如果 newLength 小于 srcArray.length,则复制到第 newLength 个元素(索引值为 newLength-1)即止。

import java.util.Arrays;

public class Solution {
    public static void main(String[] args) {
        int[] nums1 = {1, 2, 3, 4};

        int[] arr2 = Arrays.copyOf(nums1, 3);
        // 打印结果为: "[1, 2, 3]"
        System.out.println(Arrays.toString(arr2));
        
        int[] arr3 = Arrays.copyOf(nums1, 5);
        // 打印结果为: "[1, 2, 3, 4, 0]"
        System.out.println(Arrays.toString(arr3));
    }
}
5.3.4、Arrays.copyOfRange()方法

Arrays.copyOfRange() 是一个 Java 的静态方法,它是 Arrays 类中除的 Arrays.copyOf() 方法外的另一种复制数组的方法。

Arrays.copyOfRange() 的格式定义如下(以 int 类型为例):

    /**
     * 数组复制
     * @param srcArray   源数组
     * @param startIndex 开始复制的起始索引
     * @param endIndex   完成复制的结束索引(不包括索引值本身)
     * @return 新数组
     */
    public static <T> T[] copyOfRange(T[] srcArray, int startIndex, int endIndex) {...}

其中:startIndex 必须在 0 到 srcArray.length 之间;endIndex 必须大于等于 startIndex,可以大于 srcArray.length,如果大于 srcArray.length,则目标数组中使用默认值填充。

5.3.5、Arrays.equals()方法

Arrays.equals() 是一个 Java 的静态方法,用以比较数组内容是否相等,如果相等返回 true,否则返回 false。

Arrays.equals() 的格式定义如下(以 int 类型为例):

    /**
     * 比较两个数组是否相等
     * @param a1 数组1
     * @param a2 数组2
     * @return 是否相等 true/false
     */
    public static boolean equals(int[] a1, int[] a2)

Java 中的 Arrays.equals 方法设计用于比较一维数组。对于二维数组,它会将整个数组对象进行比较,而不会逐个比较数组中的元素。如果需要比较二维数组的内容,可能需要使用其他方法,比如使用嵌套循环逐个比较数组元素。

5.3.6、Arrays.fill()方法

Arrays.fill() 是一个 Java 的静态方法,它允许我们填充一个数组的所有元素,将它们设置为指定的值。

Arrays.fill() 的格式定义如下(以 int 类型为例):

    /**
     * 填充数组中的全部元素
     * @param srcArray 源数组
     * @param val      填充的元素值
     */
    public static void fill(int[] srcArray, int val) {...}

    /**
     * 填充数组中指定范围的全部元素
     * @param srcArray  源数组
     * @param fromIndex 填充范围的起始索引
     * @param toIndex   填充范围的结束索引(不包括索引值本身)
     * @param val       填充的元素值
     */
    public static void fill(int[] srcArray, int fromIndex, int toIndex, int val) {...}
5.3.7、Arrays.sort()方法

Arrays.sort() 是一个 Java 的静态方法,它的作用是给数组排序,默认是升序的。

Arrays.sort() 的格式定义如下(以 int 类型为例):

    /**
     * 数组中的全部元素排序(升序)
     * @param srcArray 源数组
     */
    public static void sort(int[] srcArray) {...}

    /**
     * 数组中指定范围的元素排序(升序)
     * @param srcArray  源数组
     * @param fromIndex 范围排序的起始索引
     * @param toIndex   范围排序的结束索引(不包括索引值本身)
     */
    public static void sort(int[] srcArray, int fromIndex, int toIndex){...}
5.3.8、Arrays.toString()方法

Arrays.toString() 是一个 Java 的静态方法,用于将数组转换为字符串,可以快速的输出数组的内容。

Arrays.toString() 的格式定义如下(以 int 类型为例):

    /**
     * 将数组转换为字符串形式
     * @param srcArray 源数组
     * @return 字符串形式的数组
     */
    public static String toString(int[] srcArray){...}
5.4、多维数组

在 Java 中,多维数组是使用多个下标来访问数组元素的,它适用于表示表格或更复杂的排列形式。

以下是二维数组的定义格式:

  1. 指定行和列的长度:
// 在这个例子中,arr 是一个 3x4 的二维数组,即二维数组的行数为 3,每一行的列数为 4。
int[][] arr = new int[3][4];
  1. 只指定行的长度:
// 在这个例子中,arr 是一个二维数组,二维数组的行数为 3,每一行的列数不确定,需要单独为每一行分配内存。
int[][] arr = new int[3][];
  1. 直接指定所有元素的值:
// 在这个例子中,arr 是一个二维数组,二维数组中定义了三个元素,这三个元素都是数组,分别为 {1, 2}、{3, 4, 5, 6}、{7, 8, 9}。
int[][] arr = {{1, 2}, {3, 4, 5, 6}, {7, 8, 9}};

在内存中,二维数组是按照行来存储的。例如,int[][] arr = new int[3][2];,外层数组在内存中开辟了连续的 3 个大的内存空间,每一个大内存空间里又开辟了连续的两个小的内存空间。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部