30

所以,我刚开始学习Java,发现根本没有“引用传递”这回事。我正在将应用程序从 C# 移植到 Java,原始应用程序具有整数和双精度值,它们是“ref”或“out”参数。

起初我以为我可以传入“整数”或“双精度”,因为那是引用类型,所以值会改变。但后来我了解到这些引用类型是不可变的。

所以,然后我创建了一个“MutableInteger”类和一个“MutableDouble”类,并将它们传递给我的函数。它有效,但我想我必须违背该语言的原始设计意图。

一般糟糕的设计是“通过引用传递”吗?我应该如何改变我的思维方式?

有这样的功能似乎是合理的:

bool MyClass::changeMyAandB(ref int a, ref int b)
{
    // perform some computation on a and b here

    if (success)
        return true;
    else return false;
}

这是糟糕的设计吗?

4

8 回答 8

16

在适当支持的语言中,它的设计还不错,(*) 但是当您必须定义一个MutableInt类只是为了在两个方法之间进行通信时,肯定有问题。

您发布的示例的解决方案是返回一个包含两个整数的数组,并通过null返回或异常表示失败。这并不总是有效,所以有时你必须...

  • 在当前对象上设置属性(当类中的两个方法通信时);
  • 传入该方法可能修改的整数数组(当有很多整数要多次传递时);
  • 创建一个辅助类,例如Result,封装计算结果(当您处理一个int和一个float而不是两个整数时),并且可能将实际计算作为方法或构造函数;
  • 使用您建议的习语,然后考虑在Apache Commons或其他好的库中使用对它的支持。

(*) 只是糟糕的语言设计。在 Python 或 Go 中,您将返回多个值并不必担心。

于 2013-02-28T17:37:53.237 回答
16

如果您将代码构造成干净、可理解的抽象,那么面向对象的编程是最好的。

数字,作为一种抽象,是不可变的并且没有身份(即“五”总是一个“五”,并且不存在“五的多个实例”这样的东西)。

您试图发明的是一个“可变数字”,它是可变的并且具有身份。这个概念有点笨拙,你可能最好用更有意义的抽象(对象)来建模你的问题。

考虑代表某物并具有特定界面的对象,而不是单个值块。

于 2013-02-28T17:46:38.753 回答
6

通过引用传递值对象通常是一个糟糕的设计。

它适用于某些场景,例如用于高性能排序操作的数组位置交换。

您需要此功能的原因很少。在 C# 中,使用 OUT 关键字通常本身就是一个缺点。out 关键字有一些可接受的用法,例如,DateTime.TryParse(datetext, out DateValue)但 out 参数的标准用法是糟糕的软件设计,通常希望模仿使用标志来表示所有状态的不良做法。

于 2013-02-28T17:38:26.210 回答
3

“通过引用”是糟糕的设计吗?

一般不会。您需要了解您的特定场景并问自己一个函数的作用。而且您需要正确定义您的编码风格,特别是如果您正在为他人编码(分发库)。

当您的函数返回多个输出时,通常会通过引用传递。ResultContainer返回一个包含函数返回的所有信息的对象通常是个好主意。以以下 C# 示例为例:

bool isTokenValid(string token, out string username)

VS

AuthenticationResult isTokenValid(string token)

class AuthenticationResult {
    public bool AuthenticationResult;
    public string Username;
}

不同之处在于带有引用(在本例中为output)参数的方法清楚地强调了它只能用于验证令牌或可选地用于提取用户信息。因此,即使您有义务传递一个参数,如果您不需要它,您也可能会丢弃它。第二个示例代码更冗长。

当然如果你有这样的方法,第二种设计是更可取的

bool doSomething(object a, ref object b, ref object c, ref object d, ... ref object z);

因为您会将它们全部包装到一个容器中。

现在让我澄清一下:在 Java 和 C# 中,非原始类型始终作为克隆引用传递。这意味着objects 不会自己克隆,而只会将对它们的引用克隆到堆栈中,然后您不能指望在返回后指向完全不同的对象。相反,您总是希望该方法修改对象的状态。否则你只是clone()对象,瞧。

所以这里有诀窍:MutableInteger或者更好的Holder模式,是通过引用传递原始值的解决方案。

idl2java当您的 IDL 具有引用参数时,CORBA 编译器当前使用它。

在您的具体情况下,我无法就设计的好坏回答您,因为您展示的方法太通用了。所以想想吧。就像输入一样,如果我对多媒体信息应用某种后处理功能,甚至像加密一样,我会使用引用传递。对我来说,以下看起来不错的设计

encrypt(x);

VS

x = encrypt(x);
于 2013-02-28T17:49:08.090 回答
1

您参与的糟糕设计是使用MutableInteger类。2 永远是 2。明天就是 2。

标准的 Java/OO 模式通常是让这些项目成为一个类的实例,并让该类操作/管理它们。

接下来是AtomicInteger. 同样,我从来没有遇到过需要传递它的情况,但是如果您不想重构大量代码(您的问题是“好习惯”,所以我不得不对您很苛刻),那就更好了选项。原因是如果你让一个整数转义到另一个函数,从封装的角度来看,你不知道另一个函数将在同一个线程上运行。 因此,并发性存在问题,您可能希望原子引用类提供的并发性。(另见AtomicReference。)

于 2013-02-28T17:38:30.003 回答
1

在调用者堆栈中修改 var 的方法可能会非常令人困惑。

理想情况下,语言应该支持返回多个值,这将解决这类问题。

但在此之前,如果必须使用“out”参数,则必须这样做。

于 2013-02-28T17:42:23.360 回答
1

一般糟糕的设计是“通过引用传递”吗?我应该如何改变我的思维方式?无需制作一个 POJO bean 来保存您的值,将该 bean 发送到函数并取回新值。当然,在某些情况下,您调用的函数可以返回值(如果它总是只有一件事您想要返回,但您谈论的是输出变量,所以我认为它不止一个)。

传统上制作具有需要更改的属性的 bean 示例:

class MyProps{
int val1;
int val2;//similarly can have strings etc here
public int getVal1(){
return val1;
}
public void setVal1(int p){ 
val1 = p;
}// and so on other getters & setters

}

或者可以使用泛型创建一个类来保存任何对象

class TVal<E>{
E val;
public E getValue(){
return val;
}
public void setValue(E p){
val = p;
}
}

现在使用您的类通过容器的引用传递:

public class UseIt{
    void a (){
      TVal<Integer> v1 = new TVal<Integer>();
      v1.setValue(1);//auto boxed to Integer from int
      TVal<Integer> v2 = new TVal<Integer>();
      v2.setValue(3);
      process(v1,v2);
      System.out.println("v1 " + v1 + " v2 " + v2);
    }

    void process(TVal<Integer> v,TVal<Integer> cc){
        v.setValue(v.getValue() +1);
        cc.setValue(cc.getValue() * 2);
    }
}
于 2013-02-28T18:19:58.073 回答
1

当然,这取决于您正在处理的特定问题,但我会说,在大多数情况下,如果您需要这样的功能,您的设计就不是非常面向对象的。
你想达到什么目的?如果这个数字 a 和 b 必须一起操作,可能它们属于 MyClass 类,你需要的是一个实例方法。就像是:

class MyClass{
     private int a;
     private int b;
     //getters/setters/constructors
     public boolean dothings(){
     // perform some computation on a and b here
        if (success)
             return true;
        else return false;
        //why not just return success?
     } 

}
于 2013-02-28T17:45:51.900 回答