Перегруженный метод приватного пакета вызывает ошибку компиляции - это странная ошибка JLS или ошибка javac? - PullRequest
1 голос
/ 05 декабря 2009

Я столкнулся со странностью JLS или ошибкой JavaC (не уверен, какой именно). Пожалуйста, прочтите следующее и предоставьте объяснение со ссылкой на отрывок JLS или Sun Bug ID, в зависимости от ситуации.

Предположим, у меня есть надуманный проект с кодом в трех "модулях" -

  1. API - определяет API-интерфейс - думаю, Servlet API
  2. Impl - определяет реализацию API - думаю, контейнер Tomcat Servlet
  3. Приложение - приложение, которое я написал

Вот классы в каждом модуле:

API - MessagePrinter.java

package api;

public class MessagePrinter {

    public void print(String message) {
        System.out.println("MESSAGE: " + message);
    }
}

API - MessageHolder.java (да, он ссылается на класс "impl" - подробнее об этом позже)

package api;

import impl.MessagePrinterInternal;

public class MessageHolder {

    private final String message;

    public MessageHolder(String message) {
         this.message = message;
    }

    public void print(MessagePrinter printer) {
        printer.print(message);
    }

    /**
     * NOTE: Package-Private visibility.
     */ 
    void print(MessagePrinterInternal printer) {
        printer.print(message);    
    }

}

Impl - MessagePrinterInternal.java - Этот класс зависит от класса API. Как следует из названия, он предназначен для «внутреннего» использования в другом месте в моей маленькой структуре.

package impl;

import api.MessagePrinter;

/**
 * An "internal" class, not meant to be added to your
 * application classpath. Think the Tomcat Servlet API implementation classes.
 */
public class MessagePrinterInternal extends MessagePrinter {

    public void print(String message) {
        System.out.println("INTERNAL: " + message);
    }
}

Наконец, единственный класс в модуле App ... MyApp.java

import api.MessageHolder;
import api.MessagePrinter;

public class MyApp {

    public static void main(String[] args) {
        MessageHolder holder = new MessageHolder("Hope this compiles");
        holder.print(new MessagePrinter());
    }

}

Итак, теперь я пытаюсь скомпилировать свое маленькое приложение, MyApp.java. Предположим, мои jar API экспортируются через jar, скажем, api.jar, и, будучи хорошим гражданином, я ссылаюсь только на этот jar в моем classpath, а не на класс Impl, поставляемый в impl.jar.

Теперь, очевидно, в моей структуре есть недостаток, заключающийся в том, что классы API не должны зависеть от "внутренних" классов реализации. Однако, что стало неожиданностью, так это то, что MyApp.java вообще не компилировался.

javac -cp api.jar src\MyApp.java
src\MyApp.java:11: cannot access impl.MessagePrinterInternal class file for impl.MessagePrinterInternal not found

    holder.print(new MessagePrinter());
                 ^
      1 error

Проблема в том, что компилятор пытается разрешить использование версии print () из-за перегрузки метода. Однако ошибка компиляции является несколько неожиданной, поскольку один из методов является закрытым для пакета и поэтому не виден для MyApp.

Итак, это ошибка Java или какая-то странность JLS?

Компилятор: Sun javac 1.6.0_14

Ответы [ 3 ]

1 голос
/ 05 декабря 2009

В JLS или javac нет ничего плохого. Конечно, это не компилируется, потому что ваш класс MessageHolder ссылается на MessagePrinterInternal, которого нет в пути к классам компиляции, если я правильно понимаю ваше объяснение. Вы должны разбить эту ссылку на реализацию, например, с помощью интерфейса в вашем API.

РЕДАКТИРОВАТЬ 1: Для пояснения: Это не имеет ничего общего с методом видимого пакета, как вы, кажется, думаете. Проблема в том, что для компиляции необходим тип MessagePrinterInternal, но его нет в пути к классам. Вы не можете ожидать, что javac скомпилирует исходный код, когда у него нет доступа к ссылочным классам.

РЕДАКТИРОВАТЬ 2: Я перечитал код снова, и вот что, похоже, происходит: когда MyApp компилируется, он пытается загрузить класс MessageHolder. Класс MessageHolder ссылается на MessagePrinterInternal, поэтому он также пытается загрузить его и завершается неудачно. Я не уверен, что указано в JLS, это также может зависеть от JVM. По моему опыту работы с Sun JVM вам нужно иметь по крайней мере все статически ссылочные классы, доступные при загрузке класса; это включает типы полей, что-либо в сигнатурах метода, расширенные классы и реализованные интерфейсы. Вы можете утверждать, что это нелогично, но я бы ответил, что в целом очень мало что вы делаете с классом, в котором отсутствует такая информация: вы не можете создавать экземпляры объектов, вы не можете использовать метаданные (объект Class) и т. Д. эти базовые знания, я бы сказал, ожидаемое поведение.

0 голосов
/ 05 декабря 2009

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

Я не знаю точной спецификации по этому вопросу, и я сомневаюсь, что авторы javac допустили здесь ошибку. но кого это волнует? почему бы не поместить все зависимости в путь к классам, даже если некоторые из них являются поверхностными? это постоянно делает нашей жизнью намного проще.

0 голосов
/ 05 декабря 2009

Прежде всего, я ожидал бы, что вещи в пакете api будут интерфейсами, а не классами (в зависимости от имени). Как только вы это сделаете, проблема исчезнет, ​​поскольку у вас не будет доступа к пакетам в интерфейсах.

Следующее, что, AFAIK, это странность Java (в том смысле, что она не делает то, что вы хотели бы). Если вы избавитесь от публичного метода и сделаете пакет приватным, вы получите то же самое.

Изменение всего в пакете api на интерфейсы решит вашу проблему и обеспечит более четкое разделение кода.

...