前言
本篇文章主要讲解有关方法与数组的知识点 ,是基础篇的一部分 , 而在下一篇文章我会讲解类和对象的知识点
入门基础篇上的链接给大家放在下面啦 !
感谢大家点赞👍🏻收藏⭐评论✍🏻
欢迎各位大佬指点,相互学习,一起进步!
目录
一.方法的使用
1.什么是方法?
优点:
- 一个代码可以在多个位置使用
- 让代码更好理解更简单
- 直接调用现有的开发 , 不用重复做繁琐的工作
2.方法的定义
修饰符 返回值类型 方法名称(参数类型 形参 ...){
方法体;
return 返回值;
}
eg:判断是否为闰年
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请输入年份: ");
int year = sc.nextInt();
boolean a = isLeapYear(year);//调用方法时也要定义变量,并写上类型
System.out.println(year + "是闰年吗? 请回答:" + a);
}
}
public static boolean isLeapYear(int year) {//此为方法的部分
if (year % 100 == 0) {
if (year % 400 == 0) {
return true;
} else {
return false;
}
} else {
if (year % 4 == 0) {
return true;
} else {
return false;
}
}
}
}
上面所展示的是一个参数的写法 , 下面介绍两个参数是如何编写
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入:");
int sum1 = sc.nextInt();
int sum2 = sc.nextInt();
int d = add(sum1, sum2);//实际参数,需要两个
System.out.println(d);
}
public static int add(int x, int y) {//形式参数,需要两个
return x + y;
}
}


3.方法调用的执行过程

方法中结束的标志时遇到return,没有返回值就是执行到'' } "
下面用一个具体的例子直观的展示方法的优点:
计算1!+2!+3!+4!...+n!
不包含方法的写法:(运用了多次循环嵌套,不是很直观)
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入");
int n = sc.nextInt();
int sum = 0;
int m=1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 1; j++) {
m = m * i;
}
sum=sum+m;
}
System.out.println(sum);
}
而运用了方法之后,避免了使用二重循环,让代码更简单清晰
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入");
int n = sc.nextInt();
int sum = 0;
for (int i = 1; i <= n; i++) {
sum = sum + mul(i);
}
System.out.println(sum);
}
public static int mul(int a) {
int m = 1;
for (int i = 1; i <= a; i++) {
m = m * i;
}
return m;
}
4.实参和形参的关系

- 实参a和b是main方法中的两个变量,其空间在main方法的栈(一块特殊的内存空间)中,而形参x和y是swap方法中的两个变量,x和y的空间在swap方法运行时的栈中,因此:实参a和b 与 形参x和y是两个没有任何关联性的变量.
- 在swap方法调用时,只是将实参a和b中的值拷贝了一份传递给了形参x和y,因此对形参x和y操作不会对实参a和b 产生任何影响。即:传值调用
在c语言中可以用到指针来交换,那么Java中应该怎么办呢?
传引用类型参数,在下面的数组会讲解.
5.没有返回值
有返回值时需要接收, 可以用变量来接收,也可以放入打印里
注意:接收的变量的类型和方法的返回值类型要一致(也可以是能通过隐式转换或者是显式转换过去的)
System.out.println(add(a));
没有返回值时,返回值类型必须写成void
可以不用接收直接调用
二.方法重载
1.什么是重载?
首先看下面这张图 , 我们要实现的是两个数字相加的程序 ,由于我们只设置了int类型的两数之和的方法 , 会导致double类型的报错
所以我们需要再加上double类型的两数之和相加 , 我们发现:虽然都是时间加和的程序 , 但是它们的命名并不相同
并且如果有很多不同的相加程序,所起的名字也会变得更加麻烦 ,比如下面的程序:
都是表示相加的程序 , 但他们可能是个数不一样 , 或者是类型不一样 , 这就导致了我们要起四个不同的名字 , 会变得更加繁琐 , 那么可不可以所有表示相加的都起一样的名字呢 ?
.答案是可以的 这时,我们就可以使用方法重载
即全部命名为add,这个在Java中是可以实现的 ,但是要注意:相同的方法命名它们 不是类型不同 就是个数不同 ,否则就是重定义了,如下:
下图就表示重定义 , 即使他们变量名字不同,只要红色的地方一样 , 就不叫做方法重载
返回值类型改成double了也不可以构成重载 , 只看我画红色的地方
2.方法重载的概念
- 方法名必须相同
- 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)
- 与返回值类型是否相同无关
- 编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法
三.递归
1.什么是递归?
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入: ");
int n = scanner.nextInt();
int ret = fact(n);
System.out.println(ret);
}
public static int fact(int n) {
if (n == 1) {
return 1;
}
return n * fact(n - 1);
}
}
2.执行过程分析

