# Generics + Vererbung



## White_Fox (14. Jul 2019)

Einen schönen Sonntag allerseits.

Folgendes Problem: Ich habe hier eine Klasse, die mit generischen Datentypen arbeitet:


```
public abstract class TopLevelClass<T>{
    private T t;
    public void setT(T t){This.t = t;}
    public T getT(){return this.t}
}
```

Diese Klasse erweitere ich jetzt:

```
public class TopLevelWithName extends TopLevelClass{
    private String name;
    public void setName(String s){this.name = s;}
    public String getName(){return name;}
}
```

Soweit ich das sehe, kompilieren beide Klassen. Was ich mich jetzt allerdings frage: Woher weiß die Superklasse von TopLevelWithName jetzt, was sich hinter T verbirgt?

```
void method(){
    TopLevelClass<Integer> top;                     //Compiler erzwingt die Angabe eines Typs zwischen den <>
    TopLevelClassWithName topWithName;   //...und was ist hier?
}
```

Was ich am Ende haben will ist eine abstrakte Klasse. Abstrakt deshalb, weil der Nutzer diese selber für seinen Anwendungsfall erweitern soll. Klasse anstatt Interface deswegen, weil ich einem Interface kein Verhalten beibringen kann (dies erfordert Instanzvariablen).
Jetzt will ich erzwingen, daß der Nutzer der abstrakten Klasse aber einen Datentypen festlegen und diesen meiner abstrakten Klasse mitteilen muß.
Ich habe schon an einen Parameter `public ClassConstructor(Class<Type> T)` gedacht, aber gibt es da vielleicht etwas direkt auf Compilerebene, sodaß der Nutzer den Diamondoperator ebenfalls nutzen kann ohne ein Class-Objekt durch alle Konstruktoraufrufe durchzuschleifen?


----------



## httpdigest (14. Jul 2019)

Deine TopLevelWithName Klasse erweitert zwar TopLevelClass, aber gibt kein Typargument für dessen Typparameter <T>. Deine TopLevelWithName erweitert die Raw-Ausprägung der TopLevelClass. Das heißt, T=Object.
Du führst dann noch ein weiteres Feld `name` und entsprechende Setter und Getter ein. Das heißt, deine TopLevelWithName Klasse besitzt dann zwei Felder (`name` und `t` - wobei `t` nur sichtbar in der Oberklasse ist) und besitzt vier Methoden (die Getter/Setter `getName`/`setName` sowie `getT`/`setT`). Das willst du vermutlich nicht.
Du wolltest vielleicht folgendes schreiben:

```
public class TopLevelWithName extends TopLevelClass<String> {
}
```


----------



## White_Fox (14. Jul 2019)

httpdigest hat gesagt.:


> Deine TopLevelWithName Klasse erweitert zwar TopLevelClass, aber gibt kein Typargument für dessen Typparameter <T>.


Kann ich diese Weitergabe irgendwie forcieren? Das Ganze funktioniert, wenn die erweiternde Klasse mit `public class TopLevelWithName<T> extends TopLevel<T>{}` deklariert wird. Aber diese Deklaration liegt im Ermessen desjenigen, der von der Klasse ableitet-und diesen Ermessensspielraum würde ich gerne eingrenzen.



httpdigest hat gesagt.:


> Du führst dann noch ein weiteres Feld `name` und entsprechende Setter und Getter ein. Das heißt, deine TopLevelWithName Klasse besitzt dann zwei Felder (`name` und `t` - wobei `t` nur sichtbar in der Oberklasse ist) und besitzt vier Methoden (die Getter/Setter `getName`/`setName` sowie `getT`/`setT`). Das willst du vermutlich nicht.


Ja, dafür ist das Beispiel etwas ungünstig, grundsätzlich will ich aber schon daß die Subklasse von t nix mehr mitbekommt.


----------



## mihe7 (14. Jul 2019)

White_Fox hat gesagt.:


> Aber diese Deklaration liegt im Ermessen desjenigen, der von der Klasse ableitet-und diesen Ermessensspielraum würde ich gerne eingrenzen.


Hier ist das ein wenig erklärt: https://docs.oracle.com/javase/tutorial/java/generics/bounded.html

Wenn Du die NaturalNumber-Klasse nimmst, muss die Subklasse einen von Integer abgeleiteten Typen angeben.


----------



## ocsme (14. Jul 2019)

Ich glaube was @mihe7 meint ist das hier:


			Besondere Typen der JavaÂ SE
		


