初识Java【3】——方法与数组
前言
欢迎大家来到 初始Java 第三节的学习,本节将与大家介绍方法与数组。此篇文章,笔者是默认大家了解Java中的选择与循环语句,如果您有C语言的基础,那么也可以直接阅读。文章为了从更底层的角度去解释,需要了解部分JVM的知识,各位读者如不了解,可以自行跳转阅读此篇博客: 。
一、方法
方法的作用其实跟C语言的函数差不多,使代码高内聚低耦合,我们可以模块化的组织代码。
1.语法介绍
public class Method {
// public + static + 返回类型 + 方法名 + (形式参数)
public static void functionOne( ) {
// 方法体;
return 返回值;
}
}
这里需要先向各位读者说明:public static
并不是固定的。
public
是访问修饰限定符,访问修饰限定符作用是:限制访问方法的权限。访问修饰限定符一共有四个,分别是:private default protected public
。(这里涉及到封装的知识,笔者将在之后的文章向大家详细介绍。)此处方法由public
修饰,说明:方法可以在工程任意位置被访问。
static
是静态关键字,我们暂时只要知道两点:
(1)一旦方法被static
修饰,那么方法在第一次被调用之后,其生命周期将延长至与整个类相同。
(2)静态方法只能在静态方法中调用。因为main
方法是被static
修饰的,如果想在main
方法中调用 同属一个类的方法 必须要加上static
。(第一句话的完整表述应该是:在静态方法内部无法访问非静态的成员变量与成员方法。)
(static
的相关知识在类中才会介绍到,各位读者如果不理解,可以暂时将static
视为固定表达。)
2.调用示例
(1)示例1:两数中的最大值
public class Method {
// 方法名:小驼峰
public static int funcOne(int num1, int num2) {
return num1 > num2 ? num1 : num2; //三目操作符
}
public static void main(String[] args) {
int num1 = 10;
int num2 = 9;
int max = funcOne(num1, num2);
System.out.println(max);
}
}
//--------------------------------------------------
// 输出结果:10
上面的代码比较简单,笔者就不加赘述了。但是,不少读者应该会对上面的代码示例不太满意,毕竟a,b
的大小被写死了,哪还有比较的意义。因此,笔者简单改造一下上方代码。
import java.util.Scanner;
public class Method {
// 方法名:小驼峰
public static int funcOne(int num1, int num2) {
return num1 > num2 ? num1 : num2; //三目操作符
}
public static void main(String[] args) {
Scanner scanner1 = new Scanner(System.in);
Scanner scanner2 = new Scanner(System.in);
int num1 = scanner1.nextInt();
int num2 = scanner2.nextInt();
int max = func(num1, num2);
System.out.println(max);
}
}
//--------------------------------------------------
// 输出结果:10
1)关于import java.util.Scanner;
Java中的输入时需要调用Scanner类中的next.Int()方法
,这个方法是已经被IDEA解释器提供好的,因此调用的时候需要导入相应的类。
2)关于Scanner scanner1 = new Scanner(System.in);
此处笔者暂时对该行代码做一些解释,因为这里涉及到 引用类型 和 类 的知识(引用类型会在下方介绍数组时正式介绍)。哪怕读不懂下方的解释也没有关系,各位读者只需知道:这行代码可以让我们进行输入。
A.Scanner类
是一种引用类型。大家可以将Scanner类
视为一个新的数据类型。如果拿C语言类比的话,类跟结构体比较相似,但是又具有类似指针的功能。
B.sanner1
是引用变量,也叫做引用。引用在栈区开辟空间,空间中存储的是一段经过哈希的地址,该地址指向new Scanner(System.in)
后产生的对象。
C.new
关键字代表在堆上开辟一块连续的内存空间,这块空间用来存储Scanner类
中的成员变量(类中定义的变量)。为了避免混淆,特别说明一点:方法是开辟在栈上的,其局部变量一般也开辟在栈上。
D.Syestem.in
表示从键盘输入。
3)关于int num1 = scanner1.nextInt();
这就是对Scanner类
中的方法进行调用。Scanner类
中其实有很多方法,但我们是要输入一个整数,所以调用next.Int()方法
即可。至于为什么要这么写,各位读者可以先记住,我们将在介绍类的时候给大家介绍。
(2)示例2:两数相加
import java.util.Scanner;
public class Method {
public static int func(int num1, int num2) {
return num1 + num2;
}
public static void main(String[] args) {
Scanner scanner1 = new Scanner(System.in);
Scanner scanner2 = new Scanner(System.in);
int num1 = scanner1.nextInt();
int num2 = scanner2.nextInt();
int sum = func(num1, num2);
System.out.println(sum);
}
}
//--------------------------------------------------
// 输入数值:4 6
// 输出结果:10
3.方法重载
方法重载是一个重点,在面试的时候常出现的问题就是:“请你描述一下重载和重写的区别。”这里我们就先介绍重载(重写将在介绍抽象类的时候正式介绍到)。
笔者将直接通过代码举例子,请各位读者自行阅读下发代码。
// 代码块1
public class Method {
public static void main(String[] args) {
int num1 = 1;
int num2 = 2;
int num3 = 3;
sum(num1, num2);
sum(num1, num2, num3);
}
public static int sum(int num1, int num2) {
return num1 + num2;
}
public static int sum(int num1, int num2, int num3) {
return num1 + num2 + num3;
}
}
对比两个sum
方法,我们可以发现:方法名相同竟然可以正常运行,不过也能发现方法参数列表中的调用的参数数量不同,返回类型相同。那么还有什么方法可以构成重载呢?
// 代码块2
public class Method {
public static void main(String[] args) {
int num1 = 1;
int num2 = 2;
float num3 = 6.66F; // 记得加上F,否则将默认的 double类型 赋给 float类型 会报错。
sum(num1, num2);
sum(num1, num3);
sum(num3,num1)
}
public static int sum(int num1, int num2) { // sum1
return num1 + num2;
}
public static float sum(int num1, float num2) { // sum2
return num1 + num2;
}
public static float sum(float num1, int num2) { // sum3
return num1 + num2;
}
}
对比两个sum
方法,我们可以发现:方法名相同依旧能够正常运行,不过对比sum1和sum2
能发现方法参数列表中的调用的参数类型不同,返回类型不同。对比sum2和sum3
能发现方法参数列表中的调用的不同数据类型的参数调用的顺序不同,返回类型不同。
而对比代码块1和代码块2
就可以发现无论返回类型是否相同,都不影响程序正常运行。
因此我们可以总结出:
方法重载需要满足以下条件
(1)方法名相同
(2)调用方法的参数列表不同,包括参数列表中的参数个数,参数类型,不同数据类型的参数调用的顺序
(3)方法返回类型不做要求
让我们感到奇怪的是:方法名相同,编译器怎么确认我们调用的是那个参数呢?我们通过查看汇编代码就会发现,编译器会通过方法签名来辨认不同的方法。比如:sum:(II)I sum:(III)I sum:(IF)F sum:(FI)F
。
4.若干可能错误
(1)方法不能够嵌套定义。形如:
public class Method {
public static void main(String[] args) {
;
}
public static int sumThree(int num1, int num2, int num3) {
public static int sumTow(int num4, int num5) { // 嵌套定义——错误!
return num4 + num5;
}
return num1 + num2;
}
}
(2)方法不能够定义在类外。形如:
public class Method {
public static void main(String[] args) {
;
}
}
public static int sumTow(int num1, int num2, int num3) { // 方法定义在类外——错误!
return num1 + num2;
}
(3)形参无法改变实参。形如:
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1,num2);
System.out.println(“num1:" + num1);
System.out.println(“num2:" + num2);
}
public static void swap(int num1, int num2) {
int temp = num1;
num1 = num2;
num2 = temp;
}
}
//-----------------------------------------------------
// 输出结果:
num1:10
num2:20
我们可以看到,最终读取的是圆圈中的内容。因此打印出num1:10 ; num2:20
的结果并不奇怪。
二、数组
1.数组语法
// 以int类型为例。
// 静态初始化
int[] array1 = {1,2,3,4,5};
int[] array2 = new int[]{1,2,3,4,5};
// 动态初始化
int[] array3 = new int[5];
(1)静态初始化
以下我们将以array1
为例进行阐述。
数组是引用类型,array1
是一个引用变量,也就是引用。变量array1
开辟在栈区,存储的是一段经过哈希的地址。该地址指向堆上存放的数据{1,2,3,4,5}
,这些数据叫对象。
(2)动态初始化
以array3
为例进行阐述。
此与上方介绍的相同。new
关键字代表在堆上开辟一块连续的内存空间,这块空间用来存储new int[5]
后产生的数据。
大家看到这个图时可能会疑惑为什么array3
所指向的对象全是0
。因为Java中的数组是存放在堆区的,而堆区上的数据是设有默认值的。int[5]
数组可以拆分成5个int
,而int类型
的默认值为0
。
(3)深入理解引用变量
为了让各位读者更加深刻地理解数组在内存上的存储,笔者举了下方这个例子,请各位读者自行阅读。
public class Array {
public static void func() {
int num1 = 10;
long num2 = 20L;
float num3 = 30.00F;
int[] array3 = new int[]{1,2,3,4};
}
public static void main(String[] args) {
int[] array1 = new int[5];
array1[0] = 11;
array1[1] = 22;
array1[2] = 33;
int[] array2 = new int[]{1,2,3,4};
func();
//----------------------------------------分割线
array2[0] = 100;
array2[1] = 200;
array1 = array2;
array1[2] = 300;
array2[3] = 400;
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
}
}
当上面这段代码运行至分割线
处时,数据在内存中的存储如下图所示。
这里值得注意的是:局部变量一般是存储在栈区。我们可以看到func()方法
中初始化的三个变量num1,num2,num3
都存储在栈区。
从分割线
处继续向下运行。
当运行array1 = array2;
这行代码意味着:将array2
的引用赋给array1
。此时,array1
将不再指向编号为0X333
的地址,而是指向0X222
地址。
此时array1 和 array2
都指向同一份空间,那么无论通过谁来改,最终都能够改动该空间的值,因此最后打印array2
,结果会100,200,300,400
。
值得注意的是:编号为0X333
的地址空间会被JVM自动注销掉。当堆区对象不可能再被任何变量指向时,JVM就会自动回收这份空间。
(4)用变量指定数组大小
在展示如何用变量指定数组大小时,请各位读者先看看下方这个错例。
public static void main(String[] args) {
int[] arr;
arr[0] = 1; // error
}
数组只能被完全初始化一次,就是在它被创建的时候。
第一行代码这么写没有问题,但是这行代码根本没有意义,因为我们无法使用这个数组。这个数组没有确定有多少个数值,因此连空间都无法开辟,无法开辟空间自然就无法使用空间,那么这个数组就失效了。
接下来大家可以阅读下方代码,这就是 用变量指定数组大小 的方法。
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
int[] arr = new int[num];
}
不得不说,Java的数组能够通过变量指定数组的大小真的比C语言方便得多。
2.数组使用示例
(1)示例1:二分查找
import java.util.Scanner;
public class Array {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9,10};
Scanner scanner = new Scanner(System.in);
int num = binarySearch(arr, scanner.nextInt());
if(num >= 0) {
System.out.println("找到了,下标是:" + num);
}else{
System.out.println("找不到此数!");
}
}
public static int binarySearch(int[] arr, int key) {
int left = 0;
int right = arr.length - 1; // 次数要 -1 ,否则就会越界!
while(left <= right) {
int mid = (left + right) / 2;
if(arr[mid] > key) {
right = --mid;
}else if(arr[mid] < key) {
left = ++mid;
}else{
return mid;
}
}
return -1;
}
}
(2)示例2:冒泡排序
import java.util.Arrays;
public class Array {
public static void main(String[] args) {
int[] arr = {23,2,7,3,334,983,53,5,43,65,56,13};
int[] result = bubbleSort(arr); // 从小到大
System.out.println(Arrays.toString(result));
}
public static int[] bubbleSort (int[] arr){
for (int i = 0; i < arr.length; i++) {
boolean flag = false;
for (int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
// 如果中途已经发现数组有序了,那么就提前结束。
if(flag == false) {
return arr;
}
}
return arr;
}
}
此处Arrays
也是类,toString()
是类内的方法。toString()方法
可以将数组以字符串的形式打印出来。
(3)示例3:逆序数组
import java.util.Arrays;
public class Array {
public static void main(String[] args) {
int[] arr = {2, 3, 5, 7, 13, 23, 43, 53, 56, 65, 334, 983};
reverse(arr);
}
public static void reverse(int[] arr) {
int left = 0;
int right = arr.length - 1;
while(left <= right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
System.out.println(Arrays.toString(arr));
}
}
3.二维数组
(1)基础语法
// 以int类型为例
int[][] array = new int[行数][列数] { 初始化数据 };
二维数组在定义时可以不写列数
,但必须写行数
。这非常能体现C语言中曾经提到的:二维数组是特殊的一维数组。
(2)应用示例
public class Array {
public static void main(String[] args) {
int[][] arr = { {1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12} };
for (int row = 0; row < arr.length; row++) {
for (int col = 0; col < arr[row].length; col++) {
System.out.printf("%d\t", arr[row][col]);
}
System.out.println("");
}
}
}
结语
本节最主要的就是把握方法和数组的使用方式,先抛开细枝末节,抓住主干。各位读者还需要大量刷题,慢慢熟悉两者的用法。最后,如果你觉得文章写得不错的话,就给笔者点个赞或者留言鼓励吧!