【问题标题】:Possible to prevent Java's pass by value in methods?可以防止 Java 在方法中按值传递吗?
【发布时间】:2011-03-10 17:11:02
【问题描述】:

Java 是一种“按值传递”语言,这意味着将变量发送到方法中,将变量指向新对象,不会影响外部变量。

public void one() {

String s = "one";
two(s);
System.out.println(s);

}

public void two( String s ) {
s = "two";
}

会写“一”。

有没有办法防止这种情况发生?或者在方法中将s 实际更改为“两个”的最常见解决方案或模式是什么?

【问题讨论】:

标签: java pass-by-value


【解决方案1】:

创建一个包含字符串的对象,然后将其传递给方法。

public class StringHolder {
    public String s;

    public StringHolder(String s) {
        this.s = s;
    }
}

那么代码如下所示:

public void two(StringHolder sh) {
    sh.s = "two";
}

StringHolder sh = new StringHolder("one");
two(sh);
System.out.println(sh.s);

虽然,对于上面的例子,你可以返回你想要的值:

public String two(String s) {
    return "two";
}

String s = "one";
s = two(s);
System.out.println(s);

对于Strings,您始终可以使用StringBuffer,它是可变的:

public void two(StringBuffer buf) {
    buf.setLength(0);
    buf.append("two");
}

【讨论】:

  • 每个包装器都需要一个类,您可以使用通用包装器来实现相同的效果。
  • 是的,但这确实取决于具体情况。如果您仅 使用字符串作为输入/输出参数,则无需为泛型而烦恼。或者,更好的是,也许您将多条数据捆绑在一起并将它们作为单个参数传递给函数。我们真的无法从所问的问题中判断情况。
【解决方案2】:

如果s 是可变对象,您可以更改其值(即其数据成员的值)。该成员也可以是String。这不适用于直接使用 String 参数,因为它是不可变的,因此“更改”它的唯一方法是将引用指向不同的对象。

