接上期后续
本期继续分享尚未结束的JavaSE章节
JavaSE属于初入门阶段,内容很多~
但很基础,
大家需保持耐心,慢慢的学~
争取你们学习的速度!
跟上我更新的速度哦~
今日新篇章
数组
【主要内容】
数组的定义和特征
添加和获取元素操作
增强for循环
数组内存分配
常见数组异常
数组的常见操作
在数组中删除元素
在数组中插入元素
方法的可变参数
二维数组
1.1 数组的概念
数组概念:数组就是一种能够存放相同数据类型的有序集合。(通俗来讲数组其实就是一个容器)。
1.2 数组的创建
1.2.1 动态创建数组
语法格式:数据类型[] 数组名 = new 数据类型[数组长度];
数据类型 数组名[] = new 数据类型[数组长度];
注意:数组的声明建议大家使用第一种方式,避免数组名混淆。
【示例】
public static void main(String[] args) {
// 创建3个空间的int类型数组
int[] arr1 = new int[3];
// 创建5个空间的String类型数组
String[] arr2 = new String[5];
}
1.2.2 静态创建数组
语法格式:数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3,…};
注意:使用静态的方式来创建数组,数组的长度由元素个数来确定。
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr1 = new int[]{1, 2, 3, 4, 5};
// 创建指定内容的String类型数组
String[] arr2 = new String[]{“11”, “22”, “33”, “44”};
}
除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。
语法格式:数据类型[] 数组名 = {元素1, 元素2, 元素3,…};
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr1 = {1, 2, 3, 4, 5};
// 创建指定内容的String类型数组
String[] arr2 = {“11”, “22”, “33”, “44”};
}
注意事项:
1、 数组类型可以是任何数据类型,包括基本类型和引用类型,例如String[]和float[]。
2、 数组中存放元素的类型,必须是创建数组时指定的类型,不允许出现混合类型。
3、 创建一个数组时,必须指定数组长度,创建成功数组的大小就不可以改变了。
1.3 数组的基本操作
数组中的元素,我们可以通过下标(索引)来访问,索引从0开始。
数组索引的取值范围为:[0,数组长度-1],如果超出索引范围来操作数组元素,会抛出ArrayIndexOutOfBoundsException异常。
Ø 数组的赋值操作
【示例】
public static void main(String[] args) {
// 初始化5个空间的int类型数组
int[] arr = new int[5];
// 添加元素
arr[0] = 11; // 给第一个元素赋值
arr[1] = 22; // 给第二个元素赋值
arr[2] = 22; // 给第三个元素赋值
// 修改第二个元素的值
arr[1] = 222;
}
Ø 数组的取值操作
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 获取元素
int num1 = arr[0]; // 获取第一个元素
int num2 = arr[1]; // 获取第二个元素
int num3 = arr[2]; // 获取第三个元素
}
Ø 获取数组的长度
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 通过length属性,来获取数组的长度
System.out.println(arr.length); // 输出:5
}
注意事项:
通过length属性获取到的数组长度和开辟的内存空间的长度一致。
某些情况下,实际添加元素的个数(不算默认值元素),可能会小于了数组的长度。
Ø 通过for循环遍历数组
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 通过length属性,获取数组元素的个数
int length = arr.length;
// 通过for循环,遍历数组所有元素
for(int i = 0; i < length; i++) {
// 通过下标获取数组中的元素
System.out.println(“第”+(i+1)+“个元素值:” + arr[i]);
}
}
Ø 通过索引操作数组原理
因为数组的内存空间是连续的,我们通过数组的首地址+索引就能快速的找到数组对应的元素值,从而得出数组的优点:查找快。
索引操作数组原理:数组首地址 + 存放数据的字节数*索引。
【随堂练习】
1、 获取10个学生的成绩,然后保存在数组中,最后计算学生的总分和平均分。
1.4 数组的默认初始化
数组是引用类型,只要数组一经分配空间,那么数组中的每个元素都会被隐式的设置一个默认值。
以下是针对不同数据类型的默认值:
1、整数类型(byte、short、int、long)的基本类型变量的默认值为0。
2、浮点型(float、double)的基本类型变量的默认值为0.0。
3、字符型(char)的基本类型变量的默认为 “\u0000”。
4、布尔性的基本类型变量的默认值为 false。
5、引用类型的变量是默认值为 null(null就是空对象)。
2.1 堆和栈的概述
JVM是基于堆栈的虚拟机,堆栈是一种数据结构,是用来存储数据的。对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。
【栈内存stack】
栈内存:用来存放局部变量。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
栈内存特点:
栈内存存储特性为:“先进后出,后进先出”。
栈是一个连续的内存空间,由系统自动分配,速度快!
虚拟机会为每个线程创建一个栈,用于存放该线程执行方法的信息。
【堆内存heap】
堆内存:用来存储创建好的对象和数组(数组也是对象)
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
数组和对象在没有引用变量指向它的时候,数组或对象本身占用的堆内存就变成垃圾,不能再被使用,然后由Java虚拟机的自动垃圾回收器来管理释放该内存。
堆内存特点:
虚拟机中只有一个堆,被所有的线程共享。
堆是一个不连续的内存空间,分配灵活,但速度慢!
【思考一】
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = arr1;
arr2[2] = 33;
System.out.println(arr1[2]); // 输出结果为???
}
【思考二】
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;
arr1 = null;
System.out.println(arr1[2]); // 输出结果为???
System.out.println(arr2[2]); // 输出结果为???
}
2.2 数据类型传递
java中没有指针(所以没有引用传递),方法中所有参数都是值传递, 传递的是值的副本。
2.2.1 基本数据类型传递
实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值。
【示例】交换两个变量的值
public class ObjectDemo {
public static void main(String[] args) {
int a = 10, b = 20;
// 输出:交换之前:a:10 b: 20
System.out.println(“交换之前:a:” + a + " b: " + b);
// 交换变量a和b的值
swap(a, b);
// 输出:交换之后:a:10 b: 20
System.out.println(“交换之后:a:” + a + " b: " + b);
}
// 交换两个变量的值
public static void swap(int num1, int num2) {
int temp = num1;
num1 = num2;
num2 = temp;
}
}
总结: 方法调用时,实参把值拷贝给形参,在函数中交换的是那份拷贝的值,而不是实参数据本身。所以方法内部修改形参的值, 在方法外部的实参不会跟着形参一起改变。
2.2.2 引用数据类型传递
实际参数把它的值传递给对应的形式参数,方法执行中形式参数的改变直接影响实际参数。
【示例】交换两个变量的值
public class Test {
public static void main(String[] args) {
// 定义一个数组,用于交换两个数组元素的值
int[] arr = {11, 22};
// 交换之前,输出数组元素值
System.out.println("交换之前:arr[0] = " + arr[0] + " arr[1] = " + arr[1]);
// 调用方法,执行交换元素值的操作
swap(arr);
// 交换之后,输出数组元素值
System.out.println("交换之后:arr[0] = " + arr[0] + " arr[1] = " + arr[1]);
}
// 交换arr数组索引0和索引1的元素值
public static void swap(int[] arr) {
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}
方法调用时,实参存放的地址被传递给方法中相对应的形参,因此形参和实参都指向堆中同一块地址,在方法执行中,对形参的操作实际上就是对实参的操作。所以在方法操作形式参数,那么方法外部的实参也会跟着一起改变。
3.1 获取数组的最值
需求:获取数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}的最大值,也就是该数组的元素90。
实现:先假设第一个元素就是最大值,并赋值给maxValue变量保存,然后依次取出数组中的元素和maxValue作比较,如果取出元素大于maxValue,那么把该元素设置为最大值。
【示例】
/**
获取数组的最大值
@param arr 需要查询的数组
@return 返回查询到的最大值
*/
public static int maxElement(int[] arr) {
// 假设第一个元素的值就是最大值
int max = arr[0];
// 遍历数组元素,依次和假设的最大值作比较
for(int i = 1; i < arr.length; i++) {
// 取出每个元素的值和value作比较
if(arr[i] > max) {
// 推翻假设,更新最大值
max = arr[i];
}
}
return max;
}
思考:获取数组中最大值的索引,我们该怎么去做呢?
3.2 通过值获取索引
需求:获取元素59在数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}中的索引。
实现:通过for循环来遍历数组,把需要查询的值和数组中的元素一一做比较,如果需要查询的值和某个元素相等,则返回索引值并结束方法。如果循环完毕都没查找到,则返回-1。
【示例】
/**
根据value值,获取它在数组中的索引位置
@param arr 需要查询的数组
@param value 需要判断的值
@return 找到,则返回对应的索引;未找到,则返回-1
*/
public static int search(int[] arr, int value) {
// 遍历数组,把数组中的元素依次和value作比较
for(int i = 0; i < arr.length; i++) {
// 取出元素值和value作比较
if(arr[i] == value) {
return i; // 找到相同的元素,返回索引位置
}
}
// 未找到,则返回-1
return -1;
}
3.3 数组元素的反转
需求:将数组元素反转,原数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72},反转后为{72, 59, 28, 45, 76, 77, 18, 90, 12, 5}。
实现(一):引入一个外部数组变量,用于保存反序后的数组,然后把原数组中的元素倒序保存于新创建的数组中。
【示例一】
/**
将数组反序输出
@param arr 需要反序的数组
@return 返回反序后的数组
*/
public static int[] reverseOrderArray(int[] arr) {
// 定义一个反序后的数组
int[] desArr = new int[arr.length];
// 把原数组元素倒序遍历
for(int i = 0; i < arr.length; i++) {
// 把arr的第i个元素赋值给desArr的最后第i个元素中
desArr[arr.length - 1 - i] = arr[i];
}
// 返回倒序后的数组
return desArr;
}
实现(二):直接对数组中的元素进行收尾交换。
【示例二】
/**
将数组反序输出
@param arr 需要反序的数组
*/
public static void reverseOrderArray(int[] arr) {
// 把原数组元素倒序遍历
for(int i = 0; i < arr.length/2; i++) {
// 把数组中的元素收尾交换
int temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = temp;
}
}
3.4 数组元素的删除
需求:删除数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}索引为2的元素,删除后:{5, 12, 18, 77, 76, 45, 28, 59, 72,0}。
实现:把数组索引2以后的元素向前移动一位,最后把数组的最后一个元素的值设置为默认值(整数类型的默认值为0)。
【示例】
/**
根据索引删除数组中的元素
@param arr 需要删除元素的数组
@param index 需要删除数组元素的索引
*/
public static void deleteElement(int[] arr, int index) {
// 第一步:判断索引是否合法
if(index < 0 || index >= arr.length) {
System.out.println(“索引越界”);
return; // 索引不合法,直接结束方法
}
// 第二步:从index个元素开始,将后一个元素向前移动一位
for(int i = index; i < arr.length - 1; i++) {
// 将后一个元素向前移动一位
arr[i] = arr[i + 1];
}
// 第三步:将最后一个元素设置为默认值
arr[arr.length - 1] = 0;
}
数组的缺点:因为数组是连续的内存空间,当数组进行删除和插入操作的时候,效率相对低下!
3.5 数组元素的插入
需求:在数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}索引为2的位置插入元素222,插入后:{5, 12, 222, 90, 18, 77, 76, 45, 28, 59, 72}。
实现:首先准备给数组扩容,然后把插入索引位置之后的元素往后移动一位,最后在插入索引的位置插入元素。
【示例】
/**
在数组指定位置插入元素
@param arr 需要插入元素的数组
@param index 插入元素的位置
@param value 需要插入的元素值
@return 返回插入元素成功的数组
*/
public static int[] insertElement(int[] arr, int index, int value) {
// 第一步:判断索引是否合法
if(index < 0 || index >= arr.length) {
System.out.println(“索引越界”);
// 抛出一个索引越界异常(异常第六章学习)。
throw new ArrayIndexOutOfBoundsException(“索引越界:”+index);
}
// 第二步:给数组扩容
// 定义一个变量,用于保存实际存放元素的个数
int size = 10;
// 如果数组的空间长度等于实际存放元素的个数,则执行扩容操作
if (arr.length == size) {
// 创建一个更大的数组
int[] newArr = new int[arr.length + 1];
// 把原数组中的数据,复制给新创建的数组
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];// 拷贝操作
}
// 让arr指向堆里面的newArr数组
arr = newArr;
}
// 第三步:插入索引位置之后的元素往后移动一位
for (int i = arr.length - 2; i >= 2; i–) {
arr[i + 1] = arr[i];
}
// 第四步:给index索引位置赋值
arr[index] = value;
// 返回插入元素成功的数组
return arr;
}
4.1 for-each循环遍历
for-each是java SE 5.0增加了一种功能很强的循环结构,可以用来一次处理数组中的每个元素(其他类型的元素集合亦可)而不必为指定下标值而分心。
这种增强的for-each循环的语法格式为:
for (type element : array) {
System.out.println(element); // 输出数组中的每一个元素
}
【示例】
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
// 通过增强for循环遍历数组
for(int element : arr) {
// 依次输出数组的元素
System.out.println(element);
}
}
优点:语法简洁,比普通for循环的效率高。
缺点:相比较普通for循环,增强for循环无法获得数组下标。
4.2 main方法的形参【了解】
参数String[] args的作用就是可以在main方法运行前将参数传入main方法中。
Ø 从控制台,输入编译执行命令时传参数
但是此时args[]并没有赋值,我们需要从控制台命令行进行赋值,就像这样:
Ø 在IDEA使用String[] args参数
出现以下窗口,在“Program arguments:”窗口中输入参数,最后点击Apply保存即可。
4.3 方法的可变参数
可变参数:适用于参数个数不确定,但类型确定的情况,java把可变参数当做数组处理。
我们使用…表示可变长参数,…位于变量类型和变量名之间,前后有无空格都可以。
可变参数的特点:
1、 一个方法中可变参数最多只能有一个,并且只能出现在参数列表的最后面。
2、 调用可变参数的方法时可以给出任意个参数,在方法体中以数组的形式访问可变参数。
Arrays用于操作数组工具类,里面定义了常见操作数组的静态方法。
注意:要使用Arrays工具类,必须导入Arrays工具类。
import java.util.Arrays;
5.1 toString方法
public static String toString(Type[] arr),返回指定数组内容的字符串表示形式。
【示例】
int[] arr = {3, 5, 1, 7, 6, 2, 4};
// 把数组转化为字符串输出
System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3, 4, 5, 6]
5.2 equals判断
public static boolean equals(Type[] a1, Type[] a2), 判断两个数组中的内容是否相同。
【示例】
int[] arr1 = {3, 5, 1, 7, 6, 2, 4};
int[] arr2 = {3, 5, 1, 7, 6, 2};
// 判断两个数组的内容是否相同
boolean flag = Arrays.equals(arr1, arr2);
System.out.println(flag); // 输出:false
5.3 sort排序
public static void sort(Type[] arr) ,对数组中的内容进行升序排序。
【示例】
int[] arr = {3, 5, 1, 7, 6, 2, 4};
// 对数组中的内容进行升序排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3, 4, 5, 6, 7]
5.4 二分法查找
public static int binarySearch(Type[] arr, Type key),查找key在数组中的索引位置。如果找到,则返回索引位置;如果没找到,则返回一个负数。
注意:在调用此调用之前,必须先对数组进行排序。
【示例】
// 排序好的数组
int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 查找6在数组中的索引位置
int index = Arrays.binarySearch(arr, 6);
System.out.println(index); // 输出:6
// 查找18在数组中的索引位置
index = Arrays.binarySearch(arr, 18);
System.out.println(index); // 输出:-11,证明没找到
5.5 fill填充数组
public static void fill(Type[] a, Type val),给数组填充指定内容。
【示例】
int[] arr = new int[5];
Arrays.fill(arr, 89);
System.out.println(Arrays.toString(arr)); // 输出:[89, 89, 89, 89, 89]
5.6 数组拷贝
public static Type[] copyOf(Type[] original, Type newLength),从数组的第一个元素开始拷贝,拷贝指定长度的数组,拷贝完成返回一个新的数组。
【示例】
int[] arr = {1, 2, 3, 4, 5};
int[] newArr = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(newArr)); // 输出:[1, 2, 3]
public static Type[] copyOfRange(Type[] original, int from, int to),从指定范围拷贝数组,拷贝完成返回一个新的数组。
【示例】
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
int[] newArr = Arrays.copyOfRange(arr, 2, 5);
System.out.println(Arrays.toString(newArr)); // 输出:[3, 4, 5]
6. 二维数组【了解】
6.1 二维数组的定义
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。(通俗来讲二维数组的每一个元素又是一个一维数组)
6.2 二维数组的创建
Ø 创建格式一,创建等长的二维数组
语法语法:数据类型[][] 数组名 = new 数据类型[m][n];
m: 表示这个二维数组的长度。
n: 表示二维数组中每个元素的长度。
注意以下格式也可以表示二维数组:
数据类型 数组名[][] = new 数据类型[m][n];
数据类型[] 数组名[] = new 数据类型[m][n];
Ø 创建格式二,创建不定长二维数组
语法格式:数据类型[][] 数组名 = new 数据类型[m][];
m: 表示这个二维数组的长度。
二维数组中元素的长度没有给出,可以动态的给。
Ø 创建格式三,创建静态二维维数组
基本格式:
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2…},{元素1,元素2…},{元素1,元素2…}};
简化格式:
数据类型[][] 数组名 = {{元素1,元素2…},{元素1,元素2…},{元素1,元素2…}};
【随堂练习】
1.有三个班级,第一个班级3个学生,第二个班级4个学生,第三个班级5个学生。要求通过键盘录入三个班级学生的成绩,并计算每个班级学生的的平均成绩。
7. debug调试基础
7.1 debug的概述
debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。
7.2 debug的用法
第一步:在可能会出现问题的位置打断点,也就是在行号右侧单击打断点
第二步:通过IDEA进入debug调试,例如:在main方法左侧行号位置,然后选择“Debug Xxx.main()”
进入debug调试后,代码直接在“断点”位置停止,此时debug窗口分两部分:
a)在“Frames”窗口,显示当前代码的停留位置(类、方法、行号)
b)在“Variabls”窗口,显示当前已经存在的变量(可以查看变量的值)
第三步:通过debug调试来控制代码的执行
F8:直接执行下一行代码。
F7:如果当前行代码有调用方法,则进入该方法
Shift+F8:结束当前方法,回到方法的调用位置
第四步:结束debug调试,首先取消断点(单击断点位置),然后结束程序,最后关闭debug窗口。