在Java中每一次调用,都需要为本次方法调用在内存的栈区申请一块内存空间来保存方法调用期间的各种局部变量的值,这块空间被称为栈帧。
方法不返回,方法对应的栈帧空间就一直占用,所以函数调用中存在递归调用的话,每一次递归调用都会开辟属于自己的栈帧空间,直到递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采用方法递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出的问题。
3.练习
1)分析:输入一个整数,按照顺序打印一个整数的每一位
eg:
输入:1234 输出:1 2 3 4
1234
1234%10=4
1234/10=123
123%10=3
123/10=12
12%10=2
12/10=1
1%10=1
1/10=0
以上方法容易理解但是很麻烦,所有数都需要一个一个打,但是数字很大就无法做到了
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入: ");
int n = scanner.nextInt();
print1(n);
}
public static void print1 (int n) {
if(n>9){
print1(n/10);//目的是将位数减一位
}
System.out.println(n%10);
}
}
2).递归求 1 + 2 + 3 + ... + 10
public class Test {
public static void main(String[] args) {
int ret = add(10);
System.out.println(ret);
}
public static int add(int n) {
if(n==1){
return 1;
}
return n+add(n-1);
}
}
注意:如果上述的方法去掉n==1就返回的程序,那么会导致程序一直循环进行 , 而递归调用方法的时候会创建栈帧 , 递归几次就会创建几层 , 每层栈帧都会消耗一定的存储空间的 , 达到一定层数后就可能把栈空间给消耗完毕了
3).写一个递归方法,输入一个非负整数,返回组成它的数字之和 . 例如,输入 1729, 则应该返回 1+7+2+9,它的和是 19
public class Test {
public static void main(String[] args) {
int n = 1729;
int ret = add(n);
System.out.println(ret);
}
public static int add(int n) {
if (n < 9) {
return n;
}
return n % 10 + add(n / 10);
}
}
4)斐波那契数列
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
从这里我们也可以看到虽然才算到第6项,但是需已经调用方法很多次了,当执行fib(40)的时候,次数高达1亿多次,进行了太多的重复运算, 而每一次递归调用都会开辟属于自己的栈帧空间,所以当我们求fib(50)时,就会发现半天不出结果,结果还为负数,原因是开辟了太多的栈帧空间,导致溢出了
解决办法(针对斐波那契): 循环
public static int fib(int n) {
int last2 = 1;
int last1 = 1;
int cur = 0;
for (int i = 3; i <= n; i++) {
cur = last1 + last2;
last2 = last1;
last1 = cur;
}
return cur;
}
四.数组的基本概念
1.数组的创建与定义
如果想要打印出5个商品的价格,那么就要设立5个变量,那么要是100个商品呢,难道要设置100的变量吗?有没有一次就能打印出所有价格的方法呢?数组就可以做到
那么什么是数组?
数组就是相同类型的元素集合,且空间连在一起
下面来讲解如何创建数组?
1.数组的创建
T[] 数组名 = new T[N];
//T为数组存放的元素类型
//T[]为数组的类型
//N为数组的长度
//new为关键字
eg:创建一个可以容纳10个int类型元素的数组
int[] array = new int[10];
2.数组的初始化
1)动态初始化:直接指定数组中的元素个数
int[] array = new int[10];
2)静态初始化:不指定数组中的元素个数 , 而是根据后方{}里的元素个数 , 来计算数组中的长度
int[] array = new int[]{1,3,4,2,2,2,5,5};
注意1:在静态初始化中
- {}内的元素类型要与[]前的类型一致
- new int[]可以省略,为int[] array = {1,3,4,2,2,2,5,5};
- 虽然省略了new int[] , 但是编译器编译代码时还是会还原
注意2:在c语言中,定义数组的方法为 int array[] = {1,3,4,2,2,2,5,5};
虽然java中也可以这样写,但是不建议,因为int[]在一起更能直观的表示为int类型的数组
否则可能理解为数组的类型为int .
注意3:两种初始化格式都可以分为两步
int[] array;
array=new int[]{1,3,4,2,2,2,5,5};
但如果是静态初始化中的省略格式则不能分为两步,下面所述代码就是一个错误示范.
int[] array;
array = {1,3,4,2,2,2,5,5};
注意4:如果没有对数组进行初始化,数组中元素有默认值
eg:如果数组中的元素为基本数据类型 , 默认值为对应类型对应的默认值
类型 | 默认值 |
byte | 0 |
shut | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0 |
char | /u0000 |
boolean | FALSE |