【讨论】:

    【解决方案3】:

    字符串是不可变的...否则,您所要做的就是调用一个方法来操作同一个对象并以某种方式更改字符串值。

    我确信有许多 Java 类可以为您完成这项工作,但您也可以通过创建一个带有公共字段或 setter/getter 的封装类来创建自己的类。前者的一个例子是这样的:

    public class EncapsulatedString
    {
        public String str;
    
        public EncapsulatedString(String s)
        {
            str = s;
        }
    }
    

    【讨论】:

      【解决方案4】:

      1-length 数组有点难看,但完美的解决方案。无需创建多余的包装类:

      public void one() {
          String[] s = new String[]{"one"};
          two(s);
          System.out.println(s[0]);
      }
      
      public void two(String[] s) {
          s[0] = "two";
      }
      

      此模式在翻译已广泛应用引用传递的旧代码(例如 C)时特别有用。

      也就是说,返回一个新的、新鲜的值总是比改变“输入”值更容易混淆。

      【讨论】:

        【解决方案5】:

        您不能通过引用传递 - 至少不是变量本身。所有参数都按值传递。但是,对象包含引用 - 并且本身表示为引用。您可以随时更改对象的内部,并坚持更改。因此,发送一个数组,或者创建一个包装类,或者创建你自己的引用对象:

        class Ref<T> {
          T obj;
          public Ref(T value) {
            this.obj = value;
          }
          public void set(T value) {
            obj = value;
          }
          public T get() {
            return obj;
          }
        }
        

        正如其他人所说,String 无论如何都不是可变的,所以你实际上并没有在这里更改字符串,而是使变量指向另一个方向,所以不简单地返回并没有多大意义新字符串。

        【讨论】:

          【解决方案6】:

          创建一个包含您的对象的包装器并更改包装器的内容:

          public class StringWrapper {
              private String str;    
              public String getString() {
                 return str;
              }
              public String setString(String str){
                  this.str = str;
              }
              public String toString() {
                  return str;
              }
          }
          

          public void one() {
              StringWrapper s = new StringWrapper();
              s.setString("one");
              two(w);
              // This will print "two"
              System.out.println(s);
          }
          public void two( StringWrapper  s ) {
              s.setString("two");
          }
          

          【讨论】:

            【解决方案7】:

            你不能阻止 Java 传值;这就是语言语义。

            您可以通过一种或另一种方式绕过它,这取决于您想做什么。

            您可以根据传递的参数return 一个新值:

            static String scramble(String s) {
                return s.replaceAll("(.*) (.*)", "$2, $1");
            }
            
            // then later...
            String s = "james bond";
            s = scramble(s);
            System.out.println(s); // prints "bond, james"
            

            你也可以传递一些可变的东西:

            static void scramble(StringBuilder sb) {
                int p = sb.indexOf(" ");
                sb.append(", ").append(sb.substring(0, p)).delete(0, p+1);
            }
            
            // then later...
            StringBuilder sb = new StringBuilder("james bond");
            scramble(sb);
            System.out.println(sb); // prints "bond, james"
            

            【讨论】:

              【解决方案8】:

              Java 不是一种按值传递的语言。相反 - 它是一种引用传递语言。 (通过引用传递并不意味着您可以将原始“指针”更改为指向其他地方,就像 C++ 允许的那样)。 唯一按值传递的东西是原语(int、long、char 等)
              对象引用总是通过引用传递 - 所以如果你的 Object 能够支持对其内容的更改(例如,通过 getter 和 setter 方法) - 它可以被更改。

              String 特别是不可变的——这意味着它的内容可能永远不会改变。因此,对于您的具体问题,如果您希望局部变量 's' 引用的字符串发生更改,您需要为其提供对 String 对象的新实例的引用。

              例子:

              public void one()  
              {
                  String s = "one";
                  s = two(); // Here your local variable will point to a new instance of a String with the value "two"
                  System.out.println(s);
              }
              
              public String two() 
              {
                return "two";
              }
              

              如果您使用 String 以外的对象 - 您可以在它们上定义 setter 方法来更新它们的内容。

              【讨论】:

              • Java 是一种按值传递的语言。引用也是按值传递的。当然,这可能有点令人困惑,但事实就是如此。
              【解决方案9】:

              我想到的一个丑陋的解决方案是传递一个长度为 1 的字符串数组。我不鼓励这样做,但如果你觉得你想这样做......

                1  public class One {
                2 
                3     public static void main( String[] _ ) {
                4         String[] s = {"one"};
                5         two(s);
                6         System.out.println(s[0]);
                7     }
                8 
                9     public static void two( String[] s ) {
               10         s[0] = "two";
               11     }
               12 }
              

              【讨论】:

                【解决方案10】:

                无法阻止。

                您可以使用这样的通用包装器来模拟它:

                class _<T>{
                    public T _;
                    public _(T t ) {
                        _ = t;
                    }
                    public String toString(){ return _.toString(); }
                }
                

                然后按你的意愿使用它。

                class GeneriWrapperDemo {
                    public static void main(String [] args ) {
                        _<String> one = new _<String>("One");
                        two( one );
                        System.out.println( one );
                    }
                    public static void two( _<String> s ) {
                        s._ = "two";
                    }
                }
                

                但是看起来很丑。我认为最好的办法是自行更改参考:

                public String two( String a ) {
                     return "two";
                }
                

                并使用它

                 String one = "one";
                 one = two( one );
                

                :)

                【讨论】:

                • 在 Java 代码审查中,如果我发现有人在调用类 _,我会立即将他送回办公桌。
                • @Jesper,你知道吗?我会为属性public T _; 做同样的事情,看起来很有趣不是吗?
                【解决方案11】:

                使用可变的 StringBuffer 或 StringBuilder。

                【讨论】:

                • 如果我们不是在谈论字符串,Apache 提供了一个 MutableLong 等等。
                猜你喜欢
                • 2013-05-31
                • 2020-05-25
                • 1970-01-01
                • 2014-09-08
                • 1970-01-01
                • 2012-08-23
                • 2014-08-22
                • 2013-04-12
                • 1970-01-01
                相关资源
                最近更新 更多