Как обеспечить на Java 8 время компиляции, когда подпись метода "реализует" функциональный интерфейс

Есть ли в Java 8 любой аналог для ключевого слова implements для методов?

Скажем, у меня есть функциональный интерфейс:

@FunctionalInterface
interface LongHasher {
    int hash(long x);
}

И библиотека из 3 статических методов "реализующая этот функциональный интерфейс:

class LongHashes {
    static int xorHash(long x) {
        return (int)(x ^ (x >>> 32));
    }
    static int continuingHash(long x) {
        return (int)(x + (x >>> 32));
    }
    static int randomHash(long x) {
         return xorHash(x * 0x5DEECE66DL + 0xBL);
    }
}

В будущем я хочу иметь возможность взаимозаменяемо использовать любые ссылки на эти 3 метода в качестве параметра. Например:

static LongHashMap createHashMap(LongHasher hasher) { ... }
...
public static void main(String[] args) {
    LongHashMap map = createHashMap(LongHashes::randomHash);
    ...
}

Как я могу обеспечить во время компиляции, что LongHashes::xorHash, LongHashes::continuingHash и LongHashes::randomHash имеют ту же подпись, что и LongHasher.hash(long x)?

Ответ 1

Нет такой конструкции синтаксиса, о которой вы просите. Однако вы можете создать статическую константу, где вы явно назначаете ссылку на метод для вашего интерфейса:

class LongHashes {
    private static final LongHasher XOR_HASH = LongHashes::xorHash;
    private static final LongHasher CONTINUING_HASH = LongHashes::continuingHash;
    private static final LongHasher RANDOM_HASH = LongHashes::randomHash;

    static int xorHash(long x) {
        return (int)(x ^ (x >>> 32));
    }
    static int continuingHash(long x) {
        return (int)(x + (x >>> 32));
    }
    static int randomHash(long x) {
         return xorHash(x * 0x5DEECE66DL + 0xBL);
    }
}

Таким образом, ваша компиляция будет нарушена, если какая-либо подпись или интерфейс метода несовместимы. Если вы хотите, вы можете объявить их public и использовать вместо ссылок на методы.

Если вам все равно, что эти статические lambdas будут висящими в памяти во время выполнения, вы можете переместить это объявление в отдельный класс (например, вложенный), который компилируется, но никогда не загружается.

Ответ 2

Я тоже хотел этого, в прошлом, но ты не можешь этого сделать. Но ты знаешь. Была Java до Java 8. Сделайте это вместо:

enum LongHashes implements LongHasher {
    XOR {
        @Override
        public int hash(long x) { ... }
    },
    CONTINUING {
        @Override
        public int hash(long x) { ... }
    },
    RANDOM {
        @Override
        public int hash(long x) { ... }
    }
}

И затем:

public static void main(String[] args) {
    LongHashMap map = createHashMap(LongHashes.RANDOM);
    ...
}

Ответ 3

Вы можете объявлять объекты функции, а не методы.

class LongHashes {

    static final LongHasher xorHash = x -> {
        return (int)(x ^ (x >>> 32));
    };

    ... etc


    LongHashMap map = createHashMap(LongHashes.randomHash);

Ответ 4

Одним из способов было бы вернуть LongHasher непосредственно из класса LongHashes:

class LongHashes {
  private static int xorHashImpl(long x) {
    return (int)(x ^ (x >>> 32));
  }
  static LongHasher xorHash() {
    return LongHashes::xorHashImpl;
  }
}

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

Ответ 5

Или просто создайте 3 класса, которые реализуют LongHasher. Когда вам понадобится LongHasher, получите или создайте экземпляр и передайте его:

LongHasher longHasher = ... // new RandomLongHasher(), factory, .......
LongHashMap map = createHashMap(longHasher);

Написание функций как статических методов здесь:

  • затрудняет понимание
  • звучит как изобретать колесо; колесо является интерфейсом/классами, заново изобретая намек на использование "интерфейса" между кавычками в описании проблемы; -)

Мы не вынуждены везде использовать лямбды.

Ответ 6

Хотя я считаю, что Tagir отвечает на хороший хак, легко забыть добавить частную константу, когда вы создаете новый хешер.

Как обычно, имея дело с потенциальными проблемами рефакторинга, я считаю, что тестирование - это ответ:

public class LongHashesTest {

    @Test
    public void xorHash() {
        LongHasher xorHash = LongHashes::xorHash;

        assertEquals(1768181579, xorHash.hash(34312465426524234l));
    }

    @Test
    public void continuingHash() {
        LongHasher continuingHash = LongHashes::continuingHash;

        assertEquals(1529080340, continuingHash.hash(74543524355l));
    }

    @Test
    public void randomHash() {
        LongHasher randomHash = LongHashes::randomHash;

        assertEquals(-1100764221, randomHash.hash(4343245345432154353l));
    }
}