Java深入浅出---------方法与数组

系列文章目录



前言

在编程中,某段功能的代码可能频繁使用到,如果在每个位置都重新实现一遍,会:

  1. 使程序变得繁琐
  2. 开发效率低下,做了大量重复性的工作
  3. 不利于维护,需要改动时,所有用到该段代码的位置都需要修改
  4. 不利于复用
    因此,在编程中,我们也可以将频繁使用的代码封装成"帖子”(方法),需要时直接拿来链接(即方法名–方法的入口地址)使用即可,避免了一遍一遍的累赘。

一、Java中的方法

方法就是一个代码片段. 类似于 C 语言中的 “函数”。相信学过C的同学们都知道函数是组成代码的重要组成部分,所以方法的地位亦是如此。

1.1方法的定义

//方法定义
修饰符 返回值类型 方法名称 (形式参数列表){
	方法体代码
}

上面就是Java方法的语法格式,大家在使用时要严格按照语法,和英语中的语法一样,错了就无法使用了。
示例:实现一个方法,检测一个年份是否为闰年


public static boolean isLeapYear(int year){
        if((0 == year % 4 && 0 != year % 100) || 0 == year % 400){
            return true;
        }else {
            return false;
        }
    }
    

下面,就要说说定义方法时的注意事项:

  1. 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成 void
  2. 方法名字:采用小驼峰命名(首字母小写,后面每个单词的首字母大写)
  3. 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开
  4. 方法体:方法内部要执行的语句
  5. 在java当中,方法必须写在类当中
  6. 在java当中,方法不能嵌套定义
  7. 在java当中,没有方法声明一说

在这里插入图片描述)

1.2方法调用的执行过程

示例:计算1!+2!+3!+…+n!


public static int fac(int n){
        
        int ret = 1;
        for (int i = 1; i <= n ; i++) {
            ret *= i;
        }
        System.out.println("计算每一次的阶乘 = " + n);
        return ret;
    }

 public static void main(String[] args) {
        int sum = 0;
        System.out.println("请输入你想要求的数:");
        Scanner scanner = new Scanner(System.in);
        int x = scanner.nextInt();
        for (int i = 1; i <= x; i++) {
            sum += fac(i);
        }
        System.out.println("sum = " + sum);
    }
    

在这里插入图片描述
方法调用的过程:调用方法—>传递参数—>找到方法地址—>执行被调方法的方法体—>被调方法结束返回—>回到主调方法继续往下执行
根据上面的示例可以知道

  • 定义方法的时候,不会执行方法的代码,只有在调用的时候才会执行。
  • 一个方法可以被多次调用

在这里插入图片描述
在Java虚拟机栈中,每调用一次方法会为其开辟一块空间,用于存储方法中的变量,随着方法的调用结束,这块空间也会被编译器回收掉。

1.3实参和形参的关系(重点)

方法的形参相当于数学函数中的自变量,比如:公式sum(n) = n x (n + 1)
Java中方法的形参就相当于sum函数中的自变量n,用来接收sum函数在调用时传递的值的。形参的名字可以随意取名,对方法都没有任何影响,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。


public static int add(int a,int b){
        return a + b;
    }

public static void main(String[] args) {
	add(2,3);        //2和3是实参,在调用时传给形参a和b
}

注意:在Java中,实参的值永远都是赋值到形参中,形参和实参本质是两个实体。

  • 示例:交换两个整型变量

 public static void swap(int x,int y){
        int tmp = x;
        x = y;
        y = tmp;
        System.out.println("swap:x = " + x + "y = " + y);
    }

public static void main(String[] args) {
	int a = 10;
    int b = 20;
    swap(a,b);
    System.out.println("main:a = " + a + "b = " + b);
}

在这里插入图片描述
可以看到,在swap函数交换之后,形参x和y的值发生了改变,但是main方法中a和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[] arr){
        int tmp = arr[0];
        arr[0] = arr[1];
        arr[1] = tmp;
}
        

2.方法的重载(重点)

在自然语言中,经常出现“一词多义”的现象,一个词语如果有多重含义,那么就说该词语被重载了,具体代表什么含义需要结合具体的场景。在Java中方法也是可以重载的。
重载的条件

  1. 方法名必须相同的
  2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序不同)
  3. 与返回值类型是否相同无关

示例:


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(3, 4));
        System.out.println(add(1.5, 2.5));
        System.out.println(add(1, 2, 3));
    }
    

在这里插入图片描述
从上面的示例中可以看出,add这个方法构成了重载,编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法,这样就解决了取名字这一让人头疼的事情。

3.递归

