【问题标题】:int vs Integer vs a user made class to achieve effective pass by reference behavior in Javaint vs Integer vs 用户创建的类以在Java中实现有效的引用传递行为
【发布时间】:2017-06-14 01:49:55
【问题描述】:

假设你有一个包含三个对象的类 您希望使用一种方法以相同方式更改的整数字段。

让我们保持简单,并说该方法所做的只是将传递给它的参数加 1。

也就是说,期望的行为是,当方法完成时,相关字段已经增加了 1。

在 Java 中,这些字段使用原始类型“int”是不可能实现的。
我知道 Java 如何“总是”按值传递,而不是按引用传递, - 并且 - 我在互联网上听到耳语说这是 Integer 类以及其他对象“包装器”类存在的原因之一 对于通常的原始类型,例如 int 和 double。

理论上,将对象作为参数发送给方法应该提供一种[有效地,如果不是技术上]通过引用传递的方法,因为传递的值应该是对对象的引用的值。 非常棘手。但是——这就是我烦恼的地方——我尝试通过传递一个整数参数而不是一个 int,但仍没有实现所需的行为。 1 未添加到相关字段。

然而,当我创建我自己的对象时,它只包含一个字段,一个 int 值,并将该对象的一个​​实例作为参数传递 对于一个简单地将 1 添加到传递的参数的适当方法,实际上已经实现了所需的行为。 1 已添加到相关字段。

所以围绕这个查询的问题是——真的有必要制作我自己的自制类来携带一个简单的整数值 每次我想实现这种期望的行为? Java提供的现有工具Integer真的不能完成这个简单的任务吗?

我没有用一种很好的、​​简洁的方法来处理我在开头提到的所有三个假设的整数字段,而是觉得有必要(在一个单独的、类似的项目中,最终引发了这种思路)制作一个单独的方法来对应到三个字段中的每一个,每个字段中的代码基本相同。这似乎效率很低。

从表面上看,编写三个类似的方法而不是一个似乎没什么大不了的,但要澄清为什么这让我感到沮丧 - 想象一下,而不是像我所说的那样具有三个整数字段的对象,有人说,我不知道,四千。最好只写一件东西来执行相同的行为,而不是复制和粘贴(以及更改任何必要的小部分)四千次。

所以我想最终的问题是, 为什么 Integer 不能以合理的方式运行?如果它甚至不能帮助执行这么简单的事情,那么将原语包装在 Object 中的意义何在?我是否遗漏了一些关于如何让 Integer 以所需方式运行的简单信息? (希望如此)答案似乎很接近但令人发指的是遥不可及,因为“RInteger”会产生所需的行为,而“Integer”却不会。

我在试图弄清楚如何构建这个艰苦的问题时使用的整个源代码如下。

package r9mp;

import javax.swing.SwingUtilities;

public class RefTest2 {
//[main m]
public static void main(String[] args){
    SwingUtilities.invokeLater(new Runnable(){
        public void run(){
            new RefTest2();
        }
    });
}
//[fields]
int i;
Integer I;
RInteger RI;

//[constr]
public RefTest2(){
    intTest();
    IntegerTest();
    RIntegerTest();

    display();
}

//[methods]
private void intTest(){
    i = 100;
    intMethod(i);
}
private void IntegerTest(){
    I = 100;  //boxing? auto?
    IntegerMethod(I);
    I = 100; //just in case.
    IntegerMethod2(I);
}
private void RIntegerTest(){
    RI = new RInteger(100);
    RIntegerMethod(RI);
}

private void intMethod(int ipar){
    ipar = ipar + 1;//no change. expected.
}
private void IntegerMethod(Integer IPar){
    IPar = IPar + 1;//no change. frustrating.
    pln("From inside IntegerMethod: IPar = " + IPar );
    pln("From inside IntegerMethod: I = " + I );
}
private void IntegerMethod2(Integer IPar){
    IPar = new Integer(IPar+1);//still no change.  there are no set methods for Integer, or I'd try them.
}
private void RIntegerMethod(RInteger riPar){
    riPar.value = riPar.value + 1;
}

private void display(){
    pln(
            "Display...    \n" +
            "i: "  + i  + "\n" +
            "I: "  + I  + "\n" +
            "RI: " + RI + "\n" +
            "--------"
            );
}

private void pln(){
    pln("");
}
private void pln(String s){
    System.out.println(s);
}

//[internal class]
private class RInteger{
    int value;
    public RInteger(int v){
        value = v;
    }
    public String toString(){
        return ""+value;

    }
}

}

而且,这里是输出...

