Подтвердить что ты не робот

Java определяет, какой метод вызывать с использованием аргумента generic type?

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

Сегодня я натолкнулся на следующий фрагмент кода, в котором, по-видимому, Java определяет аргумент типа collection/list, какой конструктор вызывает:

public static class MyClass {
    public MyClass(final Collection<String> coll) {
        System.out.println("Constructor 1");
    }
    public MyClass(final List<Integer> list) {
        System.out.println("Constructor 2");
    }
}

Выполняются следующие вызовы:

new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2

Теперь, если я стираю аргументы типа:

public static class MyClass2 {
    public MyClass2(final Collection coll) {
        System.out.println("Constructor 1");
    }
    public MyClass2(final List list) {
        System.out.println("Constructor 2");
    }
}

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

new MyClass2(new HashSet<String>()); // Constructor 1
new MyClass2(new ArrayList<String>()); // Constructor 2
new MyClass2(new ArrayList<Integer>()); // Constructor 2

Похоже, что информация обобщений хранится в скомпилированном классе (в данном случае MyClass) и не отбрасывается в конце концов, но ее следует отбросить. Что я не понимаю?

4b9b3361

Ответ 1

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

В среднем случае он укажет виртуальной машине на вызов MyClass2<init>(Collection) (т.е. сгенерирует байт-код, соответствующий этому конкретному конструктору).

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

Вот почему приведенный выше код работает, даже несмотря на то, что общая информация была удалена во время выполнения.

[EDIT]. Чтобы уточнить: код байта содержит дополнительную информацию, которую компилятор может видеть и использовать. Вы можете получить ту же информацию через отражение.

Erasure означает, что интерпретатор байтового кода и JIT не заботятся о дженериках, поэтому вы не можете иметь setFoo(List<String>) и setFoo(List<Integer>) в одном классе: хотя компилятор может различать два, среда выполнения может " т.

В частности, когда вы исследуете метод с помощью отражения, вы получите информацию о генериках, но интерпретатор байтового кода /JIT не использует отражение. Вместо этого он использует сжатую подпись метода, которая читает что-то вроде Method com/pany/Type/setFoo(Ljava.util.List;)V - здесь нет дженериков.

по теме:

Ответ 2

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


Вызов метода для перегруженного метода разрешается компилятором во время компиляции. И generics используются для проверки типа только во время компиляции. Таким образом, это не имеет ничего общего с стиранием типа, которое полностью является рабочим временем выполнения.

Рассмотрим ваш первый случай:

public MyClass(final Collection<String> coll)
public MyClass(final List<Integer> list)

Теперь, когда вы вызываете метод как:

new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2

Компилятор решит, какой метод имеет более специфический тип для переданного аргумента. Рассмотрим случай 2, где ваше главное сомнение, я думаю.

ArrayList<String> является подтипом Collection<String>, но это не подтип List<Integer>. Дженерики являются инвариантными. Таким образом, компилятор привяжет вызов второго метода к первому методу.

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

public MyClass2(final Collection coll)
public MyClass2(final List list)

Теперь ArrayList<String> является подтипом List и Collection обоих. Но List более специфичен для ArrayList чем Collection. Таким образом, компилятор свяжет вызов метода:

new MyClass2(new ArrayList<String>());

с тем, который принимает List как аргумент.


Литература:

Ответ 3

Проще говоря, компилятор выбирает самый узкий тип, который соответствует.

В исходной версии параметра очевидно, что будет выбрано, но в общей версии тип коллекции/списка включен в соответствующую информацию: ArrayList - это коллекция и список, но вы можете видеть легко сломать галстук, также сравнивая тип ArrayList - теперь будет только один метод.