Java:如何通过引用传递 byte[]?

Java: How to pass byte[] by reference?

提问人:Mahendra 提问时间:12/2/2008 最后编辑:Mahendra 更新时间:11/16/2016 访问量:55930

问:

可以在 .NET 中使用关键字“ref”来执行此操作。在 Java 中有什么方法可以做到这一点吗?

Java 按引用传递

评论


答:

7赞 Gishu 12/2/2008 #1

Java 对方法参数使用传递 by 值

  • 原语(int、boolean 等)是 Java 中的特例。不是对象本身。在这种情况下,将基元(参数)的副本传递到函数中。这与价值传递理论相吻合。
  • 对于对象,发生的情况是对象的引用按值传递(创建引用的副本而不是对象)...但两个引用都指向同一个对象。因此,如果在方法中修改对象参数,则实际对象将被修改。

这篇文章应该对你有所帮助。http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html

至于 OP 的问题,只需将对 byte[] 数组的引用传递给方法即可。最终结果类似于通过引用传递。如果修改字节数组,调用方将能够看到方法执行后的更改。

更新以平息电阻 :) => 表示输出

.NET 土地

class Counter
{
  private int m_count = 0;
  public override string  ToString()
  {
     return String.Format("Counter ID{0} : Value {1}", this.GetHashCode(), m_count);
  }
  public void Increment()
  {  m_count++;  }
}
class MakeAPass
{
   public void PassByValueAndModify(int i)
   {   i = 20;    }

   public void PassByRefAndModify(ref int i)
   {   i = 20;   }

   public void PassByValueAndModify(Counter c)
   {   c.Increment();   }

   public void PassByRefAndModify(ref Counter c)
   {   c.Increment();   }

   public void PassByRefAndReassign(ref Counter c)
   {
      c = new Counter();
      for (int i=0; i<5; ++i)
         c.Increment();
   }
}

static void Main(string[] args)
{
   MakeAPass obj = new MakeAPass();
   int intVal = 10;
   obj.PassByValueAndModify(intVal);
   Console.WriteLine(intVal);              // => 10
   obj.PassByRefAndModify(ref intVal);
   Console.WriteLine(intVal);              // => 20

   Counter obCounter = new Counter();
   obj.PassByValueAndModify(obCounter);
   Console.WriteLine(obCounter.ToString());  // => Counter ID58225482 : Value 1
   obj.PassByRefAndModify(ref obCounter);
   Console.WriteLine(obCounter.ToString());  // => Counter ID58225482 : Value 2
   obj.PassByRefAndReassign(ref obCounter);
   Console.WriteLine(obCounter.ToString());  // => Counter ID54267293 : Value 5
}

爪哇土地

次要修改 reqd:使用 hashCode() 和 + 在 Counter.java 中连接字符串...

class MakeAPass
{
   public void PassByValueAndModify(int i)
   {   i = 20;   }

   // can't be done.. Use Integer class which wraps primitive
   //public void PassByRefAndModify(ref int i)

   public void PassByValueAndModify(Counter c)
   {   c.Increment();   }

   // same as above. no ref keyword though
   //public void PassByRefAndModify(ref Counter c)

   // this can't be done as in .net
   //public void PassByRefAndReassign(ref Counter c)
   public void PassAndReassign(Counter c)
   {
      c = new Counter();
      for (int i=0; i<5; ++i)
         c.Increment();
   }
}
public static void main(String args[])
{
   MakeAPass obj = new MakeAPass();
   int intVal = 10;
   obj.PassByValueAndModify(intVal);
   System.out.println(intVal);                 // => 10 
   //obj.PassByRefAndModify(ref intVal);
   //System.out.println(intVal);               // can't get it to say 20

   Counter obCounter = new Counter();
   obj.PassByValueAndModify(obCounter);
   System.out.println(obCounter.ToString());    // => Counter ID3541984 : Value 1
   //obj.PassByRefAndModify(ref obCounter);
   //Console.WriteLine(obCounter.ToString());   // no ref. but can make it 2 by repeating prev call
   obj.PassAndReassign(obCounter);
   System.out.println(obCounter.ToString());    // => Counter ID3541984 : Value 1
                                                // can't get it to say 5  
}

评论

0赞 Jon Skeet 12/2/2008
就参数传递而言,我不认为基元是“特殊情况”。无论表达式的值是基元值还是引用,它都是按值传递的。方法中参数的值与参数的值相同,但变量没有别名。
0赞 Jon Skeet 12/2/2008
这里的“按值传递”和“按引用传递”之间存在非常显着的区别 - 如果方法将参数的值更改为其他值(例如 someParameter = new byte[200]),则调用者将看不到它。不幸的是,我们不知道马亨德拉是否需要它。
0赞 Jon Skeet 12/2/2008
奥斯卡:我认为吉舒确切地知道他在说什么,实际上(尽管答案中的一些细节不准确)。变量(包括参数)的值从来都不是对象,所以他并没有声称对象正在被复制。
0赞 Jon Skeet 12/2/2008
不仅仅是 .NET 背景 - 计算机科学背景。通过引用语义定义得非常好,而 Java 没有。
0赞 Gishu 12/2/2008
给你。唷。。。我已经有一段时间没有在命令行上玩 javac 和 java 了。
19赞 coobird 12/2/2008 #2

