数组(续)

168 阅读15分钟

Day6-数组(续)

4.5 二维数组

1.什么是二维数组?

一个一维数组只能存储一组同类型的数据,如果需要同时存储多组同类型的数据,就需要使用二维数组。例如,使用一维数组存储一个小组的学员成绩,使用二维数组可以存储多个小组的学员成绩。

  • 二维数组:本质上就是元素为一维数组的一个数组。
  • 二维数组的标记:[][]
int[][] arr; //arr是一个二维数组,可以看成元素是int[]一维数组类型的一维数组

==二维数组也可以看成一个二维表,行*列组成的二维表==,只不过这个二维表,每一行的列数还可能不同。但是每一个单元格中的元素的数据类型是一致的,例如:都是int,都是String等。

==二维数组也可以看成一个一维数组,只是此时元素是一维数组对象==。

1561524724397.png

2.二维数组的声明

二维数组声明的语法格式:

//推荐
元素的数据类型[][] 二维数组的名称;
​
//不推荐
元素的数据类型  二维数组名[][];
//不推荐
元素的数据类型[]  二维数组名[];

例如:

public class Test20TwoDimensionalArrayDefine {
    public static void main(String[] args) {
        //存储多组成绩
        int[][] grades;
​
        //存储多组姓名
        String[][] names;
    }
}

面试:

int[] x, y[];
//x是一维数组,y是二维数组

3.二维数组的静态初始化

静态初始化就是用静态数据(编译时已知)为数组初始化。

//以下格式要求声明与静态初始化必须一起完成
元素的数据类型[][] 二维数组的名称 = {
            {元素1,元素2,元素3 。。。}, 
            {第二行的值列表},
            ...
            {第n行的值列表}
        };
​
元素的数据类型[][] 二维数组名 = new 元素的数据类型[][]{
            {元素1,元素2,元素3 。。。}, 
            {第二行的值列表},
            ...
            {第n行的值列表}
        };
​
元素的数据类型[][] 二维数组名;
二维数组名 = new 元素的数据类型[][]{
            {元素1,元素2,元素3 。。。}, 
            {第二行的值列表},
            ...
            {第n行的值列表}
        };

如果是静态初始化,右边new 数据类型[][]中不能写数字,因为行数和列数,由{}的元素个数决定

举例:

    int[][] arr = {{1,2,3},{4,5,6},{7,8,9,10}};//声明与初始化必须在一句完成
​
    int[][] arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};
​
    int[][] arr;
    arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};arr = new int[3][3]{{1,2,3},{4,5,6},{7,8,9,10}};//错误,静态初始化右边new 数据类型[][]中不能写数字

二维数组静态初始化演示:

public class Test21TwoDimensionalArrayInitialize {
    public static void main(String[] args) {
        //存储多组成绩
        int[][] grades = {
                    {89,75,99,100},
                    {88,96,78,63,100,86},
                    {56,63,58},
                    {99,66,77,88}
                };
​
        //存储多组姓名
        String[][] names = {
            {"张三","李四", "王五", "赵六"},
            {"刘备","关羽","张飞","诸葛亮","赵云","马超"},
            {"曹丕","曹植","曹冲"},
            {"孙权","周瑜","鲁肃","黄盖"}
        };
    }
}

4.二维数组的使用

因为二维数组是用来存储多组数据的,因此要比一维数组麻烦一些,需要我们搞清楚如下几个概念:

  • 二维数组的长度/行数:二维数组名.length
  • 二维数组的某一行:二维数组名[行下标],此时相当于获取其中一组数据。它本质上是一个一维数组。行下标的范围:[0, 二维数组名.length-1]。此时把二维数组看成一维数组的话,元素是行对象。
  • 某一行的列数:二维数组名[行下标].length,因为二维数组的每一行是一个一维数组。
  • 某一个元素:二维数组名[行下标][列下标],即先确定行/组,再确定列。
