Как ограничить создание объекта с помощью generics?

#java #generics

#java #дженерики

Вопрос:

Я хочу иметь иерархию типов, в которой только Foo объекты контролируют создание Bar объектов. Например:

 public abstract class Foo<F extends Foo<F>> {
    public abstract  Bar<F> makeBar();
}

public abstract class Bar<F extends Foo<F>> {}
  

Теперь подкласс Foo мог бы реализовать подкласс Bar и вернуть его обратно:

 public class FooImpl extends Foo<FooImpl> {

    private static class BarImpl extends Bar<FooImpl> {}

    @Override
    public Bar<FooImpl> makeBar() { return new BarImpl(); }
}
  

Однако это все еще позволяет создавать Bar объекты в другом месте:

 public class FakeBar extends Bar<FooImpl> {}
  

Как я могу ограничить Bar<FooImpl> (используя только систему типов, а не проверки во время выполнения) таким образом, чтобы он должен создаваться экземпляром FooImpl и не может быть создан ни в каком другом месте?

Ответ №1:

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

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

Извините, не могу сделать. В любом случае, какой в этом был бы смысл?

Комментарии:

1. «В любом случае, какой в этом был бы смысл?» Введите классы, подобные Haskell или Scala.

Ответ №2:

Это не работает (с ограничением: введите только system, никаких проверок во время выполнения). Мы можем либо запретить создание подклассов вообще ( final класс), либо разрешить его. Если (общедоступный) класс не является окончательным, любой другой класс может быть подклассом.

Вы могли бы попробовать поиграть с аннотациями — например, создать аннотацию, в которой указано разрешенное имя класса, но это зависит от обработки аннотаций.

Пример:

 @AllowedSubclasses(classnames="com.example.FooBar; *.AnyFooBar; com.example.foobars.*")
public abstract class Bar<F extends Foo<F>> {}
  

Затем обработчик аннотаций выдаст ошибку, если какой-либо другой класс подклассирует этот аннотированный класс.


Как насчет смешанного подхода: пометьте свои внутренние методы надписью «HANDS OFF» в javaDoc и документируйте, что любое нарушение приведет к исключениям во время выполнения. После вызова метода вы можете проверить внутри метода, является ли вызывающий объект экземпляром одного из классов, которым разрешено использовать эту функцию.

Комментарии:

1. Я думал о том, чтобы «встроить что-то», над чем имеют контроль только подклассы Foo (защищенные внутренние классы?), Но я не могу запустить это.

2. Даже если бы отсутствовало оружие «API отражения / вызова», оно может разрушить любой из этих щитов.

3. Если бы это было безопасно для «обычного» использования, этого было бы достаточно. Если кто-то использует отражение, и это взрывается у него перед носом, что ж…

Ответ №3:

Ну, вместо того, чтобы полагаться на дженерики и т.д., Более простой способ добиться этого — обеспечить, чтобы вам нужен экземпляр Foo для создания Bar

 public Bar(Foo foo){...}
  

Таким образом, никто не сможет создать панель независимо от Foo . Это объединяет два класса и также указывает пользователям на этот факт. Есть и другие способы добиться этого … это всего лишь один пример

Комментарии:

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

2. Ну, хорошо, но какова цель этого? Что кто-либо получает, получая Bar только из Foo и нигде больше? Это то, на что вам нужно ответить

3. @Java Drinker: Подкласс like FooImpl содержит определенный подкласс BarImpl of Bar<FooImpl> , который должен быть в значительной степени скрыт извне (который видит только Bar s). Суть в том, что когда FooImpl возвращается Bar<FooImpl> экземпляр, ему нужно привести его обратно к оригиналу BarImpl , что безопасно только в том случае, если мы уверены, что он действительно был создан FooImpl в первую очередь. Проверки во время выполнения могут быть достаточно хорошими, если у вас есть полный контроль над кодом, но это не обязательно так.

4. Хорошо, это справедливо, но опять же, зачем FooImpl нужно приводить его обратно к BarImpl? Если у вас есть абстрактный класс или интерфейс, доступ к его функциональности должен осуществляться через общедоступный api. Я думаю, что я хочу знать, это то, что FooImpl получает от BarImpl, чего он не может получить, например, из Bar<FooImpl>?

5. @Java Drinker: Я хочу иметь своего рода «фреймворк», который имеет дело только с Bar s. Это означает, что метод фреймворка будет принимать a BarImpl (в конце концов, это Bar a), но после его обработки / преобразования его тип будет потерян, и все, что у меня было бы, это Bar<FooImpl> . Весь смысл в том, чтобы написать код как можно более общим. Чтобы справиться с этим, я делаю Bar s как можно более тупыми и помещаю логику в Foo s. Но мне все еще нужно получить эти данные безопасным способом.

Ответ №4:

Вы можете создать makeBar конкретный метод, который вызывает a protected abstract makeBar0 , который выполняет фактическое создание. makeBar () может взять результат и проверить его на объекте любым удобным для вас способом.

 public abstract class Foo<F extends Foo<F>> {
    public Bar<F> makeBar() {
        Bar<F> bar = makeBar0();
        // check bar
        return bar;
    }
}
  

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

Комментарии:

1. Не могли бы вы, пожалуйста, немного пояснить это, это звучит интересно, но я не совсем понимаю…

2. это зависит от проверок во время выполнения makeBar() , которых операционная система хочет избежать. (И я думаю, вы хотели сказать, что makeBar() должен быть конкретный метод .)

3. @Stephen C, спасибо. Проверка во время выполнения не должна быть большой проблемой, потому что это правило, которое не следует нарушать слишком регулярно. т. Е. любой разработчик сделает это всего несколько раз, прежде чем они решат, что делать. 😉