Nun bringt mich das Thema hier aber auch zu einer Frage.
Wenn man eine Generische Klasse hat die zu einem Generischen Typ wird bei Konkretisierung des Typs, wenn ich diese ableite und nehme bei der Ableitung den RAW Typ macht das Sinn?
Ich kann dabei kein Anwendungsbeispiel finden und stehe deswegen auch mega auf dem Schlauch dachte ich hätte das so halb wegs verstanden und jetzt werfe ich wieder alles durcheinander


----------



## White_Fox (14. Jul 2019)

Ich bin mir nicht so sicher welchen Link mir mihe7 wirklich geben wollte. Aber in seinem Link hab ich auch die Multiple Bounds gefunden-das ist mir auch sehr nützlich.

Was ich halt einfach gerne hätte ist, daß 2. einfach gleich rot markiert wird und der Code gar nicht erst zu schreiben ist. Das sieht augenscheinlich sonst erstmal so aus, als würde das gehen. Oder ist das normal, daß der Compiler das nicht gleich prüft?

```
public abstract class TopLevelClass<T>{
    private T t;
    public void setT(T t){This.t = t;}
    public T getT(){return this.t}
}

// 1. Hier ist alles gut.
public class PoorlyTopLevelWithName<T> extends TopLevelClass<T>{
    private String name;
    public void setName(String s){this.name = s;}
    public String getName(){return name;}
}

// 2. Das soll der Compiler mir bitte um die Ohren hauen während ich das so schreibe.
public class BetterTopLevelWithName extends TopLevelClass{
    private String name;
    public void setName(String s){this.name = s;}
    public String getName(){return name;}
}
```


----------



## httpdigest (14. Jul 2019)

Mal dumme Frage: Warum genau stört es dich, wenn ein Client deinen (Library-)Code raw verwendet und kein Typargument angibt? Prüfst du zur Laufzeit den reified Type per Reflection getGenericSuperclass() oder sowas und holst dir das Typargument zur Laufzeit? Was ich nur meine: Was für einen Unterschied macht es denn für dich?


----------



## mihe7 (14. Jul 2019)

White_Fox hat gesagt.:


> Das sieht augenscheinlich sonst erstmal so aus, als würde das gehen. Oder ist das normal, daß der Compiler das nicht gleich prüft?


Ja, aus Gründen der Rückwärtskompatibilität.


----------



## White_Fox (15. Jul 2019)

httpdigest hat gesagt.:


> Was ich nur meine: Was für einen Unterschied macht es denn für dich?


Ich schreibe mir gerade eine kleine Bibliothek (siehe Nachbarthread in Softwareentwicklung), und zu deren Verwendung gehört, daß man eine Klasse für seine Zwecke ableiten muß. Ich befürchte da Probleme zur Laufzeit, wenn man da einfach wild mit Object arbeitet-außer, man will es.

Außerdem ist es auch eine Komfortfrage. Der Benutzer soll Objekte einlegen und wieder entnehmen können, ähnlich wie z.B. bei einer ArrayList oder anderen Collections. Ansonsten wird das ein wildes Herumgecaste...das ist Mist.




mihe7 hat gesagt.:


> Ja, aus Gründen der Rückwärtskompatibilität.


Also bleibt nichts außer gründlicher Dokumentation...richtig?


----------



## mihe7 (15. Jul 2019)

White_Fox hat gesagt.:


> Also bleibt nichts außer gründlicher Dokumentation...richtig?


Was willst Du da dokumentieren? Der Supertyp erwartet Typparameter -> das steht ja schon dabei. 

Wenn Du das hart durchsetzen willst,  sollte das mittels Annotation Processing funktionieren.


----------



## White_Fox (15. Jul 2019)

Och mihe7...ich hab doch gerade mal die Generics so halbwegs verstanden (irgendwo stand daß das schon zu den komplizierteren Dingen in Java gehört), jetzt haust du mir schon wieder so einen Brocken um die Ohren. 

Ich hab mir mal das Tutorial zu Annotations von Oracle angesehn, aber ich hab keine Idee wie ich mir das zunutze machen kann. Hast du einen Tipp?


----------



## mihe7 (16. Jul 2019)

Jetzt bin ich nicht umhingekommen, mich an einem Annotation Processor zu versuchen - man lernt ja nie aus: https://github.com/mihe7/jfqa/tree/master/gst - das Teil ist sehr rudimentär, bei Fragen fragen.


----------



## White_Fox (16. Jul 2019)