public class Test22TwoDimensionalArrayUse {
    public static void main(String[] args){
        //存储3个小组的学员的成绩,分开存储,使用二维数组。
        /*
        int[][] scores1;
        int scores2[][];
        int[] scores3[];*/
​
        int[][] scores = {
                {85,96,85,75},
                {99,96,74,72,75},
                {52,42,56,75}
        };
​
        System.out.println(scores);//[[I@15db9742
        System.out.println("一共有" + scores.length +"组成绩.");
​
        //[[:代表二维数组,I代表元素类型是int
        System.out.println(scores[0]);//[I@6d06d69c
        //[:代表一维数组,I代表元素类型是int
        System.out.println(scores[1]);//[I@7852e922
        System.out.println(scores[2]);//[I@4e25154f
        //System.out.println(scores[3]);//ArrayIndexOutOfBoundsException: 3
​
        System.out.println("第1组有" + scores[0].length +"个学员.");
        System.out.println("第2组有" + scores[1].length +"个学员.");
        System.out.println("第3组有" + scores[2].length +"个学员.");
​
        System.out.println("第1组的每一个学员成绩如下:");
        //第一行的元素
        System.out.println(scores[0][0]);//85
        System.out.println(scores[0][1]);//96
        System.out.println(scores[0][2]);//85
        System.out.println(scores[0][3]);//75
        //System.out.println(scores[0][4]);//java.lang.ArrayIndexOutOfBoundsException: 4
    }
}

5.二维数组的遍历

for(int i=0; i<二维数组名.length; i++){ //二维数组对象.length
    for(int j=0; j<二维数组名[i].length; j++){//二维数组行对象.length
        System.out.print(二维数组名[i][j]);
    }
    System.out.println();
}
public class Test23TwoDimensionalArrayIterate {
    public static void main(String[] args) {
        //存储3个小组的学员的成绩,分开存储,使用二维数组。
        int[][] scores = {
                {85,96,85,75},
                {99,96,74,72,75},
                {52,42,56,75}
        };
​
        System.out.println("一共有" + scores.length +"组成绩.");
        for (int i = 0; i < scores.length; i++) {
            System.out.print("第" + (i+1) +"组有" + scores[i].length + "个学员,成绩如下:");
            for (int j = 0; j < scores[i].length; j++) {
                System.out.print(scores[i][j]+"\t");
            }
            System.out.println();
        }
    }
}

6.二维数组动态初始化

如果二维数组的每一个数据,甚至是每一行的列数,需要后期单独确定,那么就只能使用动态初始化方式了。动态初始化方式分为两种格式:

(1)规则二维表:每一行的列数是相同的
//(1)确定行数和列数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
    m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
    n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格
​
//此时创建完数组,行数、列数确定,而且元素也都有默认值
​
//(2)再为元素赋新值
二维数组名[行下标][列下标] = 值;
/*
 1 1 1 1 1
 2 2 2 2 2
 3 3 3 3 3
 4 4 4 4 4
 */
public class Test24SameElementCount {
    public static void main(String[] args) {
        //1、声明二维数组,并确定行数和列数
        int[][] arr = new int[4][5];
​
        //2、确定元素的值
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                arr[i][j] = i + 1;
            }
        }
​
        //3、遍历显示
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
    }
}
(2)不规则:每一行的列数不一样
//(1)先确定总行数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[总行数][];
​
//此时只是确定了总行数,每一行里面现在是null//(2)再确定每一行的列数,创建每一行的一维数组
二维数组名[行下标] = new 元素的数据类型[该行的总列数];
​
//此时已经new完的行的元素就有默认值了,没有new的行还是null//(3)再为元素赋值
二维数组名[行下标][列下标] = 值;
/*
 1
 2 2
 3 3 3
 4 4 4 4
 5 5 5 5 5
 */
public class Test25DifferentElementCount {
    public static void main(String[] args){
        //1、声明一个二维数组,并且确定行数
        //因为每一行的列数不同,这里无法直接确定列数
        int[][]  arr = new int[5][];
​
        //2、确定每一行的列数
        for(int i=0; i<arr.length; i++){
            /*
            arr[0] 的列数是1
            arr[1] 的列数是2
            arr[2] 的列数是3
            arr[3] 的列数是4
            arr[4] 的列数是5
            */
            arr[i] = new int[i+1];
        }
​
        //3、确定元素的值
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                arr[i][j] = i+1;
            }
        }
​
        //4、遍历显示
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
​
    }
}
(3)警惕空指针异常

观察一下代码,运行后会出现什么结果。

public class Test26NullPointerException {
    public static void main(String[] args) {
        //定义数组
        int[][] arr = new int[3][];
​
        System.out.println(arr[0][0]);//NullPointerException
    }
}

