Java-метод неограниченного типа или возврата класса

Я читал эту статью о создании подкласса класса строителя. Я понял статью, но меня беспокоил один маленький кусочек. Был этот метод,

public static Builder<?> builder() {
        return new Builder2();
}

Когда я изменился Builder<?> в Builderнеобработанный тип, компилятор не будет компилировать код. Ошибка была,

Rectangle.java:33: error: cannot find symbol
System.out.println(Rectangle.builder().opacity(0.5).height(250);

Какую дополнительную информацию передали компилятору, используя дополнительную <?>? Я подозревал, что это был компилятор, который не мог определить правильный экземпляр во время компиляции. Если я удаляю маркеры комментариев в (A), код скомпилирован и работает нормально. Все время он имел в виду экземпляр Rectangle. Итак, я думаю, что это был компилятор, который потерпел неудачу.

Было бы замечательно, если бы кто-то мог указать мне на статью, которая объясняет это или приводит к выяснению дополнительной информации к этому. Благодарю.

Я вставил код здесь:

public class Shape {

  private final double opacity;

     public static class Builder<T extends Builder<T>> {
         private double opacity;

         public T opacity(double opacity) {
             this.opacity = opacity;
             return self();
         }

 /* Remove comment markers to make compilation works (A)
         public T height(double height) {
             System.out.println("height not set");
             return self();
         }
 */
         protected T self() {
             System.out.println("shape.self -> " + this);
             return (T) this;
         }

         public Shape build() {
             return new Shape(this);
         }
     }

     public static Builder<?> builder() {
         return new Builder();
     }

     protected Shape(Builder builder) {
         this.opacity = builder.opacity;
     }
 }

 public class Rectangle extends Shape {
     private final double height;

     public static class Builder<T extends Builder<T>> extends Shape.Builder<T> {
         private double height;

         public T height(double height) {
             System.out.println("height is set");
             this.height = height;
             return self();
         }

         public Rectangle build() {
             return new Rectangle(this);
         }
     }

     public static Builder<?> builder() {
         return new Builder();
     }

     protected Rectangle(Builder builder) {
         super(builder);
         this.height = builder.height;
     }

     public static void main(String[] args) {
         Rectangle r = Rectangle.builder().opacity(0.5).height(250).build();
     }
}

3 ответа

Решение

Какую дополнительную информацию передали компилятору, используя дополнительную <?>?

Дополнительная информация с использованием подстановочного знака <?> было, что тот вернулся Rectangle.Builder<?> это супер класс всех возможных родовых Rectangle.Builder<T> классы (см. подстановочные знаки). И с тех пор Rectangle.Builder<T> гарантированно имеет аргумент типа T, который сам является подклассом Rectangle.Builder До тех пор, пока его общая типизация не игнорируется, Rectangle.Builder<?> также гарантированно будет по крайней мере типа Rectangle.Builder<? extends Rectangle.Builder<?>>, Если вы полностью игнорируете обобщенные элементы, удаляя подстановочный знак, эта информация будет потеряна, и код будет скомпилирован как обычный код, предшествующий Java5.0 (там, где обобщенные значения не существовали). Это необходимо для обратной совместимости.

Чтобы увидеть разницу, рассмотрим подкласс Rectangle.Builder, который игнорирует типичную типизацию:

public static class BadBuilder extends Rectangle.Builder {
    private double height;

    public BadBuilder height(double height) {
        System.out.println("height is set");
        this.height = height;
        return (BadBuilder) self();
    }

    @Override
    public Shape.Builder opacity(double opacity) {
        return new Shape.Builder();
    }

    public Rectangle build() {
        return new Rectangle(this);
    }
}

Обратите внимание, что этот класс перезаписывает Shape.Builder#opacity не возвращая подкласс сам по себе. Компилятор не будет генерировать ошибки для этого класса (но он может предупредить вас, что класс игнорирует универсальную типизацию). Таким образом, без общей информации, законно возвращать тип Shape.Builder из метода непрозрачности. Как только вы добавите аргумент типа в BadBuilder, этот код больше не будет компилироваться:

public static class BadBuilder extends Rectangle.Builder<BadBuilder> // -> compile time error

Так что причина, по которой вы получаете ошибку компилятора cannot find symbol есть, потому что класс Shape.Builder сам не объявляет метод / символ T Shape.Builder#heigth() и заявленный метод T Shape.Builder#opacity() только гарантирует, что возвращаемый объект имеет тип Shape.Builder, как объявлено в аргументе типа class Shape.Builder<T extends Shape.Builder<T>>, Поэтому вызывая цепочку методов Rectangle.builder().opacity(0.5).height(250) будет работать, только если Rectangle.builder() фактически гарантированно возвращает Builder, который набирается с подклассом Rectangle.Builder. И эта гарантия может быть предоставлена ​​только в том случае, если универсальная типизация не игнорируется (как видно из примера BadBuilder).

Когда вы добавляете метод Shape.Builder#heigth, удалив комментарий в вашем коде, эта ошибка, очевидно, исчезнет, ​​потому что тогда Shape.Builder объект, возвращенный Shape.Builder#opacity также будет иметь соответствующий метод. Вы также можете удалить эту ошибку, повторно объявив Shape.Builder#opacity в Rectangle.Builder так:

@Override
public T opacity(double opacity) {
    return super.opacity(opacity);
}

Если вы сделаете это, то гарантированно, что возвращаемый объект T Rectangle.Builder#opacity() имеет тип Rectangle.Builder, как объявлено в аргументах типа class Rectangle.Builder<T extends Rectangle.Builder<T>> extends Shape.Builder<T>,

Надеюсь это поможет.

Это различие заключается в том, что когда вы используете необработанный тип в методе, он превращается в обобщение для ВСЕХ вещей, которые вы делаете с этим типом.

Например, предположим, Builder был метод foo() который возвращает List<String>, Если вы позвоните foo() по выражению типа Builder<?>, это будет типа List<String>, С другой стороны, если позвонить foo() по выражению необработанного типа Builder тип этого выражения List не List<String> хотя тип List<String> не имеет отношения к T на всех. Это лечится, как если бы метод foo() Возвращаемый тип был стиранием того, что есть на самом деле.

Так что в вашем случае, предположим, Rectangle.builder() возвращаемый тип Rectangle.Builder<?>, Для удобства давайте дадим имя этому ?, сказать X, Так что у тебя есть Rectangle.Builder<X> (который наследует от Shape.Builder<X>) а ты позвони opacity() на нем, что приводит к X, Мы знаем, потому что X это параметр типа Rectangle.Builder, X должен быть подтипом Rectangle.Builder<X>, Так что мы можем позвонить height() в теме.

Однако если Rectangle.builder() вернул необработанный тип Rectangle.Builder и ты звонишь opacity() на нем это отключает генерики по методу opacity(), поэтому он возвращает стирание своего возвращаемого типа, который Shape.Builder, И ты не можешь позвонить height() на что.

Я тот, кто задает подобные вопросы. Благодаря ответам Балдера и НьюАкта. Я попытался обобщить это в словах непрофессионала, которые я могу запомнить.

  • Без <?>, Компилятор знает только возвращаемый тип Rectangle.Builder а также Rectangle.Builder это подкласс Shape.Builder без какой-либо другой информации. Так как по определению T Shape.Builder#Opacity() в классе Shape.Builder<T extends Shape.Builder<T> лучшее знание для компилятора, что возвращенный T это подкласс Shape.Builder следовательно, метод opacity() возвращает тип Shape.Builder и этот тип не может получить доступ к методу height(),
  • С <?> Компилятор знает

    1. возвращаемый тип Rectangle.Builder<Something>;
    2. Этот тип Что-то по определению является подклассом Rectangle.Builder, так как T extends Rectangle.Builder<T>;
    3. возвращаемый тип также является подклассом Shape.Builder<Something> потому что в определении Rectangle.Builder<T ...> extends Shape.Builder<T>,

По пункту 3 компилятор знает, что T Shape.Builder#opacity() возвращает T это что-то типа; По пункту 2 компилятор знает, что Type Something является подклассом Rectangle.Builder поэтому после вызова метода opacity() возвращенный тип может получить доступ Rectangle.Builder метод height(),

Я надеюсь, что компилятор действительно думает как выше.

Другие вопросы по тегам