一个方法在执行过程中调用自身,称为“递归”。递归相当于数学上的“数学归纳法”,有一个起始条件,然后有一个递推公式。自身中又包含了自己,该种思想在数学和编程中非常有用,因为有些时候,我们遇到的问题直接并不好解决,但是发现将原问题拆分成其子问题之后,子问题与原问题有相同的解法,等子问题解决之后,原问题就迎刃而解了

示例:递归求n!


  public static int factor(int n){
        if(1 == n){
            return 1;
        }

        return n * factor(n - 1);
    }
    

在这里插入图片描述
根据递归求4!,画出上图来分析递归的执行过程,递归求解的难点在于找出那个递推公式

示例:求斐波那契数列的第n项
斐波那契数列:1,1,2,3,5,8,13,21,34,55,89…


public static int fib(int n){
        if(n == 1 || n == 2){
            return 1;
        }

        return fib(n - 1) + fib(n - 2);
    }
    

根据递归的思想可以很快写出代码来,后两个数等于前两位数的和。但是,我们也可以很明显地看出其中的问题,当要求的n很大时,程序执行速度极慢,究其原因是进行了大量的重复运算。所以,在真正解决这个问题时就不应该用递归的方式,而是要用循环的方式来求解斐波那契数列问题,避免出现冗余运算


public static int fib(int n){
        int f1 = 1;
        int f2 = 1;
        int f3 = 0;
        for (int i = 3; i <= n ; i++) {
            f3 = f1 + f2;
            f1 = f2;
            f2 = f3;
        }
        return f3;
    }
    

此时程序的执行效率就大大提高了。

二、Java中的数组

数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。和C语言的数组基本类似。

1、数组的创建和初始化

1.1、数组的创建

T[] 数组名 = new T[N];

T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度

int[] array1 = new int[10];         // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5];    // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new String[3];    // 创建一个可以容纳3个字符串元素的数组
1.2、数组的初始化

数组的初始化只要分为动态初始化静态初始化

  • 动态初始化:在创建数组时,直接指定数组中元素的个数。
int[] array = new int[10];
  • 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
int[] array1 = new int[]{1,2,3,4,5,6,7,8,9,10};
int[] array2 = {1,2,3,4,5,6,7,8,9,10};

Attention:
1、静态初始化虽然没有指定数组的长度,但是编译器在编译时会根据{ }中的元素个数来确定数组的长度
2、静态初始化时,{ }中数据类型必须与[ ]前数据类型一致
3、静态初始化可以将后面的new T[ ]省略进行简写

  • 静态和动态初始化也可以分为两步进行

int[] array1;
array1 = new int[5];

int[] array2;
array2 = new int[]{1,2,3,4,5};

int array3;
array3 = {1,2,3,4,5};
//编译失败,省略格式是不可以进行拆分的

  • 如果对数组进行动态初始化,且没有对数组内容进行指定,那么数组中的元素就是其数组类型的默认值
    byte—>0 、short—>0、int—>0、long—>0、float—>0.0f、double—>0.0、char—>/u0000
    boolean—>false、引用类型—>null

2、数组的简单使用

2.1、访问数组中元素

数组在内存中是一段连续的空间(因此支持随机访问),空间的编号都是从0开始依次递增,该编号称为数组的下标,数组可以通过下标快速访问其任意位置的元素。

		
	int[] array = new int[]{10,20,30,40,50};
    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]);

在这里插入图片描述
注意:下标从0开始,介于[0,N)之间不包含N(N为元素个数),不能越界,否则会抛出下标越界异常 java.lang.ArraylndexOutOfBoundsException 所以,使用数组一定要谨防下标越界。

2.2、遍历数组

“遍历”是指将数组中的所有元素都访问一遍。

  • JAVA和C一样,可以通过for循环来对数组进行遍历
		int[] array = new int[]{1,2,3,4,5};
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }

注意:在Java中,可以通过 数组对象.length 来获取数组的长度。

  • for-each遍历数组
		for (int x:array) {
            System.out.print(x + " ");
        }

for-each 是for循环的另外一种使用方式,能够更方便地完成对数组的遍历,可以避免循环条件和更新条件写错。

3、数组是引用类型

3.1、基本类型变量与引用类型变量的区别

基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址
示例:

	public static void func(){
        int a = 10;
        int[] array = new int[]{1,2,3,4};
    }

在这里插入图片描述
在上述代码示例中,a、array,都是函数内部的变量,因此其空间都在func方法对应的栈帧中分配。
a是内置类型的变量,因此其空间中保存的就是给该变量初始化的值
array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址
array这个引用指向了数组对象,这样,引用变量便可以去操作对象。