因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出NullPointerException 空指针异常。 v

空指针异常.jpg 空指针异常在内存图中的表现

1572338767825.png 1572338767825

7.二维数组的内存图分析

二维数组本质上是元素类型是一维数组的一维数组。

那么二维数组名中存储的是 首地址,这个首地址是元素是一维数组的一维数组的首地址。

二维数组名[下标]存储的也是首地址,这个搜地址是每一行这个一维数组的首地址。

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

1562112672215.png

        //1、声明二维数组,并确定行数和列数
        int[][] arr = new int[4][5];
        
        //2、确定元素的值
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                arr[i][j] = i + 1;
            }
        }

1562113179785.png

        //1、声明一个二维数组,并且确定行数
        //因为每一行的列数不同,这里无法直接确定列数
        int[][]  arr = new int[5][];
        
        //2、确定每一行的列数
        for(int i=0; i<arr.length; i++){
            /*
            arr[0] 的列数是1
            arr[1] 的列数是2
            arr[2] 的列数是3
            arr[3] 的列数是4
            arr[4] 的列数是5
            */
            arr[i] = new int[i+1];
        }
        
        //3、确定元素的值
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                arr[i][j] = i+1;
            }
        }

1562113981079.png

Day6 第5章 面向对象基础

5.1 方法

5.1.1 方法的概念

方法也叫函数,是一组代码语句的封装,从而实现代码重用,从而减少冗余代码,通常它是一个独立功能的定义,方法是一个类中最基本的功能单元。

Math.random()的random()方法
Math.sqrt(x)的sqrt(x)方法
System.out.println(x)的println(x)方法
​
Scanner input = new Scanner(System.in);
input.nextInt()的nextInt()方法

5.1.2 方法的特点

(1)必须先声明后使用

类,变量,方法等都要先声明后使用

(2)不调用不执行,调用一次执行一次。

5.1.3 如何声明方法

1、声明方法的位置

声明方法的位置==必须在类中方法外==,即不能在一个方法中直接定义另一个方法。

声明位置示例:

类{
    方法1(){
        
    }
    方法2(){
        
    }
}

错误示例:

类{
    方法1(){
        方法2(){  //位置错误
        
        }
    }
}

2、声明方法的语法格式

【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{
        方法体的功能代码
}

一个完整的方法 = 方法头 + 方法体。

  • 方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】,也称为方法签名,通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。方法头可能包含5个部分,但是有些部分是可能缺省的。
  • 方法体就是方法被调用后要指定的代码,也是完成方法功能的具体实现代码,对于调用者来说,不了解方法体如何实现的,并影响方法的使用。

3、方法每一个部分的含义

(1)方法名(必选) :给方法起一个名字,见名知意,能准确代表该方法功能的名字

(2)【修饰符】(可选) :会影响方法的调用方式,以及方法的可见性范围等

  • 修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。其中根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法。==接下来咱们先学习静态方法==。

(3) 【throws 异常列表】(可选) :这个部分在异常章节再讲

(4) (【参数列表】)(()必选,参数列表可选) :表示完成方法体功能时需要外部提供的数据列表

  • 无论是否有参数,()不能省略

  • 如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例如:

    • 一个参数: (数据类型 参数名)
    • 二个参数: (数据类型1 参数1, 数据类型2 参数2)
  • 参数的类型可以是基本数据类型、引用数据类型

(5)返回值类型(必选) : 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者

  • 基本数据类型
  • 引用数据类型
  • 无返回值类型:void

(6) {方法体}(必选) :方法体必须有{}括起来,在{}中编写完成方法功能的代码,具有方法体的方法才能被调用执行。

关于方法体中return语句的说明:

  • return语句的作用是结束方法的执行,并将方法的结果返回去
  • 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
  • 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
  • return语句后面就不能再写其他代码了,否则会报错:Unreachable code

示例:


/**
 * 方法定义案例演示
 */
public class MethodDefineDemo {
    /**
     * 无参无返回值方法的演示
     */
    public static void sayHello(){
        System.out.println("hello");
    }
​
    /**
     * 有参无返回值方法的演示
     * @param length int 第一个参数,表示矩形的长
     * @param width int 第二个参数,表示矩形的宽
     * @param sign char 第三个参数,表示填充矩形图形的符号
     */
    public static void printRectangle(int length, int width, char sign){
        for (int i = 1; i <= length ; i++) {
            for(int j=1; j <= width; j++){
                System.out.print(sign);
            }
            System.out.println();
        }
    }
​
    /**
     * 无参有返回值方法的演示
     * @return
     */
    public static int getIntBetweenOneToHundred(){
        return (int)(Math.random()*100+1);
    }
    