Mensch mihe7...dankeschön. 

Eine Frage noch:
Wie funktiniert das Ding? Ich will ja auch dazulernen.  Der Code ist ja von der Menge her recht übersichtlich, und wie eine eigene Annotation geschrieben wird ist von Oracle gut erklärt. Aber die meisten, die etwas über AnnotationProcessing auf StackOverflow gefragt haben, scheinen da nicht bei null angefangen zu haben.

Ansonsten:
Fall jemand allgemein ein gutes Tutorial sucht, DuckDuckGo hat dann doch noch eins für mich gefunden (sogar auf deutsch):


			https://cloudogu.com/downloads/publications/2018-02-JavaPro--Java_Annotation_Prozessoren.pdf
		


Damit hab ich zumindest einen Teil von mihe7s Prozessor verstanden.


----------



## mihe7 (16. Jul 2019)

Das ist auch für mich alles Neuland. Der Annotation Processor ist eine Implementierung des Interfaces Processor, hier in Form einer Implementierung der abstrakten Basisklasse AbstractProcessor. Die Verarbeitung von Annotationen lässt sich über verschiedene Wege starten, z. B. über das Tool apt (im JDK) oder auch direkt über den Compiler (wie hier). Dazu wird die Implementierung über den Service Loader Mechanismus registriert (im Jar unter META-INF/services eine Datei mit dem FQN der zu implementierenden Schnittstelle anlegen, die als Inhalt den FQN der Implementierung enthält). Im Projekt findest Du die Datei unter src/extras/services (und nicht wie unter Maven üblich unter src/main/resources/META-INF/services) , weil es unter Maven sonst zu einem Henne-Ei-Problem kommt. Daher auch der Spaß via github.

Dadurch, dass die Jar im Classpath liegt, werden vom Java Compiler automatisch Annotationen unter Verwendung von GenericClassProcessor (GCP) verarbeitet.

Während der Verarbeitung arbeitet man mit einem anderen Modell, als das, was man sonst von java.lang/java.lang.reflect her kennt, weil die Klassen zum Zeitpunkt der Verarbeitung noch nicht geladen sind. 

Im Wesentlichen bekommt die process-Methode einen Satz von Annotationen, der Processor kann nun fragen, welche Typen diese Annotationen implementieren (ich hoffe mal, dass nur Annotationen von den mit `@SupportedAnnotationTypes` genannten Typen übergeben werden - sonst ist der Code falsch). 


```
Set<? extends Element> annotated = annotations.stream()
            .flatMap(a -> roundEnv.getElementsAnnotatedWith(a).stream())
            .collect(Collectors.toSet());
```

Das wären in diesem Fall (bzw. sollten sein) die mit @GenericClass annotierten Klassen. Im zweiten Schritt hole ich mir die "root elements", das sind - soweit ich das verstanden habe - alle in den vom Processor zu verarbeitenden Klassen/Typen/etc., die kompiliert werden sollen. Jedes Element wird überprüft, ob es eine direkte Subklasse einer annotierten Klasse (annotated) ist und falls ja, ob Typ-Argumente angegeben wurden. Ist die Liste der Typ-Argumente leer, wird ein Fehler ausgegeben.


```
roundEnv.getRootElements().stream()
            .filter(TypeElement.class::isInstance)
            .map(TypeElement.class::cast)
            .filter(e -> e.getTypeParameters().isEmpty())
            .filter(e -> annotated.contains(((DeclaredType) e.getSuperclass()).asElement()))
            .filter(e -> ((DeclaredType) e.getSuperclass()).getTypeArguments().isEmpty())
            .forEach(e -> {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                   String.format("%s tries to implement %s without specifying type parameters",
                       e.getSimpleName().toString(),
                       ((DeclaredType) e.getSuperclass()).asElement().toString()),
                   e);
            });
```


----------



## White_Fox (16. Jul 2019)

So...da wäre ich nochmal: Ich Netbeans leider nicht dazu überredet bekommen, das, was ich von Github runtergeladen habe, als Annotationsprozessor zu akzeptieren. Ich bin die entsprechende Anleitung schrittweise durchgegangen, aber Netbeans meckerte ständig.
Ich bin dann das Tutorial nochmal durchgegangen (inkl. Prozessor- und Interfacedeklaration, das ansich ist ja noch easypeasy) und hab selbst einen Prozessor nebst Interface deklariert.

