所以,我刚开始学习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;
}
那是不好的设计?
答案 0 :(得分:14)
使用适当支持的语言设计并不错,(*)但是当你必须定义一个MutableInt
类只是为了在两种方法之间进行通信时,肯定是错误的。
您发布的示例的解决方案是返回两个整数的数组,并通过null
返回或异常发出信号失败。这并不总是有效,所以有时你必须......
Result
,它封装了计算结果(当你处理int
和float
而不是两个整数时),也许将实际计算作为方法或构造函数; (*)语言设计不好。在Python或Go中,您将返回多个值并停止担心。
答案 1 :(得分:14)
如果将代码结构化为干净,可理解的抽象,那么面向对象的编程就是最好的。
作为抽象的数字是不可变的并且没有身份(即“5”总是“5”并且没有“多个”这样的东西五个实例。)。
你想要发明的是一个“可变数字”,它是可变的并具有同一性。这个概念有点笨拙,使用更有意义的抽象(对象)建模你的问题可能会更好。
在代表某事物且具有特定界面的对象中思考,而不是单个值的单个部分。
答案 2 :(得分:6)
通过引用传递值对象通常是一个糟糕的设计。
在某些情况下它是有效的,例如用于高性能排序操作的数组位置交换。
您应该需要此功能的原因很少。在C#中,OUT关键字的使用通常是一个缺点。 out DateTime.TryParse(datetext, out DateValue)
之类的out关键字有一些可接受的用法,但out参数的标准用法是软件设计不佳,希望通常模拟使用标志作为状态的不良做法。
答案 3 :(得分:1)
您参与的糟糕设计是使用MutableInteger
课程。 2将永远是2.明天将是2。
标准的Java / OO模式通常是让这些项成为类的实例,让该类操作/管理它们。
接下来是AtomicInteger
。再一次,我从来没有遇到需要传递它的情况但是如果你不想重构很多代码(你的问题是一个“好习惯”,所以我必须努力对你)这是一个更好的选项。原因是如果你让一个整数转义到另一个函数,从封装的角度来看,你不知道另一个函数将在同一个线程上运行。因为这样的并发性是有问题的,你会可能想要原子引用类提供的并发性。 (另见AtomicReference
。)
答案 4 :(得分:1)
修改调用者堆栈中的var的方法可能非常混乱。
理想情况下,语言应该支持返回多个值,这将解决这类问题。
但在此之前,如果你必须使用“out”参数,你必须这样做。
答案 5 :(得分: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?
}
}
答案 6 :(得分:1)
不一般。您需要了解您的具体情况,并问自己功能的作用。并且您需要正确定义编码风格,尤其是在为其他人编写代码(分发库)时。
当函数返回多个输出时,通常按引用传递。返回包含函数返回的所有信息的ResultContainer
对象通常是个好主意。采用以下C#示例:
bool isTokenValid(string token, out string username)
VS
AuthenticationResult isTokenValid(string token)
class AuthenticationResult {
public bool AuthenticationResult;
public string Username;
}
不同之处在于带参考的方法(在本例中为out
put)参数清楚地突出显示它只能用于验证令牌或可选地用于提取用户信息。因此,即使您有义务传递参数,如果您不需要也可以将其丢弃。第二个例子更加代码冗长。
当然,如果你有这样的方法,第二种设计是可取的
bool doSomething(object a, ref object b, ref object c, ref object d, ... ref object z);
因为你要将它们全部包装到容器中。
现在让我澄清一下:在Java和C#中,非原始类型始终作为克隆引用传递。这意味着object
s本身没有被克隆,但只有对它们的引用被克隆到堆栈中,然后你不能期望在返回后指向一个完全不同的对象。相反,您始终期望通过该方法修改对象的状态。否则你只需clone()
对象和vo。
这就是诀窍:MutableInteger
,或更好Holder
模式,是通过引用传递原始值的解决方案。
当您的IDL具有引用参数时,CORBA idl2java
编译器当前使用它。
在您的具体情况下,我无法回答您关于好或坏设计的问题,因为您展示的方法过于通用。所以想一想。作为输入,如果我有一些应用于多媒体信息的后处理功能,即使像加密,我也会使用引用传递。对我来说,以下看起来是一个很好的设计
encrypt(x);
VS
x = encrypt(x);
答案 7 :(得分: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);
}
}