✨一、 数组基本用法
1.数组的作用
能让我们批量创建相同类型的变量
2.数组的创建
下面这三种创建方式都是可以的
int[] arr1=new int[] {1,2,3,4};
int[] arr2={1,2,3,4};
int[] arr3=new int[4];
3. 数组的使用
(1)获取长度
注意事项
使用
arr.length
能够获取到数组的长度. . 这个操作为成员访问操作符. 后面在面向对象中会经常用到
代码示例:
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(array.length);
}
(2)访问数组中的元素
注意事项:
- 使用 [ ] 按下标取数组元素. 需要注意, 下标从 0 开始计数
- 使用 [ ] 操作既能读取数据, 也能修改数据.
- 下标访问操作
不能超出有效范围 [0, length)
, 如果超出有效范围, 会出现下标越界异常
代码示例:
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
//获取数组中的元素的个数
//System.out.println(array.length);
//访问数组中的元素
System.out.println(array[1]); // 执行结果: 2
System.out.println(array[2]); // 执行结果: 3
//修改数组中的元素
array[2] = 100;
System.out.println(array[2]); // 执行结果: 100
}
(3)下标越界
数组下标从0开始,范围是 [0,arr.length) ,左闭右开的区间,或者是[ 0,arr.length-1].
如果我们将下标的值超出数组的范围…
如下所示
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(arr[100]);
}
(4)遍历数组(三种方法)
1.遍历方式(一)----- for循环
public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
2.遍历方式(二)----> for-each
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for (int x:arr) {
System.out.print(x+" ");
}
}
for-each 遍历的原理
遍历array 里面的每一个元素,把每一个元素取出来,然后赋值给了 x ,最后打印 x ,直到 array 里面的元素全部都遍历完成.
两种遍历的方式我们介绍完了,那么for循环和for-each有什么区别?
for循环是可以拿到数组下标的,for-each拿不到数组下标,所以for-each只能够全部遍历,无法对数组元素进行修改或进行操作.
3.遍历方式(三)-----> 使用操作数组的工具类进行数组的打印(Arrays)
Arrays 就是操作Java数组的工具类,你要对数组做什么事情,就可以通过他来做,当然有些事情他是完成不了的.
比如说:我们要打印数组,我们本来是用for循环 或者 for-each 来写的,但是我们也可以用Arrays的工具类打印.
通过JDK的工具文档,我们查找到了相应的工具类.
代码如下:
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
System.out.println(Arrays.toString(arr));
}
⛲二、数组作为参数的方法
1.理解引用类型
(1)参数传内置类型
我们用 内置类型作为参数,进行交换变量,但是最后编译的结果 两个变量却并未发生交换.
这是为什么呢?
(2)参数传数组类型
结果:
我们用数组作为参数
,进行交换变量,编译运行后,发现成功交换
两个变量的值。此时数组名 arr 是一个 “引用”
. 当传参的时候, 是按照引用传参
.
那么为什么传引用类型可以 形参可对实参进行操作呢?
这里我们就要先从内存说起
数组在内存中的储存:
我们可以知道,
数组这个在栈中存放的变量实际存放的是 堆中数据的地址
,当我们 arr 数组作为参数 传入 方法里,我们就把 堆中数据的地址 传入了进去,在方法内部,我们可以根据 这个地址 找到堆中的数据进而修改数据,从而实现了形参改变了实参的操作.
如下图:
总结:
所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
(3)JVM内存区域划分
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array ={1,2,3,4,5};
int a = 10;
}
}
array 和 a 都是局部变量,在栈上开辟空间 那么问题来了,a的空间里存的是 10,但是数组后面的数据,放在哪里? 和 10 一样放在栈上? 对象是存放在 堆上的
看一下JVM的5个部分
关于上面的划分方式, 后面慢慢理解. 此处我们重点理解 虚拟机 栈 和 堆
.
局部变量和引用保存在栈上, new 出的对象保存在堆上.
- 堆的空间非常大, 栈的空间比较小.
- 堆是整个 JVM 共享一个, 而栈每个线程具有一份(一个 Java 程序中可能存在多个栈).
(4)初始null
引用很像指针,最大的区别就是不能对 引用 解引用,可以对指针解引用,因为在Java没有传址的概念的,其他功能类似,但时两个东西,不是同一个东西,不能混淆在一起。
指针有空指针,那引用有空引用(引用里面存的地址是空的)吗?
有的,只不过跟C语言不同,C是大写NULL,Java是小写null
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] array1 = null;
//当一个引用被赋予null,就代表这该引用不指向任何对象
// 既然该引用不指向任何对象,那么我这样写代码会怎么样?
System.out.println(array1.length);
// 求一个空引用所指向的对象的长度,但是空引用不会指向任何一个对象的
// 何来的长度?那么代码运行的效果如何?
图14,由图可知该写法是错误的,另外引用被指为null,堆上是不会分配内存给它的
// 意思是你都不创建任何对象,你还想拿我空间,疯了?
System.out.println(array1[0]);
// 这个写法毫无疑问也是错的,没对象,你怎么访问对象里的东西?
}
}
⛹️三、数组作为方法的返回值
代码示例:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
transform(arr);
printArray(arr);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]); }
}
public static void transform(int[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2; }
}
这个代码固然可行, 但是破坏了原有数组. 有时候我们不希望破坏原数组, 就需要在方法内部创建一个新的数组, 并由方法返回出来。
修改后的代码:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
int[] output = transform(arr);
printArray(output);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] transform(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i] * 2;
}
return ret;
}
※思考题※
题目一:
输出是什么
import java.util.Arrays;
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
func1(array);
System.out.println(Arrays.toString(array));// 图 18
}
public static void func1(int[] array){
array = new int[]{11,2,13,4,51};
}
}
题目二:
输出是什么
import java.util.Arrays;
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
func2(array);
System.out.println(Arrays.toString(array));
}
public static void func2(int[] array){
array[0] = 99;
}
}
题解:
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] array2 = array;
这代表 array2 这个引用 指向 引用array指向 的对象
array 和 array2 指向的是同一个对象
}
}
⛵四、数组练习
1.模拟实现toString
题目要求:
实现一个方法 toString, 把一个整型数组转换成字符串.
例如 :数组 {1, 2, 3} , 返回的字符串为 “[1, 2, 3]”, 注意 逗号 的位置和数量.
public static String my_toString(int[] arr) {
String s="[";
for(int i=0;i<arr.length;i++)
{
s=s+arr[i];
if(i<arr.length-1)
s+=",";
}
s+="]";
return s;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
String ss=my_toString(arr);
System.out.println(ss);
}
2.数组拷贝
数组的拷贝方式
1.for循环拷贝
//数组拷贝
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
int[] copy = new int[arr.length];
for (int i = 0; i <arr.length ; i++) {
copy[i] = arr[i];
}
System.out.println(Arrays.toString(copy));
}
在这种拷贝方式中,我们首先通过 new 一个新的和 arr 一样长度数组 copy,再通过 for 循环将 arr 数组中的内容 一 一赋给copy数组,达到 最终的数组拷贝的效果.
2.Arrays数组的工具类
(1)copyOf()
我们先通过JKD的工具文档,来看一下拷贝工具的用法
功能:复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度.
具体看一下Java当中copyOf方法的具体实现
首先 Arrays.copyOf() 的返回类型是 int [ ] ,第一个参数是 原数组(要拷贝的数组),第二个参数是新数组的长度(可以自己定),如果新的数组的长度比原数组长的话,大于原数组长度的元素都补为0 。 具体如下所示…
(2)copyOfRange()
我们先通过JDK文档来查看这个工具类的功能
功能:将指定数组的指定范围复制到新的数组中.
具体看一下Java当中copyof方法的具体实现
copyOfRange 方法的返回类型是 int [ ] ,第一个参数是 原数组 ,第二、三参数是要拷贝原数组数据的下标 ,一定要切记 是左闭右开的区间 , [ from , to ).
代码示例
3.System.arraycopy
我们打开 System.arraycopy 方法的具体实现,发现没有和上面几种拷贝方法的实现过程,System.arraycopy 有前面的 native 可知,这是一个本地方法。
本地方法
1.运行在
本地方法栈
上2.底层是由
C/C++代码实现
的
System.arraycopy 没有返回值,第一个参数原数组(要拷贝的数组),第二个参数是原始数组要拷贝的下标,第三个参数是目的地数组 ,第四个参数是 目的地数组的下标,第五个数组是要拷贝的长度.
代码示例:
注意点:
System.arraycopy 最后一个参数 ——要拷贝的数组长度,这个数据不能超过原数组的长度,否则编辑器会发生错误报告:数组越界。
4.
数组名.clone
----> 产生当前数组的一个副本
clone可以理解为是一份资料的备份
代码示例:
⌚五、理解深拷贝与浅拷贝
浅拷贝
:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。
深拷贝
:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。
深拷贝和浅拷贝
最根本的区别
在于是否真正获取一个对象的复制实体
,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
通俗来说就是:
- 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
- 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
⚽六、二维数组
1.二维数组的创建
跟一维数组几乎一样,只要初始化了数据,你的[][]里就不能有数字的存在
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
// 创建一个 2行3列的二维数组
int[][] array = {{1,2,3},{4,5,6}};
int[][] array2 = new int[][]{{1,2,3},{4,5,6}};
int[][] array3 = new int[2][3];
}
}
2.二维数组的打印
方法一:
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
// 创建一个 2行3列的二维数组
int[][] array = {{1,2,3},{4,5,6}};
int[][] array2 = new int[][]{{1,2,3},{4,5,6}};
int[][] array3 = new int[2][3];
print(array);
}
public static void print(int[][] array){
// 按照我们以前对C的理解,二维数组的存储模式应该为 图60
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
//打印一行数据
System.out.print(array[i][j] + " ");
}
System.out.println();// 换行
}
}
}
如果计算的算,每个二维数组都自己去算它有几个元素吧。 怎么去获得 行 和 列? 这里就引用C语言的一个概念,二维数组是一个特殊的一维数组
经过前面讲解,大家都知道数组是存储在堆上的,再加上面这句话的概念
修改代码如下:
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
// 创建一个 2行3列的二维数组
int[][] array = {{1,2,3},{4,5,6}};
int[][] array2 = new int[][]{{1,2,3},{4,5,6}};
int[][] array3 = new int[2][3];
print(array);
// 这里我们在用实例证明一下
// array.length 是否能的得到 行数 2
System.out.println(array.length);
// array[下标].length 是否能得到 列数3
System.out.println(array[0].length);
System.out.println(array[1].length);
}
public static void print(int[][] array){
// 按照我们以前对C的理解,二维数组的存储模式应该为图60
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
//打印一行数据
System.out.print(array[i][j] + " ");
}
System.out.println();// 换行
}
}
}
方法二:
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[][] array = {{1,2,3},{4,5,6}};
print(array);
}
public static void print(int[][] array){
for (int[] ret:array) {// array元素的数据类型是一个一维数组,我就需要一个相同的类型的变量来接收
for (int x:ret) {
// ret是一个一维数组的数组名,接下来就跟前面使用foreach是一样,将 引用ret 所指向的对象(数组)的元素,
// 读取并赋值给 与其元素类型相同的变量, 我们再将其输出。就可以了
System.out.print(x + " " );
}
System.out.println();//换行
}
}
}
**方法三:**二维数组也有对应的 方法:Arrays.deepToString( 数组名 )
import java.util.Arrays;
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[][] array = {{1,2,3},{4,5,6}};
System.out.println(Arrays.deepToString(array));
}
}
⌛七、不规则的二维数组
(1)不规则二维数组定义
Java中不规则二维数组的定义
什么是不规则的二维数组?
在之前的规则的二维数组中,每一行的数据个数都相同,列数也相同。而不规则的二维数组,规定了行数,列数有我们自己定,每一行有多少列由我们自己规定。
在C语言中,我们定义二维数组可以 只定义列,不用规定行的值。
C语言中数组的定义
int[][2] = {1,2,3,4,5,6,7};
而在Java中我们只能 定义行,列不用规定值
int[][] arr = new int[2][];
代码如下:
public static void main(String[] args) {
int[][] arr = new int[2][];
arr[0] = new int[]{1,2,3};
arr[1] = new int[]{4,5};
System.out.println(Arrays.deepToString(arr));
}
编译结果:
首先我们规定了一个 有两行的二维数组
int [ ] [ ] arr = new int [2] [ ];
我们自己给每一行的数组规定有多少列。
arr [ 0 ] = new int [ ] { 1,2,3 }
arr [ 1 ] = new int [ ] { 4,5 };
这就是不规则的二维数组的定义。
(2)内存中的不规则二维数组
与规则的二维数组内存存储基本相同。