在Java中交换两个字符串,通过传递它们到一个实用函数中,但不返回对象或使用包装类。

9

我正在尝试在Java中交换两个字符串。我从未真正理解“字符串是不可变的”。我在理论上理解它,但在实践中从未遇到过。

此外,由于String在Java中是一个对象而不是原始类型,我不明白为什么以下代码会两次打印相同的结果,而不是交换这两个单词!

public static void main(String[] args)
{
    String s1 = "Hello";
    String s2 = "World";

    System.out.println(s1 + " " + s2);

    Swap(s1, s2);

    System.out.println(s1 + " " + s2);
}

public static void Swap(String s1, String s2)
{
    String temp = s1;
    s1 = s2;
    s2 = temp;
}

我希望它能够打印

Hello World
World Hello

但它正在打印。
Hello World
Hello World

我以为s1和s2是引用,因此应该交换引用,使新的引用分别指向另一个引用。我错在哪里了?
10个回答

15
我认为s1和s2是引用,因此应该交换引用,并将新的引用分别指向另一个引用。
是的,在swap函数内部,这正是发生的事情。
然而,s1和s2仅仅是传递到函数中的引用的拷贝,因此影响仅限于本地。请注意,它不是复制字符串(因为String是一个引用类型),但是引用被复制了。
...由于Java中参数引用总是被复制的,按照您的规范编写swap函数是不可能的。
如果您无法理解差异,请考虑这一点:您想给朋友写信,所以您从地址簿中复制她的邮政地址到信封上。通过这个过程,你肯定没有复制她的住所(在实践中复制整个房子有点困难)--你只复制了地址。
嗯,一个地址引用她的家,所以它就像一个Java引用。

如何在不返回任何对象的情况下,通过将它们传递给另一个函数来交换两个字符串?Java中是否不可能实现? - anon355079
现在我才明白“引用的副本”这部分。谢谢 :) - anon355079
@Senthil:抱歉,忘记提到了。是的,在Java中编写这样的“swap”方法确实是不可能的。 - Konrad Rudolph
我知道引用的概念,但我不知道Java有多少不允许使用它进行编程。感谢您的解释 :) - anon355079

6
以下代码是一个“不可行”的反模式,永远不要实现这个反模式,永远不要在不可变对象的私有最终内部进行更改。不要因为展示了这个黑客而责怪我,应该因为Oracle的jvm没有禁止它而责怪他们。
但是,如果有一天你发现某些代码可以运行以下操作:
 String a = "Hello";
 String b = "World";
 String c = "World";
 swap(a,b);
 System.out.printf("%s %s%n", a,b);  // prints: World Hello !!

 System.out.println(c);  // Side effect: prints "Hello" instead of "World"....

实现swap的代码可能长成这样:(请自行决定是否阅读,我已经警告过了!)
 private void swap(String a, String b) {
   try {
     Field value = String.class.get("value");
     value.setAccessible(true);    // this should be forbidden!!

     char[] temp = value.get(a);
     value.set(a, value.get(b));
     value.set(b, temp);           // Aaargh, please forgive me
   } catch(Exception e) {
     e.printStackTrace(e);
   }
 }

这并不交换对象,它只是改变了对象的内容。 - newacct

3

在Java中你不能实现swap方法。原因是Java参数采用的是按值传递的语义。所以当你的swap方法将s2赋值给s1等操作时,它完全是在处理本地变量s1s2,而不是调用方法main中的s1s2变量。

相比之下,如果你要在C中实现swap方法,代码会类似于:

void swap(char ** s1, char ** s2) {
    char * temp = *s1;
    *s1 = *s2;
    *s2 = temp;
}

你需要这样调用它:

char *s1 = "Hello World";
char *s2 = "Goodbye World";
swap(&s1, &s2);

请注意,我们实际上正在传递指向“字符指针”的变量的地址。
在Java中,您无法这样做,因为您无法获取变量的地址。这是不被支持的。

2
最近有一个非常类似的问题,涉及到交换两个整数。我当时的答案是使用原子引用:链接1链接2链接3
public void swap(AtomicReference<String> a, AtomicReference<String> b){
    // update: look mom, no variables
    a.set(b.getAndSet(a.get()));
}

当然,这并不是非常令人满意的,因为你几乎从不针对AtomicReferences进行开发。但基本上你需要使用某种类型的容器,因为你不能仅仅交换引用。所以你可以交换一个两个元素的数组或列表中的元素,但是你无法在没有访问原始变量的情况下交换两个字符串(因此你无法在辅助函数中完成此操作)。

不使用包装类 - newacct
@newacct 是的,但在Java中不可能实现(正如您可以在已接受的答案中读到的那样),因此我提供了一个解决方法。 - Sean Patrick Floyd

2

Java的String是通过引用实现的,因此您需要交换它们的引用。

String s1 = "Hello";
String s2 = "World";
AtomicReference<String> String1 = new AtomicReference<String>(s1);
AtomicReference<String> String2 = new AtomicReference<String>(s2);
String1.set(String2.getAndSet(String1.get()));
System.out.println(String1 + " " + String2);

它将会给你这个输出:
World Hello

1

在你的函数swap中,s1和s2变量是该函数内部的局部变量。

你需要将s1和s2定义为类的私有变量,或者从你的函数中返回一个字符串数组。


1
String s1 = "Hello";
String s2 = "World";
    s1=s1+" "+s2;
s2=s1.split(" ")[0];
s1=s1.split(" ")[1];
System.out.println(s1 + " " + s2);

1

由于Java使用传值调用,您定义的交换函数并不会进行任何交换操作;指针s1和s2在本地被交换,但交换的结果并不会持久存在。您可以实现的最接近的方法是将要交换的项包装在某个类中,并在该类中定义一个交换方法。这将允许您在此类的实例之间交换数据,尽管它实际上并不会交换底层数据。以下是一个示例:

  public class Swapper<T> {
      public Swapper(T obj) {
             data_ = obj;
      }
      public T get() { return data_; }
      public void set(T value) { data_ = value; }
      public void swap(Swapper<T> o) {
           T tmp = o.data_;
           o.data_ = data_;
           data_ = tmp;
      }
      private T data_ = null;
  }

  // .. Now you can do:
  Swapper<String> a = new Swapper("Hello");
  Swapper<String> b = new Swapper("World");
  // a.get().equals("Hello")
  a.swap(b); // now a.get().equals("World")

0
Java将对象在内存中的地址传递给函数。然后函数中的某些局部变量保存该地址。如果您将一些新地址(另一个对象)分配给这些局部变量,则原始对象没有改变的理由。
但是,有了地址,对原始对象的字段进行更改是会被应用的。(与C/C++中的“.”运算符相关) 此链接也可能有所帮助。

0
通过使用 StringBufferReaderStringBuilder 可以解决这个问题。首先从用户获取值,然后将它们拆分并存储在数组中。以相反的顺序运行 for 循环,以便最后一个字符串将被附加并显示为第一个,而第一个将被显示为最后一个。 输入:hello world 输出:

import java.io.*;
import java.util.*;
import java.lang.*;
public class Solution {
  public static void main(String[] args) {
    try {
      int s,i,j;
      StringBuilder str = new StringBuilder();
      String st,t;
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      t = br.readLine();
      s = Integer.parseInt(t);
      for (i = 0; i < s; i++) {
        st=br.readLine();
        String st1[]=st.split("\\s+");
        // System.out.println("l="+st1.length);
        for (j = st1.length - 1; j >= 0; j--) {
          str.append(st1[j] + " ");
        }
        System.out.println(str);
        str.setLength(0);
      }
    } catch (Exception e) {
      System.out.println(e);
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接