Das ist zwar nix worauf ich stolz sein kann, aber immerhin frißt Netbeans den Prozessor und die Annotaion jetzt so, daß ich diese in meiner Klasse verwenden kann. Außerdem hab ich diese jetzt in einem Subpackage direkt am Ort der Verwendung. Ich überlege ob ich das als eigenständige Bibliothek auslagere, da finde ich das schon praktisch wenn der AP direkt drin sitzt und ich nicht nochmal nach externen Quellen verlinken muß. Und naja...da kann ich den Rest noch selber schreiben.

Jetzt will ich aber auch deinen Code nicht schamlos kopieren, wenigstens genau verstehen was der da macht. Code einbauen den man nichtmal nachvollziehen kann...sowas kann ich ja gar nicht leiden.
Allerdings komme ich mit den funktionalen Ausdrücken nicht klar, ich hab keine Ahnung was das heißen soll. Könntest du das vielleicht bitte noch näher erläutern?

Wo hast du eigentlich die Informationen her, was da wie in RoundEnviroment übergeben wird? Gibt es da von Oracle irgendeine Übersicht oder so?


----------



## mihe7 (17. Jul 2019)

White_Fox hat gesagt.:


> Wo hast du eigentlich die Informationen her, was da wie in RoundEnviroment übergeben wird?


Das habe ich mir mühselig zusammengesucht und getestet.



White_Fox hat gesagt.:


> Allerdings komme ich mit den funktionalen Ausdrücken nicht klar, ich hab keine Ahnung was das heißen soll. Könntest du das vielleicht bitte noch näher erläutern?


Logisch:

```
Set<? extends Element> annotated = annotations.stream()
            .flatMap(a -> roundEnv.getElementsAnnotatedWith(a).stream())
            .collect(Collectors.toSet());
```
ist etwa wie

```
Set<? extends Element> annotated = new HashSet<>();
for (TypeElement annotation : annotations) {
    for (Element elem : annotation.getElementsAnnotatedWith(annotation)) {
        annotated.add(elem);
    }
}
```
und

```
roundEnv.getRootElements().stream()
            .filter(TypeElement.class::isInstance)
            .map(TypeElement.class::cast)
            .filter(e -> e.getTypeParameters().isEmpty())
            .filter(e -> annotated.contains(((DeclaredType) e.getSuperclass()).asElement()))
            .filter(e -> ((DeclaredType) e.getSuperclass()).getTypeArguments().isEmpty())
            .forEach(e -> {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                   String.format("%s tries to implement %s without specifying type parameters",
                       e.getSimpleName().toString(),
                       ((DeclaredType) e.getSuperclass()).asElement().toString()),
                   e);
            });
```
ist in etwa (hoffe, ich habe mich nirgends mit den Klammern vertan):

```
for (Element elem : roundEnv.getRootElements()) {                                   // stream()
    if (elem instanceof TypeElement) {                                              // filter
        TypeElement e = (TypElement) elem;                                          // map
        if (e.getTypeParameters().isEmpty() &&                                      // filter
                annotated.contains((DecaredType) e.getSuperclass()).asElement()) && // filter
                ((DeclaredType) e.getSuperclass()).getTypeArguments().isEmpty()) {  // filter
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,         // forEach
                   String.format("%s tries to implement %s without specifying type parameters",
                       e.getSimpleName().toString(),
                       ((DeclaredType) e.getSuperclass()).asElement().toString()),
                   e);
        }
    }
}
```

Willst Du eine Erklärung zu den Streams im Allgemeinen?


----------



## Xyz1 (17. Jul 2019)

White_Fox hat gesagt.:


> TopLevelClass<Integer> top; //Compiler erzwingt die Angabe eines Typs zwischen den <> TopLevelClassWithName topWithName; //...und was ist hier?


Du kannst in beiden Fällen raw types verwenden. Nachzulesen hier:


			Chapter 4. Types, Values, and Variables
		


Wenn die abgeleitete Klasse nicht parametrisiert wurde, ist es die übergeordnete auch nicht.
Wenn die übergeordnete Klasse nicht parametrisiert wurde, darf die abgeleitete Klasse auch nicht parametrisiert werden.

Man hat sich für raw types entschieden um die Abwärtskompatibilität zu gewährleisten.


----------



## Xyz1 (17. Jul 2019)

Tobias-nrw hat gesagt.:


> Wenn die übergeordnete Klasse nicht parametrisiert wurde, darf die abgeleitete Klasse auch nicht parametrisiert werden.


 was schreib ich...

Gegeben sei:

