Java SE之数组基础知识详解

简介: Java SE之数组基础知识详解

1.什么是数组?

数组就是用来存储一批同种类型数据内存区域可以理解成容器)。

示例代码如下:

int[] arr= {10, 20, 30, 40, 50};
String[] names= {"张三", "李四", "王五"};

2.数组的初始化

数组初始化定义包括静态初始化数组和动态初始化数组,二者在使用中有一定区别。

2.1静态初始化数组

定义数组的时候直接给数组赋值,即数组定义出来后,里面的元素值是确定的。

格式:

// 静态初始化数组完整格式

数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,...};

示例代码如下:

double[] scores=newdouble[]{68.9,77.3,90.5,85.0};
int[] ages=newint[]{18,20,35,19};


// 静态初始化数组简化形式

数据类型[] 数组名 = {元素1,元素2,元素3,...}

示例代码如下:

intages[] = {18,20,35,19};

 

数组的基本原理:

等号左侧为在内存中创建的一块区域,用存放数组变量,等号右侧为new出来的一个连续对象,其中存放各个元素(静态初始化数组元素值为给定值,动态初始化数组元素值为默认值)。在创建的时候,将此对象地址交由等号左侧的数组变量存储,这样在访问该数组变量时就能获取存放在该数组变量中的对象的地址。

ArrayPrinciple.png

示例代码如下:

String[] names= {"张三", "李四", "王五"};
System.out.println(names); // [Ljava.lang.String;@1540e19d 为new String[]{"张三", "李四", "王五"}的地址

 

注意:数组变量中存储的是数组在内存中的地址,数组是引用类型(数组变量中存放的是地址,不是具体内容,根据此地址去寻找具体内容)。

引用类型存放的是地址,基本数据类型存放的是数据。

2.1.1数组的访问

索引:数组中各个元素的编号(从0开始)。

在进行数组访问时,数组变量中存放的是指向数组对象的地址,再根据索引定位在这个地址中具体是哪个元素

数组的访问: 数组名[索引]

示例代码如下:

rr[2]); // 100

int[] arr= {12, 24, 36};
//        取值:数组名称[索引]System.out.println(arr[0]); // 12//        赋值:数组名称[索引] = 数据;arr[2] =100;
System.out.println(arr[2]); // 100

 

length:记录数组长度(元素个数),arr.length是数组的属性,不带()

示例代码如下:

//        获取数组长度,即该数组中的元素个数int[] arr= {12, 24, 36};
System.out.println(arr.length); // 3

 

数组的最大索引可以怎么表示?

数组名.length - 1 //前提:元素个数大于0

2.1.2数组注意事项

(1)数据类型[] 数组名也可以写成数据类型 数组名[]([]和数组名可以颠倒)。

示例代码如下:

//        1.“数据类型[] 数组名”也可以写成”数据类型 数组名[]”([]和数组名可以颠倒)。int[] ages= {11, 20, 28};
//        int ages[] = {11, 23, 28};

 

(2)什么类型的数组必须存放什么类型的数据,否则报错。

示例代码如下:

//        2.什么类型的数组必须存放什么类型的数据,否则报错。int[] number= {30, 40, "张三"}; // 报错,需要int型数据

 

(3)数组一旦被定义,在程序的执行过程中,其长度、类型就固定了。

示例代码如下:

//        3.数组一旦被定义,在程序的执行过程中,其长度、类型就固定了。String[] names= {"张三", "李四", "王五"};
System.out.println(names[3]); // 报错,数组names[]长度固定为3,访问第4个元素,超过数组边界异常ArrayIndexOutOfBoundsException: 3

 

2.2动态初始化数组

定义数组的时候只确定元素的类型和数组的长度,之后再存入具体数据。

数组的动态初始化格式:

数据类型[] 数组名 = new 数据类型[长度];

示例代码如下:

double[] scores=newdouble[3]; // 0.0, 0.0, 0.0//                                           0    1    2//        赋值scores[0] =99.5;
System.out.println(scores[0]); // 99.5System.out.println(scores[2]); // 0.0

