使用Java方式返回具有多个值的结果

时间:2017-04-25 19:41:01

标签: java

我习惯用Python开发,但出于工作原因必须用Java来完成。我正面临着一个在Python中无足轻重的任务,我想了解如何以正确的方式在Java中处理这个问题。

我需要解析持续时间字符串。它可以是毫秒(235毫秒)或秒(32秒)。它也可以是"< 1ms的"作为一个特例。

解析在代码中至少发生三次,所以我想将它分成一个方法。但是我的代码确实需要知道,不仅仅是以ms为单位的结果值,还有它是ms还是s,以及它是否为<1ms(0是一个不同的值)。

在Python中我只会返回一个元组:

return (value_in_milliseconds,is_in_seconds,is_under_1ms)

在C中,我将定义这三个值的结构并返回它。在Pascal我定义了一条记录。

在Java中,我无法返回元组,我无法定义记录,所以我该怎么办?

我唯一能想到的是创建一个表示持续时间值的类。构造函数将获取字符串并解析它。该类将具有字段:int milliseconds,boolean inSeconds,boolean小于1ms。

但这听起来非常重要 - 是否有更好的解决方案?

10 个答案:

答案 0 :(得分:22)

不要绕过一组必须在它们之间保持一致的旗帜才能形成合理的状态。如果is_in_secondsis_under_1ms都是true,该怎么办?为什么包含单词milliseconds的变量会被解释为秒?代码中的错误外观。如果你能做任何事情,看起来错误的代码是不好的 - 我们应该编写正确/错误的外观与现实相匹配的代码 - 应用于代码的最小惊讶原则,甚至可能是成功之坑对于后来的大脑将会爆炸的开发者。

这可能听起来像是Primitive Obsession代码气味/反模式,可能来自你的Python背景? (我对Python几乎一无所知,所以可以随意忽略这个猜测。)

解决方案:制作一个真实的域级对象,表示大致持续时间的概念。

一种可能的实现方式:

  1. 创建包含DurationScaleSecondMillisecond成员的SubMillisecond枚举。

  2. 创建一个ApproximateDuration类,它带有duration整数和durationScale枚举值。

  3. 现在在您的其他代码中使用此对象。如果您需要对一系列这些持续时间求和,请创建一个知道如何解释每个持续时间并将它们添加到一起的类。或者向这个类添加方法。

  4. DurationScale这样的概念的替代方案可以是MarginOfError,它本身可以表示为任意数毫秒。这可以允许您使用严格的数学公式来适当地增加误差范围,因为您将不同的ApproximateDuration个对象合并为一个新的ApproximateDuration对象。

    注意:您可以看到一些further discussion我推荐此方法的原因。

    您确定的实现也是处理它的好方法,您可以在其中明确说明下限和上限:

    public final class ApproximateDuration {
       private final int lowMilliseconds;
       private final int highMilliseconds;
    
       public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
          this.lowMilliseconds = lowMilliseconds;
          this.highMilliseconds = highMilliseconds;
       }
    
       public int getLowMilliseconds() {
          return lowMilliseconds;
       }
    
       public int getHighMilliseconds() {
          return highMilliseconds;
       }
    }
    

    请注意,将单词Milliseconds放在变量和属性名称中很重要,这个类的不变性也是如此。

答案 1 :(得分:20)

它与C结构没有什么不同,但你最终会以某种方式继承自Object

class Blah {
   public String value_in_milliseconds;
   public String is_in_seconds;
   public String is_under_1ms;
}

答案 2 :(得分:14)

定义您自己的类或使用例如apache commons

return Triple.of(value_in_milliseconds, is_in_seconds, is_under_1ms);

答案 3 :(得分:8)

我尝试了以下传递Object[]的示例,并保持简单:

public class MultiWayTest
{
    public static Object[] send() {
        return new Object[] { 1002322, false, true };
    }

    public static void main(String[] arg) {
        Object[] recieve=send();
    }
}

你可能已经注意到了return new Object[] { 1002322, false, true };