```
class A<T> {
	
}

class B<T> extends A<T> {
	
}
```


```
public static void main(String[] args) {
		A a = new A();
		A b = new B();
		A<String> c = new A();
		A<String> d = new B();
		A e = new A<String>();
		A f = new B<String>();
		A<String> g = new A<String>();
		A<String> h = new B<String>();
		
		//B i = new A();
		B j = new B();
		//B<String> k = new A();
		B<String> l = new B();
		//B m = new A<String>();
		B n = new B<String>();
		//B<String> o = new A<String>();
		B<String> p = new B<String>();
		
		//A<String> q = new A<Integer>();
	}
```

ist erlaubt


----------



## White_Fox (17. Jul 2019)

mihe7 hat gesagt.:


> Das habe ich mir mühselig zusammengesucht und getestet.


Nachts um drei? Und da produzierst du noch kompilierfähigen Code? Eiderneieiei...



mihe7 hat gesagt.:


> Willst Du eine Erklärung zu den Streams im Allgemeinen?


Danke, nein, ich denke mit Streams kenne ich mich halbweg aus. Auch wenn ich selber an dieser Stelle nie auf die Idee gekommen wäre einen Stream zu verwenden und tatsächlich eher geschachtelte for-Schleifen (vermutlich in mehreren Methoden ausgelagert) geschrieben hätte. 

Vielen Dank für die Erklärung...das macht es sehr verständlich. Es ist noch nichtmal Mittag, und ich hab wieder etwas gelernt-der Tag war nicht umsonst. 
Ich sehs schon kommen, mihe7 macht aus mir nochmal einen Softwareentwickler...

@Tobias-nrw 
Ja, ich denke das mit der Abwärtskompatibilität hab ich halbwegs verstanden, allerdings will ich diese hier keineswegs haben, da dann Probleme zu erwarten sind.


----------



## Xyz1 (17. Jul 2019)

Das stimmt wohl, raw types sollte man vermeiden dennoch gibt es die Möglichkeit  ... Aber zwingt dich ja keiner nicht parametrisierte Typen zu verwenden...


----------



## mihe7 (17. Jul 2019)

White_Fox hat gesagt.:


> Ich Netbeans leider nicht dazu überredet bekommen, das, was ich von Github runtergeladen habe, als Annotationsprozessor zu akzeptieren.


Das würde mich interessieren: wo gabs da ein Problem? Es sollte reichen, wenn Du einfach die gebaute Jar in den Classpath aufnimmst.


----------



## White_Fox (17. Jul 2019)

@mihe7
Ich weiß nicht, wo das Problem lag. Jedenfalls hat er die Annotation nicht nicht gefunden und diese stets rot markiert, und beim Codehacken hat er diese auch nicht vorgeschlagen.

Ich denke aber nicht, daß es an deinem Code lag. Wahrscheinlicher ist, daß ich doch noch irgendwo etwas übersehen habe.

@Tobias-nrw 
Das stimmt schon, MICH zwingt niemand dazu.  Aber ich will den Nutzer (also vornehmlich mich selbst, also werd ich ja doch wieder gezwungen  ) zwingen, die Generics mitzuschleifen.
Wenn ich irgendwann mal den Code wiederverwenden will und absolut keine Ahnung mehr hab auf was ich da achten muß, will ich nicht in folgendes Problem laufen:


```
public class ModifiedArrayList extends ArrayList{  //Anstatt: ModifiedArrayList<T> extends ArrayList<T>
    //...
}

public static main(String Args[]){
    ModifiedArrayList list = new ModifiedArrayList();
    SpecialClass complexObject = new SpecialClass();
    
    list.add(complexObject); //complexObject ist ja vom Typ Object
    
    //...

    list.get(i).rufeSpecialClassMethodeAuf(); //...und der Compiler so: Nöööö!
    //Alternativ: Wildes Herumgecaste
}
```

Sicher, ich kann mich auch hinstellen und sagen, nur ich verwende daß, ich weiß ja was ich gemacht habe. Dann bleibt es aber immer noch potentiell fehleranfälliges Design, und damit in meinen Augen schlechtes Design. Und auf schlechtes Design hab ich keine Lust, die Welt ist schlecht genug, man muß sie durch schlechte Software nicht noch schlechter machen.

Und selbst wenn ich für alle Ewigkeit weiß was ich da gemacht habe (Illusion): Vielleicht gebe ich mein Werk ja mal weiter, da hab ich dann keine Lust viel erklären zu müssen.


