我才刚刚开始学习Java中的OOP编程。我已经用C ++进行了一些编程,而在Java中我最想念的一件事就是返回多个值的可能性。的确,C ++函数仅严格返回一个变量,但是我们可以使用by-reference参数返回更多变量。相反,在Java中,我们无法做到这一点,至少对于原始类型,我们无法做到。
我考虑过的解决方案是创建一个类,将要返回的变量分组并返回该类的实例。例如,我需要在一个数组中查找一个对象,并且想要返回一个布尔值(是否找到)和一个索引。我知道我可以将索引设置为-1(如果未找到任何内容),但我认为反之则更清楚。
问题是,有人告诉我,我对Java的了解比我知道的要多,我不应该为返回多个值(即使它们是相关的)而创建类。他告诉类永远不要将C类结构用作组元素。他还说方法不应返回非原始对象,而应从外部接收对象并仅对其进行修改。以下哪项是对的?
答案 0 :(得分:2)
我不应出于返回多个值的目的而创建类
类绝不能仅用作对元素进行分组的C ++结构。
方法不应返回非原始对象,它们应从外部接收对象并仅对其进行修改
对于以上任何陈述,绝对不是这种情况。数据对象很有用,实际上,从包含大量逻辑的类中分离纯数据是一种很好的做法。
在Java中,最接近结构的是POJO(普通的旧Java对象),在其他语言中通常称为数据类。这些类只是数据的分组。根据经验,POJO应该只包含基元,简单类型(字符串,盒装基元等),简单容器(映射,数组,列表等)或其他POJO类。基本上是可以轻松序列化的类。
通常将两个,三个或n
对象配对在一起。有时,数据足够重要,可以保证使用一个全新的类,而在其他情况下则不是。在这些情况下,程序员经常使用Pair
或Tuple
类。这是一个包含两个元素的通用元组的简单示例。
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);
}
创建这样的数据类的一个缺点是,为了提高生活质量,我们必须实现toString
,hashCode
,equals
getter,setter,构造函数等内容。对于每个不同大小的元组,您都必须创建一个新类(Tuple2
,Tuple3
,Tuple4
等)。创建所有这些方法会在我们的应用程序中引入一些细微的错误。由于这些原因,开发人员通常会避免创建数据类。
像龙目岛这样的图书馆对于克服这些挑战非常有帮助。使用上面列出的所有方法,我们对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
)。如果合适的话,取决于应用程序:您不想为“正确的”输入抛出异常,应该保留它以用于不规则的情况。
在函数式语言中,它们通常为此具有内置的数据结构(例如Try
,Option
或Either
),它们实际上也在内部执行statusCode + resultValue,但要确保您实际上是在尝试访问结果值之前检查了该状态代码。 Java现在也有Optional
。如果我想走这条路,我会从库中提取这些包装器类型,而不要构成自己的临时“结构”(因为这只会使人们感到困惑)。
“方法不应返回非原始对象,它们应从外部接收对象并仅对其进行修改”
这可能是非常传统的OOP思维,但是即使在OOP中,不可变数据的使用也绝对有其价值(在我的书中进行线程安全编程的唯一明智的方法),因此就地修改内容的准则是太可怕了如果某些东西被认为是“数据对象”(而不是“实体”),则您应该更喜欢返回修改后的副本,而不是改变输入。
答案 2 :(得分:0)
对于某些静态信息,您可以使用静态final选项。声明为static final的变量可以从任何地方访问。
否则,通常使用getter / setter概念在类中接收和设置参数。
答案 3 :(得分:0)
严格来说,这是一种语言限制,Java本身不支持将元组作为返回值(请参阅相关讨论here)。这样做是为了保持语言清洁。但是,在其他大多数语言中,made也是相同的决定。当然,这样做是要记住,在必要的情况下,可以通过可用的手段来实现这种行为。因此,这里是选项(除了第二个选项,所有选项都允许组合任意类型的返回组件,不一定是原始的):
使用专门设计为包含一组要返回的相关值的类(通常是静态的,自制的或预定义的)。此选项在其他答案中都有介绍。
如果可能,将两个或多个原始值合并为一个返回值。可以将两个int
合并为一个long
,可以将四个byte
合并为一个int
,boolean
和未签名的int
少于Integer.MAX_VALUE
的字符可以合并为一个有符号的int
(例如,查看Arrays.binarySearch(...)
方法如何返回其结果),正数double
和boolean
可以组合成单个带符号的double
等。返回时,通过比较(如果boolean
在其中)和位运算(对于移位的整数分量)提取分量。
2a。一个特殊的情况值得一提。返回null
表示实际上返回的值是无效的,这是常见的约定(并且被广泛使用)。严格来说,此约定替换了两字段结果-您在检查时使用的一个隐式布尔字段
if (returnValue != null)
和其他包含结果本身的非基本字段(可以只是基本字段的包装)。您在进行上述检查后即可使用它:
ResultClass result = returnValue;
如果不想弄乱数据类,可以随时返回一个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);
}
您可以在您的类中引入字段以容纳辅助返回组件,并显式仅返回主要组件(您可以确定哪个是主要组件):
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
就足够了。请注意,这种方法不是线程安全的。