动态初始化数组的元素默认值

元素默认规则

数据类型

明细

默认值

基本类型

byte、short、char、int、long

0

float、double

0.0

boolean

false

引用类型

类、接口、数组、String

null

示例代码如下:

//        1.整型数组的元素默认值都是0int[] intArr=newint[3];
System.out.println(intArr[0]); // 0System.out.println(intArr[2]); // 0//        2.字符型数组的元素默认值是0char[] charArr=newchar[4];
System.out.println(charArr[0]); // 打印不到,因为0在字符型中没有对应值System.out.println((int) charArr[2]); // 进行强制类型转换,判断字符型数组的元素默认值是0//        3.浮点型数组的元素默认值是0.0double[] doubleArr=newdouble[4];
System.out.println(doubleArr[0]); // 0.0System.out.println(doubleArr[3]); // 0.0//        4.布尔型数组的元素默认值是falseboolean[] booleanArr=newboolean[5];
System.out.println(booleanArr[0]); // falseSystem.out.println(booleanArr[2]); // false//        5.引用数据类型数组的元素默认值是nullString[] strArr=newString[2];
System.out.println(strArr[0]); // nullSystem.out.println(strArr[1]); // null

 

注:字符型数组元素默认值为0,因为0在字符型中没有对应值,所以打印不到,需要进行强制类型转换为int型(没有运算不会自动转换)。

静态初始化数组与动态初始化数组使用场景有什么区别?

静态初始化:开始就存入元素值,适合当前已经知道存入的元素值的业务场景。

动态初始化:只指定数组长度,后期再赋值,适合当前已经知道数据的数量,但是还不清楚要存入哪些具体元素值的业务场景。

两种初始化数组方式的格式是独立的,不可以混用!!!

3.数组的遍历

遍历:就是一个一个数据的访问。

为什么要遍历?搜索、数据统计等等都需要用到遍历。

如何遍历数组?

通过一个for循环,索引从0开始,判断条件为小于该数组长度,就可以循环遍历出数组中的每一个元素。

示例代码如下:

int[] arr= {10, 22, 36, 75};
for (inti=0; i<arr.length; i++) {
System.out.println(arr[i]);
}


4.数组的案例

4.1数组元素求和

需求:某部门5名员工的销售额分别是:16、26、36、6、100,请计算出他们部门的总销售额。

分析:

①拿到这些数据-->使用数组。

②遍历数组中的每个数据,然后在外面定义求和变量,把这些数据累加起来。

示例代码如下:

publicstaticvoidmain(String[] args) {
//        1.定义一个静态初始化数组,存储这些数据int[] arr= {16, 26, 36, 6, 100};
//        2.定义求和变量,用于存放累加数值intsum=0;
//        3.遍历数组中的每个数据,然后把这些数据累加起来for (inti=0; i<arr.length; i++) {
sum+=arr[i];
        }
//        4.将求和变量输出System.out.println("总销售额为"+sum); // 184    }


4.2数组求最值

需求:某部门6名员工的销售额分别是:15、9000、10000、20000、9500、-5,请计算出他们的最大销售额。

分析:

①拿到这些数据-->使用数组。

②定义一个变量用于记录最大值,这个变量建议默认存储第一个元素值(避免出现自定义的初始变量值是最值的情况),此时第一个元素作为参照,从第二个元素开始遍历

示例代码如下:

publicstaticvoidmain(String[] args) {
//        1.定义一个静态初始化数组,存储这些数据int[] arr= {15, 9000, 10000, 20000, 9500, -5};
//        2.定义变量,用于存储最大值,建议使用第一个值作为参照intmaxNum=arr[0];
//        3.变量数组中的每一个元素,依次与当前最大值变量存储的数据进行比较,若该元素较大,则该元素替换最大值变量存储的数据for (inti=1; i<arr.length; i++) { // 第一个元素作为参照,从第二个元素开始遍历if (arr[i] >maxNum) {
maxNum=arr[i];
            }
        }
