Java有陷阱,用时需谨慎——慎用入参做返回值

文章讨论了在Java中不应将入参用作返回值的问题,解释了由于Java的值传递特性导致的修改参数无效的情况,并提供了解决方案,包括避免在方法中为参数赋值新对象,而是直接修改参数对象的属性。

正常情况下,在Java中入参是不建议用做返回值的。除了造成代码不易理解、语义不清等问题外,可能还埋下了陷阱等你入坑。

问题背景 比如有这么一段代码:

@Named
public class AService {   
private SupplyAssignment localSupply = new SupplyAssignment();
    @Inject
    private BService bervice;

    public List<Supply> calcSupplyAssignment()
       List<Supply> supplyList = bService.getLocalSupplyList(this.localSupply);
        …
       return supplyList;
    }//欢迎加入Java开发交流君样:909038429
}
复制代码

上面代码,服务A希望调用服务B,以获取supplyList,但同时,服务A又希望修改localSupply的状态值,未能避免修改calcSupplyAssignment接口的(不想改返回的类型),将localSupply作为了入参但同时也用作了返回值。

服务B代码如下:

@Named
public class BService {   

public List<Supply> getLocalSupplyList (SupplyAssignment localSupply)
    SupplyAssignment supplyAssignment = this.getSupplyAssignment();
        // 希望localSupply被重新赋值后返回
        localSupply = supplyAssignment;
        …
        return supplyList;

    }
}
复制代码

在服务B代码内部,服务A的入参localSupply被传入,希望重新被supplyAssignment赋值而后返回新值。然而,这样做是无效的。

问题原因 先来看下编程语言中关于参数传递的类型:

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 因为Java程序设计语言是采用的值传递,因为Java没有指针的概念。也就是说方法得到的是所有参数值的一个拷贝,方法并不能修改传递给它的任何参数变量的内容。

因此,上述代码中,服务A调用服务B时,服务B的参数localSupply实际上是服务A的localSupply的一个拷贝,当然,这两个都是指向了同一个地址对象supplyAssignment1。 在这里插入图片描述

当在服务B内部对参数localSupply进行重新赋值是localSupply = supplyAssignment,实际上,只是对B的参数localSupply做了从新赋值,B的参数localSupply会指向一个新的地址对象supplyAssignment2。 在这里插入图片描述

从上图可以清晰看到,因此,服务A的localSupply和B的参数localSupply已经指向了不同的对象了,对B的参数localSupply做任何的修改,都不会影响服务A的localSupply的原值。这就是问题的原因,你希望服务B来修改服务A入参的状态,并将改后的值返回给服务A,但并不奏效。

解决方案 方案1:入参不要用作返回值 当然,这个是最清晰的且易于理解的,但这会导致有的接口的返回类型产生变化。

有时确实想要入参做返回值,那看方案2。

方案2:入参不要赋值新对象 这个方案就是直接在入参的对象上做状态的修改,而不要去赋值新对象。还是这个图: 在这里插入图片描述

在这个图中,只要我们是一直在B的参数localSupply修改的是supplyAssignment1的状态值,那结果就能反馈到服务A的localSupply上。如何实现?看下下面代码:

@Named
public class BService {   

    public List<Supply> getLocalSupplyList (SupplyAssignment localSupply)
        
        SupplyAssignment supplyAssignment = this.getSupplyAssignment();

        // 针对localSupply不能新建引用,只能重新赋值属性
        BeanUtils.copyProperties(supplyAssignment, localSupply);
        …
        return supplyList;

    }

}
复制代码

在上面的方法中,我们用到了Spring的工具类BeanUtils,该类的copyProperties方法的实质是将supplyAssignment的属性值,赋值到了localSupply的属性上。这意味着我们是修改的B的参数localSupply上的属性,而并未新建对象。

