一.数组
1.定义:数组是存储同一种数据类型数据的集合容器。
2.数组的定义格式:
(1)格式一:
数据类型[ ] 数组名 = new 数据类型[长度];
如:int[ ] arr = new int[5];
(2)格式二:
数据类型[ ] 数组名 = new 数据类型[ ]{元素1,元素2,……};
如:int[ ] arr = new int[ ]{3,5,1,7};
int[ ] arr = {3,5,1,7};
3.分析数组:
(1)左边:声明数组
int[ ] arr : 声明了一个int类型的数组变量,变量名为arr。
int : 表明该数组容器只能存储int类型的数据。
[ ] : 这是一个数组类型。
arr : 数组名,变量名。
(2)右边 :创建数组
new int[5] : 创建了一个长度为5的int类型数组对象。
new : 创建数组对象的关键字。
int : 表示该数组对象只能存储int类型数据。
[ ] : 表示数组类型。
5 : 数组的容量,该数组最多能存储5个数据。
4.数组的好处:对分配到数组对象中每一个数据都分配一个编号(索引值、角标、下标),索引值的范围是从0开始,最大是:长度-1。
5.补充:
(1)局部变量:如果一个变量是在一个方法(函数)的内部声明的,那么该变量就是一个局部变量。
(2)成员变量:成员变量就是定义在方法之外,类之内的。
二.数组的内存分析
1.实例一:
(1)需求:求数组的长度并且遍历查看数组中的所有元素。
class Demo1 {
public static void main(String[] args){
//定义一个数组
int[] arr = new int[4];
arr[0] = 10;
arr[1] = 30;
arr[2] = 50;
arr[3] = 90;
//数组有一个length属性,可以查看数组的容量
System.out.println("数组的容量:"+arr.length);
//查看数组中的所有数据
for(int index = 0;index < arr.length;index++){
System.out.println(arr[index]);
}
}
}
(2)运行结果如下图所示:
(3)注意:
内存分析图中,arr存储的是对象的内存地址,所以,System.out.println(arr);的结果是输出arr的内存地址,并不能查看数组中的所有数据元素。
(4)实例一内存分析:
JVM管理的其中两片内存,一片栈内存,一片是堆内存。arr是在main方法内部声明的,属于局部变量,而局部变量都是存储在栈内存中。所以首先会在栈内存中声明一个名字为arr的变量,接着new一个int类型的数组,JVM会在堆内存中开辟一片空间用于创建一个数组对象,在这片空间中创建了一个数组容量为4的数组对象,把这片空间平均划分成四等份,紧接着会给每一份空间从0开始分配一个编号,也就是我们常说的索引值,索引值从0开始,最大为数组长度-1;而这个数组对象是在内存中进行划分的,每一片内存都有地址,假设这片空间的内存地址为0x98,而等号是赋值运算符,这时候的等号是把数组对象的内存地址赋予给arr变量,一旦执行赋值运算符,此时arr记录的就是0x98,所以arr指向的就是这个内存的内存地址。此时就需要往划分好的每一个格子里存放数据,假设要往索引值为2的格子里存储90,首先要先找到这片内存空间,整个这片内存空间(0x98)可以看作是一栋楼,每栋楼有很多个房子,索引值就相当于每个房间的门牌号,arr这个变量记录了这个数组对象的内存空间,找到arr也只是找到这栋楼而已,要进入某一个房间是需要由门牌号确定的,所以arr[2] = 90;就可以将数据存储在索引值为2的格子里。如果直接输出arr,那么得到的结果会是这个数组对象的内存地址。
2.实例二
(1)实例:
class Demo2 {
public static void main(String[] args){
int[] arr1 = new int[2];
int[] arr2 = new int[2];
arr1[1] = 10;
arr2[1] = 20;
System.out.println(arr1[1]);//10
}
}
(2)运行结果如下图所示:
(3)实例二内存分析:
①第一句代码首先在栈内存中声明了一个变量arr1,然后在堆内存中new一个容量为2的数组对象,把这片内存空间分成两等份,并分配索引值0和1。比如它的内存地址还是0x98,这时候的等号相当于把这个内存地址赋予给了arr1,也就是说,arr1记录了这个数组对象。
②第二句代码首先在栈内存中声明了一个变量arr2,然后在堆内存中new一个容量为2的数组对象,把这片内存空间分成两等份,并分配索引值0和1。比如它的内存地址还是0x67,这时候的等号相当于把这个内存地址赋予给了arr2,也就是说,arr2指向了这个数组对象。
③第三句代码通过arr1找到第一个数组对象,然后找索引值为1的格子,在这片内存中存储10。
④第四句代码通过arr2找到第二个数组对象,然后找索引值为1的格子,在这片内存中存储20。
⑤第五句代码输出的是arr1[1],要输出的是第一个数组对象中索引值为1号的内存空间所存储的值,由以上分析可知,这片内存空间中存储的值是10。
3.实例三:
(1)实例:
class Demo3{
public static void main(String[] args){
int[] arr1 = new int[2];
arr1[1] = 100;
int[] arr2 = arr1;
arr1[1] = 10;
arr2[1] = 20;
System.out.println(arr1[1]);//20
}
}
(2)运行结果如下图所示:
(3)实例三内存分析:
①第一句代码首先在栈内存中声明了一个变量arr1,然后在堆内存中new一个容量为2的数组对象,把这片内存空间分成两等份,并分配索引值0和1。比如它的内存地址还是0x98,这时候的等号相当于把这个内存地址赋予给了arr1,也就是说,arr1记录了这个数组对象。
②第二句代码通过arr1找到第一个数组对象,然后找索引值为1的格子,在这片内存中存储100。
③第三句代码是把arr1所存储的值赋予给了arr2,而arr1记录的是内存地址,也就是说此时arr2也记录了0x98。
④第四句代码通过arr1找到这个数组对象,然后找索引值为1的格子,在这片内存中存储的值改为10。
⑤第五句代码通过arr2找到这个数组对象,然后找索引值为1的格子,又找到了这片内存,然后在这片内存中把存储的值改为20。
⑥第六句代码输出的是arr1[1],要输出的是第一个数组对象中索引值为1号的内存空间所存储的值,由以上分析可知,这片内存空间中存储的值变成了20。
综上所述,输出的值变为20的根本原因是它们操作的是同一个数组对象。
三.数组中最常见的问题:
1. NullPointerException 空指针异常
(1)原因:
是因为引用类型变量没有指向任何对象,而访问了对象的属性或者是调用了对象的方法所引起的。
(除了8种数据类型以外的所有数据类型都是引用数据类型)
(2)实例:
class Demo1{
public static void main(String[] args){
int[] arr = new int[2];
arr = null;
arr[1] = 10;
System.out.println(arr[1]); //报错
}
}
(3)运行结果:
(4)内存分析:
①第一句代码首先在栈内存中声明了一个变量arr,然后在堆内存中new一个容量为2的数组对象,把这片内存空间分成两等份,并分配索引值0和1。比如它的内存地址还是0x98,这时候的等号相当于把这个内存地址赋予给了arr,也就是说,arr记录了这个数组对象。
②第二句代码的意思是让该变量不要引用任何的对象,不要记录任何的内存地址,此时堆内存中的对象已经使用完毕。因为以前是通过了一个变量记录了这个对象的内存地址,而现在没有任何变量记录了这个对象的内存地址,所以此时就没法找到这个对象,这时这个对象就被称为垃圾对象。
③第三句代码因为此时arr找不到数据对象,所以更不可能给1号元素赋值。所以输出1号元素的值会报错空指针异常。
2.ArrayIndexOutOfBoundsException 索引值越界(下标越界)。
(1)原因:
访问了不存在的索引值。
(2)实例:
class Demo2 {
public static void main(String[] args){
int[] arr = new int[4];
arr[0] = 10;
arr[1] = 30;
arr[2] = 40;
arr[3] = 50;
//遍历的时候最容易出现索引值越界,错在了<=,而应该是去掉等号。
for(int index = 0;index <= arr.length;index++){
System.out.println(arr[index]+",");
}
}
}
(3)运行结果:
(4)分析:
在遍历的时候最容易出现索引值越界,本实例中错在了<=,去掉等于号后则可以正确输出。因为如果加上等于号,相当于要输出System.out.println(arr[4]);访问索引值为4的内存空间存储的值,所以数组下标越界。
四.数组的初始化方式:
1.方式一:动态初始化
数据类型[ ] 变量名 = new 数据类型[长度];
如:
int[] arr = new int[10];
2.方式二:静态初始化
数据类型 [ ] 变量名 = {元素1,元素2,……};
如:
int[] arr = {10,20,30,40,50};
3.注意:
(1)如果程序一开始就已经确定了数据,那么这时候建议使用静态初始化;如果数据一开始还不太明确,这时候就建议使用动态初始化。
(2)静态初始化创建的数组对象也是在堆内存空间中创建的,只不过是JVM帮你省略了new,但是它在内存中实在创建的时候,JVM会帮你加上new关键字。
五.实例:
1.实例一
(1)需求:
定义一个函数接收一个int类型的数组对象,找出数组对象中的最大元素返回给调用者。
(2)实例:
class Demo3 {
public static void main(String[] args){
int[] arr = {-12,-14,-5,-26,-4};
int max = getMax(arr);
System.out.println("最大值:"+max);
}
public static int getMax(int[] arr){
int max = arr[0]; //用于记录最大值
for(int i = 1;i<arr.length;i++){
if(arr[i] > max){ //如果发现有元素比max大,那么max变量就记录该元素。
max = arr[i];
}
}
return max;
}
}
(3)运行结果如下图所示:
注意:如果开始时定义int max = 0;那么如果要比较的数都是负数,则最大值就变为0,所以应该假设最大值的初始值就是第一个元素。
五.数组的常用工具类:
1.常用工具类(Arrays):
(1)遍历:
toString( ):将数组的元素以字符串的形式返回。
(2)排序:
sort( ):将数组按照升序排列。
(3)查找:
binarySearch( ):在指定数组中查找指定元素,返回元素的索引,如果没有找到则返回插入点-1。
注意:使用查找功能的时候,数组一定要先排序。
2.实例:
import java.util.*;
class Demo5{
public static void main(String[] args){
int[] arr = {12,3,1,10,8};
//排序的方法:
Arrays.sort(arr);
System.out.println("----------toString()方法的运行结果----------");
String info = Arrays.toString(arr);
System.out.println("数组的元素:"+info);
System.out.println("----------binarySearch()方法的运行结果----------");
int index = Arrays.binarySearch(arr,5);
System.out.println("找到的索引值:"+index);
}
}
运行结果如下图所示:
注意:
数组工具类中的二分法查找binarySearch();如果能在数组中找到对应的元素,那么就返回该数据的索引值,如果没有找到就返回一个负数表示。
六.二维数组:
1.定义:
二维数组就是数组中的数组。实质是存储着一维数组。
2.二维数组的定义格式:
数据类型[ ] 变量名 = new 数据类型[长度1][长度2];
可以简单地理解为一根烟是一个变量;一盒烟是一维数组;一条烟是二维数组。长度1指的是一条烟有多少盒,长度2指的是一盒烟有多少根。
3.二维数组的初始化方式:
(1)动态初始化:
数据类型[ ] 变量名 = new 数据类型[长度1][长度2];
(2)静态初始化:
数据类型[ ] 变量名 = {{元素1,元素2,……},{元素1,元素2,……},{元素1,元素2,……}……};
4.实例一
(1)实例
class Demo6{
public static void main(String[] args){
//定义了一个二维数组
int[][] arr = new int[3][4];
arr[1][1] = 100;
System.out.println("二维数组的长度:"+arr.length);//3
System.out.println("arr[1]的长度:"+arr[1].length);//4
System.out.println("数组的元素:"+arr[1][1]);
}
}
运行结果如下图所示:
(2)内存分析:
①为什么System.out.println(arr.length);的输出结果是3?
首先在栈内存中声明一个arr的变量,然后在堆内存中new一个int类型的长度为3的数组对象,把这片空间分成3等份,然后给它们分配对应的索引值,假如这个数组对象的内存地址是0x98。紧接着还会有三个长度为4的一维数组,每一个一维数组又都会有一片内存地址,假如是0x67,0x56,0x45。也就是说刚开始的三个格子分别记录了0x56,0x67,0x45.而栈内存的arr记录了0x98,实际上它的数据都是存储在了下面的一维数组中了,这时候就体现了二维数组是数组的数组。
②为什么System.out.println(arr[1].length);的输出结果是4?
因为arr是记录了刚开始的那个一维数组的内存地址,而这个一维数组的长度正好是3,而如果换成了arr[1].length,那么arr找到了索引值为1号的内存空间,而这个内存空间记录了0x67这个内存地址,所以这时候输出的是4.
5.实例二:遍历输出二维数组中的元素
(1)实例
class Demo7{
public static void main(String[] args){
int[][] arr = {{10,11,9},{67,12},{33,35,39,40}};
//遍历二维数组
for(int i = 0;i<arr.length;i++){
for(int j = 0;j<arr[i].length;j++){
System.out.print(arr[i][j]+",");
}
//换行
System.out.println();
}
}
}
运行结果如下图所示:
七.总结:
1.数组的特点:
(1)数组只能存储同一种数据类型的数据。
(2)数组是会给存储到数组中的元素分配一个索引值的,索引值从0开始,最大的索引值是length-1。
(3)数组一旦初始化,长度固定。
(4)数组中的元素与元素之间的内存地址是连续的。
注意:并不是说数组对象是连续的。比如在二维数组中,arr[1][1]和arr[1][2]它们的内存地址是连续的,如果arr[1][1]的内存地址是0x98,那么arr[1][2]是0x99。并不是说如果arr[1]是0x98,则arr[2]是0x99,因为arr[1]和arr[2]是数组对象。
2.实例:
(1)代码:
class Demo8{
public static void main(String[] args){
int[] arr = new int[3];//长度为3
arr = new int[4];//长度为4,并没有改变之前的数组对象的长度,只是新指向了另一个数组 //对象。
System.out.println(arr.length);
}
}
(2)运行结果:
(3)分析:
首先在内存中声明一个arr的变量,在堆内存中new一个int类型的长度为3的数组,把这片内存空间分成3份,假如说内存地址是0x98,此时arr就记录了0x98,arr指向这个数组对象。
紧接着arr = new int[4];这时候在堆内存会创建一个新的数组对象,它的长度是4,假设它的内存地址是0x86,而等号就会把这个内存地址赋值给arr,也就是说这时候这个变量不再记录0x98,而是记录0x86,也就是arr指向了新的数组对象,所以最后的输出结果会是4而不是3。