
一、Java方法1.什么是方法System.out.println()Java的方法是语句的集合它们一起执行一个功能方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建在其他地方被引用设计方法的原则方法的本意是功能块就是实现某个功能的语句块的集合。我们设计方法的时候最好保持方法的原子性就是一个方法只完成 1 个功能这样利于我们后期的扩展。2.方法的定义及调用重要方法的定义◆ Java的方法类似于其它语言的函数是一段用来完成特定功能的代码片段一般情况下定义一个方法包含以下语法◆方法包含一个方法头和一个方法体。 下面是一个方法的所有部分修饰符修饰符这是可选的告诉编译器如何调用该方法。定义了该方法的访问类型。返回值类型方法可能会返回值。returnValueType 是方法返回值的数据类型。有些方法执行所需的操作但没有返回值。在这种情况下returnValueType 是关键字void。方法名是方法的实际名称。方法名和参数表共同构成方法签名。参数类型参数像是一个占位符。当方法被调用时传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的方法可以不包含任何参数。形式参数在方法被调用时用于接收外界输入的数据。实参调用方法时实际传给方法的数据。方法体方法体包含具体的语句定义该方法的功能。修饰符 返回值类型 方法名(参数类型 参数名){ ... 方法体 ... return 返回值; }3.方法重载重要重载就是在一个类中有相同的函数名称但形参不同的函数。方法的重载的规则方法名称必须相同。参数列表必须不同个数不同、或类型不同、参数排列顺序不同等。方法的返回类型可以相同也可以不相同。仅仅返回类型不同不足以成为方法的重载。实现理论方法名称相同时编译器会根据调用方法的参数个数、参数类型等去逐个匹配以选择对应的方法如果匹配失败则编译器报错。下面用一段代码演示方法重载同一个类中定义了两个同名方法max一个接收double参数一个接收int参数编译器会根据传入实参的类型自动匹配对应的方法。package com.fan.method; public class Demo02 { public static void main(String[] args) { int max max(10, 10); System.out.println(max); System.out.println(max(4, 6)); } // 比大小double 版本 public static double max(double num1, double num2) { double result 0; if (num1 num2) { System.out.println(num1num2); return 0; } if (num1 num2) { result num1; } else { result num2; } return result; } // 比大小int 版本 public static int max(int num1, int num2) { int result 0; if (num1 num2) { System.out.println(num1num2); return 0; } if (num1 num2) { result num1; } else { result num2; } return result; } }要点说明两个max方法名称相同但参数类型不同double与int符合重载规则。调用max(10, 10)时传入的是两个int字面量编译器会匹配int版本返回值为int可以直接赋给int max。调用max(4, 6)同样匹配int版本输出结果为 6。如果传入max(4.0, 6.0)则会匹配double版本返回值为double类型。两个方法在参数相等时都会打印num1num2并返回 0这是重载方法各自独立的实现互不影响。重载只与参数列表有关与返回值类型无关如果两个方法只有返回值类型不同编译器会直接报错。要以理解为主后续会经常使用4.命令行传参命令行传参指的是在运行程序时通过命令行把参数传递给main方法。前面已经看到main方法的签名是public static void main(String[] args)其中的String[] args就是用来接收命令行参数的字符串数组。先写一个最简单的示例把接收到的参数逐个打印出来package com.fan.method; public class Demo03 { public static void main(String[] args) { // args 数组用来接收命令行参数 for (int i 0; i args.length; i) { System.out.println(args[ i ] args[i]); } } }运行步骤先编译源文件在源码所在目录执行javac Demo03.java生成Demo03.class。回到包所在的根目录即com目录的上一级执行java com.fan.method.Demo03 参数1 参数2。控制台会依次输出args[0]参数1、args[1]参数2。要点说明命令行参数以空格分隔每个参数对应args数组中的一个元素参数本身包含空格时需要用引号包裹。运行时必须写类的全限定名包名 类名不能直接写java Demo03否则会提示找不到或无法加载主类。如果没有传入任何参数args.length为 0循环体不会执行程序正常结束。直接使用args[0]取值前最好先判断args.length避免数组下标越界异常。在 IDEA 中调试时可以在运行配置的 Program arguments 一栏填写参数效果与命令行传参一致。5.可变参数重要在方法声明中在指定参数类型后加一个省略号 (...)。一个方法中只能指定一个可变参数它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。可变参数也叫可变长参数允许调用方法时传入任意个数的同类型实参方法内部会把这些实参自动封装成一个数组来处理。它的语法是在参数类型后面加三个点...例如int... i、double... numbers。先看第一个示例定义一个接收可变参数的方法调用时传入 5 个整数方法内部按数组下标取值。package com.fan.method; public class Demo04 { public static void main(String[] args) { Demo04 demo04 new Demo04(); demo04.test(1, 2, 32, 16, 16); } public void test(int... i) { // 可变长参数必须放在最后 System.out.println(i[0]); System.out.println(i[1]); System.out.println(i[2]); System.out.println(i[3]); System.out.println(i[4]); } }要点说明调用test(1, 2, 32, 16, 16)时这 5 个实参会被自动打包成int[]数组方法内部可以像操作普通数组一样用i[0]、i[1]取值。可变参数本质上就是一个数组因此test(new int[]{1, 2, 32, 16, 16})这种直接传数组的写法也是合法的。可变参数必须是参数列表中的最后一个参数否则编译报错一个方法中最多只能有一个可变参数。如果可变参数前面还有普通参数普通参数必须写在可变参数之前例如test(String name, int... i)。方法内部按下标取值时要注意数组长度传入的实参个数不足会抛出数组下标越界异常。第二个示例更实用在任意个double数中找出最大值演示可变参数配合循环遍历的常见用法。package com.fan.method; public class Demo05 { public static void main(String[] args) { printMax(15, 321.1, 15.0, 15.3); } public static void printMax(double... numbers) { if (numbers.length 0) { System.out.println(No argument passed); return; } double result numbers[0]; // 排序 for (int i 1; i numbers.length; i) { if (numbers[i] result) { result numbers[i]; } } System.out.println(The max value is result); } }要点说明调用printMax(15, 321.1, 15.0, 15.3)时4 个实参被封装成double[]数组numbers.length为 4。先判断numbers.length 0没有传参时直接提示并return避免后面访问numbers[0]时越界。把第一个元素numbers[0]作为初始最大值再从下标 1 开始逐个比较遇到更大的值就更新result这是求最值的通用写法。本例输出结果为The max value is 321.1。可变参数让方法调用更灵活不用提前创建数组也不用为不同参数个数写多个重载方法。6.递归A 方法调用 B 方法我们很容易理解递归就是A 方法调用 A 方法就是自己调用自己利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。递归结构包括两个部分递归头什么时候不调用自身方法。如果没有头将陷入死循环。递归体什么时候需要调用自身方法。递归的核心是「自己调用自己」但必须要有终止条件否则会不断压栈直到栈空间耗尽。下面用求阶乘的例子来演示递归的完整写法。package com.fan.method; public class Demo06 { public static void main(String[] args) { Demo06 test new Demo06(); // StackOverflowError 栈溢出异常 System.out.println(f(6)); } public static int f(int n) { // n 的阶乘 if (n 1) { return 1; } else { return n * f(n - 1); } } }要点说明if (n 1) return 1;就是递归头也叫边界条件它决定了递归什么时候停止。没有递归头方法会一直调用自身最终抛出StackOverflowError栈溢出异常。return n * f(n - 1);就是递归体把规模为 n 的问题转化为规模为 n-1 的同类问题逐步向递归头靠近。以f(6)为例执行过程是6 * f(5)→6 * 5 * f(4)→ … →6 * 5 * 4 * 3 * 2 * f(1)最终返回 720。每次递归调用都会在栈中压入一个栈帧递归层数过深例如把参数改成很大的数就会导致栈溢出所以递归必须有明确的终止条件且递归深度不宜过大。递归能用少量代码描述重复计算但性能上不如循环能改写为循环的场景优先考虑循环。本例中Demo06 test new Demo06();创建了对象但并未使用因为f是静态方法可以直接通过类名调用写成Demo06.f(6)即可。小练习下面用前面学过的方法定义、方法调用和 switch 语句写一个简易的控制台计算器支持加、减、乘、除四种运算输入 0 退出程序。package com.fan.method; import java.util.Scanner; public class Demo07 { static double add(double a, double b) { return a b; } static double sub(double a, double b) { return a - b; } static double mul(double a, double b) { return a * b; } static double div(double a, double b) { return a / b; } public static void main(String[] args) { Scanner s new Scanner(System.in); while (true) { System.out.print(1 2- 3* 4/ 0退出请输入); int op s.nextInt(); if (op 0) break; System.out.print(数1); double a s.nextDouble(); System.out.print(数2); double b s.nextDouble(); double r 0; switch (op) { case 1: r add(a, b); break; case 2: r sub(a, b); break; case 3: r mul(a, b); break; case 4: if (b 0) { System.out.println(不能除0); continue; } r div(a, b); } System.out.println(结果 r); } s.close(); } }要点说明四个运算方法都声明为static因此可以在main方法中直接按方法名调用不需要先创建Demo07对象。用while (true)配合if (op 0) break;实现循环菜单输入 0 时跳出循环并结束程序。switch根据运算符编号分发到对应的方法每个case处理完要写break否则会继续执行后面的分支。除法分支先判断b 0为 0 时提示「不能除0」并用continue跳过本次循环避免出现Infinity或异常。四个方法都返回double所以统一用double r接收结果整数运算也会自动提升为double输出。程序结束时调用s.close()关闭Scanner释放输入流资源这是良好的编程习惯。可以进一步扩展把四个方法改成重载形式或增加取余、幂运算等分支练习方法定义与 switch 的组合使用。二、Java数组1.什么是数组数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据按照一定的先后次序排列组合而成。其中每一个数据称作一个数组元素每个数组元素可以通过一个下标来访问它们。举例来说如果声明一个保存 5 个学生成绩的数组那么这 5 个成绩在内存中是连续存放的可以通过scores[0]、scores[1]一直到scores[4]来访问下标从 0 开始计数。2.数组声明创建使用数组需要两步先声明一个数组变量再为它创建分配实际的内存空间。// 声明数组的两种写法 int[] nums; // 推荐写法类型和变量名分离更清晰 int scores[]; // C/C 风格Java 也支持但不推荐 // 创建数组使用 new 关键字分配空间 nums new int[10]; // 创建一个长度为 10 的 int 数组元素默认值为 0 // 声明和创建合并写法 int[] arr new int[5]; // 声明的同时初始化 int[] a {1, 2, 3, 4, 5}; int[] b new int[]{1, 2, 3, 4, 5};要点说明声明数组时只是定义了一个引用变量此时并没有分配数组元素的内存空间变量值为null。使用new创建数组后JVM 才会在堆中分配连续的内存空间并把数组对象的引用赋给变量。数组长度在创建时通过new int[10]中的数字指定也可以通过数组名.length获取。创建后如果没有显式赋值元素会有默认值整型为 0浮点型为 0.0布尔型为 false引用类型为 null。数组下标从 0 开始长度为 n 的数组合法下标范围是 0 到 n-1越界会抛出ArrayIndexOutOfBoundsException。3.数组的四个基本特点长度是确定的数组一旦被创建它的大小就是不可以改变的。元素类型必须相同不允许出现混合类型一个int[]中不能存放String。元素可以是任何数据类型包括基本类型和引用类型例如int[]、double[]、String[]、Object[]。数组变量属引用类型数组也可以看成是对象数组中的每个元素相当于该对象的成员变量。数组本身就是对象Java 中对象是在堆中的因此数组无论保存原始类型还是其他对象类型数组对象本身是在堆中的。4.Java 内存分析理解数组的存储方式需要先了解 Java 运行时数据区的三个核心部分堆Heap存放new出来的对象和数组由 JVM 统一管理所有线程共享是垃圾回收的主要区域。栈Stack存放方法调用时的局部变量、基本类型变量和对象的引用每个线程有独立的栈方法执行完自动释放。方法区Method Area存放类的元信息、常量、静态变量等所有线程共享。下面用一段代码演示数组在内存中的分布package com.fan.array; public class Demo01 { public static void main(String[] args) { int[] nums new int[3]; // 栈中 nums 保存引用堆中分配 3 个 int 空间 nums[0] 1; nums[1] 2; nums[2] 3; // 数组也是对象可以调用 length 属性 System.out.println(nums.length); // 输出 3 // 引用赋值两个变量指向同一个堆中的数组对象 int[] nums2 nums; nums2[0] 100; System.out.println(nums[0]); // 输出 100 } }要点说明int[] nums new int[3];执行时栈中保存变量nums堆中分配一块连续空间存放 3 个 int 元素nums中保存的是这块空间的首地址。nums2 nums只是把引用复制了一份两个变量指向同一个数组对象因此通过nums2修改元素nums看到的也是修改后的值。数组对象本身在堆中方法结束后栈中的局部变量被回收堆中的数组对象由垃圾回收器在合适时机回收。如果数组变量没有指向任何对象例如int[] arr null;访问arr.length会抛出NullPointerException。5.数组使用数组最常见的操作是遍历、求最值和作为方法参数传递。package com.fan.array; public class Demo02 { public static void main(String[] args) { int[] arrays {1, 2, 3, 4, 5}; // 1. 普通 for 循环遍历可以拿到下标 for (int i 0; i arrays.length; i) { System.out.println(arrays[ i ] arrays[i]); } // 2. 增强 for 循环for-each适合只取值不需要下标 for (int array : arrays) { System.out.println(array); } // 3. 数组作为方法参数 printArray(arrays); // 4. 求最大值 System.out.println(最大值 max(arrays)); } // 打印数组 public static void printArray(int[] arrays) { for (int i 0; i arrays.length; i) { System.out.print(arrays[i] ); } System.out.println(); } // 求数组最大值 public static int max(int[] arrays) { int result arrays[0]; for (int i 1; i arrays.length; i) { if (arrays[i] result) { result arrays[i]; } } return result; } }要点说明普通for循环通过下标访问元素适合需要下标的场景例如倒序输出、按位置修改元素。增强for循环写法更简洁但拿不到下标也不能在循环中修改数组元素的值。数组作为方法参数传递的是引用方法内部修改元素会影响原数组。求最大值时先把arrays[0]作为初始值再从下标 1 开始比较避免把初始值写成 0 导致全负数数组结果错误。遍历前要保证数组不为null否则arrays.length会抛出空指针异常。6.多维数组多维数组可以看成是数组的数组最常见的是二维数组例如一个班级多名学生多门课的成绩表。package com.fan.array; public class Demo03 { public static void main(String[] args) { // 二维数组3 行 2 列 int[][] arrays {{1, 2}, {3, 4}, {5, 6}}; // 遍历二维数组 for (int i 0; i arrays.length; i) { for (int j 0; j arrays[i].length; j) { System.out.print(arrays[i][j] ); } System.out.println(); } // 动态创建二维数组 int[][] nums new int[3][4]; // 3 行 4 列元素默认值为 0 System.out.println(nums[0][0]); // 输出 0 } }要点说明int[][] arrays可以理解为「数组的数组」arrays.length是行数arrays[i].length是第 i 行的列数。二维数组的每一行长度可以不同例如int[][] a new int[3][];之后再分别给每行分配长度这就是不规则数组。遍历二维数组需要两层循环外层控制行内层控制列。创建二维数组时只指定行数不指定列数此时每行是null直接访问元素会抛出空指针异常。7.Arrays 类java.util.Arrays是 JDK 提供的数组工具类封装了排序、查找、比较、填充、转字符串等常用操作。package com.fan.array; import java.util.Arrays; public class Demo04 { public static void main(String[] args) { int[] a {1, 5, 3, 9, 7}; // 1. 打印数组内容 System.out.println(Arrays.toString(a)); // 2. 排序默认升序 Arrays.sort(a); System.out.println(Arrays.toString(a)); // 3. 二分查找必须先排序 int index Arrays.binarySearch(a, 5); System.out.println(5 的下标 index); // 4. 填充 int[] b new int[5]; Arrays.fill(b, 8); System.out.println(Arrays.toString(b)); // 5. 比较两个数组内容是否相等 System.out.println(Arrays.equals(a, b)); } }要点说明Arrays.toString()可以直接把数组内容格式化为[1, 3, 5, 7, 9]的形式比手写循环方便。Arrays.sort()对基本类型数组使用双轴快速排序对引用类型数组使用归并排序默认都是升序。Arrays.binarySearch()使用二分查找前提是数组已经排好序否则结果不确定。Arrays.fill()可以把数组所有元素填充为同一个值常用于初始化。Arrays.equals()比较的是数组内容而直接用比较的是两个数组的引用地址。8.稀疏数组当一个数组中大部分元素为 0 或为同一值时可以使用稀疏数组来保存从而压缩存储空间。稀疏数组的处理方式是记录数组一共有几行几列、有多少个不同的值然后把具有不同值的元素的行、列、值记录在一个小规模的数组中。package com.fan.array; public class Demo05 { public static void main(String[] args) { // 1. 创建一个 11 行 11 列的棋盘0 表示空1 表示黑子2 表示白子 int[][] chessArr new int[11][11]; chessArr[1][2] 1; chessArr[2][3] 2; // 2. 统计非 0 元素个数 int sum 0; for (int[] row : chessArr) { for (int data : row) { if (data ! 0) { sum; } } } // 3. 创建稀疏数组第一行记录原始数组的行数、列数、有效值个数 int[][] sparseArr new int[sum 1][3]; sparseArr[0][0] 11; sparseArr[0][1] 11; sparseArr[0][2] sum; // 4. 遍历原始数组把非 0 元素的行、列、值存入稀疏数组 int count 0; for (int i 0; i 11; i) { for (int j 0; j 11; j) { if (chessArr[i][j] ! 0) { count; sparseArr[count][0] i; sparseArr[count][1] j; sparseArr[count][2] chessArr[i][j]; } } } // 5. 输出稀疏数组 for (int i 0; i sparseArr.length; i) { System.out.println(sparseArr[i][0] \t sparseArr[i][1] \t sparseArr[i][2]); } } }要点说明稀疏数组的第一行固定记录原始数组的行数、列数和有效值个数后面的每一行记录一个有效值的行下标、列下标和值。稀疏数组的行数为「有效值个数 1」列数固定为 3。稀疏数组适合有效数据远少于总容量的场景例如棋盘、地图、矩阵如果数组元素大部分都有值使用稀疏数组反而更占空间。稀疏数组可以保存到文件中实现数据的持久化读取时再还原成原始二维数组。还原时先读取第一行得到原始数组的行列数再按后续每行的行列下标把值填回去。9.冒泡排序冒泡排序Bubble Sort是最经典的排序算法之一也是理解「双重循环 元素交换」的最佳入门案例。它的名字来源于排序过程中较大的元素会像气泡一样一点点「浮」到数组末尾。算法思路比较数组中两个相邻的元素如果第一个数比第二个数大就交换它们的位置。每一轮比较结束后都会产生一个最大或最小的数被放到数组的末尾。下一轮比较时末尾已经排好的元素不再参与因此比较次数少一次。重复上述过程直到没有元素需要交换为止。下面用一段完整代码演示冒泡排序先用Arrays.toString()打印初始数组再调用自己写的sort方法排序最后打印排序后的结果。package com.fan.array; import java.util.Arrays; public class Demo08 { public static void main(String[] args) { int[] a {1, 15, 15, 6, 468, 13, 16, 156, 651, 65, 165}; System.out.println(Arrays.toString(a)); // 初始的 a 数组 int[] sort sort(a); // 调用自己写的排序算法 System.out.println(Arrays.toString(sort)); // 使用自己写的排序算法后的 a 数组 } // 冒泡排序 // 1. 比较数组中两个相邻的元素如果第一个数比第二个数大交换他们的位置 // 2. 每次比较都会产生一个最大或者一个最小数 // 3. 下一次就少一次排序 // 4. 循环到结束 public static int[] sort(int[] a) { int temp 0; // 外层循环判断我们这个要走多少次 for (int i 0; i a.length - 1; i) { boolean flag false; // 内层循环比较两个数如果第一个数比第二个数大则交换位置 for (int j 0; j a.length - 1 - i; j) { if (a[j] a[j 1]) { temp a[j]; a[j] a[j 1]; a[j 1] temp; flag true; } } if (flag false) { break; } } return a; } }要点说明外层循环for (int i 0; i a.length - 1; i)控制比较的轮数长度为 n 的数组最多需要 n-1 轮。内层循环for (int j 0; j a.length - 1 - i; j)控制每轮比较的次数- i表示每轮结束后末尾已经排好的元素不再参与比较。a[j] a[j 1]是升序判断条件如果改成就变成降序排序。交换两个元素需要借助临时变量temp先把a[j]存起来再依次赋值否则会丢失数据。flag是优化标记如果某一轮比较中没有发生任何交换说明数组已经有序直接break跳出循环减少不必要的比较。冒泡排序的时间复杂度为 O(n²)空间复杂度为 O(1)数据量较大时效率不高实际开发中更常用Arrays.sort()。冒泡排序是稳定的排序算法相等元素在排序前后的相对位置不会改变。执行过程示例以数组{3, 1, 2}为例第一轮比较3和1交换得到{1, 3, 2}再比较3和2交换得到{1, 2, 3}此时最大值 3 已经「浮」到末尾第二轮只需比较前两个元素发现已经有序flag保持false循环提前结束。三、面向对象四、异常
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。