Можно ли дать переменные экземпляра Enums собственного типа в Groovy?

Я делаю текстовую приключенческую игру в Groovy как своего рода упражнение, и я сталкиваюсь со странной ошибкой.

прямо сейчас у меня есть enum для направлений, которые игрок сможет пойти, в настоящее время содержащий Север, Юг, Восток, Запад, вверх и вниз.

у меня есть Room класс, который содержит Map других комнат и их направления. Когда я добавляю Room в другой Room в некоторых Direction, Я хочу иметь возможность добавить текущую Room другой Room на напротив направлении.

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

в настоящее время я пытаюсь реализовать это с помощью enum имени Direction С вложенной переменной экземпляра opposite (типа Direction). Разве это запрещено? Я не получаю ошибки компилятора или что-то еще, но я не могу его получить работать.

вот полный enum объявления:

public enum Direction {
    North(South), South(North), East(West), West(East), Up(Down), Down(Up)
    private Direction opposite
    Direction(Direction d){
        opposite = d
    }
    public opposite(){
        return opposite
    }
}

и это метод, из которого я его вызываю:

public void addConnection(Direction d, Spot spot){
    connections[d] = spot
    spot.connections[d.opposite()] = this
}

здесь connections это public Map<Direction, Spot>.

в этом случае запись добавляется в connections это выглядит так:

null:Spot@some_hexadecimal_representation

любая помощь будет большое. Спасибо!

3 ответов


Groovy, похоже, обходит то, что на Java ошибка компиляции:

Main.java:2: illegal forward reference
    North(South), South(North), East(West), West(East), Up(Down), Down(Up);
          ^
Main.java:2: illegal forward reference
    North(South), South(North), East(West), West(East), Up(Down), Down(Up);
                                     ^
Main.java:2: illegal forward reference
    North(South), South(North), East(West), West(East), Up(Down), Down(Up);
                                                           ^
3 errors

компилятор groovy не жалуется на это, но инициализирует значения перечисления, которые нуждаются в объявлениях вперед как null:

public enum Direction {
    North(South), South(North), East(West), West(East), Up(Down), Down(Up)
    Direction(Direction d){
        println "opposite of $this is $d"
    }
}

Direction.South // Force enum instantiation in GroovyConsole.

выходы:

opposite of North is null
opposite of South is North
opposite of East is null
opposite of West is East
opposite of Up is null
opposite of Down is Up

одним из решений, которое, кажется, работает просто отлично в Java, является добавлять static блок Direction класс инициализации opposite значения. Переведенный для Groovy это было бы:

enum Direction {
    North, South, East, West, Up, Down
    private Direction opposite
    Direction getOpposite() { opposite }

    static {
        def opposites = { d1, d2 -> d1.opposite = d2; d2.opposite = d1 }
        opposites(North, South)
        opposites(East, West)
        opposites(Up, Down)
    }
}

Direction.values().each { 
    println "opposite of $it is $it.opposite"
}

что теперь выводит правильные значения:

opposite of North is South
opposite of South is North
opposite of East is West
opposite of West is East
opposite of Up is Down
opposite of Down is Up

обновление

еще один, возможно, более простое решение может использовать индексы направлений в перечислении, чтобы найти противоположности:

public enum Direction {
    North(1), South(0), East(3), West(2), Up(5), Down(4)
    private oppositeIndex
    Direction getOpposite() { 
        values()[oppositeIndex]
    }
    Direction(oppositeIndex) { 
        this.oppositeIndex = oppositeIndex
    }
}

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

обновление 2

теперь, я, вероятно, немного в гольф земли здесь, но вы можете получить противоположное направление без необходимости дополнительного поля, просто используя перечисление значений' ordinal() (индекс):

enum Direction {
    North, South, East, West, Up, Down
    Direction getOpposite() { 
        values()[ordinal() + ordinal() % 2 * -2 + 1]
    }
}

это не так страшно, как кажется! Даже направления (Север, Восток, вверх) возвращают направление в ordinal() + 1 как их противоположность, в то время как нечетные направления (другие) возвращают те в ordinal() - 1. Конечно, это сильно зависит от порядка элементов в перечислении, но разве вам не нравится лаконичность? =D


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

public enum Direction {
    North({South}), South({North}), East({West}), West({East}), Up({Down}), Down({Up})
    private def opp
    Direction(opp) {
        this.opp = opp
    }
    public opposite() {
        return opp()
    }
}
public static void main(String[] args) {
    Direction.each { d ->
        println "${d} ... ${d.opposite()}"
    }
}

выходы:

North ... South
South ... North
East ... West
West ... East
Up ... Down
Down ... Up

похоже на половину Directions не инициализируются при вызове конструктора перечисления. То есть, когда вы звоните North(South), Юг не был инициализирован. Он следующий в очереди.

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

public enum Direction {
    North(1), South(~1), East(2), West(~2), Up(4), Down(~4);

    private int value;

    Direction(int d){
        value = d;
    }

    private int getValue() {
        return value;
    }

    public Direction opposite(){
        for (Direction d : Direction.values()) {
            if (value == ~(d.getValue())) 
                return d;
        }
        return null;
    }
}

это использует побитовый оператор ~ различать противоположности.