JavaSE之可变参数、递归、数组算法与参数传递全解析

JavaSE之可变参数、递归、数组算法与参数传递全解析

一、可变参数:灵活处理不确定个数的参数

1.1 可变参数概述

在实际开发中,经常会遇到「参数类型确定,但参数个数不确定」的场景(例如:实现多个整数相加、多字符串拼接)。可变参数正是为解决这类问题而生,它允许方法接收任意个数的同类型参数,底层本质是数组,但调用时无需手动创建数组,极大简化了代码。

核心要点:
  • 定义格式数据类型... 变量名(例如 int... numsString... strs
  • 本质:编译后自动转换为数组(String... strs 等价于 String[] strs
  • 使用限制
    1. 一个方法中只能定义一个可变参数
    2. 可变参数必须位于参数列表的最后一位(避免参数歧义)

1.2 可变参数实战练习:字符串拼接

需求:定义方法,用指定分隔符拼接多个字符串(如用 - 拼接 ["张无忌","赵敏","小昭","小华"],结果为 张无忌-赵敏-小昭-小华)。

public class Day08Train {

    public static void main(String[] args) {
        // 调用可变参数方法,直接传递多个字符串,无需手动创建数组
        String str = concat("-", "张无忌", "赵敏", "小昭", "小華");
        System.out.println(str); // 输出结果:张无忌-赵敏-小昭-小華
    }

    /**
     * 字符串拼接方法
     * @param s 分隔符(固定参数,位于可变参数前)
     * @param arr 待拼接的字符串数组(可变参数)
     * @return 拼接后的完整字符串
     */
    public static String concat(String s, String... arr) {
        String str = "";
        for (int i = 0; i < arr.length; i++) {
            // 最后一个元素后不添加分隔符
            if (i == arr.length - 1) {
                str += arr[i];
            } else {
                str += arr[i] + s;
            }
        }
        return str;
    }
}

二、递归算法:自己调用自己的解题思路

2.1 递归概述

递归是一种特殊的编程思想,指方法内部直接或间接调用自身,核心是将复杂问题拆解为「与原问题结构相同但规模更小的子问题」,直到子问题可直接解决(即「递归出口」)。

递归分类:
  • 直接递归:方法自身直接调用自己(如 method() 中调用 method()
  • 间接递归:多个方法循环调用(如 A() 调用 B()B() 调用 C()C() 调用 A()
关键注意事项:
  1. 必须有明确的递归出口:否则会陷入无限递归,导致「栈内存溢出(StackOverflowError)」
  2. 控制递归调用次数:即使有出口,若调用次数过多(如递归深度超过 1 万次),也会触发栈溢出

2.2 递归经典练习

练习 1:利用递归输出 3 到 1

需求:从指定数字 x 倒序输出到 1,递归出口为 x == 1

public class RecursionTest1 {
    public static void main(String[] args) {
        method(3); // 输出结果:3 2 1
    }

    public static void method(int x) {
        if (x == 1) {
            System.out.println(x);
            return; // 递归出口:x=1时终止调用
        } else {
            System.out.println(x);
            x--;
            method(x); // 递归调用:方法自身调用,参数规模减小
        }
    }
}
练习 2:求 n 的阶乘

阶乘定义:n! = n × (n-1) × (n-2) × ... × 1(如 5! = 5×4×3×2×1 = 120),递归出口为 n == 1(因 1! = 1)。

public class RecursionTest2 {
    public static void main(String[] args) {
        int result = factorial(5);
        System.out.println(result); // 输出结果:120
    }

    public static int factorial(int n) {
        int sum = 0;
        if (n == 1) {
            return 1; // 递归出口:n=1时返回1
        } else {
            sum = n * factorial(n - 1); // 递归逻辑:n! = n × (n-1)!
        }
        return sum;
    }
}
练习 3:计算斐波那契数列第 n 个值

斐波那契数列规律:从第 3 项开始,每一项等于前两项之和(如 1, 1, 2, 3, 5, 8, 13...),递归出口为 n == 1n == 2(前两项均为 1)。

public class RecursionTest3 {
    public static void main(String[] args) {
        int result = feibo(6);
        System.out.println(result); // 输出结果:8(第6项为8)
    }

    public static int feibo(int n) {
        int x = 0;
        if (n == 1 || n == 2) {
            return 1; // 递归出口:前两项均返回1
        } else {
            x = feibo(n - 1) + feibo(n - 2); // 递归逻辑:第n项 = 第n-1项 + 第n-2项
            return x;
        }
    }
}

三、数组算法:反转、排序与查找实战

数组是 Java 中最常用的数据结构之一,反转、冒泡排序、二分查找是数组操作的经典算法,需熟练掌握其逻辑与实现。

3.1 数组反转

需求:将数组中「对称索引位置的元素互换」(如 [1,2,3,4,5] 反转后为 [5,4,3,2,1])。

public class ArrayReverse {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        FanZhuan(arr); // 调用反转方法
    }

    /**
     * 数组反转方法
     * @param arr 待反转的数组
     */
    public static void FanZhuan(int[] arr) {
        int arr1[] = new int[arr.length]; // 创建新数组存储反转后的数据
        for (int i = 0; i < arr.length; i++) {
            // 对称索引:原数组索引i 对应 新数组索引arr.length-1-i
            arr1[i] = arr[arr.length-1-i];
        }
        
        // 方式1:普通for循环打印反转后数组
        for (int i = 0; i < arr1.length; i++){
            System.out.println(arr1[i]); // 输出:5 4 3 2 1
        }

        // 方式2:增强for循环打印(更简洁)
        for (int i : arr1){
            System.out.println(i); // 输出:5 4 3 2 1
        }
    }
}

3.2 冒泡排序

冒泡排序核心逻辑:通过相邻元素的多次比较与交换,将最大元素逐步“冒泡”到数组末尾,每轮排序后,未排序区间的最大元素确定位置。

关键规律:
  • 排序轮数:arr.length - 1(n 个元素需 n-1 轮,最后一个元素无需排序)
  • 每轮比较次数:arr.length - 1 - i(i 为当前轮数,已排序的元素无需再比较)
public class BubbleSortDemo {
    public static void main(String[] args) {
        int[] arr = {3, 2, 1, 5, 4};
        bubbleSort(arr); // 调用冒泡排序方法
    }

    /**
     * 冒泡排序方法(升序)
     * @param arr 待排序的数组
     */
    public static void bubbleSort(int[] arr) {
        // 外层循环:控制排序轮数(共arr.length-1轮)
        for (int i = 0; i < arr.length; i++) { 
            // 内层循环:控制每轮比较次数(每轮减少i次,因i个元素已排序)
            for (int j = 0; j < arr.length - 1 - i; j++) {  
                int temp = 0;
                // 相邻元素比较:前元素 > 后元素则交换(升序逻辑)
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        
        // 增强for循环打印排序后数组
        for (int i : arr) {
            System.out.println(i); // 输出:1 2 3 4 5
        }
    }
}
排序过程示例(数组 [3,2,1,5,4]):
  • 第 1 轮:[2,1,3,4,5](比较 4 次,最大元素 5 冒泡到末尾)
  • 第 2 轮:[1,2,3,4,5](比较 3 次,第二大元素 4 冒泡到倒数第二位)
  • 第 3 轮:无交换(比较 2 次,数组已有序)
  • 第 4 轮:无交换(比较 1 次,排序结束)

3.3 二分查找

二分查找(又称折半查找)是高效的查找算法,适用于「已排序的数组」,时间复杂度为 O(log n)(远优于线性查找的 O(n))。

核心注意事项:
  1. 前提条件:必须作用于已排序数组(升序/降序需与比较逻辑匹配)
  2. 边界计算
    • 中间索引:mid = left + (right - left)/2(避免 left + right 导致的整数溢出)
    • 循环条件:left <= right(包含等于,确保不遗漏最后一个元素)
  3. 范围调整
    • 找到目标:立即返回 mid(索引)
    • 目标更大:left = mid + 1(向右缩小查找范围)
    • 目标更小:right = mid - 1(向左缩小查找范围)
  4. 终止条件:循环结束返回 -1(表示目标不存在)
  5. mid 更新:每次循环必须重新计算 mid(不能在循环外固定赋值)
public class BinarySearchDemo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5}; // 二分查找必须用有序数组
        int index = midSort(arr, 5); // 查找目标值5
        if (index != -1) {
            System.out.println("目标值找到,索引为:" + index); // 输出:目标值找到,索引为:4
        } else {
            System.out.println("目标值不存在于数组中");
        }
    }

    /**
     * 二分查找方法(升序数组)
     * @param arr 已排序的数组
     * @param target 待查找的目标值
     * @return 目标值的索引(未找到返回-1)
     */
    public static int midSort(int[] arr, int target) {
        int left = 0; // 左边界索引
        int right = arr.length - 1; // 右边界索引

        // 先判断数组合法性(空数组或长度为0直接返回-1)
        if (arr.length == 0 || arr == null) {
            return -1;
        }
        
        // 循环查找:左边界 <= 右边界
        while (left <= right) {
            int mid = (left + right) / 2; // 计算中间索引
            if (arr[mid] == target) {
                return mid; // 找到目标,返回索引
            } else if (arr[mid] < target) {
                left = mid + 1; // 目标在右侧,调整左边界
            } else {
                right = mid - 1; // 目标在左侧,调整右边界
            }
        }
        return -1; // 循环结束未找到,返回-1
    }
}

3.4 对象数组

对象数组是「存储对象引用的数组」,数组中的每个元素都是对象的地址,而非对象本身。通过对象数组可批量管理多个对象。

需求:定义数组存储 3 个 Person 对象,并遍历获取属性值
1. Person 类(封装姓名、年龄属性)
public class Person {
    // 私有属性(封装)
    private String name;
    private int age;

    // 无参构造方法
    public Person() {
    }

    // 有参构造方法(用于初始化属性)
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter方法(获取私有属性值)
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // Setter方法(修改私有属性值,可选)
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
2. 对象数组的创建与遍历
public class ObjectArrayDemo {
    public static void main(String[] args) {
        // 1. 创建对象数组(长度为3,存储3个Person对象)
        Person[] arr = new Person[3];
        
        // 2. 创建Person对象
        Person p1 = new Person("张三", 18);
        Person p2 = new Person("lisi", 19);
        Person p3 = new Person("wangwu", 20);
        
        // 3. 将对象引用存入数组
        arr[0] = p1;
        arr[1] = p2;
        arr[2] = p3;

        // 4. 遍历对象数组,获取属性值(需通过Getter方法)
        for (int i = 0; i < arr.length; i++) {
            // 直接打印数组元素:输出对象的地址(如com.code.Person@41629346)
            System.out.println(arr[i]);
            // 通过Getter方法获取属性值
            System.out.println(arr[i].getName() + arr[i].getAge()); 
        }
    }
}
输出结果:
com.code.day08.Person@41629346
张三18
com.code.day08.Person@4f3f5b24
lisi19
com.code.day08.Person@15aeb7ab
wangwu20

四、方法参数传递:基本类型 vs 引用类型

Java 中方法参数传递的核心规则是「值传递」,但基本类型和引用类型的传递效果不同,本质是因为两者在内存中的存储方式不同。

4.1 基本数据类型做方法参数传递

基本类型(如 intdoubleboolean)存储的是「具体值」,传递时会复制一份值给方法参数,方法内部修改参数值不会影响原变量。

public class BasicParamDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println("调用前:a=" + a + ", b=" + b); // 输出:调用前:a=10, b=20
        
        // 传递的是a和b的值副本,而非原变量
        method(a, b);
        
        System.out.println("调用后:a=" + a + ", b=" + b); // 输出:调用后:a=10, b=20(原变量未变)
    }

    private static void method(int a, int b) {
        a += 10; // a变为20(修改的是副本)
        b += 20; // b变为40(修改的是副本)
        System.out.println("方法内:a=" + a + ", b=" + b); // 输出:方法内:a=20,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值