这句话

希望有所帮助

答案 4 :(得分:6)

就个人而言,我会这样做:

class Duration {
    final int millis;
    final boolean specifiedInSeconds;
    final boolean under1ms;

    Duration(String s) {
        // parsing logic
    }
}

此解决方案相当短,允许编译器检测字段名称中的拼写错误,封装可能不会设置布尔值的不变量,并且可以安全地与其他代码共享而不会有aliasing bugs的风险。

答案 5 :(得分:4)

您已完全按照Java的方式处理此问题: 您创建一个表示答案的类,通常称为POJO(Plain Old Java Object)。它只是一个拥有私有成员的类来保存每个字段,构造函数,getter和可选的setter。

也就是说,有些库引入了Tuples或Tuple-esque抽象,但它们都在POJO实现上工作。

我也开发的Scala在JVM上运行,可以与Java库互操作,并且在其许多其他功能中都有元组。

答案 6 :(得分:4)

Separation of concerns

您的方法会返回持续时间。它不应该关心调用者是否希望有一个特殊的标志为<1ms持续时间(假设是0ms,我假设?)或者必须测试你是否返回秒或毫秒。

我假设你有一个如下所示的流程:

 #include <iostream>
    #include <string>
    #include <vector>
    #include "linkedlist.h"
    using namespace std;

    LinkedList::LinkedList() : head(nullptr), tail(nullptr){
        // Implement this function
    }

    LinkedList::~LinkedList() {
        // Implement this function
        this->clear();
    }

    LinkedList::LinkedList(const LinkedList& source) {
        // Implement this function
        head = nullptr;
        tail = nullptr;
        Node* tempNode = source.head;

        while(tempNode != nullptr)  {
            insert(tempNode->loc, tempNode->yr, tempNode->mo, tempNode->temp);
            tempNode = tempNode->next;
        }

    }

    LinkedList& LinkedList::operator=(const LinkedList& source) {
        if (this != &source) {
            this->clear();
            Node* tempNode = source.head;
            while(tempNode != nullptr){
                insert(tempNode->loc, tempNode->yr, tempNode->mo, tempNode->temp);
                tempNode = tempNode->next;
            }
        }
        return *this;
    }



    void LinkedList::insert(int location, int year, int month, double temperature) {
        // Implement this function
        Node* newNode = new Node();
        newNode->loc = location;
        newNode->yr = year;
        newNode->mo = month;
        newNode->temp = temperature;
        Node* tempNode = head;

        if(tail == nullptr & head == nullptr){
            newNode = head;
        }
        while(tempNode != nullptr){
            if((tempNode->loc == newNode->loc) && (tempNode->yr == newNode->yr)){
                if(tempNode->mo > newNode->mo){
                    newNode->next = tempNode->next;
                    tempNode->next = newNode;
                }
                if(tempNode->mo < newNode->mo){
                    newNode->next = tempNode;
                }
            }
            if(tempNode->loc > newNode->loc){
                newNode->next = tempNode->next;
                tempNode->next = newNode;
            }
            if(tempNode->loc < newNode->loc){
                newNode->next = tempNode->next;
                tempNode->next = newNode;
            }
            tempNode = tempNode->next;
        }
    }

    void LinkedList::clear() {
        // Implement this function
        Node* current = head;
        while (current != nullptr) {
            Node* deleteNode = current;
            current = current->next;
            delete deleteNode;
        }
        head = nullptr;
        tail = nullptr;
    }

    void LinkedList::print() const {
        /* Do not modify this function */
        print(cout);
    }

    void LinkedList::print(ostream& os) const {
        /* Do not modify this function */
        os << *this;
    }

    ostream& operator<<(ostream& os, const LinkedList& ll) {
        // Implement this function
        Node* tempNode = ll.head;
        if (tempNode == nullptr) {
            os << " <Empty List>";
        }
        while (tempNode != nullptr) {
            if (tempNode != ll.head)
            cout << " " << tempNode->loc << " " << tempNode->yr << " " << tempNode->mo << " " << tempNode->temp << endl;
        }
        tempNode = tempNode->next;
        return os;
    }