    /**
     * 有参有返回值方法的演示
     * @param a int 第一个参数,要比较大小的整数之一
     * @param b int 第二个参数,要比较大小的整数之二
     * @return int 比较大小的两个整数中较大者的值
     */
    public static int max(int a, int b){
        return a > b ? a : b;
    }
}
​

5.1.4 如何调用静态方法

1、方法调用语法格式

本类中

静态方法(【实参列表】)

其他类中

类名.静态方法(【实参列表】)

例如:

/**
 * 方法调用案例演示
 */
public class MethodInvokeDemo {
    public static void main(String[] args) {
        System.out.println("-----------------------方法调用演示-------------------------");
​
        //调用MethodDefineDemo类中无参无返回值的方法sayHello
        MethodDefineDemo.sayHello();
        MethodDefineDemo.sayHello();
        MethodDefineDemo.sayHello();
        //调用一次,执行一次,不调用不执行
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中有参无返回值的方法printRectangle
        MethodDefineDemo.printRectangle(5,10,'@');
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中无参有返回值的方法getIntBetweenOneToHundred
        MethodDefineDemo.getIntBetweenOneToHundred();//语法没问题,就是结果丢失
​
        int num = MethodDefineDemo.getIntBetweenOneToHundred();
        System.out.println("num = " + num);
​
        System.out.println(MethodDefineDemo.getIntBetweenOneToHundred());
        //上面的代码调用了getIntBetweenOneToHundred三次,这个方法执行了三次
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中有参有返回值的方法max
        MethodDefineDemo.max(3,6);//语法没问题,就是结果丢失
        
        int bigger = MethodDefineDemo.max(5,6);
        System.out.println("bigger = " + bigger);
​
        System.out.println("8,3中较大者是:" + MethodDefineDemo.max(8,9));
    }
}
​

2、形参和实参

  • 形参(formal parameter):在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
  • 实参(actual parameter):调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
  • 调用时,实参的个数、类型、顺序顺序要与形参列表一一对应。如果方法没有形参,就不需要也不能传实参。
  • 无论是否有参数,声明方法和调用方法是==()都不能丢失==

3、返回值问题

方法调用表达式是一个特殊的表达式:

  • 如果被调用方法的返回值类型是void,调用时不需要也不能接收和处理返回值结果,即方法调用表达式==只能==直接加;成为一个独立语句。

  • 如果被调用方法有返回值,即返回值类型不是void:

    • 方法调用表达式的结果可以作为赋值表达式的值,
    • 方法调用表达式的结果可以作为计算表达式的一个操作数,
    • 方法调用表达式的结果可以作为另一次方法调用的实参,
    • 方法调用表达式的结果可以不接收和处理,方法调用表达式直接加;成为一个独立的语句,这种情况,返回值丢失。
public class MethodReturnValue {
    public static void main(String[] args) {
        //无返回值的都只能单独加;成一个独立语句
        //调用MethodDefineDemo类中无参无返回值的方法sayHello
        MethodDefineDemo.sayHello();
        //调用MethodDefineDemo类中有参无返回值的方法printRectangle
        MethodDefineDemo.printRectangle(5,10,'@');
​
        //有返回值的
        //(1)方法调用表达式可以作为赋值表达式的值
        int bigger = MethodDefineDemo.max(7,3);
        System.out.println("bigger = " + bigger);
​
        //(2)方法调用表达式可以作为计算表达式的一个操作数
        //随机产生两个[1,100]之间的整数,并求和
        int sum = MethodDefineDemo.getIntBetweenOneToHundred() + MethodDefineDemo.getIntBetweenOneToHundred();
        System.out.println("sum = " + sum);
​
        //(3)方法调用表达式可以作为另一次方法调用的实参
        int x = 4;
        int y = 5;
        int z = 2;
        int biggest = MethodDefineDemo.max(MethodDefineDemo.max(x,y),z);
        System.out.println("biggest = " + biggest);
​
        //(4)方法调用表达式直接加;成为一个独立的语句,这种情况,返回值丢失
        MethodDefineDemo.getIntBetweenOneToHundred();
    }
}

