Подтвердить что ты не робот

Возвращение результата с несколькими значениями в Java

Я привык к разработке в Python, но по соображениям работы приходится делать это на Java. Я столкнулся с задачей, которая была бы тривиальной в Python, и я хотел бы получить некоторые советы о том, как правильно обращаться с этим в Java.

Мне нужно проанализировать строку продолжительности. Это может быть в миллисекундах (235 мс) или секундах (32 с). И это также может быть "< 1ms" в качестве специального случая.

Разбор происходит не менее трех раз в коде, поэтому я хотел бы разделить его на метод. Но мой код действительно должен знать, а не только результирующее значение в мс, но также и то, было ли оно в мс или s и было ли оно < 1ms (0 - другое значение).

В Python я бы просто вернул кортеж:

return (value_in_milliseconds,is_in_seconds,is_under_1ms)

В C я бы определил структуру этих трех значений и вернул ее. В Паскале я бы определил запись.

В Java я не могу вернуть кортеж, и я не могу определить запись, и что мне делать?

Единственное, о чем я могу думать, это создать класс, представляющий значение продолжительности. Конструктор возьмет строку и проанализирует ее. Класс будет иметь поля: int milliseconds, boolean inSeconds, boolean под 1ms.

Но это звучит ужасно тяжело - есть ли лучшее решение?

4b9b3361

Ответ 1

Не пропускайте набор флагов, которые должны быть согласованы между ними, чтобы сделать разумное состояние. Что делать, если is_in_seconds и is_under_1ms являются true? И почему переменная, содержащая слово milliseconds, когда-либо интерпретируется как секунды? Как неверно выглядит, что будет в коде. Неверный код не очень хорош, если вы можете что-то с этим сделать - мы должны написать код, чье появление правильности/неправильности соответствует действительности - принцип наименьшего удивления применяется к коду и, возможно, даже к Пит Успеха для последующего разработчики, чьи мозги будут взорваться на этом.

Похоже, что это может быть немного "Примитивный навязчивый" код запах/антипаттерн, возможно, из вашего фона на Python? (Я почти ничего не знаю о Python, поэтому не стесняйтесь игнорировать это предположение.)

Решение: сделайте реальный объект уровня домена, представляющий идею приблизительной продолжительности.

Одна из возможных реализаций этого:

  • Создайте перечисление DurationScale, в котором есть члены Second, Millisecond, SubMillisecond.

  • Создайте класс ApproximateDuration, который принимает значение duration integer и DurationScale enum.

  • Теперь используйте этот объект в другом коде. Если вам нужно суммировать серию этих длительностей, создайте класс, который знает, как интерпретировать каждый и добавлять их вместе. Или добавьте методы в этот класс.

Альтернативой некоторому понятию типа DurationScale может быть MarginOfError, который может быть выражен в некотором произвольном количестве миллисекунд. Это может позволить вам использовать строгую математическую формулу, чтобы соответствующим образом увеличить допустимую погрешность при суммировании различных объектов ApproximateDuration вместе с новым объектом ApproximateDuration.

Примечание. Вы можете увидеть некоторые дальнейшие обсуждения о том, почему я рекомендую этот подход.

Реализация, на которую вы остановились, также является хорошим способом ее обработки, когда вы явно указываете нижнюю и верхнюю границы:

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 в именах переменных и свойств важно, равно как и неизменность этого класса.

Ответ 2

Это ничем не отличается от структуры C, но вы каким-то образом наследуете от Object

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

Ответ 3

Определите свой собственный класс или используйте, например, apache commons:

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

Ответ 4

Я попытался использовать ниже пример передачи 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 };

Надеюсь, что поможет

Ответ 5

Лично я бы сделал:

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

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

Это решение является достаточно коротким, позволяет компилятору обнаруживать опечатки в именах полей, инкапсулирует инвариант, который не может быть установлен как логическое, и может безопасно делиться другим кодом без риска ошибки псевдонимов.

Ответ 6

Вы столкнулись именно с способом Java: Вы создаете класс для представления ответа, обычно называемый POJO (обычный объект Java). Это просто класс с частными членами для хранения каждого поля, конструктора, геттеров и, необязательно, сеттеров.

Тем не менее, есть библиотеки, которые вводят абстракции Tuples или Tuple-esque, но все они работают над реализациями POJO.

Scala, который я также разрабатываю, работает на JVM, может взаимодействовать с библиотеками Java и имеет кортежи среди множества других функций.

Ответ 7

Разделение проблем

Ваш метод возвращает продолжительность. Он не должен заботиться о том, хочет ли вызывающий абонент иметь специальный флаг для продолжительности < 1ms (который будет 0 мс, я предполагаю?), Или для проверки, если вы возвращаете секунды или миллисекунды.

Я предполагаю, что у вас есть поток, который выглядит так:

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

Таким образом, get_duration не должен предполагать, как информация будет использоваться вызывающим абонентом.

Что делать, если кому-то нужны минуты и часы? Вам нужно будет переписать все ваши методы вызова, чтобы они соответствовали изменению формы кортежа. Лучше просто позволить вызывающему пользователю обработать это.

Ответ 8

У меня есть Три способа решения этой проблемы. Как вы упомянули, вы не хотите, чтобы правильный метод был моим первым выбором. Использование 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 для String и Object, читайте больше о доступе к HashMap, HashMap является частью JDK, поэтому никакой внешней банки не требуется. С помощью 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;
    }
}

Ответ 9

Вы писали:

Единственное, о чем я могу думать, это создать класс, представляющий значение продолжительности. Конструктор возьмет строку и проанализирует ее. Класс будет иметь поля: int milliseconds, boolean inSeconds, boolean under1ms.

Это близко к тому, что я сделал бы, не совсем тем, что я сделал бы.

Я бы ввести объект параметра, конструктор которого ParseResult(String valInMS, boolean isInSeconds, boolean isUnder1MS). Затем я бы добавил метод public static ParseResult parse(String), который вызывает конструктор и возвращает результат.

Кстати, в вашем примере похоже, что вы хотите рассмотреть использование объекта java.util.Duration.

Ответ 10

Создание нового класса здесь действительно раздражает, но это в основном навязывается вам тем фактом, что Java статически типизирован, а Python - нет. (EDIT: не совсем, см. Комментарий ниже.) Если вы готовы отказаться от преимуществ статической типизации, то один из ответов, приведенных здесь (массив объектов), дает вам в значительной степени то, что у вас было на Python. Эргономика не такая приятная, как Python, потому что вызывающим вашим методам нужно будет использовать явные приведения, но если есть только три вызывающих абонента, то, возможно, в некоторых ситуациях стоит избегать определения класса. Я не думаю, что это одна из таких ситуаций, потому что, если вы действительно считаете, сколько строк занимает, большинство строк берутся путем разбора на обоих языках. К остроумию:

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 строки действительно покупают нам преимущество над кодом 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. Но обратите внимание, как мало кода это сэкономит вам и сколько вам еще нужно писать.