你在这里做的是将你的表达逻辑放在你的获取方法中。

我会像那样改写它:

def main():
  val, is_s, is_0 = get_duration()
  if is_0:
    print "Less than 1 ms"
  elif is_s:
    print str(val) + " s"
  else:
    print str(val) + " ms"

def get_duration():
  # ...
  ms = # some value computed elsewhere, presumably in ms
  if ms > 1000:
    return (ms / 1000, true, false)
  elif ms < 1:
    return (ms, false, true)
  else:
    return (ms, false, false)

那样def main(): ms = get_duration() if ms > 1000: str(val / 1000) + " s" elif ms < 1: print "Less than 1 ms" else: print str(ms) + " ms" def get_duration(): # ... ms = # some value computed elsewhere, presumably in ms return ms 就不必假定调用者将如何使用这些信息。

如果有人需要分钟和小时怎么办?您必须重新编写所有调用方法,以适应元组形状的变化。最好让调用者处理它。

答案 7 :(得分:3)

我有三种方式可以解决此问题。如你所说,你不想要正确的方法,这是我的第一选择。使用POJO(普通旧Java对象)。而且我知道你会喜欢第三种选择,因为这是我的最爱。

<强> 1 即可。 使用POJO

使用pojo,你说你强烈建议只返回Class中的指定属性,仅此而已。

public class DataObjectClass {

private String value_in_milliseconds;

private boolean is_in_seconds;

private boolean is_under_1ms;

public String getValue_in_milliseconds() {
    return value_in_milliseconds;
}

public void setValue_in_milliseconds(String value_in_milliseconds) {
    this.value_in_milliseconds = value_in_milliseconds;
}

public boolean Is_in_seconds() {
    return is_in_seconds;
}

public void setIs_in_seconds(boolean is_in_seconds) {
    this.is_in_seconds = is_in_seconds;
}

public boolean Is_under_1ms() {
    return is_under_1ms;
}

public void setIs_under_1ms(boolean is_under_1ms) {
    this.is_under_1ms = is_under_1ms;
}

public static void main(String[] args) {
    DataObjectClass dataObjectClassFromMethod = anyMethod();
    System.out.println(dataObjectClassFromMethod.getValue_in_milliseconds());
    System.out.println(dataObjectClassFromMethod.Is_in_seconds());
    System.out.println(dataObjectClassFromMethod.Is_under_1ms());
}

public static DataObjectClass anyMethod() {
    DataObjectClass dataObjectClass = new DataObjectClass();
    dataObjectClass.setValue_in_milliseconds("value");
    dataObjectClass.setIs_in_seconds(true);
    dataObjectClass.setIs_under_1ms(true);
    return dataObjectClass;
}
}

在上面的代码片段中,我创建了另一个类DataObjectClass,它将保存组合数据。现在每当我需要来自任何方法的多个数据时。我将在该方法中创建该类的Object。我将设置对象的属性。现在我将从方法返回该对象。显然返回类型的方法是DataObjectClass。虽然听起来很重,但这是正确的方法。例如,您可能需要更多信息,然后您不能使用元组。 您还可以使用Lombok批注来减少代码。您可以获取Lombok.jar并将其包含在构建路径中。

@Data
public class DataObjectClass {
    private String value_in_milliseconds;

    private boolean is_in_seconds;

    private boolean is_under_1ms;
}
public static void main(String[] args) {
        DataObjectClass dataObjectClassFromMethod = anyMethod();
    }

    public static DataObjectClass anyMethod() {
        DataObjectClass dataObjectClass = new DataObjectClass();
        dataObjectClass.setValue_in_milliseconds("value");
        dataObjectClass.setIs_in_seconds(true);
        dataObjectClass.setIs_under_1ms(true);
        return dataObjectClass;
    }
}

<强> 2 即可。的的JSONObject