【问题讨论】:

  • 嗨,Ryan,您似乎正在以一种奇怪的方式使用您的 Java 类 RefTest2。通常,该类将具有更新的字段。因此,setter 方法变量是通过引用传递还是通过值传递并不重要,因为我们只将它们用作传递信息的只读字段。
  • 如果你想写一个有副作用的方法(这已经很糟糕了),那么使用数组。很明显的解决方法。
  • 由于Integer 是不可变的,它在将其传递给方法并对其进行更改方面基本上与int 的行为方式相同。您无法更改 Integer 中的值,您只能创建新实例,然后覆盖传递的值并保留先前的引用。
  • 您写入字段,但您从未真正读取它们。这是故意的吗?我不清楚你期望发生什么。
  • 也许很多人不希望您对 Integer 的“期望”行为?还是对许多人来说是不受欢迎的?

标签: java integer int pass-by-reference pass-by-value


【解决方案1】:

原语及其包装器的一种方法怎么样?

private int incInteger(int value)
{
    return value + 1;       
}

并呼吁:

int intVal = 100;
intVal = incInteger(intVal);

Integer integerVal = 200;
integerVal = incInteger(integerVal);

【讨论】:

  • 这可能最终成为最好的答案......至少,如果我可以确定这将是在所有或大多数情况下做事的最佳方式。您的解决方案不是在方法中单独执行整个事情,而是将实际的值设置部分放在它之外,使用“intVal =”和“integerVal =”。如果这种事情确实是最好的/唯一的解决方案,那么我将不得不努力将它塞进我的大脑以这种方式进行编码,以避免给自己除了编写大量几乎相同的方法之外别无选择...
  • 对原语有好处。对于其他类,在方法中用新实例替换对象是个坏主意。如果需要,必须在调用者代码中完成。当然里面传递的对象的方法属性是可以改变的。这是一种常见的做法。同样到处都有很多这样的东西,比如在需要时传递的带有 Object 值的“Holder”。您的 RInteger 类是“持有者”。但基元不需要它。
【解决方案2】:

首先,您需要阅读关于不变性的内容,以了解为什么拥有它是一件非常好的事情。甚至存在利用它的整个语言(主要是函数式)。

读完之后,请阅读 Eric Lippert 关于不变性的系列文章。从这里开始:https://blogs.msdn.microsoft.com/ericlippert/2007/11/13/immutability-in-c-part-one-kinds-of-immutability/Mind = 吹。

但是为了给你一个简单的提示,为什么像 Integer 这样的原始包装器是不可变的,我只想说这些类经常用作哈希映射中的键,并且键必须是不可变的,因此它的 @987654324 @ 永远不会改变,否则哈希映射将失败,很难追踪行为。哈希图中的可变键是令人讨厌的错误。

您可以使用自己设计的类来实现您想要的功能,该类充当引用的角色,或者只需传递一个数组并修改array[0] 处的元素。

我的个人喜好如下:

  • 我会尽量使用返回值。
  • 当返回值不适用时(与invokeLater 一样),可以访问封闭类字段的内部/嵌套/匿名类是我的下一个偏好。
  • 如果这也不是一个选项,那么为手头的应用程序专门设计的特殊类是我的下一个选项。 (MyMutableNumberWrapper.)
  • 当我只想要快速而肮脏的东西时,像Ref<T> 这样的通用类(甚至是单元素数组)将是我的最终选择。

【讨论】:

  • 我知道数组也可以实现所需的行为。事实上,这是我在 RefTest 中测试的一部分,但我没有将它包含在 RefTest2 中。我有点不愿意使用数组,只是因为它看起来有点令人困惑。如果用户制造的对象是真正需要的,来实现这种事情,那么我想这就是我最终要做的事情。至于哈希映射,或者根本不是 Java 中的映射,我知道它们,但我几乎没有对它们进行过实验。
  • 我同意,数组是解决该问题的笨拙而笨拙的解决方案。我用更多信息修改了我的答案。
  • 我很确定“Java 总是按值传递参数”这句话有很大的误导性。对于原语是正确的,但对于对象,Java 总是将对象引用作为参数传递。对象本身不是。为了使其与原语一致,它们的包装器是不可变的,它允许将代码设置为“Integer a = 100;”工作而不是'Integer a = new Integer(100);`
  • @Vadim 哦,是的,我完全同意。请参阅我以前的答案:softwareengineering.stackexchange.com/a/286013/41811
  • @MikeNakis 我在发布代码时想知道 main 方法中的“invokeLater”位是否会改变所需的行为,不知何故……我已经习惯了这样写,因为我'一直在玩弄用摇摆元素制作简单的用户界面.. JFrames 和 JPanels 等。我觉得如果这会修改输出,那么找出确切原因将是一项艰巨的任务。我隐约知道匿名类,但我不会猜到它们会适用,我也没有尝试过使用它们。 Ref 的建议很有趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
  • 2018-12-16
  • 2014-09-14
  • 2011-04-21
  • 2011-08-17
  • 2012-10-02
  • 1970-01-01
相关资源
最近更新 更多