Код Java компилируется, но выдает ошибку IllegalAccessError

#java #generics #types #module #packages

#java #обобщения #типы #модуль #пакет

Вопрос:

В приведенном ниже коде я привожу два основных класса — TestWorks и TestCompilesButFails. Я не уверен, что понимаю причину сбоя — может показаться, что выражению Arrays.asList() присваивается тип «Список абстрактного базового класса», но почему вообще было бы правильно указывать здесь тип, который ссылается на локальный класс пакета в другом пакете?

 // failing test class
import somepackage.*;
import java.util.Arrays;

public class TestCompilesButFails {
    public static void main(String [] args){
        // fails here with java.lang.IllegalAccessError: 
        // tried to access class somepackage.AbstractBaseClass 
        // from class TestCompilesButFails
        for (Object o : Arrays.asList(new ConcreteA(), new ConcreteB())) { 
            System.out.println(o);
        }
    }
}


// package-local abstract base class
package somepackage;

abstract class AbstractBaseClass {
    public abstract void doSomething();
}

// next two classes - public extenders of abstract base class
package somepackage;

public class ConcreteA extends AbstractBaseClass {
    public void doSomething(){
        System.out.print("Look, ma!n");
    }
}

package somepackage;

public class ConcreteB extends AbstractBaseClass {
    public void doSomething(){
        System.out.print("No types!n");
    }
}

// working test 
import somepackage.*;

public class TestWorks {
    public static void main(String [] args){
        new ConcreteA().doSomething();
        new ConcreteB().doSomething();
    }
}
  

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

1. Просто для устранения другого фактора — замена Arrays.asList() на private static <T> List<T> myAsList(T first, T second) приводит к той же проблеме — это не имеет ничего общего с переменными значениями в Arrays.asList()

Ответ №1:

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

Ограничение супертипа T :> X подразумевает, что решение является одним из супертипов X. Учитывая несколько таких ограничений на T, мы можем пересекать наборы супертипов, подразумеваемых каждым из ограничений, поскольку параметр типа должен быть членом всех из них. Затем мы можем выбрать наиболее конкретный тип, который находится в пересечении.

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

Обратите также внимание, что вывод типа никоим образом не влияет на надежность. Если выведенные типы бессмысленны, вызов выдаст ошибку типа. Алгоритм вывода типов следует рассматривать как эвристический, разработанный для хорошей работы на практике. Если не удается вывести желаемый результат, вместо этого могут быть использованы явные параметры типа.

Что в вашем случае было бы:

     for (Object o : Arrays.<Object>asList(new ConcreteA(), new ConcreteB())) { 
        System.out.println(o);
    }
  

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

1. Спасибо, что нашли соответствующую часть в спецификации java — я подозревал, что это просто поиск наиболее «специфичного» общего предка без учета видимости.

2. Я все еще не уверен, почему указание наиболее конкретного видимого предка было бы более сложным на практике… Мне придется подумать об этом.

Ответ №2:

Я думаю, что вы имеете в виду классификацию java «защищенный пакетом»; метод или переменная, которые не объявлены общедоступными, частными или защищенными, таким образом, «защищены пакетом» и, следовательно, могут быть доступны любому классу в этом пакете. Он использовался не так часто (к счастью), и я не думаю, что это было хорошим значением по умолчанию, но так было начиная с Java 1.0.

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

1. Изменил мой вопрос, чтобы ссылаться на локальный пакет, чтобы быть более явным. Однако мой вопрос остается.

2. Кроме того, я считаю, что локальные классы пакета чрезвычайно полезны.

3. Старайтесь не использовать область видимости пакета, если это действительно не необходимо. Это вредит повторному использованию.

4. @Steven Да — область действия пакета полезна, когда вы не хотите повторного использования, а скорее когда вы хотите, чтобы информация скрывалась на уровне класса.

5. Я регулярно создаю локальные классы пакета в одном из моих проектов среднего размера (около 20 тыс. loc). При написании api часто имеет смысл писать вспомогательные классы для внутреннего использования, не выставляя их напоказ внешнему миру. Я не уверен, что понимаю, что такое «взломать код» по этому поводу…