//        4.将最大值变量输出System.out.println("最大销售额为"+maxNum); // 20000    }


4.3猜数字游戏

需求开发一个幸运小游戏,游戏规则如下:

游戏后台随机生成1-20之间的5个数(无所谓是否重复),然后让大家来猜数字:

➢ 未猜中提示:“未命中”,并继续猜测

➢ 猜中提示:“运气不错,猜中了”,并输出该数据第一次出现的位置,且输出全部5个数据,最终结束本游戏。

分析:

①随机生成5个1-20之间的数据存储起来 --> 使用数组

②定义一个死循环,输入数据猜测,遍历数组,判断数据是否在数组中,如果在,进行对应提示并结束死循环;如果没有猜中,提示继续猜测直到猜中为止。

示例代码如下:

publicstaticvoidmain(String[] args) {
//        需求:定义5个1-20之间的随机数,让用户猜测,若要提示猜中//        还要输出该数据在数组中第一次出现是第几位,并打印数组内容,若没有猜中则继续//        1.定义一个动态初始化数组存储5个随机的1-20之间的数据int[] arr=newint[5];
//        2.动态生成5个1-20之间的随机数并存储到数组中去Randomr=newRandom();
for (inti=0; i<arr.length; i++) {
arr[i] =r.nextInt(20) +1;
        }
//        3.使用一个死循环让用户猜测Scannersc=newScanner(System.in);
OUT:
// OUT标签,用于标记循环,可用break OUT;或continue OUT;跳出整个被OUT标记的循环while (true) {
System.out.println("请输入1-20之间的整数数字:");
intnum=sc.nextInt();
//        4.遍历数组中的每个数据,看是否有数据与猜测的数据相同,相同则代表猜中,且需要给出相应提示for (inti=0; i<arr.length; i++) {
if (num==arr[i]) {
System.out.println("运气不错,猜中了,该数字第一次出现在第"+ (i+1) +"位");
//        5.输出数组的全部数据System.out.print("5个数字为:");
for (intj=0; j<arr.length; j++) {
System.out.print(arr[j] +"\t");
                    }
//                    break; // 只会结束内部遍历各个元素的for循环breakOUT; // 结束整个死循环                }
            }
System.out.println("未命中");
        }
    }

 

break用于跳出包含它的最内层循环

OUT标签用于标记循环,可用break OUT;或continue OUT;跳出整个被OUT标记的循环

4.4随机排名

需求:某公司开发部5名开发人员,要进行项目进展汇报演讲,现在采取随机排名后进行汇报。

请先依次录入5名员工的工号,然后展示出一组随机的排名顺序。

分析:

①随机生成5名员工的工号数据存储起来 --> 使用数组

依次遍历数组中的每个元素,随机一个索引数据,让当前元素与该索引位置处的元素进行交换。

示例代码如下:

publicstaticvoidmain(String[] args) {
//        键盘录入一组工号,最终打乱顺序随机一组出来作为排名顺序//        1.动态初始化一个数组,存储5个工号int[] codes=newint[5];
//        2.定义一个循环,循环5次,每次录入一个工号,存储到相应位置Scannersc=newScanner(System.in);
for (inti=0; i<codes.length; i++) {
// 正式录入工号System.out.println("请输入第"+ (i+1) +"个员工工号:");
intcode=sc.nextInt();
// 将输入的工号存入到数组中codes[i] =code;
        }
//        3.遍历数组中的每个元素,然后随机一个索引出来,让该元素与随机索引位置的元素进行值交换(本节重点)Randomr=newRandom();
// 定义一个临时变量存储随机索引位置的元素值inttemp=0;
// 打印排序前的数据System.out.println("原顺序为:");
for (inti=0; i<codes.length; i++) {
System.out.print(codes[i] +"\t");
        }
System.out.println();
for (inti=0; i<codes.length; i++) {
// 当前遍历的元素值:codes[i]// 随机一个索引位置出来intnum=r.nextInt(codes.length);
temp=codes[num];
codes[num] =codes[i];
codes[i] =temp;
        }
//        4.遍历数组元素后打印,就是随机排名后的结果System.out.println("随机后的顺序为:");
for (inti=0; i<codes.length; i++) {
System.out.print(codes[i] +"\t");
        }
    }