5.1.5 方法调用内存分析

方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。

栈结构:先进后出,后进先出。

5.2 可变参数

JDK1.5之后,当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数。

1、可变参数的格式:

【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){  }

2、可变参数的声明:

(1)一个方法最多只能有一个可变参数

(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个

3、可变参数的使用:

(1)在声明它的方法中,可变参数当成数组使用

(2)调用带可变参数的方法时:

  • 非可变参数部分必须传入对应类型和个数的实参;
  • 可变参数部分按照可变参数的规则传入0~n个对应类型的实参或传入1个对应类型的数组实参;

4、对比可变参数与数组类型的参数:

其实”数据类型...“约等于”数据类型[]“ ,只是”数据类型[]“ 这种定义,在调用时必须传递数组,而”数据类型...“更灵活,既可以传递数组,又可以直接传递数组的元素。

案例:求n个整数的和

public class VarParamTest {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        System.out.println(add(arr));
        System.out.println(add(new int[]{1,2,3,4,5}));
        System.out.println(add(new int[]{1}));
//        System.out.println(add());//报错,必须new一个数组
        System.out.println(add(new int[0]));//数组的长度为,表示没有元素
​
        System.out.println(sum(1));
        System.out.println(sum(1,2,3));
        System.out.println(sum(1,2,3,4,5));
        System.out.println(sum());
        System.out.println(sum(new int[]{1,2,3,4,5}));
    }
​
    /*
    声明一个方法,可以求任意个整数的和
     */
    public static int add(int[] arr){
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result += arr[i];
        }
        return result;
    }
​
    public static int sum(int... arr){//把arr当成数组 int[]即可
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result += arr[i];
        }
        return result;
    }
}
​

5.3 方法的参数传递机制

方法的参数传递机制:实参给形参赋值,那么反过来形参会影响实参吗?

  • 方法的形参是基本数据类型时,形参值的改变不会影响实参;

  • 方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。

    • 注意:String、Integer等特殊类型容易错

1、形参是基本数据类型

案例:编写方法,交换两个整型变量的值

public class PrimitiveTypeParam {
    public static void swap(int a, int b){//交换两个形参的值
        int temp = a;
        a = b;
        b = temp;
    }
​
    public static void main(String[] args) {
        int x = 1;
        int y = 2;
        System.out.println("交换之前:x = " + x +",y = " + y);//1,2
        swap(x,y);//实参x,y是基本数据类型,给形参的是数据的“副本”,调用完之后,x与y的值不变
        System.out.println("交换之后:x = " + x +",y = " + y);//1,2
    }
}

2、形参是引用数据类型