来源:https://juejin.cn/post/6926031880420589582
<think> 我们使用CALL语句调用Oracle存储过程时,如果存储过程有数(包括IN、OUT、IN OUT数),特别是需要返回值(即OUT数)时,通常需要结合匿名块来执行。因为CALL语句本身在Oracle中并不直接支持绑定变量来接收OUT数(在SQL*Plus中可以用绑定变量,但在PL/SQL块中更常见的是用局部变量接收)。 下面我们详细说明如何使用CALL调用有存储过程并获取返回值(OUT数)的方法: 步骤: 1. 在匿名PL/SQL块中使用CALL语句。 2. 在DECLARE部分声明变量来存储OUT数的值。 3. 在BEGIN部分使用CALL语句调用存储过程,并将声明的变量作为OUT数的实。 4. 可以通过DBMS_OUTPUT输出返回值,或者通过其他方式使用这些变量。 示例存储过程定义: ```sql CREATE OR REPLACE PROCEDURE get_employee_info( p_emp_id IN NUMBER, p_emp_name OUT VARCHAR2, p_salary OUT NUMBER ) AS BEGIN SELECT ename, sal INTO p_emp_name, p_salary FROM emp WHERE empno = p_emp_id; END; ``` 使用CALL调用上述存储过程的匿名块: ```sql DECLARE v_emp_name VARCHAR2(100); v_salary NUMBER; BEGIN -- 使用CALL语句调用存储过程,注意:OUT数需要用变量接收 CALL get_employee_info(7369, v_emp_name, v_salary); -- 或者使用命名数方式(推荐,清晰) CALL get_employee_info(p_emp_id=>7369, p_emp_name=>v_emp_name, p_salary=>v_salary); -- 输出结果 DBMS_OUTPUT.PUT_LINE(&#39;员工姓名:&#39; || v_emp_name || &#39;, 工资:&#39; || v_salary); END; ``` 注意: - 在DBeaver中执行上述匿名块之前,需要确保开启了DBMS_OUTPUT(点击底部面板的DBMS Output,然后点击开启按钮)。 - CALL语句在PL/SQL块中使用时数传递规则与直接调用存储过程相同。 另外,也可以使用传统的存储过程调用方式(不使用CALL): ```sql DECLARE v_emp_name VARCHAR2(100); v_salary NUMBER; BEGIN -- 直接调用存储过程 get_employee_info(7369, v_emp_name, v_salary); -- 或者命名方式 get_employee_info(p_emp_id => 7369, p_emp_name => v_emp_name, p_salary => v_salary); DBMS_OUTPUT.PUT_LINE(&#39;员工姓名:&#39; || v_emp_name || &#39;, 工资:&#39; || v_salary); END; ``` 使用CALL与直接调用的区别不大,但CALL是SQL标准语法,而直接调用是PL/SQL语法。在PL/SQL块中,两种方式都可以。 特别提示:如果存储过程有返回值(OUT数),则不能单独使用一个CALL语句(即不在块中)来调用,因为这样无法获取OUT数的值。例如,以下语句在SQL*Plus中可以配合绑定变量使用,但在PL/SQL块中则不行(在DBeaver的SQL编辑器中,单独执行CALL语句通常只适合无OUT数的情况): ```sql -- 以下方式在DBeaver中执行会报错,因为v_emp_name和v_salary未定义 CALL get_employee_info(7369, v_emp_name, v_salary); ``` 因此,在DBeaver中执行带OUT数的存储过程,推荐使用匿名块包装。 另外,对于函数返回值(函数使用RETURN语句返回),则可以使用SELECT语句调用,但存储过程的OUT数只能通过上述方式获取。 总结:在DBeaver中执行带返回值的存储过程(即有OUT数)时,使用匿名PL/SQL块并在其中用CALL或直接调用存储过程,然后在块中声明变量接收OUT数的值。 如果使用JDBC调用,则又是另一种方式(如引用[2]中所提到的Java代码),但用户当前问题是在DBeaver中执行,所以我们只讨论在DBeaver中的写法。 希望以上内容能够解答用户问题。 </think>在 Oracle 中,使用 `CALL` 语句调用存储过程并获取返回值时,需要结合匿名 PL/SQL 块处理 OUT 数。以下是具体实现方法: ### 1. 存储过程定义(带 OUT 返回值) ```sql CREATE OR REPLACE PROCEDURE get_employee_salary( p_emp_id IN NUMBER, -- 输数 p_salary OUT NUMBER -- 输出数(返回值) ) AS BEGIN SELECT sal INTO p_salary FROM emp WHERE empno = p_emp_id; END; ``` ### 2. 使用 CALL 获取返回值(推荐方法) ```sql -- 匿名块处理OUT数 DECLARE v_salary NUMBER; -- 声明变量接收返回值 BEGIN -- 使用CALL调用存储过程 CALL get_employee_salary(7369, v_salary); -- 输出返回值 DBMS_OUTPUT.PUT_LINE(&#39;员工薪资: &#39; || v_salary); END; ``` ### 3. 直接调用(无需 CALL) ```sql DECLARE v_salary NUMBER; BEGIN -- 直接调用存储过程(效果等同CALL) get_employee_salary(p_emp_id => 7369, p_salary => v_salary); DBMS_OUTPUT.PUT_LINE(&#39;薪资结果: &#39; || v_salary); END; ``` ### 4. 处理多个返回值 ```sql -- 带多个OUT数的存储过程 CREATE OR REPLACE PROCEDURE get_employee_info( p_emp_id IN NUMBER, p_name OUT VARCHAR2, p_job OUT VARCHAR2, p_salary OUT NUMBER ) AS BEGIN ... END; -- 调用方法 DECLARE v_name VARCHAR2(100); v_job VARCHAR2(100); v_salary NUMBER; BEGIN CALL get_employee_info(7369, v_name, v_job, v_salary); DBMS_OUTPUT.PUT_LINE(v_name || &#39; | &#39; || v_job || &#39; | &#39; || v_salary); END; ``` ### ⚠️ 注意事项 1. **OUT数处理** 必须声明变量接收输出数,直接尝试 `CALL proc(123, ?)` 会报错 2. **DBMS输出查看** - 执行前打开 DBeaver 底部 `DBMS Output` 面板 - 点击 `+` 添加当前连接 - 点击红色圆形按钮 🔴 启用输出 3. **数据类型匹配** 接收变量的数据类型必须与存储过程定义的 OUT 数类型一致 4. **错误处理** ```sql DECLARE v_result NUMBER; BEGIN CALL your_proc(&#39;input&#39;, v_result); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(&#39;错误: &#39; || SQLERRM); END; ``` > **提示**:在 Java 中调用时需使用 `CallableStatement` 注册 OUT 数(见引用[2]),但在 DBeaver 中直接使用上述 PL/SQL 块即可[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值