----------



## White_Fox (17. Jul 2019)

Mal eine Frage zur Syntax:

```
if(e instanceof TypeElement){
    e = (TypeElement) e;
    if(e.getTypeElements().isEmpty()){

    }
}
```

Wieso macht er mir die getTypeElement()-Methode rot und sagt "Can not find symbol: symbol: getTypeElement Location: e variable of TypeElement"? Der Import `import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;` ist drin.
Ich glaube ich verstehe aber so langsam, was die Methode genau macht. Respekt daß du das in so kurzer Zeit herausgefunden hast, mihe7.


----------



## kneitzel (17. Jul 2019)

White_Fox hat gesagt.:


> Mal eine Frage zur Syntax:
> 
> ```
> if(e instanceof TypeElement){
> ...



Du veränderst doch nicht den Typ einer Variable. Der Konstrukt e = (TypeElement) e; verändert also nichts.

Du solltest statt dessen Code wie den folgenden schreiben:

```
if(e instanceof TypeElement){
    TypeElement te = (TypeElement) e;
    if(te.getTypeElements().isEmpty()){

    }
}
```
Sprich: Du deklarierst eine neue Variable mit dem korrekten Typ und nutzt diese dann.


----------



## mihe7 (17. Jul 2019)

White_Fox hat gesagt.:


> Wenn ich irgendwann mal den Code wiederverwenden will und absolut keine Ahnung mehr hab auf was ich da achten muß, will ich nicht in folgendes Problem laufen:


Die Frage wäre, ob das wirklich ein Problem darstellt. Wenn Du eine Klasse ohne Typparameter implementieren würdest, dann würdest Du automatisch mit Object arbeiten müssen. Spätestens dann ist aber klar, dass das entweder unabsichtlich passierte oder aber gewollt war. 

Anders formuliert: dieses Problem hat sich irgendwie noch keinem gestellt


----------



## White_Fox (17. Jul 2019)

mihe7 hat gesagt.:


> Anders formuliert: dieses Problem hat sich irgendwie noch keinem gestellt


Nun, das wiederum mag ich nicht beurteilen. Ich fand das aber schon unschön, als ich den Unittest für die Klasse, für die ich das benutzen will, geschrieben habe.

Aber mal was anderes:
Ich denk ich hab den Code soweit adaptiert. Das kam dabei raus:

```
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes("ForceUsingDeclaredGenerics")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ForceUsingDeclaredGenericsProcessor extends AbstractProcessor{
    
    private HashSet<Element> getAnnotatedElementsInEnviroment(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
        HashSet<Element> elements = new HashSet<>();
        for(TypeElement annotation : annotations){
            for(Element e : roundEnv.getElementsAnnotatedWith(annotation)){
                elements.add(e);
            }
        }
        return elements;
    }
    
    private boolean declarationTypeIsMissing(TypeElement e, HashSet<Element> elements){
        return e.getTypeParameters().isEmpty() &&
                elements.contains(((DeclaredType) e.getSuperclass()).asElement()) &&
                ((DeclaredType) e.getSuperclass()).getTypeArguments().isEmpty();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        HashSet<Element> annotatedElements = getAnnotatedElementsInEnviroment(annotations, roundEnv);
        String message;
        TypeElement te;
        
        for(Element e : roundEnv.getRootElements()){
            if(e instanceof TypeElement && declarationTypeIsMissing((TypeElement) e, annotatedElements)){
                te = (TypeElement) e;
                message = String.format("%s extends %s without defining generic type definition.",
                        te.getSimpleName().toString(),
                        ((DeclaredType) te.getSuperclass()).asElement().toString());
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
            }
        }
        return true;
    }
}


@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface ForceUsingDeclaredGenerics {}
```

Wenn ich den Unittest für die betreffende Klasse schreibe, kann ich die Generics immer noch übergehen ohne einen Fehler zu erhalten. Ich hab mal ein System.out.println-Anweisung in den Prozessor eingebaut-die kam auf der Ausgabe auch nicht.
Ich habe testweise auch den Code original von mihe7 drin,

Die Klasse(n), wo die Annotation verwendet werden soll, ist wie folgt deklariert:

```
@ForceUsingDeclaredGenerics
abstract class TableLine<IT extends Itemable> { //Itemable ist ein Interface, daß Items implementieren müssen
    
    private ArrayList<IT> items;

    public TableLine() {
        this.items = new ArrayList<>();
    }
    
    void addElement(IT item){
        items.add(item);
    }
    