public class ArrayTypeParam {
    public static void sort(int[] arr){//这里对arr数组进行排序,就相当于对nums数组进行排序
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length - i; j++) {
                if(arr[j] > arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
​
    public static void iterate(int[] arr){//输出数组的元素,元素之间使用空格分隔,元素打印完之后换行
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
        System.out.println();
    }
​
    public static void main(String[] args) {
        int[] nums = {4,3,1,6,7};
        System.out.println("排序之前:");
        iterate(nums);//实参nums把数组的首地址给形参arr,这个调用相当于输出nums数组的元素
​
        sort(nums);//对nums数组进行排序
​
        System.out.println("排序之后:");
        iterate(nums);//输出nums数组的元素
        //上面的代码,从头到尾,堆中只有一个数组,没有产生新数组,无论是排序还是遍历输出都是同一个数组
    }
}

5.4 方法的重载

  • 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。

  • 参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。

  • 重载方法调用:JVM通过方法的参数列表,调用匹配的方法。

    • 先找个数、类型最匹配的
    • 再找个数和类型唯一可以兼容的,如果同时多个方法可以兼容将会报错

案例,用重载实现:

(1)定义方法求两个整数的最大值

(2)定义方法求三个整数的最大值

(3)定义方法求两个小数的最大值

(4)定义方法求n个整数最大值

public class MathTools {
    //求两个整数的最大值
    public static int max(int a,int b){
        return a>b?a:b;
    }
​
    //求两个小数的最大值
    public static double max(double a, double b){
        return a>b?a:b;
    }
​
    //求三个整数的最大值
    public static int max(int a, int b, int c){
        return max(max(a,b),c);
    }
​
    //求n整数的最大值
    public static int max(int... nums){
        int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }
}

1、找最匹配的

package com.atguigu.test06.overload;
​
public class MethodOverloadMosthMatch {
    public static void main(String[] args) {
        System.out.println(MathTools.max(5,3));
        System.out.println(MathTools.max(5,3,8));
        System.out.println(MathTools.max(5.7,2.5));
    }
}

2、找唯一可以兼容的

public class MethodOverloadMostCompatible {
    public static void main(String[] args) {
        System.out.println(MathTools.max(5.7,9));
        System.out.println(MathTools.max(5,6,8,3));
//        System.out.println(MathTools.max(5.7,9.2,6.9)); //没有兼容的
    }
}

3、多个方法可以匹配或兼容

package com.atguigu.test06.overload;
​
public class MathTools {
    //求两个整数的最大值
    public static int max(int a,int b){
        return a>b?a:b;
    }
​
    //求两个小数的最大值
    public static double max(double a, double b){
        return a>b?a:b;
    }
​
    //求三个整数的最大值
    public static int max(int a, int b, int c){
        return max(max(a,b),c);
    }
​
    //求n整数的最大值
    public static int max(int... nums){
        int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }
​
/*    //求n整数的最大值
    public static int max(int[] nums){  //编译就报错,与(int... nums)无法区分
        int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }*//*    //求n整数的最大值
    public static int max(int first, int... nums){  //当前类不报错,但是调用时会引起多个方法同时匹配
        int max = first;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }*/
}

4、方法的重载和返回值类型无关

public class MathTools {
    public static int getOneToHundred(){
        return (int)(Math.random()*100);
    }
    
    public static double getOneToHundred(){
        return Math.random()*100;
    }
}
//以上方法不是重载

5.5 方法的递归调用

递归调用:方法自己调用自己的现象就称为递归。

递归的分类:

  • 递归分为两种,直接递归和间接递归。
  • 直接递归称为方法自身调用自己。
  • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

注意事项

  • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。

  • 在递归中虽然有限定条件,但是递归深度不能太深,否则效率低下,或者也会发生栈内存溢出。

    • 能够使用循环代替的,尽量使用循环代替递归

案例:计算斐波那契数列(Fibonacci)的第n个值,斐波那契数列满足如下规律,

1,1,2,3,5,8,13,21,....

即从第三个数开始,一个数等于前两个数之和。假设f(n)代表斐波那契数列的第n个值,那么f(n)满足:

f(n) = f(n-2) + f(n-1);


​
public class FibonacciTest {
    public static void main(String[] args) {
        System.out.println(f(20));//6765
        System.out.println(fValue(20));//6765
        
        System.out.println("-----------------------------");
        
        for(int i=1; i<=10; i++){
            System.out.println("斐波那契数列第" +i +"个数:" + f(i));
        }
        System.out.println("-----------------------------");
        for(int i=1; i<=10; i++){
            System.out.println("斐波那契数列第" +i +"个数:" + fValue(i));
        }
        
    }
​
    //使用递归的写法
   public static int f(int n){//计算斐波那契数列第n个值是多少
        if(n<1){//负数是返回特殊值1,表示不计算负数情况
            return 1;
        }
        if(n==1 || n==2){
            return 1;
        }
        return f(n-2) + f(n-1);
    }
​
    //不用递归
    public static int fValue(int n){//计算斐波那契数列第n个值是多少
        if(n<1){//负数是返回特殊值1,表示不计算负数情况
            return 1;
        }
        if(n==1 || n==2){
            return 1;
        }
        //从第三个数开始,  等于 前两个整数相加
        int beforeBefore = 1; //相当于n=1时的值
        int before = 1;//相当于n=2时的值
        int current = beforeBefore + before; //相当于n=3的值
        //再完后
        for(int i=4; i<=n; i++){
            beforeBefore = before;
            before = current;
            current = beforeBefore + before;
            /*
            假设i=4
                beforeBefore = before; //相当于n=2时的值
                before = current; //相当于n=3的值
                current = beforeBefore + before; //相当于n = 4的值
            假设i=5
                beforeBefore = before; //相当于n=3的值
                before = current; //相当于n = 4的值
                current = beforeBefore + before; //相当于n = 5的值
                ....
             */
        }
        return current;
    }
}