方法与数组
方法
什么是方法?
方法就是一个代码片段,Java的方法类似于C语言的函数,方法的意义在于:
- 能够模块化地组织代码
- 能偶做到代码被重复使用,而不需要重新写一份,一份代码可以在多个位置使用
- 便于理解代码,增加代码可读性
方法的定义
方法的语法格式
修饰符 返回值类型 方法名称(参数类型 形参……){
方法具体代码;
[return 返回值];
}
与C语言对比,Java的方法定义多了修饰符,且代码风格规定花括号不换行。
我们实现一个简单的两个整数的加法方法:
public static int add(int x, int y){
return x + y;
}
注意:
- Java学习初期,修饰符固定写
public static
即可,这部分知识从类和对象部分开始 - 返回值类型必须与返回的实体类型一致,如果没有返回值,返回类型处写成
void
- 方法命名建议采用小驼峰命名方式,即从第二个单词开始,首字母大写,例如
sumOfScore
- 在Java中方法必须写在类中
- Java中方法不能嵌套定义
- Java中没有方法声明一说,如果将方法的定义写在了调用前,也是没问题的
方法的执行
方法的调用过程:
调用方法——>传递参数——>找到方法地址——>执行被调方法的方法体——>被调方法结束返回——>回到主调方法继续往下执行
实参与形参
与C语言一样,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。
在Java中,实参永远都是拷贝到形参中,形参和实参本质是两个实体
以交换两数为例:
public static void swap(int x, int y){
int tmp = x;
x = y;
y = tmp;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println("交换前" + "a:" + a + " " + "b:" + b);
swap(a, b);
System.out.println("交换后" + "a:" + a + " " + "b:" + b);
}
【分析】
实参a和b是main方法中的两个变量,其空间在main方法的栈中,而形参x和y是swap方法中的两个变量,x和y的空间在swap方法运行时的栈中,因此:a和b与x和y是没有任何关联的。
在swap方法调用时,只是将实参a和b中的值拷贝了一份传递给了形参x和y,所以对形参x和y的操作对实参a和b没有影响。
对于基础类型来说,形参相当于实参的拷贝,即传值调用。那么我们怎么使用方法来交换两个变量的值呢?
传引用类型参数(例如数组):
public static void swap(int[] array){
int tmp = array[0];
array[0] = array[1];
array[1] = tmp;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int[] array = new int[2];
array[0] = scanner.nextInt();
array[1] = scanner.nextInt();
System.out.println("交换前:" + array[0] + " " + array[1]);
swap(array);
System.out.println("交换前:" + array[0] + " " + array[1]);
}
方法重载
用一种情景引入:
public static int add(int x, int y) {
return x + y;
}
如上,我们实现了一个整数加法方法。如果我们这时候想求两个double
类型数据的和,上面的方法不适用了,我们该怎么办?
首先想到的是再实现一个方法,针对double
类型,这确实可以解决问题,麻烦的一点是,我们需要提供许多不同的方法名,调用时还需要考虑调用哪个,而这些方法的逻辑都是相加。针对这个问题,Java提供了方法重载,允许将上述方法名都写成同一个,例如add
方法重载的概念
在Java中,如果多个方法的名字相同,参数列表不同,则称该几种方法被重载了。
【代码示例】
public static int add(int x, int y) {
return x + y;
}
public static double add(double x, double y){
return x + y;
}
public static int add(int x, int y, int z){
return x + y + z;
}
public static void main(String[] args) {
System.out.println(add(10, 20));
System.out.println(add(1.5, 2.5));
System.out.println(add(10, 20, 30));
}
没有报错,结果也没有问题。
在IDEA上也会有相应的提示。
什么情况下叫方法的重载?
- 方法名一样
- 参数列表不一样,参数的【个数、类型、顺序】
- 与返回值无关(也就是说,只有返回值不一样,两个方法不能称为构成重载)
- 这样的若干个方法互为方法的重载
编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法。
方法签名
在同一个作用域中不能定义两个相同的标识符。那么为什么类中就可以定义方法名相同的方法呢?
因为有方法签名,重载方法的函数名虽然相同,但是它们的方法签名不同。
方法签名即,经过编译器编译修改后的最终的名字。完整名字包含:
方法全路径名 + 参数列表 + 返回值类型
我们可以使用JDK自带的javap反汇编工具查看,具体操作:
- 在源代码目录打开
cmd
- 使用
javac
编译生成.class
文件,如果源代码有汉字,那么要统一编码 - 输入:
javap -v 字节码文件名
完成后寻找签名:
-
work
是类名 -
add
是方法名 -
括号里的内容代表方法的参数列表信息
-
括号后面代表返回值信息
-
如图,
I
表示int
类型,D
表示double
类型,更多的信息如下:
数组
学过C语言的小伙伴应该对数组了解不少也使用了很多吧,接下来我们介绍一下Java中的数组。
创建与初始化
数组的创建
C语言中,创建一个数组通常这么写:
int array[10];
C语言形式有以下特点:
- 数组名在方括号的左边
- 如果不初始化,方括号中必须给定数组大小,且必须是整型常量
array是数组名,类型认为是int[10]
,这与我们创建变量的规则类型 + 变量名
不符。所以,Java不倾向于这样写。
Java创建一个数组有以下常见写法:(注意事项均在注释中给出)
int array1[];//与C语言的风格类似,不过Java中的[]中不能写数字,要是空的,否则报错
int[] array2;//第三种的简写版,符合 类型+变量名 的习惯,[]中仍然不能写数字,否则报错
int[] array3 = new int[10];//看起来最难懂的,是第二种的完整版,前面的[]必须为空,后面的[]中不能为空,且这种写法不能直接花括号初始化,具体怎么初始化见数组的初始化的内容。
数组的初始化
常见的初始化方式有三种:
int array1[] = {1, 2, 3};
int[] array2 = {1, 2, 3};
int[] array3 = new int[]{1, 2, 3};
- 前两种初始化都是基于创建的前两种进行初始化的
- 第三种初始化与上面第三种创建方式相似,需要注意的是:
- 第二个
[]
必须为空 - 第二个方括号后不需要加
=
,直接花括号初始化即可
- 第二个
了解到这,我们看一下第三种的错误写法,这里很易混
int array[] = new int[3] = {1, 2, 3};//ERROR,错误的,第二个方括号必须是空
int array[] = new int[];//ERROR,错误的,没有初始化,第二个方括号不能为空
如果我们没有对数组进行初始化,数组中的元素有其默认值:
-
如果数组中存储的是基本类型,默认值如下表:
-
如果数组中元素存储的是引用类型,默认值是
null
不过,在Java中也允许使用C语言的书写方式,这并不会报错
数组的使用
【数组元素的访问】
与C语言一样,Java中的数组是一块连续的空间,空间的编号都是从0开始,该编号称为数组的下标,通过下标就可以访问数组元素了,所以对于有5个元素的数组,它的元素下标从0 ~ 4。
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[4]);
}
【数组的遍历】
我们介绍三种数组遍历的方式,它们均能打印出所有的数组元素。
int[] array = new int[]{1, 2, 3, 4, 5};
//1.for循环遍历
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
//2.foreach循环遍历,增强型for循环
for (int x:array) {
System.out.print(x + " ");
}
//3.导入包,使用 Arrays.toString
import java.util.Arrays;
System.out.println(Arrays.toString(array));
我们看到三种方式都将数组元素打印出来了。
-
我们使用
for
循环打印时,要考虑循环次数,就是数组容量大小,Java中不存在C语言的sizeof
,Java中通过数组对象.length
获取数组长度,如上面的代码所示。 -
foreach
更方便,不过功能没有for
循环强大。for
后面的括号中有一个:
,它的右边是集合的名称,左边是集合元素类型+变量名(自定义的)。不过,在需要使用到下标(索引)的情景下,foreach
循环就无能为力了。 -
第三种方式是将数组元素转换成字符串类型,然后进行打印,打印的格式是固定如此的。
数组的类型
数组是引用类型,引用类型不直接存储数据值,而是存储对数据的引用的类型。
说到引用类型,不得不先了解JVM的内存划分:
我们发现这与C语言的内存划分不同。
我们这里仅理解 运行时数据区 的 虚拟机栈 和 堆 即可。
-
虚拟机栈就是我们C语言学习过程中常说的栈:
存储与方法调用相关的信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含:局部变量表、操作数栈、动态链接、返回地址等其他信息。比如局部变量。当方法运行结束后,即栈帧中保存的数据也会被销毁
-
堆与C语言的堆不同,C语言动态开辟的内存都是在堆上,而Java的堆:
JVM所管理的最大内存区域,使用
new
创建的对象都是在堆上保存,堆是随着程序开始运行而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁
引用数据类型创建的变量,一般称作对象的引用,其空间中存储的是对象所在空间的地址。
我们观察一个代码:
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5};
System.out.println(array);
}
打印结果如下:
对于这个打印结果:
[
表示 引用变量array指向的是一个数组对象I
表示其指向的数组对象的每个元素都是int
类型@
是分割符1b6d3586
可以理解为 “地址”
看下面段代码:
public static void Test(){
int a = 10;
int b = 20;
int[] array = new int[]{1, 2, 3, 4, 5}
}
调用Test
函数时,会为Test
方法在栈上创建一个栈帧,Test
栈帧内创建了局部变量a
、b
、array
,由于a
、b
都是基本类型,所以在栈区存储的就是其数据,array
是引用类型,所以它存储的是指向数组对象的"地址",引用变量array
创建时,同时会在堆区开辟一块空间,这块空间存储数组对象的数据。array
变量存储的"地址"指向堆上指定位置的数组对象,我们常说:“引用指向了对象”。
再看一段代码:
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[]{6, 7, 8, 9, 0};
array1 = array2;
}
这段代码中array1 = array2
的效果是什么?这是引用变量的相互赋值。
效果是:array1
引用指向了array2
引用指向的对象。
我们通过代码验证一下:
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[]{6, 7, 8, 9, 0};
array1 = array2;
//赋值后打印验证
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
System.out.println(array1);
System.out.println(array2);
}
引用指向对象,我们可以将一个引用的值赋给另一个引用,就是 “一个引用指向了另一个引用指向的对象”,这时候,其中一个对象没有引用指向它了,Java会自动检测没有引用指向的对象,它在堆上的内存会被自动回收。
空引用
null
就是空引用,我们可以给引用变量初始化为空引用。
null
就是不指向任何对象的引用,与C语言的NULL
类似,都是指一块无效的内存地址。
数组应用
我们这里介绍的是Java内部实现好的功能(类的成员方法),对于数组的这些功能,我们导入包中的Arrays
类,便可以直接使用了。
转字符串
介绍遍历数组时我们已经介绍了这一应用:
Arrays.toString()
传入数组即可。
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5};
System.out.println(Arrays.toString(array));
}
验证转换后的结果是String
类型,我们用一个String
类型的变量接收,不会报错。
排序
Arrays.sort()
public static void main(String[] args) {
int[] array = new int[]{9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
System.out.println("排序前:");
System.out.println(Arrays.toString(array));
System.out.println("排序后:");
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
- 此方法默认排升序
查找(二分)
Arrays.binarySearch()
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5};
int ret = Arrays.binarySearch(array, 2);
System.out.println("查找后的下标为: " + ret);
}
当然,Java中还重载了很多的有关二分查找的类方法,例如:不同类型数据的查找、指定范围内查找
我们再介绍一下指定范围内查找的方法:
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5};
int ret = Arrays.binarySearch(array, 0, 2, 3);
System.out.println("在下标0~1之间查找:" + ret);
}
这里我们介绍两点:
-
指定范围内查找,参数有4个,分别是数组、开始点、结束点、待查找值
值得注意的是,按指定区间查找都是[)
的, 如果我们想在下标为1~3的区间查找,我们传入的起始值分别要是:1、4 -
为什么返回值是-3?
首先-3肯定代表查找失败,此方法内部实现的查找失败的返回值是:
return -(low + 1)
,low
就是我们自己实现二分的left
填充
Arrays.fill()
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("填充前: " + Arrays.toString(array));
Arrays.fill(array, 5);
System.out.println("填充后: " + Arrays.toString(array));
}
当然,我们也可以在指定范围内填充,传入的起始点也是遵循[)
的
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("填充前: " + Arrays.toString(array));
Arrays.fill(array, 1, 5, 5);
System.out.println("填充后: " + Arrays.toString(array));
}
拷贝
Arrays.copyOf()
这个类的方法有两个作用:1. 拷贝 2. 扩容
另外,指定范围内拷贝:
Arrays.copyOfRange()
【拷贝功能】
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[5];
System.out.println("拷贝前:" + Arrays.toString(array2));
array2 = Arrays.copyOf(array1, 5);
System.out.println("拷贝后:" + Arrays.toString(array2));
}
【扩容功能】
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[5];
System.out.println("扩容前:" + Arrays.toString(array2));
array2 = Arrays.copyOf(array1, 10);
System.out.println("扩容后:" + Arrays.toString(array2));
}
【指定范围内拷贝】
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[5];
System.out.println("拷贝前:" + Arrays.toString(array2));
array2 = Arrays.copyOfRange(array1, 1, 3);
System.out.println("拷贝下标1~2的元素后:" + Arrays.toString(array2));
}
此外,还有一种数组拷贝的方法:
System.arraycopy()
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[5];
System.out.println("拷贝前:" + Arrays.toString(array2));
System.arraycopy(array1, 0, array2, 0, 3);
System.out.println("拷贝后:" + Arrays.toString(array2));
}
System.arraycopy(array1, 0, array2, 0, 3);
语句的含义是:从array1
数组的 0 下标位置开始拷贝 3 个元素到 array2
数组,从 0 位置开始。
- 四个参数:待拷贝数组、拷贝起始位置、待存放数组、存放的起始位置、拷贝的元素个数
判等
Arrays.equal()
public static void main(String[] args) {
int[] array1 = new int[]{1, 2, 3, 4, 5};
int[] array2 = new int[]{1, 2, 3, 4, 5};
System.out.println(Arrays.equals(array1, array2));
}
我们判断两个数组相等不能简单的使用==
实现,因为数组名存放的是"地址"。
我们可以调用这样一个方法实现判等操作。
二维数组
二维数组本质上就是一维数组的数组,其每一个元素都是一维数组
创建及初始化
//创建方法一 :规则与一维数组一样,后两个[]内必须有内容,不能为空
数据类型[][] 数据类型 = new 数据类型[行数][列数];//默认都是0
//创建方法一并初始化 :此时所有的[]都必须为空
数据类型[][] 数组名称 = new 数据类型[][]{初始化};
//创建方法二 :第一种的简写
数据类型[][] 数组名称;
//创建方法二并创建 :
数据类型[][] 数组名称 = {初始化};
我们继续将Java与C语言对比:
-
语句开始的两个
[]
中不能添加内容 -
C语言中定义二维数组时可以省略行数,而Java中可以省略列数。
这里针对的是第一种创建方式并初始化的情况,后两个
[]
,这里有一个知识点,就是不规则数组 -
由于C语言不可省略列数,所以初始化时可以在
{}
中直接罗列数据,C语言会帮我们区分行列;而Java中不允许,必须在{}
内部再加{}
来手动区分行数和列数
遍历
双循环遍历即可打印
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
利用Arrays.deepToString()
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
System.out.println(Arrays.deepToString(array));
}
foreach
循环
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
for (int[] arraytmp : array) {
for (int data : arraytmp) {
System.out.print(data + " ");
}
System.out.println();
}
}
现在有两个问题:
-
foreach
遍历虽然不需要行数和列数,但是它是有局限性的。如果我们采用第一种方式遍历,我们怎么拿到二维数组的行数和列数呢?每次都要自己数吗?当然不用,有办法,就像遍历一维数组时的
array.length
2.deepToString
能遍历我了解了,前面一维数组介绍的toString
不行吗?
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
System.out.println(Arrays.toString(array));
}
想要真正理解上面的问题,我们得了解二维数组的本质和内存分布
本质和内存分布
二维数组就是一个特殊的一维数组,存储的元素是一维数组的"地址",前面toString
打印结果我们都已经见过了。
每一个"地址"都能找到特定的一维数组。
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
System.out.println(array);
System.out.println(Arrays.toString(array));
}
对于第一行打印:
[[
指二维数组I
指每个元素是int
类型@
是分隔符- 后面的是"地址"
- 所以,二维数组也是一个引用变量,指向了一个数组对象,数组中存放的是引用变量,数组元素数量是行数,每个引用变量指向了一个数组对象,数组中存放的是
int
类型
根据上述,我们可以画出内存分布图:
图中的地址都是随便给出的,这不重要。
了解到这,我们可以写出第一种遍历方式的通写形式了:
public static void main(String[] args) {
int[][] array = new int[][]{{1, 2}, {3, 4}, {5, 6}};
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();
}
}
外层循环使用array.length
就是行数,内层循环使用array[i].length
是列数
不规则二维数组
我们知道,Java中的二维数组的列数可以省略,行数不可以省略,例如:
int[][] array = new int[2][];//不清楚列数
这时候,我们尝试遍历:
public static void main(String[] args) {
int[][] array = new int[2][];
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.println(array[i][j] + " ");
}
System.out.println();
}
}
抛出了"空指针异常"(Java中没有指针,习惯这么讲),此时没有初始化列:
我们如果想要使用,必须对列初始化,这就能体现出不规则了。
根据上图,二维数组的每个元素(引用类型)为null
,说明不指向任何对象。
public static void main(String[] args) {
int[][] array = new int[2][];
array[0] = new int[3];
array[1] = new int[5];
System.out.println(Arrays.deepToString(array));
}
每一行的列数不必相等,这就体现了不规则
进行到这,我们二维数组的两个元素指向了各自的数组对象,此时可以打印了:
其实,我们也可以通过初始化数组体现不规则:
public static void main(String[] args) {
int[][] array = new int[][]{{1, 3}, {5, 6, 7}};
System.out.println(Arrays.deepToString(array));
}
以上就是我们要介绍的内容了,小裤也是边学边总结,如果有问题,希望大家帮忙指正。