Внедрение в интерфейс 26.1.2
Узнайте, как реализовывать интерфейсы в классах Minecraft в декомпилированных исходниках.
Вставка интерфейсов — это вид модификации классов, используемый для добавления реализаций интерфейсов к классам Minecraft в декомпилированном исходном коде.
Отображение реализации в декомпилированном исходном коде класса устраняет необходимость приведения к интерфейсу для использования его методов.
Кроме того, внедрение интерфейсов может быть транзитивным, что позволяет библиотекам легче предоставлять свои добавленные методы модам, которые от них зависят.
Чтобы продемонстрировать внедрение интерфейсов, в примерах кода на этой странице мы добавим новый вспомогательный метод в класс FlowingFluid.
Создание интерфейса
В пакете, отличном от вашего пакета миксинов (mixin package), создайте интерфейс, который вы хотите внедрить:
java
public interface BucketEmptySoundGetter {
default Optional<SoundEvent> example_mod$getBucketEmptySound() {
throw new AssertionError("Implemented in Mixin");
}
}1
2
3
4
5
2
3
4
5
В нашем случае мы настроим выброс исключения по умолчанию (throw by default), так как планируем реализовать этот метод через миксин.
WARNING
Все методы внедряемых интерфейсов обязательно должны быть типа default, чтобы их можно было внедрить с помощью модификатора классов — даже если вы планируете реализовать эти методы в целевом классе через миксин.
К названиям методов также следует добавлять префикс с ID вашего мода и разделителем (например, $ или _), чтобы они не конфликтовали с методами, добавленными другими модами.
Реализация интерфейса
TIP
Если методы интерфейса полностью реализованы через его собственные default-методы, вам не нужно использовать миксин для внедрения интерфейса — будет достаточно одной записи в модификаторе классов.
Чтобы переопределить методы интерфейса в целевом классе, вам следует использовать миксин, который реализует этот интерфейс и указывает в качестве цели класс, в который вы хотите его внедрить.
java
@Mixin(FlowingFluid.class)
abstract class FlowingFluidMixin extends Fluid implements BucketEmptySoundGetter {
@Override
public Optional<SoundEvent> example_mod$getBucketEmptySound() {
return Optional.of(this.is(FluidTags.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA : SoundEvents.BUCKET_EMPTY);
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Эти переопределения будут добавлены в целевой класс при выполнении программы, но они не появятся в декомпилированном исходном коде, даже если вы используете модификатор классов, чтобы сделать видимой саму реализацию интерфейса.
Создание записи модификатора класса
Внедрение в интерфейс использует следующий синтаксис:
classtweaker
inject-interface <targetClassName> <injectedInterfaceName>При изменении классов, классы и интерфейсы используют свои внутренние имена.
Для нашего примера интерфейса, запись будет выглядеть следующим образом:
classtweaker
inject-interface net/minecraft/world/level/material/FlowingFluid com/example/docs/interface_injection/BucketEmptySoundGetterДженерик-интерфейсы
Если в вашем интерфейсе есть обобщения, вы можете указать их в записи. Для этого добавьте угловые скобки <> в конце имени интерфейса, а между скобками укажите общие типы в формате сигнатуры байт-кода Java.
Формат сигнатуры:
| Тип данных | Пример Java | Синтаксис | Пример формата сигнатуры |
|---|---|---|---|
| Ссылочный тип (Класс) | java.lang.String | Формат дескриптора | Ljava/lang/String; |
| Массив | java.lang.String[] | Формат дескриптора | [Ljava/lang/String; |
| Примитивный тип | boolean | Символ дескриптора | Z |
| Параметр типа (Дженерик) | T | T + имя + ; | TT; |
| Класс с дженериком | java.util.List<T> | L + внутр. имя + < + дженерики + >; | Ljava/util/List<TT;>; |
| Универсальный символ (Wildcard) | ?, java.util.List<?> | Символ * | *, java/util/List<*>; |
| Ограничение сверху (extends) | ? extends String | + + ограничение | +Ljava/lang/String; |
| Ограничение снизу (super) | ? super String | - + ограничение | -Ljava/lang/String; |
Чтобы внедриться в данный интерфейс:
java
package com.example.docs.interface_injection;
public interface GenericInterface<T, U> {
}1
2
3
4
5
2
3
4
5
дженериком <? extends String, Boolean[]>
Запись модификатора класса должна выглядеть вот так:
classtweaker
inject-interface net/minecraft/world/level/material/FlowingFluid com/example/docs/interface_injection/GenericInterface<+Ljava/lang/String;[Ljava/lang/Boolean;>Применение изменений
Чтобы увидеть применённые изменения, необходимо обновить Gradle-проект и перегенерировать исходники. Если изменения не появились, попробуйте проверить файл на валидность и посмотреть, не возникли ли ошибки.
Теперь добавленные методы можно использовать на экземплярах того класса, в который был внедрён интерфейс:
java
void example(FlowingFluid flowingFluid) {
Optional<SoundEvent> sound = flowingFluid.example_mod$getBucketEmptySound();
// ...
}1
2
3
4
2
3
4
При необходимости вы также можете переопределять эти методы в подклассах (наследниках) того класса, куда был внедрён интерфейс.