2.数组的使用
2.1.数组访问:
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。比如:

同时,我们也可以通过赋值改变某项的值
注意1:数组的内存空间是连续的 , 因此支持随机访问
(ps:随机访问:随机访问是指可以直接访问任意存储单元中的数据,而不需要按照顺序逐个读取 , 数组的随机访问意味着可以直接、快速地访问数组中的任何元素,而无需按顺序遍历数组)
注意2:注意不要越界 , 例如上图中array数组中最大的下标为4 , 不能超过4,否则会出现以下提示
错误提升如下
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at Test.main(Test.java:8)
//线程 “main” java.lang 中出现异常。at Test.main(Test.java:8)数组索引越界异常:
索引 5 超出长度 5 的界外
2.2.遍历数组:
1) 第一种方法是分别访问数组中的每一个元素并进行打印
int[] array = {1,3,5,7,9};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[5]);
但是如果数组中元素有很多长达几百个时 , 难道需要几百个打印语句吗 ?
2)循环打印数组中的每一个元素
int[] array = {1,3,5,7,9};
for(int i=0;i<array.length;i++) {//注意:.是成员访问运算符,array.length就是求数组中的元素个数
System.out.println(array[i]);
}
//打印结果:
1
3
5
7
9
3)使用for-each遍历数组
int[] array = {1,3,5,7,9};
for(int num:array){
System.out.println(num);
}
//打印结果
1
3
5
7
9
注意: for-each方法虽然简单 , 但存在很多限制 , 只能读取数组元素 , 而不能修改 , 只能按照顺序从前往后读取 (只是将数组中的元素保存在了变量中 )
eg: 当我们想要修改数组中的元素时,for-each的改法如下,但事实上并没有改变数组的元素大小,而是改变了num这个变量的大小 .
int[] array = {1,3,5,7,9};
for(int num:array){
num = num*100;
System.out.println(num);
}
//此代码相当于for循环中的如下写法
for(int i=0;i< array.length;i++){
int num = array[i];
num= num*100;
System.out.println(num);
}
正确方法如下:
int[] array = {1,3,5,7,9};
for(int i=0;i< array.length;i++){
array[i] = array[i]*100;
System.out.println(array[i]);
}
//打印结果
100
200
300
400
500
4)通过数组名来打印数组
int[] array = {1,3,5,7,9};
System.out.println(Arrays.toString(array));
//打印结果
[1, 3, 5, 7, 9]
3.数组是引用类型
3.1基本数据类型与引用数据类型的区别
引用数据类型包括:类、 接口、 数组、 枚举、 注解等
主要区别:
基本数据类型是直接保存在栈中的
引用数据类型在栈中保存的是一个地址引用,这个地址指向的是其在堆内存中的实际位置。(栈中保存的是一个地址,而实际的内容是在堆中,通过地址去找它实际存放的位置)
引用数据类型在创建时,系统会分配两块空间,一块存放引用地址,一块存放实际的对象。
引用数据类型赋值给另一个变量时,赋的是内存地址,而不是实际的数据
public static void func() {
int a = 10;
int b = 20;
int[] arr1 = new int[]{1,2,3};
int[] arr2 = arr1;//将arr1的地址赋值给arr2
}
如果两个引用数据的变量指向同一个对象时,一个变量修改值,另一个变量的值一样会受到影响。
public static void func() {
int a = 10;
int b = 20;
int[] arr1 = new int[]{1,2,3};
int[] arr2 = arr1;//将arr1的地址赋值给arr2
arr2[0]=100;
}
因为arr1和arr2的地址是一样的 , 那他们对应的堆中对应的内容就是一样的 ( 因为是根据地址来找对应的堆的内容 )
当代码添加arr2[0]=100时,也就相当于修改了堆中的内容 , 所以arr1中的内容也会被修改
举例来说:
如果引用是一把钥匙(arr1是一把钥匙 , 又配了一把一样的钥匙 , 也就是arr2)
存在于堆中的 new 出来的数组本体,看作是一个房子 , 那么如果通过arr2钥匙修改了房子中的内容那么arr1钥匙打开看到的也是被修改的.
从上图可以看到,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。只有以下四种:
- 引用像指针一样能保存内存地址
- 引用可以相互赋值 或 判断相等
- 针对数组的引用 , 可以用 [] 访问元素
- 成员访问功能
3.2空引用null
null在Java中表示"空引用",即不指向对象的引用
类似c中的空指针NULL ,都表示无效的内存位置
那么空引用到底有什么用处呢?
空引用主要是用来表示,一个对象还没有被初始化,当你在代码里可能需要先声明一个变量,但是呢,又不想马上给它分配内存空间,这时候就可以用空引用了。还有就是,比如说你要返回一个对象,但是呢在某些情况下又没有合适的对象可以返回,这时候也可以返回一个空引用。不过,空引用确实容易出问题,所以在Java里边,从Java七开始吧,就引入了Optional类,来处理空引用的问题,这样就更安全一些。
3.3参数传参
1)
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3};
func(arr1);
System.out.println(arr1[0]);
}
public static void func(int[] arr2) {//将arr1的地址复制给arr2
arr2[0] = 10;//通过地址来找到实际的内容并修改
System.out.println(arr2[0]);
}
//打印结果
10
10
注意:传过去的是地址,所以arr2可以根据地址找到实际的值并修改,所以arr1与arr2都被修改
2)
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3};
func(arr1);
System.out.println("arr1[0]为"+arr1[0]);
}
public static void func(int[] arr2) {//将arr1的地址复制给arr2
arr2 = new int[]{3,4,5};//将此数组的首地址赋值给arr2
//注意这块不要重定义
// 上一行代码已经定义过它是一个int类型的数组了
System.out.println("arr2[0]为"+arr2[0]);//此打印是根据arr2来找到的实际的内容
}
//打印结果
arr2[0]为3
arr1[0]为1
注意:虽然传过去的也是地址但是在func方法中修改了arr2的地址
即:1)修改了实际的内容(改了房子里的布置)
2)修改了地址(换成了另一把钥匙打开了另一座房子)
总结:
- 方法内部针对引用的修改 , 不总是能影响到外面的 , 关键要看对引用修改是怎样的操作
- 如果修改的是引用指向的对象本体(通过 [ ] 或 . ) , 此时的修改是能够被方法外部感知到的
- 如果修改的是引用本身(通过=修改引用中保存的地址), 此时的修改不能被方法外部感知到
3.4返回值
(上图画红框处,左边的代码为Java,右边为c语言)
在C语言中定义的a数组是局部变量 , 当执行完函数之后 , 局部变量就释放内存 , 如果在函数外部调用 , 由于数组所在的空间已经被销毁了 , 返回了数组的首地址也根本不会找到它 , 即为访问非法的内存空间 , 因此右侧c语言的写法是错误的 .
(相当于已经把酒店的房间退了 , 但是用私自配的房卡 , 强行开这个门 , 看到的房间里的内容是随机的 )
在Java中数组的实际内容 , 是存在于堆中的 , 但是随着方法的结束 , 销毁的是a引用 , 并没有消除堆上数组的实际内容 , 所以a引用的地址指向的内存是仍然有效的 . 因此Java的写法是正确的
那怎么释放堆上的空间呢?什么时候会被释放掉呢?---"垃圾回收机制 "
答案是:你new出来的对象或数组当你的代码中没有任何地方使用这样的对象或数组的时候就会被JVM自动释放掉 , 完全不需要自己写代码来进行干预 , JVM察觉出来你一定不会用到它时就会释放掉 , 这样的机制称为"垃圾回收机制",简称GC.
- 缺点:
- 消耗更多的系统资源来实现自动释放内存功能
- 运行过程中消耗更多的时间
例如:求斐波那契数列前10项.
public class Test {
public static void main(String[] args) {
System.out.println("请打印斐波那契数列的前十项: ");
int arr[] = func(10);
System.out.println( Arrays.toString(arr));
}
public static int[] func(int n) {
int[] arr= new int[n];
if (n <= 0) {
return null;
}
if (n > 0) {
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i < n; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
}
return arr;
}
}
五.数组练习
1.数组转字符串
使用Java自带的方法---Arrays.toString(arr)
需要添加---import java.util.Arrays
import java.util.Arrays
int[] arr = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
System.out.println(newArr);
// 执行结果
[1, 2, 3, 4, 5, 6]
那么这个自带的方法是如何实现的呢?
在这之前大家需要知道的是字符串是可以通过加号连接在一起的
所以模拟实现此方法的代码为:
public static String ArraytoString(int[] arr) {
String result = "[";
for (int i = 0; i < arr.length; i++) {
result += arr[i];
if(i<arr.length-1){
result += ",";
}
}
result += "]";
return result;
}
2.数组拷贝
第一种方法: int[] newarr = arr;//复制数组对应的地址
第二种方法: Arrays.copyOf(arr, arr.length);//复制数组的实际内容
import java.util.Arrays;
public static void func(){
// newarr和arr引用的是同一个数组
// 因此newarr修改空间中内容之后,arr也可以看到修改的结果
int[] arr = {1,2,3};
int[] newarr = arr;
newarr[0] = 100;
System.out.println("newarr: " + Arrays.toString(arr));//打印结果为{100,2,3}
// 使用Arrays中copyOf方法完成数组的拷贝:
// copyOf方法在进行数组拷贝时,创建了一个新的数组,只是内容一样,但无关系
// arr和newarr引用的不是同一个数组,是两个内容相同的数组
arr[0] = 100;
newarr = Arrays.copyOf(arr, arr.length);
System.out.println("newarr: " + Arrays.toString(newarr));//打印结果为{1,2,3}
}
所以第二种方法在修改其中一个数组内容的时候 ,不会影响另一个,都是独立个体.
注意:也可以拷贝某一部分的范围
同样我们来实现Arrays.copyOf此方法的编写过程
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int[] arr2=copyOf(arr,7);
}
public static int[] copyOf(int[] arr,int newLength) {
int[] result = new int[newLength];//定义数组长度为7
int len = Math.min(arr.length,newLength);
for(int i=0;i<len;i++){
result[i]=arr[i];//只复制过去了前五项,一开始全部为0
}
return result;//最后返回数组的内容
}
}
3.查找数组中的指定元素
给定一个数组, 再给定一个元素, 找出该元素在数组中的位置
(顺序查找)
//顺序查找
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int ret = search(arr,3);
System.out.println(ret);
}
public static int search(int[] arr,int a) {
for(int i=0;i<arr.length;i++){
if(a==arr[i]){
return i;
}
}
return -1;
}
}
//结果为2
(二分查找)
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,15,16,17,18,19,20};
System.out.println(binarysearch(arr1, 8));
}
public static int binarysearch(int[] arr, int tofind) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (tofind < arr[mid]) {
right = mid - 1;
} else if (tofind > arr[mid]) {
left = mid + 1;
} else {
return mid;//找到了返回下标
}
}
return -1;//没找到退出
}
}
4.判断一个数字是否存在于数组中
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
Scanner sc = new Scanner(System.in);
int ret =sc.nextInt();
System.out.println(search(arr,ret));
}
public static boolean search(int[] arr,int a) {
for(int i=0;i<arr.length;i++){
if(a==arr[i]){
return true;
}
}
return false;
}
}
5.冒泡排序
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr1 = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 48, 19, 50};
bubblesort(arr1);
System.out.println(Arrays.toString(arr1));
}
public static void bubblesort(int[] arr2) {
for (int i = 0; i < arr2.length - 1; i++) {
for (int j = 1; j < arr2.length - i;j++) {
if (arr2[j - 1] > arr2[j]) {
int tmp = arr2[j - 1];
arr2[j - 1] = arr2[j];
arr2[j] = tmp;
}
}
}
}
}
6.数组逆序
public class Test {
public static void main(String[] args) {
int[] arr = {2,1,5,8,6,3};
reserve(arr);
System.out.println(Arrays.toString(arr));
}
public static void reserve(int[] arr) {
int left=0;
int right=arr.length-1;
while(left<right){
int tmp=arr[left];
arr[left]=arr[right];
arr[right]=tmp;
left++;
right--;
}
}
}
六.二维数组