在java中返回多个原始对象。不推荐?

时间:2018-11-23 21:21:54

标签: java struct return return-value

我才刚刚开始学习Java中的OOP编程。我已经用C ++进行了一些编程,而在Java中我最想念的一件事就是返回多个值的可能性。的确,C ++函数仅严格返回一个变量,但是我们可以使用by-reference参数返回更多变量。相反,在Java中,我们无法做到这一点,至少对于原始类型,我们无法做到。

我考虑过的解决方案是创建一个类,将要返回的变量分组并返回该类的实例。例如,我需要在一个数组中查找一个对象,并且想要返回一个布尔值(是否找到)和一个索引。我知道我可以将索引设置为-1(如果未找到任何内容),但我认为反之则更清楚。

问题是,有人告诉我,我对Java的了解比我知道的要多,我不应该为返回多个值(即使它们是相关的)而创建类。他告诉类永远不要将C类结构用作组元素。他还说方法不应返回非原始对象,而应从外部接收对象并仅对其进行修改。以下哪项是对的?

4 个答案:

答案 0 :(得分:2)

  

我不应出于返回多个值的目的而创建类

     

类绝不能仅用作对元素进行分组的C ++结构。

     

方法不应返回非原始对象,它们应从外部接收对象并仅对其进行修改

对于以上任何陈述,绝对不是这种情况。数据对象很有用,实际上,从包含大量逻辑的类中分离纯数据是一种很好的做法。

在Java中,最接近结构的是POJO(普通的旧Java对象),在其他语言中通常称为数据类。这些类只是数据的分组。根据经验,POJO应该只包含基元,简单类型(字符串,盒装基元等),简单容器(映射,数组,列表等)或其他POJO类。基本上是可以轻松序列化的类。

通常将两个,三个或n对象配对在一起。有时,数据足够重要,可以保证使用一个全新的类,而在其他情况下则不是。在这些情况下,程序员经常使用PairTuple类。这是一个包含两个元素的通用元组的简单示例。

public class Tuple2<T,U>{
    private final T first;
    private final U second;

    public Tuple2(T first, U second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() { return first; }
    public U getSecond() { return second; }
}

使用元组作为方法签名的一部分的类可能看起来像:

public interface Container<T> {
     ...
     public Tuple2<Boolean, Integer> search(T key);
}

创建这样的数据类的一个缺点是,为了提高生活质量,我们必须实现toStringhashCodeequals getter,setter,构造函数等内容。对于每个不同大小的元组,您都必须创建一个新类(Tuple2Tuple3Tuple4等)。创建所有这些方法会在我们的应用程序中引入一些细微的错误。由于这些原因,开发人员通常会避免创建数据类。

像龙目岛这样的图书馆对于克服这些挑战非常有帮助。使用上面列出的所有方法,我们对Tuple2的定义可以写为:

@Data
public class Tuple2<T,U>{
    private final T first;
    private final U second;
}

这也使创建自定义响应类非常容易。使用自定义类可以避免使用泛型自动装箱,并大大提高了可读性。例如:

@Data
public class SearchResult {
    private final boolean found;
    private final int index;
}
...
public interface Container<T> {
     ...
     public SearchResult search(T key);
}

  

方法应该从外部接收对象并且只能对其进行修改

这是个坏建议。围绕不变性设计数据要好得多。来自Effective Java 2nd Edition, p75

  

不变的对象很简单。一个不可变的对象可以恰好处于一种状态,即创建该对象的状态。如果您确保所有构造函数都建立类不变式,那么可以保证这些不变式在所有时间内都保持不变,而您或使用该类的程序员则无需再作任何努力。另一方面,可变对象可以具有任意复杂的状态空间。如果文档未提供对由mutator方法执行的状态转换的精确描述,则可能难以或不可能可靠地使用可变类。

     

不可变对象本质上是线程安全的;他们不需要同步。它们不能被同时访问它们的多个线程破坏。这无疑是实现线程安全的最简单方法。实际上,没有线程能够观察到另一个线程对不可变对象的任何影响。因此,不可变对象可以自由共享

答案 1 :(得分:1)

关于您的特定示例(“如何同时返回错误状态和结果?”)

  

我需要在一个数组中寻找一个对象,我想返回一个布尔值(是否找到)和一个索引。我知道只要未发现任何问题,我就可以将索引设置为-1,但是我认为反之亦然。

为“未找到”返回特殊的无效结果值,例如-1确实很常见,我同意您的看法,这种说法不太好。

但是,返回(statusCode, resultValue)的元组并不是唯一的选择。

在Java中,报告异常的最惯用的方法是使用异常。因此,返回结果,或者如果无法产生结果,则抛出异常(在这种情况下为NoSuchElementException)。如果合适的话,取决于应用程序:您不想为“正确的”输入抛出异常,应该保留它以用于不规则的情况。

在函数式语言中,它们通常为此具有内置的数据结构(例如TryOptionEither),它们实际上也在内部执行statusCode + resultValue,但要确保您实际上是在尝试访问结果值之前检查了该状态代码。 Java现在也有Optional。如果我想走这条路,我会从库中提取这些包装器类型,而不要构成自己的临时“结构”(因为这只会使人们感到困惑)。

  

“方法不应返回非原始对象,它们应从外部接收对象并仅对其进行修改”

这可能是非常传统的OOP思维,但是即使在OOP中,不可变数据的使用也绝对有其价值(在我的书中进行线程安全编程的唯一明智的方法),因此就地修改内容的准则是太可怕了如果某些东西被认为是“数据对象”(而不是“实体”),则您应该更喜欢返回修改后的副本,而不是改变输入。

答案 2 :(得分:0)

对于某些静态信息,您可以使用静态final选项。声明为static final的变量可以从任何地方访问。

否则,通常使用getter / setter概念在类中接收和设置参数。

答案 3 :(得分:0)

严格来说,这是一种语言限制,Java本身不支持将元组作为返回值(请参阅相关讨论here)。这样做是为了保持语言清洁。但是,在其他大多数语言中,made也是相同的决定。当然,这样做是要记住,在必要的情况下,可以通过可用的手段来实现这种行为。因此,这里是选项(除了第二个选项,所有选项都允许组合任意类型的返回组件,不一定是原始的):