4.5数组排序——冒泡排序

数组排序就是对数组中的元素,进行升序(由小到大)或者降序(由大到小)的操作。

冒泡排序的思想:从头开始,两两比较,若前大后小则交换数据。以此类推,每次从数组中找出最大值,放到数组的最后面。

如:第一轮:用数组第0个索引值5与第1个索引值2进行比较,若第0个索引值5大于第1个索引值2,则两个具体值交换位置,即第0个索引值为2与第1个索引值5,否则不变。然后再用数组第1个索引值5与第2个索引值3进行比较,依次类推。就可以找出第一轮比较中所有元素的最大值,且将其放到了数组最后一位。

然后进行第二轮比较,找到剩下数据中的最大值(即所有数据中的次大值),放到数组中的倒数第二位。

以此类推,直至所有数据都被排序完毕。

BubbleSorting.png

关键步骤分析

第1轮比较次数 3

第2轮比较次数 2

第3轮比较次数 1

①确定总共需要做几轮:数组长度 - 1

②每轮比较几次:数组长度 - 当前循环轮数

示例代码如下:

publicstaticvoidmain(String[] args) {
//        1.动态初始化一个数组,存储4条数据int[] arr=newint[4];
//        2.定义一个循环,循环4次,每次录入一个工号,存储到相应位置Scannersc=newScanner(System.in);
for (inti=0; i<arr.length; i++) {
// 正式录入数据System.out.println("请输入第"+ (i+1) +"个数据");
intnum=sc.nextInt();
// 将数据存储到数组中arr[i] =num;
        }
// 打印排序前的数据System.out.println("原数据为:");
for (inti=0; i<arr.length; i++) {
System.out.print(arr[i] +"\t");
        }
// 定义一个临时变量存储第j个索引(即占位元素索引)的元素值inttemp=0;
//        3.定义一个循环,控制比较轮数for (inti=0; i<arr.length-1; i++) {
// i ==0    比较次数 3   j == 0 1 2// i ==1    比较次数 2  j == 0 1// i ==2    比较次数 1  j == 0// 比较次数 = (arr.length - (i + 1))//       4. 定义一个循环控制每轮比较的次数,占位for (intj=0; j<arr.length- (i+1); j++) {
//                判断j当前位置元素值是否大于后一个位置元素值,若是,则交换if (arr[j] >arr[j+1]) {
temp=arr[j];
arr[j] =arr[j+1];
arr[j+1] =temp;
                }
            }
        }
System.out.println();
//        5.遍历数组元素后打印,就是从小到大冒泡排名后的结果System.out.println("排序后的的数据为:");
for (inti=0; i<arr.length; i++) {
System.out.print(arr[i] +"\t");
        }
    }


5.数组的内存图

5.1Java内存分配、数组内存图

Java为了执行Java程序,以便于管理和维护,将Java内存区域进行了一定的划分,分为:栈、堆、方法区、本地方法栈、寄存器。

方法区是字节码文件加载时进入的内存区域。栈内存是方法运行时所进入的内存,变量也是存入在此。new出来的东西(对象)会在堆内存中开辟空间并产生地址。

Java在内存中具体如何执行,原理是什么?

示例代码如下:

