You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin函数参数Val无法重新赋值问题解决(含BST示例)

Kotlin中参数重新赋值的解决方案(附BST节点删除场景)

问题描述

用Kotlin实现二叉搜索树(BST)节点删除的递归函数时,遇到编译错误:Val cannot be reassigned。已知Kotlin函数参数默认是val(不可修改),但需要找到对参数赋值的方法。

错误代码如下(标注错误行):

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KMutableProperty0

data class Node(var value:Int?){
    var data:Int? = value
    var left:Node? = null
    var right:Node? = null
}
fun insert(root:Node?, value:Int?):Node{
    var root = root
    if(root==null){
        root = Node(value)
        return root
    }
    value?.let {
        if (root.data!! > value) {
            root.left = insert(root.left, value)
        } else {
            root.right = insert(root.right, value)
        }
    }
    return root
}
fun inorder(root: Node?){
    if(root == null ) return

    root.data?.let {
        inorder(root.left)
        print("${root.data} ")
        inorder(root.right)
    }
}

fun srchDelete(root: Node?, key: Int){
    if(root == null){
        return
    }
    else if (root.data == key){
        if(root.left == null && root.right == null){
            root.data = null
            return
        }
        if(root.left != null && root.right == null){
            root = root.left // 错误:Val cannot be reassigned
        }
    }
    else {
        srchDelete(root.left, key)
        srchDelete(root.right, key)
    }
}

fun main(){
    var values = arrayOf(5,1,3,4,2,7)
    var root:Node? = null
    for(i in values){
        root = insert(root, i)
    }
    srchDelete(root, 2)
    inorder(root)
}

另外,对比Java和Kotlin的数组示例,Java允许在函数内重新赋值参数,Kotlin则不行:

Java示例

import java.util.stream.IntStream;

public class DeleteNode {
    static int[] fun (int[] arr){
        arr[0] = 1;
        arr[1] = 9;
        arr[7] = 1;
        arr = new int[9]; // Java允许此操作
        arr[0] = 9;
        return arr;
    }
    public static void main(String[] args) {
        int[] arr = new int[8];
        int[] array = fun(arr);
        IntStream.range(0, arr.length).forEach(h -> System.out.print(" " + arr[h]));
        System.out.println();
        IntStream.range(0, array.length).forEach(h -> System.out.print(" " + array[h]));

    }
}

Kotlin示例

fun list(arr:IntArray):IntArray{
    arr[0] = 1
    arr[8] = 1
    arr = IntArray(8){0} // Kotlin不允许此操作
    arr[0] = 1
    return arr
}
fun main(){
    var arr = IntArray(9){0}
    var array = list(arr)
    for(value in arr) print("$value ")
    for(value in array) print("$value ")
}

请问如何让Kotlin支持这种参数重新赋值操作?


解决方案

1. 将参数转为局部可变变量

这是最直接的处理方式:在函数内部把val参数赋值给一个var类型的局部变量,后续操作这个局部变量即可。

针对BST删除函数的修改示例:

fun srchDelete(root: Node?, key: Int){
    var mutableRoot = root // 声明可变局部变量
    if(mutableRoot == null){
        return
    }
    else if (mutableRoot.data == key){
        if(mutableRoot.left == null && mutableRoot.right == null){
            mutableRoot.data = null
            return
        }
        if(mutableRoot.left != null && mutableRoot.right == null){
            mutableRoot = mutableRoot.left // 现在可正常赋值
        }
    }
    else {
        srchDelete(mutableRoot.left, key)
        srchDelete(mutableRoot.right, key)
    }
}

注意:这种方式仅修改局部变量的引用,不会影响函数外部的原变量。如果需要让外部感知到引用变化(比如BST删除后更新根节点),必须让函数返回修改后的节点,就像你的insert函数那样。

正确的BST删除函数应该设计为返回Node?:

fun srchDelete(root: Node?, key: Int): Node? {
    var mutableRoot = root
    if (mutableRoot == null) {
        return null
    }
    when {
        key < mutableRoot.data!! -> mutableRoot.left = srchDelete(mutableRoot.left, key)
        key > mutableRoot.data!! -> mutableRoot.right = srchDelete(mutableRoot.right, key)
        else -> {
            // 处理节点删除的三种情况
            if (mutableRoot.left == null) return mutableRoot.right
            if (mutableRoot.right == null) return mutableRoot.left
            // 有两个子节点时,取右子树最小节点替换当前节点
            var temp = mutableRoot.right
            while (temp?.left != null) temp = temp.left
            mutableRoot.data = temp?.data
            mutableRoot.right = srchDelete(mutableRoot.right, temp?.data!!)
        }
    }
    return mutableRoot
}

在main函数中更新根节点:

root = srchDelete(root, 2)

2. 使用包装类传递可变引用

如果需要让函数内部的修改直接影响外部变量,可以用包装类(比如MutableList、自定义包装对象),但这种方式可读性较差,一般不推荐。

示例(用MutableList包装节点):

fun srchDelete(rootWrapper: MutableList<Node?>, key: Int){
    val root = rootWrapper[0]
    if(root == null){
        return
    }
    else if (root.data == key){
        if(root.left == null && root.right == null){
            rootWrapper[0] = null
            return
        }
        if(root.left != null && root.right == null){
            rootWrapper[0] = root.left // 修改包装类中的引用,外部可感知
        }
    }
    else {
        srchDelete(mutableListOf(root.left), key)
        srchDelete(mutableListOf(root.right), key)
    }
}

调用方式:

val rootWrapper = mutableListOf(root)
srchDelete(rootWrapper, 2)
root = rootWrapper[0]

3. 理解Kotlin参数不可变的设计意图

Kotlin默认将函数参数设为val,是为了避免因意外修改参数引用导致的bug。Java中允许修改参数引用,但这种修改仅在函数内部有效,外部变量不会受影响(就像你的Java数组示例,外部的arr仍是原数组,函数返回的是新数组)。因此在Kotlin中,更推荐通过返回值传递修改后的引用,而非修改参数本身。


内容的提问来源于stack exchange,提问作者JavaBoi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:14:54