    public int getLenght(){
        return items.size();
    }

    public IT getItem(int i){
        return (i >= items.size() || i < 0) ? null : items.get(i);
    }
}


@ForceUsingDeclaredGenerics
public abstract class Row<RT extends RowHeader, IT extends Itemable> extends TableLine<IT>{
    RT rowheader;
    
    public Row(){
        this.rowheader = null;
    }

    public Row(RT rowheader){
        this.rowheader = rowheader;
    }
    
    public RT getRowheader(){
        return null;
    }
```

Der Test der Klasse Row:

```
public class RowTest {
    
    public RowTest() {
    }
    
    class ExtendedRow extends Row{      //Hier erwarte ich Ärger und kriege keinen
        
        public ExtendedRow(RowHeader rowheader) {
            super(rowheader);
        }
    }
    
    class RowItem implements Itemable{

        @Override
        public <T extends ItemModel> T getItemModel(Class<T> m) {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }

        @Override
        public void takeTableinformation(RowHeader row, ColumnHeader col) {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }
    }
    
    class ExtendedRowheader extends RowHeader{
        
    }
    
    /**
     * Test of getRowheader method, of class Row.
     */
    @Test
    public void testGetRowheader() {
        Row instance;
        
        System.out.println("getRowheader");
        
        instance = new ExtendedRow(new ExtendedRowheader());
        
        // TODO review the generated test code and remove the default call to fail.
        fail("The test case is a prototype.");
    }
}
```

Hast du noch eine Idee, mihe7? Wenn ich mir die Javadocs so ansehe (insbesondere zu den Annotationen über dem Annotations-@Interface) finde ich kein Problem. Hast du das mal ausprobiert?


----------



## mihe7 (17. Jul 2019)

Hast Du den Annotation Processor registriert (via META-INF/services/...)?


----------



## White_Fox (17. Jul 2019)

Ja, hab ich. Wobei...ich hab den ja als Quellcode (noch) in meinem Projekt drin. Kann es sein, daß der Compiler den zu spät kompiliert? Ich meine, ich hab da irgendwas in der Richtung irgendwo gelesen. Irgendeine Compileroption, mit der man den Build verzögern kann oder so. Ich komm aber nicht mehr drauf, wo das war.


----------



## White_Fox (17. Jul 2019)

Ich hab den Prozessor mal in eine eigene Klassenbibliothek verschoben, die Bibliothek erstellt, in mein Projekt eingebunden und den Prozessor annotiert.

Irgendwas haut da noch nicht so richtig hin, jetzt findet er die Deklaration nicht mehr. Merkwürdig.


----------



## mihe7 (17. Jul 2019)

Kann es sein, dass die Basisklasse oder die abgeleitete Klasse keien TopLevel-Klassen sind? Mit inneren haut das aktuell nicht hin (habe ich eben festgestellt, da wird man vermutlich noch was dransetzen müssen, damit auch die Internas der Klassen überprüft werden).

Probier mal folgendes:

```
import gst.GenericClass;

@GenericClass
class A<T> {}

public class Test extends A {
}
```

Das sollte einen Fehler werfen. 

Das hier:

```
public class Test {
    @GenericClass
    class A<T> {}
    class B extends A {}
}
```
läuft dagegen durch.


----------



## mihe7 (17. Jul 2019)

mihe7 hat gesagt.:


> Kann es sein, dass die Basisklasse oder die abgeleitete Klasse keien TopLevel-Klassen sind?


Sorry, Du hast ja den Code gepostet: ja, ist so


----------



## White_Fox (18. Jul 2019)

Hm...ja, das erklärt es wohl. 

Naja, erstmal sehen wie ich Netbeans überredet bekomme, die Bibliothek einzubinden. Manchmal ist es da zickig.


----------



## mihe7 (18. Jul 2019)

White_Fox hat gesagt.:


> Manchmal ist es da zickig.


Hatte damit noch nie ein Problem. Ist das ein Maven-Projekt oder ein Ant Projekt? Im Endeffekt aber auch egal: bei Maven fügst Du einfach die Dependency in das pom ein (geht auch via NB) und bei Ant nimmst Du das Jar einfach unter Project -> Libraries im Compile-Reiter auf.


----------



## White_Fox (18. Jul 2019)

Ich habs jetzt: In Netbeans kann man Bibliotheken auf zweierlei Arten einbinden: Einen Haufen Jars mit Klassenpfad und Javadocs als Bibliothek anmelden und diese Bibliothek dann einbinden. Oder: direkt das entsprechende Projekt einbinden.

Wenn ich die kompilierte Bibliothek (also Jars und Javadoc) einbinde, findet er die Annotation nicht. Wenn ich jedoch das Projekt selbst einbinde, dann geht es.

Hat jemand eine Idee, was Netbeans da im Hintergrund veranstaltet? Das Projekt ist ein Ant-Projekt.


----------



## mihe7 (18. Jul 2019)

Schau mal unter Build -> Compiling. Ist da der Haken gesetzt?


----------



## White_Fox (18. Jul 2019)

Welcher...da sind vier Haken:


----------



## mihe7 (18. Jul 2019)

Warte, ich probier das mal schnell in NB aus.


----------



## mihe7 (18. Jul 2019)

Habe jetzt auf einem anderen Rechner folgendes gemacht:
1. das jqfa-Projekt als Zip aus github geholt, entpackt.
2. NB 11 gestartet -> keine Projekte offen
3. Das entpackte Maven-Projekt gst geöffnen -> Clean & Build
4. Neues Ant-Projekt angelegt
5. Projekteigenschaften geöffnet, dort 
a) In den Libraries das unter 3. erzeugte gst-1.0-SNAPSHOT.jar zum Classpath hinzugefügt
b) Unter Build die Haken wie in Deinem Bild gesetzt
6. Die von NB unter 4. erzeugte Klasse bearbeitet.
Ergebnis:


----------



## White_Fox (18. Jul 2019)

mihe7 hat gesagt.:


> a) In den Libraries das unter 3. erzeugte gst-1.0-SNAPSHOT.jar zum Classpath hinzugefügt


Was ist da eigentlich der Unterschied zwischen Sources unc Classpath? (Den Fehler hab ich übrigens an dieser Stelle gemacht, jetzt gehts bei mir auch. Danke. )


----------



## mrBrown (18. Jul 2019)

Sollte es nicht auch reichen, die passenden  Compiler-Warnungen zu aktivieren und alle Warnungen als Fehler zu behandeln?


----------



## White_Fox (18. Jul 2019)

@mrBrown 
Ich werd den Code, für den ich das verwenden will, mal hier reinstellen wenn er fertig ist. Danach würde mich eure Meinung dazu interessieren, bzw. wie ihr damit umgegangen wärt.


----------



## mihe7 (18. Jul 2019)

White_Fox hat gesagt.:


> Was ist da eigentlich der Unterschied zwischen Sources unc Classpath?


Der sourcepath gibt an, wo der Compiler nach Quelltext suchen soll. 
Der classpath gibt an, wo der Compiler nach class-Files suchen soll.
Wird der sourcepath nicht angegeben, ist er identisch zum classpath.


----------



## mihe7 (18. Jul 2019)

mrBrown hat gesagt.:


> Sollte es nicht auch reichen, die passenden Compiler-Warnungen


Oh, mein Gott... das wäre nun wirklich zu einfach.

@White_Fox die Warnung gibts von Haus aus. Übersetz mal mit -Xlint dann bekommst Du etwas wie:

Test.java:2: warning: [rawtypes] found raw type: X
public class Test extends X {}
                          ^
  missing type arguments for generic class X<T>
  where T is a type-variable:
    T extends Object declared in class X
2 warnings

Du kannst Dir den Annotation Processor also ruhig sparen - war trotzdem interessant


----------



## White_Fox (18. Jul 2019)

Warnungen...sind doch da, um ignoriert zu werden. Oder nicht?

Die Lösung mit dem AP gefällt mir sehr viel besser (und wer weiß wozu das mal gut sein kann zu wissen, wie man einen AP schreibt), aber tatsächlich: Einfacher sind die Wahrnungen auf jeden Fall.


----------



## mihe7 (18. Jul 2019)

Schreib mal unter Compiling unter Additional Compiler Options `-Werror -Xlint:rawtypes` und staune


----------



## Xyz1 (18. Jul 2019)

White_Fox hat gesagt.:


> Warnungen...sind doch da, um ignoriert zu werden. Oder nicht


Rauchen ist also gar net tödlich?


----------



## mrBrown (18. Jul 2019)

White_Fox hat gesagt.:


> Warnungen...sind doch da, um ignoriert zu werden. Oder nicht?


Deshalb `-Werror`


----------



## mihe7 (18. Jul 2019)

Ach, darum nennt sich das smoke testing - man lernt nie aus.


----------