  1. 使用专门设计为包含一组要返回的相关值的类(通常是静态的,自制的或预定义的)。此选项在其他答案中都有介绍。

  2. 如果可能,将两个或多个原始值合并为一个返回值。可以将两个int合并为一个long,可以将四个byte合并为一个intboolean和未签名的int少于Integer.MAX_VALUE的字符可以合并为一个有符号的int(例如,查看Arrays.binarySearch(...)方法如何返回其结果),正数doubleboolean可以组合成单个带符号的double等。返回时,通过比较(如果boolean在其中)和位运算(对于移位的整数分量)提取分量。

    2a。一个特殊的情况值得一提。返回null表示实际上返回的值是无效的,这是常见的约定(并且被广泛使用)。严格来说,此约定替换了两字段结果-您在检查时使用的一个隐式布尔字段

    if (returnValue != null)
    

    和其他包含结果本身的非基本字段(可以只是基本字段的包装)。您在进行上述检查后即可使用它:

    ResultClass result = returnValue;
    
  3. 如果不想弄乱数据类,可以随时返回一个Object s数组:

    public Object[] returnTuple() {
        return new Object[]{1234, "Text", true};
    }
    

    ,然后将其组件类型转换为所需的类型:

    public void useTuple() {
        Object[] t = returnTuple();
        int x = (int)t[0];
        String s = (String)t[1];
        boolean b = (boolean)t[2];
        System.out.println(x + ", " + s + ", " + b);
    }
    
  4. 您可以在您的类中引入字段以容纳辅助返回组件,并显式仅返回主要组件(您可以确定哪个是主要组件):

    public class LastResultAware {
        public static boolean found;
        public static int errorCode;
    
        public static int findLetter(String src, char letter) {
            int i = src.toLowerCase().indexOf(Character.toLowerCase(letter));
            found = i >= 0;
            return i;
        }
    
        public static int findUniqueLetter(String src, char letter) {
            src = src.toLowerCase();
            letter = Character.toLowerCase(letter);
            int i = src.indexOf(letter);
            if (i < 0)
                errorCode = -1; // not found
            else {
                int j = src.indexOf(letter, i + 1);
                if (j >= 0)
                    errorCode = -2; // ambiguous result
                else
                    errorCode = 0; // success
            }
            return i;
        }
    
        public static void main(String[] args) {
            int charIndex = findLetter("ABC", 'b');
            if (found)
                System.out.println("Letter is at position " + charIndex);
            charIndex = findUniqueLetter("aBCbD", 'b');
            if (errorCode == 0)
                System.out.println("Letter is only at position " + charIndex);
        }
    }
    

    请注意,在某些情况下,抛出异常指示错误比返回错误代码(调用者可能只是忘记检查)要好。 根据用法,此返回扩展字段可以是静态的也可以是实例的。当为静态时,它们甚至可以被多个类用于共同的目的,并避免不必要的字段创建。例如,一个public static int errorCode就足够了。请注意,这种方法不是线程安全的。