还有一种方法,你可以使用JSONObject。

public class JSONExample {

    public static void main(String[] args) throws JSONException {
        JSONObject data = anyMethod();
    }

    public static JSONObject anyMethod() throws JSONException {
        JSONObject data = new JSONObject();
        data.put("value_in_milliseconds","value");
        data.put("is_in_seconds",true);
        data.put("is_under_1ms",true);
        return data;
    }
}

第3 即可。的 HashMap中

您还可以使用字符串和对象的HashMap,阅读有关访问HashMap的更多信息,HashMap是JDK的一部分,因此不需要外部jar。使用HashMap,您可以做很酷的事情。这是最简单的方法。

public class HashMapExample {

    public static void main(String[] args) {
        Map<String,Object> data = anyMethod();
    }

    public static Map<String,Object> anyMethod()  {
        Map<String,Object> data = new HashMap<>();
        data.put("value_in_milliseconds","value");
        data.put("is_in_seconds",true);
        data.put("is_under_1ms",true);
        return data;
    }
}

答案 8 :(得分:3)

您写道:

  

我唯一能想到的是创建一个表示持续时间值的类。构造函数将获取字符串并解析它。该类将包含字段:int milliseconds,boolean inSeconds,boolean under1ms。

这与我所做的很接近,不完全是我做的事。

JSFiddle的构造函数是<Image x:Name="newlyAddedImage" Source="{Binding SelectedItem, ElementName=testListView}" /> 。然后我添加一个调用构造函数的$('input').blur(function(){ $("#myButton").removeClass('disabled'); tmpval = $(this).val(); if(tmpval == '') { $("#myButton").addClass('disabled'); } if($(this).siblings('input').val() == ''){ $("#myButton").addClass('disabled'); } }); 方法并返回结果。

顺便说一句,在您的示例中,您似乎想要考虑使用java.util.Duration对象。

答案 9 :(得分:3)

在这里创建一个新类确实很烦人,但这主要是因为Java是静态类型而Python不是。 (编辑:不完全,请参阅下面的评论。)如果你愿意放弃静态类型的好处,那么这里给出的答案之一(对象数组)几乎可以为你提供Python中的内容。人机工程学不如Python好,因为你的方法的调用者需要使用显式强制转换,但如果只有3个调用者,那么在某些情况下,避免定义类是值得的。我不认为这是其中一种情况,因为如果你实际计算了多少行,那么大多数行都是通过两种语言的解析来计算的。即:

def duration(d):
  if d == '<1ms': return 0, False, True
  if d.endswith('ms'): return int(d[:-2]), False, False
  return int(d[:-1])*1000, True, False

4行代码处理3个案例。不错。从调用者的角度来看,我不确定我喜欢这个,因为我必须保持布尔人进入的顺序,所以你可能想要在一个好的文档字符串上多花一两行。现在让我们来做Java。

class Duration{
  int ms;
  boolean isInSeconds, isUnder1ms;
  Duration(String d){
    if(d.equals("<1ms")) isUnder1ms = true;
    else if(d.endsWith("ms")) ms = new Integer(d.substring(0, d.length()-2));
    else {
      isInSeconds = true;
      ms = 1000 * new Integer(d.substring(0, d.length()-1));
  }
}

好的,这需要11行代替4.显然,Java比Python更冗长。但是,让我们尝试详细的成本细分。其中两行只是大括号,因为Java不使用缩进。最后一个else子句占用了3行,因为Java没有Python的美味字符串切片功能。最后,创建一个类的开销很大。那是另外3行左右。但请注意,这3行实际上比Python代码更有利于我们:代码站点现在自我记录!比较:

dur = duration(d)
if d[2]: print 'it was in seconds' # or was it d[1]?  I forget.

Duration dur = Duration(d);
if(dur.isInSeconds) System.out.println("It was in seconds");

如果您不关心自我记录的来电者的好处,您可以使用roshan mathew的答案。但请注意它为您节省的代码很少,以及您还需要写多少。