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

Перечисления Java: два типа перечислений, каждый из которых содержит ссылки друг на друга?

Есть ли способ обойти проблемы загрузки класса, вызванные наличием двух перечислений, которые ссылаются друг на друга?

У меня есть два набора перечислений, Foo и Bar, определенные так:

public class EnumTest {

  public enum Foo {
    A(Bar.Alpha),
    B(Bar.Delta),
    C(Bar.Alpha);

    private Foo(Bar b) {
      this.b = b;
    }

    public final Bar b;
  }

  public enum Bar {
    Alpha(Foo.A),
    Beta(Foo.C),
    Delta(Foo.C);

    private Bar(Foo f) {
      this.f = f;
    }

    public final Foo f;
  }

  public static void main (String[] args) {
    for (Foo f: Foo.values()) {
      System.out.println(f + " bar " + f.b);
    }
    for (Bar b: Bar.values()) {
      System.out.println(b + " foo " + b.f);
    }
  }
}

Вышеприведенный код выводит как результат:

A bar Alpha
B bar Delta
C bar Alpha
Alpha foo null
Beta foo null
Delta foo null

Я понимаю, почему это происходит - JVM начинает загрузку классов Foo; он видит Bar.Alpha в Foo.A-конструкторе, поэтому он запускает classloading Bar. Он видит ссылку Foo.A в вызове конструктора Bar.Alpha, но (поскольку мы все еще находимся в Foo.A-конструкторе) Foo.A на данный момент равно null, поэтому конструктор Bar.Alpha получает значение null. Если я отменил два для циклов (или в противном случае ссылку Bar перед Foo), выход изменится так, чтобы значения Bar были правильными, но значения Foo не являются.

Есть ли способ обойти это? Я знаю, что могу создать статическую карту и статическую карту в 3-м классе, но для меня это довольно грустно. Я мог бы также создавать методы Foo.getBar() и Bar.getFoo(), которые относятся к внешней карте, поэтому он даже не изменит мой интерфейс (фактические классы, в которых я использую инспекторов вместо публичных полей), но он все еще чувствует вроде нечистого для меня.

(Причина, по которой я делаю это в своей реальной системе: Foo и Bar представляют собой типы сообщений, которые 2 приложения отправляют друг другу, поля Foo.b и Bar.f представляют ожидаемый тип ответа для данного сообщения - поэтому в моем примере кода, когда app_1 получает Foo.A, ему нужно ответить с помощью Bar.Alpha и наоборот.)

Спасибо заранее!

4b9b3361

Ответ 1

Один из лучших способов - использование метода полиморфизма перечисления:

public class EnumTest {
    public enum Foo {
        A {

            @Override
            public Bar getBar() {
                return Bar.Alpha;
            }
        },
        B {

            @Override
            public Bar getBar() {
                return Bar.Delta;
            }
        },
        C {

            @Override
            public Bar getBar() {
                return Bar.Alpha;
            }
        },

        ;

        public abstract Bar getBar();
    }

    public enum Bar {
        Alpha {

            @Override
            public Foo getFoo() {
                return Foo.A;
            }
        },
        Beta {

            @Override
            public Foo getFoo() {
                return Foo.C;
            }
        },
        Delta {

            @Override
            public Foo getFoo() {
                return Foo.C;
            }
        },

        ;

        public abstract Foo getFoo();
    }

    public static void main(String[] args) {
        for (Foo f : Foo.values()) {
            System.out.println(f + " bar " + f.getBar());
        }
        for (Bar b : Bar.values()) {
            System.out.println(b + " foo " + b.getFoo());
        }
    }
}

Приведенный выше код выдает желаемый результат:

A bar Alpha
B bar Delta
C bar Alpha
Alpha foo A
Beta foo C
Delta foo C

См. также:

Ответ 2

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

Как использовать методы Foo.setResponse(Bar b) и Bar.setResponse(Foo f)? Вместо того, чтобы устанавливать Foo Bar в конструкторе Foo (и аналогично Bar Foo в конструкторе Bar), вы выполняете инициализацию с помощью метода? Например.:

Foo:

public enum Foo {
  A, B, C;

  private void setResponse(Bar b) {
    this.b = b;
  }

  private Bar b;

  public Bar getB() {
    return b;
  }

  static {
    A.setResponse(Bar.Alpha);
    B.setResponse(Bar.Delta);
    C.setResponse(Bar.Alpha);
  }
}

Бар:

public enum Bar {
  Alpha, Beta, Delta;

  private void setResponse(Foo f) {
    this.f = f;
  }

  private Foo f;

  public Foo getF() {
    return f;
  }

  static {
    Alpha.setResponse(Foo.A);
    Beta.setResponse(Foo.C);
    Delta.setResponse(Foo.C);
  }
}

Кроме того, вы отмечаете, что Foo и Bar - это два типа сообщений. Можно ли объединить их в один тип? Из того, что я вижу, их поведение здесь одно и то же. Это не исправляет круговую логику, но это может дать вам другое представление о вашем дизайне...

Ответ 3

Поскольку, похоже, вы все равно будете жестко кодироваться, почему бы не что-то вроде

public static Bar responseBar(Foo f) {
 switch(f) {
  case A: return Bar.Alpha;
  // ... etc
 }
}

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

EDIT:

Мне нравится предложение Tom's EnumMap; Я думаю, что производительность, скорее всего, выше на EnumMap, но такая элегантная конструкция, описанная в Effective Java, по-видимому, не предоставляется этой конкретной проблемой - однако предлагаемое выше решение коммутатора было бы хорошим способом построить две статические EnumMaps, то ответ может быть примерно таким:

 public static Bar response(Foo f) { return FooToBar.get(f); }
 public static Foo response(Bar b) { return BarToFoo.get(b); }

Ответ 4

Интересный дизайн. Я вижу вашу потребность, но что вы собираетесь делать, когда требования слегка меняются, так что в ответ на Foo.Epsilon приложение_1 должно отправить либо Bar.Gamma, либо Bar.Whatsit?

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

Ответ 5

Вы можете использовать EnumMap и заполнить его в одном из перечислений.

private static EnumMap<Foo, LinkedList<Bar>> enumAMap;

public static void main(String[] args) throws Exception {
    enumAMap = new EnumMap<Foo, LinkedList<Bar>>(Foo.class);
    System.out.println(Bar.values().length); // initialize enums, prevents NPE
    for (Foo a : Foo.values()) {
        for (Bar b : enumAMap.get(a)) {
            System.out.println(a + " -> " + b);
        }
    }
}

public enum Foo {
    Foo1(1),
    Foo2(2);

    private int num;

    private Foo(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

public enum Bar {
    Bar1(1, Foo.Foo1),
    Bar2(2, Foo.Foo1),
    Bar3(3, Foo.Foo2),
    Bar4(4, Foo.Foo2);

    private int num;
    private Foo foo;

    private Bar(int num, Foo foo) {
        this.num = num;
        this.foo = foo;
        if (!enumAMap.containsKey(foo)) {
            enumAMap.put(foo, new LinkedList<Bar>());
        }
        enumAMap.get(foo).addLast(this);
    }

    public int getNum() {
        return num;
    }

    public Foo getFoo() {
        return foo;
    }
}

Вывод:

4
Foo1 -> Bar1
Foo1 -> Bar2
Foo2 -> Bar3
Foo2 -> Bar4