publicstaticvoidmain(String[] args) {
// 基本数据类型中存储的是数据本身inta=10;
System.out.println(a); // 10int[] arr= {11, 22, 33};
// 引用数据类型中存放的是等号右侧对象的地址,根据地址访问数据System.out.println(arr); // [I@1540e19darr[0] =44;
arr[1] =55;
arr[2] =66;
System.out.println(arr[0]); // 44System.out.println(arr[1]); // 55System.out.println(arr[2]); // 66}


如下方数组内存图所示,Java程序在执行过程中,首先把.java类文件编译为.class文件,加载到方法区中

第二步,将main方法提取到栈内存中运行,开始执行main方法中的代码。在栈内存中开辟一块区域,代表a变量,并将值10赋给该变量,因此直接打印a变量时,输出结果为值10。开辟一块区域,代表arr数组变量,然后在堆内存中开辟一块区域,代表new的数组{11, 22, 33},其中分为三小块区域,每块小区域内存放各个索引的值,然后将该数组的地址赋给栈内存中的arr数组(引用数据类型)进行存储,在arr中存储的地址指向堆内存中new出来的数组的起始位置。

第三步,根据arr数组中存储的new出来的数组对象的地址,找到该数组对象,再通过索引值访问各个元素的具体值,并分别赋值44,55,66。

第四步,同样根据arr数组中存储的new出来的数组的地址,找到该数组对象,再通过索引值访问各个元素的具体值,并将他们依次打印出来。

JavaMemory.png

5.2两个变量指向同一个数组

在开发中,有时会将某个数组赋值给另一个数组,这样就会产生两个变量指向同一个数组的情况。

两个变量指向同一个数组的执行原理是什么?

示例代码如下:

publicstaticvoidmain(String[] args) {
int[] arr1= {11, 22, 33};
// 把数组arr1变量赋值给int类型的数组变量arr2int[] arr2=arr1; // 赋给arr2的是数组{11, 22, 33}对象在堆内存中的地址System.out.println(arr1); // [I@1540e19dSystem.out.println(arr2); // [I@1540e19d// 将arr2数组中第1个索引位置值赋为99arr2[1] =99;
System.out.println(arr1[1]); // 99System.out.println(arr2[1]); // 99}


如下方数组内存图所示,Java程序在执行过程中,首先把.java类文件编译为.class文件,加载到方法区中。

第二步,将main方法提取到栈内存中运行,开始执行main方法中的代码。开辟一块区域,代表arr1数组变量,然后在堆内存中开辟一块区域,代表new的数组{11, 22, 33},其中分为三小块区域,每块小区域内存放各个索引的值,然后将该数组的地址赋给栈内存中的arr1数组(引用数据类型)进行存储,在arr1中存储的地址指向堆内存中new出来的数组的起始位置。

第三步,开辟一块区域,代表arr2数组变量,将数组变量arr1中的数据(new出来的数组对象的地址)赋给数组变量arr2,此时两个变量的地址是一样的,数组变量arr2中存储的地址同样指向堆内存中new出来的数组的起始位置。因此打印数组arr1和arr2中存储的值,均为[I@1540e19d。

第四步,根据arr2数组中存储的new出来的数组对象的地址,找到该数组对象,再通过索引值访问第1个索引位置的具体值,并分别赋值99。

第五步,根据数组arr1和arr2中存储的new出来的数组的地址,找到该数组对象,再通过索引值访问第1个索引位置的具体值(此时已经被更改为99),并将他打印出来,打印结果均为99。

JavaMemory2.png

6.数组使用的常见问题

问题1:如果访问的元素位置超过最大索引,执行时会出现ArrayIndexOutOfBoundsException(数组索引越界异常)

 

示例代码如下:

publicstaticvoidmain(String[] args) {
int[] arr= {11, 12, 13};
System.out.println(arr[0]); // 11System.out.println(arr[1]); // 12System.out.println(arr[2]); // 13//        System.out.println(arr[3]); //报错,数组索引越界异常,超出数组的最大索引2//        System.out.println("程序结束"); // 第14行代码报错,此行代码不会执行    }


此时程序报错Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3,且不会执行下面的代码

问题2:如果数组变量中没有存储数组的地址,而是null, 在访问数组信息时会出现NullPointerException(空指针异常)

示例代码如下:

int[] arr= {11, 12, 13};
// 将数组arr的值赋为null,即在此数组变量中存储的堆内存中的数组对象的地址为nullarr=null;
System.out.println(arr); // null//        System.out.println(arr.length); // 报错,空指针异常,找不到数组的地址,因此也获取不到数组长度//        System.out.println(arr[0]); // 报错,空指针异常,找不到数组的地址,因此也获取不到数组中某个索引的值    }


此时程序报错Exception in thread "main" java.lang.NullPointerException

将数组arr的值赋为null,即在此数组变量中存储的堆内存中的数组对象的地址为null,将该数组变量中存储的值(堆内存中new出来的数组对象的地址)打印出来,为null。找不到数组的地址,因此也获取不到数组长度和数组中某个索引的值,程序报错。

7.Debug工具的使用

Debug是程序员用来观察代码、查看代码流程以及定位程序bug位置的一个调试工具。

IDEA自带断点调试(排错)工具,可以控制代码从断点开始一行一行的执行,然后详细观看程序执行的情况。

Debug工具基本使用步骤

①在需要控制的代码行左侧,点击一下,生成断点

Debug1.png

②选择使用Debug方式启动程序,启动后程序会在断点暂停

Debug2.png

③控制并调试代码一行一行的往下执行

Debug3.png

如下图红色矩形框所示,此功能为“恢复程序”,继续执行程序,直至到下一个断点或程序结束。

DebugRecovery.png

相关文章
|
3月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
2月前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
2月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
336 1
|
4月前
|
存储 Java 索引
java 数组
在 Java 中,数组是一种数据结构,用于存储多个相同类型的数据元素。数组的大小一旦创建后就不能改变,因此它是固定长度的。Java 数组是一种 对象,即使它存储的值是基本类型(如 int、double 等),它也是一个对象引用。
123 0
|
5月前
|
设计模式 算法 Java
Java SE 与 Java EE 组件封装使用方法及实践指南
本指南详细介绍了Java SE与Java EE的核心技术使用方法及组件封装策略。涵盖集合框架、文件操作、Servlet、JPA、EJB和RESTful API的使用示例,提供通用工具类与基础组件封装建议,如集合工具类、文件工具类、基础Servlet、实体基类和服务基类等。同时,通过分层架构集成示例展示Servlet、EJB和JPA的协同工作,并总结组件封装的最佳实践,包括单一职责原则、接口抽象、依赖注入、事务管理和异常处理等。适合希望提升代码可维护性和扩展性的开发者参考。
181 0
|
6月前
|
存储 人工智能 Java
打乱数组内容引发的问题( Java)
本文介绍了两种实现数组随机打乱的方法,并深入探讨了Java中原始数据类型与对象类型的差异。方法一通过自定义随机数交换数组元素位置,方法二借助`Collections.shuffle()`函数完成数组打乱。同时,文章详细解析了`int`和`Integer`的区别,包括声明方式、内存占用、初始化以及对象特性等,并讲解了自动装箱与拆箱的功能,帮助读者更好地理解Java的基础知识。
105 0
|
8月前
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
537 1
Java 中数组Array和列表List的转换
|
8月前
|
存储 Java 索引
Java 复制数组
本文介绍了Java中数组的基础知识与常用操作,包括数组的概念、创建、访问元素、遍历、复制、排序和搜索等方法。同时详细讲解了数组的五种赋值方式,并通过代码示例演示了求总和平均值、最大最小值、升序降序排序及Arrays类的常用方法。内容深入浅出,适合初学者学习掌握Java数组的核心功能与应用场景。
117 2
|
7月前
|
存储 Java 数据挖掘
Java 中数组的多种定义方式
本文深入解析了Java中数组的多种定义方式,涵盖基础的`new`关键字创建、直接初始化、动态初始化,到多维数组、`Arrays.fill()`方法以及集合类转换为数组等高级用法。通过理论与实践结合的方式,探讨了每种定义方法的适用场景、优缺点及其背后的原理,帮助开发者掌握高效、灵活的数组操作技巧,从而编写更优质的Java代码。
335 0