3.2、引用变量

下面,我们再进一步更加深入地来理解一下引用变量。
示例:


	public static void func(){
        int[] array1 = new int[]{10,20,30};

        int[] array2 = new int[]{1,2,3,4,5};
        array2[0] = 100;
        array2[1] = 200;

        array1 = array2;
        array1[2] = 300;
        array1[3] = 400;
        array1[4] = 500;
        for (int i = 0; i < array2.length; i++) {
            System.out.print(array2[i] + " ");
        }
    }

大家可以先看看上面的示例代码,看看能不能一眼看出最后的输出结果

在这里插入图片描述
你做对了吗?那么,为什么会是这个结果呢,我们来画图深刻理解一下。

在这里插入图片描述
首先,在栈中创建了array1和array2两个局部变量,因为它们也同样都是引用变量,所以在堆中创建了两个对象。array2这个引用指向了数组对象后,通过下标将前两个数组元素的值进行了修改。“array1 = array2”这条语句表示array1这个引用也指向了array2这个引用所指向的对象。最后通过array1这个引用把数组对象当中的值进行了修改。

  • 认识null
int[] arr = null;
System.out.println(arr[0]);
// 执行结果
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:6)

null 在 Java 中表示 “空引用” , 也就是一个不指向对象的引用
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作。 一旦尝试读写, 就会抛出 NullPointerException。

4、二维数组

二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组

数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
		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("");
        }
// 执行结果
1 2 3 4
5 6 7 8
9 10 11 12
  • 不规则的二维数组
    Java可以允许二维数组中每行中的元素个数不同
	 int[][] array = {{1,2,3},{4,5,6,7,8}};
     System.out.println(Arrays.deepToString(array));

打印二维数组就行上面一样,Arrays.deepToString(数组名)。

5、数组的一些操作

Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法。

  • 数组转字符串
import java.util.Arrays;
	
	int[] array = {1,2,3,4,5,6,7,8};
	String newArr = Arrays.toString(array);
	System.out.println(newArr);
	//System.out.println(Arrays.toString(array));

//执行结果
[1, 2, 3, 4, 5, 6, 7, 8]

使用 toString 这个方法后,打印数组就更方便了。

  • 拷贝字符串
        int[] arr = {1,2,3,4,5,6};
        int[] copy1 = new int[6];
        copy1 = Arrays.copyOf(arr, arr.length);
        System.out.println("arr: " + Arrays.toString(arr));
        System.out.println("copy1: " + Arrays.toString(copy1));

		int[] copy2 = new int[3];
		copy2 = Arrays.copyOfRange(arr,1,4);
		System.out.println("copy2: " + Arrays.toString(copy2));
        
//执行结果
arr: [1, 2, 3, 4, 5, 6]
copy1: [1, 2, 3, 4, 5, 6]
copy2: [2, 3, 4]

使用 copyOf 这个方法来拷贝指定的数组。
使用 copyOfRange 这个方法来拷贝指定的数组的指定范围。

  • 排序
int[] array = {1,12,32,2,34,43,21};
Arrays.sort(array);
System.out.println(Arrays.toString(array));

//执行结果
[1, 2, 12, 21, 32, 34, 43]

使用 sort 方法可以直接将数组进行升序排序。

  • 二分查找

针对有序数组, 可以使用更高效的二分查找

int[] array = {1,2,3,4,5,6,7};
System.out.println(Arrays.binarySearch(array,5));

//执行结果
4

所以,在对一个无序的数组查找元素的时候,我们可以先对它进行 sort 排序处理,再使用 binarySearch 进行二分查找元素。

下面是二分查找的代码具体实现:

	
	private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }
    
  • 比较两个数组对应位置数据是否一样
int[] arr1 = {1,3,5,7,9};
int[] arr2 = {1,3,5,7,9};
System.out.println(Arrays.equals(arr1, arr2));

//执行结果
true

equals 方法,如果两个指定的int数组彼此 相等 ,则返回 true

=

  • 填充数据
int[] arr = new int[4];
Arrays.fill(arr,1);
System.out.println(Arrays.toString(arr));

//执行结果
[1, 1, 1, 1]

fill 方法,将指定的int值分配给指定的int数组的每个元素


总结

这一节,我们学习了Java中的方法和数组,虽然和C有些许不同,但大多数地方还是非常相似的,主要解决以下问题:
怎样定义方法?
方法的执行过程是什么?
实参和形参存在怎样的关系?
什么是方法的重载?
递归是怎么实现的?
怎么初始化数组?
数组为什么称作引用变量?
数组在空间中是怎么指向的?
数组的常用方法有哪些?

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值