实际上,在 Java 中,引用是按值传递的。

在本例中,引用是一个对象。任何影响对象本身的更改都将从调用方方法中看到。byte[]

但是,如果尝试替换引用(例如,使用 ),则只会替换通过按值传递获取的引用,因此不会更改调用方方法中的引用。new byte[length]

这里有一个关于这个问题的有趣读物:Java is Pass-by-Value Dammit!


下面是一个具体的例子:

public class PassByValue
{
    public static void modifyArray(byte[] array)
    {
        System.out.println("Method Entry:  Length: " + array.length);
        array = new byte[16];
        System.out.println("Method Exit:   Length: " + array.length);
    }

    public static void main(String[] args)
    {
        byte[] array = new byte[8];
        System.out.println("Before Method: Length: " + array.length);
        modifyArray(array);
        System.out.println("After Method:  Length: " + array.length);
    }
}

该程序将在方法中创建一个长度数组,该数组将调用该方法,其中将创建一个新的长度数组。byte8mainmodifyArraybyte16

通过在方法中创建一个新数组,返回该方法时数组的长度似乎是 ,但是,运行此程序会发现一些不同的东西:bytemodifyArraybytemain16

Before Method: Length: 8
Method Entry:  Length: 8
Method Exit:   Length: 16
After Method:  Length: 8

从该方法返回时,数组的长度将恢复为 而不是 。bytemodifyArray816

为什么?

这是因为该方法调用了该方法,并使用 pass-by-value 发送了对新 byte[8] 的复制引用。然后,该方法通过创建一个新 byte[16] 来丢弃复制的引用。当我们离开时,对 的引用已经超出了范围(最终将被垃圾回收)。但是,该方法仍然引用了 ,因为它只发送了复制的引用,而不是对引用的实际引用。mainmodifyArraymodifyArraymodifyArraynew byte[16]mainnew byte[8]

这应该表明 Java 将使用 pass-by-value 传递引用。

46赞 Jon Skeet 12/2/2008 #3

你的方法在做什么?如果只是填充现有数组,则不需要按引用传递语义 - 无论是在 .NET 中还是在 Java 中。在这两种情况下,引用都将按值传递 - 因此调用方将看到对对象的更改。这就像告诉别人你家的地址,然后让他们把东西送到那里——没问题。

如果你真的想要按引用传递的语义,即调用者将看到对参数本身所做的任何更改,例如将其设置为 null 或对不同字节数组的引用,那么任一方法都需要返回新值,或者你需要将引用传递给某种包含对字节数组的引用的“持有者”, 并且以后可以从中获取(可能更改的)引用。

换句话说,如果您的方法如下所示:

public void doSomething(byte[] data)
{
    for (int i=0; i < data.length; i++)
    {
        data[i] = (byte) i;
    }
}

那你就没事了。如果您的方法如下所示:

public void createArray(byte[] data, int length)
{
    // Eek! Change to parameter won't get seen by caller
    data = new byte[length]; 
    for (int i=0; i < data.length; i++)
    {
        data[i] = (byte) i;
    }
}

然后,您需要将其更改为:

public byte[] createArray(int length)
{
    byte[] data = new byte[length]; 
    for (int i=0; i < data.length; i++)
    {
        data[i] = (byte) i;
    }
    return data;
}

或:

public class Holder<T>
{
    public T value; // Use a property in real code!
}

public void createArray(Holder<byte[]> holder, int length)
{
    holder.value = new byte[length]; 
    for (int i=0; i < length; i++)
    {
        holder.value[i] = (byte) i;
    }
}

有关详细信息,请阅读 C# 中的参数传递Java 中的参数传递。(恐怕前者比后者写得好。总有一天我会开始做一个更新。

评论

0赞 Hosam Aly 12/2/2008
我想你可能想从上一个例子中删除“Eek!”注释。除此之外,很好的答案。我正在写一个类似的,但你的更详细。
1赞 Jon Skeet 12/2/2008
@Oscar的编辑:回滚到“行首大括号”——Java 社区中大括号位置的多样性足够多(不像方法命名),我真的认为我们不需要开始尝试在 SO 上强制执行统一性。这样做只会开始圣战。
0赞 Draemon 12/2/2008
@Jon - Java 是为数不多的具有相当广泛的编码约定的语言之一(也是我所知道的唯一一种 C 风格的语言),以至于它被(不准确地)称为“Java 风格”。为什么要与公约作斗争?虽然这样的编辑会带来麻烦,但为什么不超越它呢?
1赞 Jon Skeet 12/2/2008
@Draemon:在命名方面(私有变量除外),我完全同意你的观点——Sun 的约定几乎被普遍应用。然而,对于支撑,没有明确的主导风格。谷歌是我工作过的第一个真正使用Sun风格的地方。开源项目千差万别。
1赞 Tom Hawtin - tackline 12/2/2008
乔恩,你一定在一些地方工作过。Java 风格很清晰。我们当然在这里使用它!;)