2009-12-05 4 views
1

J'ai rencontré une bizarrerie de la JLS, ou un bug JavaC (pas sûr de quoi). Veuillez lire ce qui suit et fournir une explication, en citant le passage JLS ou l'identifiant Sun Bug, selon le cas.La méthode package-private surchargée provoque l'échec de la compilation - S'agit-il d'un bug JD ou JAVAC?

Supposons que j'ai un projet artificiel avec le code en trois "modules" -

  1. API - définit l'API cadre - pensez Servlet API
  2. Impl - définit la mise en œuvre de l'API - pensez Tomcat Servlet
  3. App - l'application, j'ai écrit

Voici les classes dans chaque module:

API - MessagePrinter.java

package api; 

public class MessagePrinter { 

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

API - MessageHolder.java (oui, il fait référence à une classe "impl" - voir plus loin)

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 - Cette classe dépend d'une classe API. Comme son nom l'indique, il est destiné à une utilisation «interne» ailleurs dans mon petit cadre.

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); 
    } 
} 

Enfin, la classe unique dans le module 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()); 
    } 

} 

Donc, maintenant je tente de compiler ma petite application, MyApp.java. Supposons que mes fichiers jar API soient exportés via un fichier jar, disons api.jar, et que je sois un bon citoyen, je ne fais référence à ce jar que dans mon classpath - pas à la classe Impl embarquée dans impl.jar.

Maintenant, il y a une faille dans la conception de mon framework, car les classes API ne doivent pas dépendre des classes d'implémentation "internes". Cependant, ce qui est arrivé comme une surprise est que MyApp.java n'a pas compilé du tout.

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 

Le problème est que le compilateur essaie de résoudre la version imprimée() à utiliser, en raison de la surcharge de méthode. Cependant, l'erreur de compilation est quelque peu inattendue, car l'une des méthodes est package-private et n'est donc pas visible par MyApp.

Alors, est-ce un bug javac, ou une bizarrerie de la JLS?

compilateur: Sun javac 1.6.0_14

+1

"en raison d'une surcharge de l'opérateur" à "dû à une surcharge de la méthode" – TofuBeer

+0

fixe - merci. – noahlz

Répondre

1

Il n'y a rien de mal avec JLS ou javac. Bien sûr, cela ne compile pas, parce que votre classe MessageHolder référence MessagePrinterInternal qui n'est pas sur le chemin de classe de compilation si je comprends bien votre explication. Vous devez casser cette référence dans l'implémentation, par exemple avec une interface dans votre API.

EDIT 1: À titre de clarification: Cela n'a rien à voir avec la méthode package-visible, comme vous le pensez. Le problème est que le type MessagePrinterInternal est nécessaire pour la compilation, mais vous ne l'avez pas sur le classpath. Vous ne pouvez pas attendre que javac compile le code source lorsqu'il n'a pas accès aux classes référencées.

EDIT 2: Je relis le code à nouveau et c'est ce qui semble se produire: Lorsque MyApp est compilé, il essaie de charger la classe MessageHolder. Class MessageHolder fait référence à MessagePrinterInternal, donc il essaie de charger cela aussi et échoue. Je ne suis pas sûr que cela soit spécifié dans le JLS, cela peut aussi dépendre de la JVM. Dans mon expérience avec la JVM Sun, vous devez avoir au moins toutes les classes référencées statiquement disponibles lorsqu'une classe est chargée; Cela inclut les types de champs, n'importe quoi dans les signatures de méthodes, les classements étendus et les interfaces implémentées. Vous pourriez argumenter que c'est contre-intuitif, mais je répondrais qu'en général il y a très peu de choses que vous faites avec une classe où de telles informations sont manquantes: vous ne pouvez pas instancier des objets, vous ne pouvez pas utiliser les métadonnées. cette connaissance de base, je dirais que le comportement que vous voyez est attendu.

+0

Oui, mais la méthode utilise MessagePrinterInternal est paquet-privé. La classe MyApp.java ne pourra jamais y accéder. Donc c'est contre-intuitif. – noahlz

+0

Cela peut être contre-intuitif pour vous, mais je pense qu'il a raison. Essayez-le et voyez. –

+0

Sa réponse réitère simplement ma question. Bien sûr, la source doit être modifiée pour pouvoir être compilée. Ma question: où dans le JLS définit-il cet échec de compilation comme comportement attendu? – noahlz

0

Tout d'abord, je pense les choses dans le paquet api pour être des interfaces plutôt que des classes (en fonction du nom). Une fois que vous faites cela, le problème disparaîtra puisque vous ne pouvez pas avoir accès au paquet dans les interfaces. La chose suivante est que, AFAIK, c'est une bizarrerie Java (en ce sens qu'elle ne fait pas ce que vous voulez). Si vous vous débarrassez de la méthode publique et faites le paquet en privé, vous obtiendrez la même chose. Changer tous les éléments du package api pour qu'ils deviennent des interfaces va résoudre votre problème et vous donner une séparation plus nette dans votre code.

+0

Même si vous avez modifié ce code pour utiliser des interfaces, vous pouvez reproduire le même comportement en déclarant l'interface package-private. Par exemple, MessagePrinterInternal pourrait être une interface dans le package API. En fait, le code de production I basé sur cette question fait exactement cela. – noahlz

+0

Si l'interface est package seulement rien en dehors du paquet peut le voir. Si vous ne codez que sur l'interface (déclarez toutes les variables et tous les paramètres comme interface), il ne devrait pas y avoir de problème. Même si un paquet appelé API contient des éléments autres que des interfaces, des énumérations, des fabriques et des exceptions, cela n'a pas de sens pour moi. – TofuBeer

+0

Désolé, mais d'une autre chose de base dans ce que vous avez dit - une API ne devrait pas avoir de paquet privé thi fs, car il est censé être une chose consommée publiquement. – TofuBeer

0

Je suppose que vous pouvez toujours faire valoir que javac peut être un peu plus intelligent, mais il doit s'arrêter quelque part. ce n'est pas humain, l'humain peut toujours être plus malin qu'un compilateur, vous pouvez toujours trouver des exemples qui ont du sens pour un humain mais qui ont du mal à trouver un compilateur.

Je ne connais pas les spécifications exactes à ce sujet, et je doute que les auteurs de javac aient fait une erreur ici. mais qui s'en soucie? pourquoi ne pas mettre toutes les dépendances dans le classpath, même si certaines d'entre elles sont superficielles? Faire cela uniformément rend nos vies beaucoup plus facile.

+0

Certaines classes ne sont pas destinées à être dans un chemin de classe de compilation, car elles ne sont destinées qu'à être utilisées au moment de l'exécution. Par exemple, les classes Sun sont livrées avec Sun Java. – noahlz

+0

par exemple? La plupart des classes de soleil sont dans rt.jar, qui est dans le classpath lorsque vous compilez. – irreputable

+0

Il est communément accepté comme une mauvaise pratique d'utiliser les classes com.sun directement: http://java.sun.com/products/jdk/faq/faq-sun-packages.html – noahlz

Questions connexes