Mit Methoden kann man definieren für was <T> steht. Geht das auch irgendwie für Variablen?

sirbender

Top Contributor
Im Code Schnipsel unten kann ich eine statische Methode erstellen, die mir erlaubt alle möglichen Collection-Konstruktor-Referenzen zu übergeben und ich bekomme direkt ein Objekt vom selben Typ zurück ohne Casting. Also ArrayList::new gibt mir ArrayList<String> zurück ohne Compilererror.

Gibt es eine Möglichkeit die Variable supplier so zu definieren, dass sie auch alle möglichen Collection-Konstruktor-Referenzen akzeptiert und mir dann auch den entsprechenden Typ zurückgibt - identisch mit der statischen Methode. Die statische Methode erlaubt es zu definieren, also z.B. <T> und auch sowas wie DEST mit DEST extends Collection<T>. Geht sowas auch irgendwie mit Variablen?

Supplier<T, ? extends Collection<T>> supplier = // akzeptiert ArrayList<String>::new, LinkedList<String>::new, etc.

Sowas wie <T, DEST extends Collection<T>> Supplier<DEST> supplier = ArrayList<String>::new; geht ja auch jeden Fall nicht.

Kann man diese Art von Definition nur bei Methoden machen und nicht bei Klassen, Variablen, etc.?

package incubator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

public class MethodReferenceTest {

public static void main(String[] args) {

String[] array = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
List<String> list = Arrays.asList(array);
ArrayList<String> result = transferElements(list, ArrayList::new);

Supplier<ArrayList<String>> supplier = ArrayList<String>::new;
ArrayList<String> get = supplier.get();
}

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements(SOURCE sourceCollection, Supplier<DEST> collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) result.add(t);
return result;
}
}
 
Kurze Antwort: Nein das geht nicht. Die generics werden zur Laufzeit aus dem Quellcode entfernt.

Lange Antwort: du benötigst eine callback Funktion, die das passende Objekt liefert.
 
Jein ... es gibt was Besseres.
Seit einigen Versionen kannst du mit dem Schlüsselwort var arbeiten.
Vorteil: Die gesamte Definition der Variable steht RECHTS von der Zuweisung.
Nachteil: Du musst wissen, was du tust. Änderst du die Quelle der Daten, so ändert sich der Typ der Variable und alle späteren Aufrufe könnten dann Compilerfehler auslösen.

Java:
//Alt
String result = new Supplier<String>{/*Hier MUSS ein String geliefert werden, da die Variable den Typ String hat*/}.get();
//Neu
var result = new Supplier<>{/*Hier wird ein eindeutiger Datentyp geliefert, die Variable kriegt den gleichen Typ*/}.get();
 
Kurze Antwort: Nein das geht nicht. Die generics werden zur Laufzeit aus dem Quellcode entfernt.

Lange Antwort: du benötigst eine callback Funktion, die das passende Objekt liefert.
Hmm. Die statische Methode gibt aber trotzdem korrekt ArrayList<String> zurück - zumindest meckert der Compiler nicht, wenn man folgendes schreibt: ArrayList<String> result = transferElements(list, ArrayList::new);

Das Argument ArrayList:new ist noch nicht mal typisiert. Und trotzdem schafft es der Kompiler irgendwie zu erkennen, dass 'result' vom Typ ArrayList<String> ist.

Ich weiss schon irgendwie was du meinst. Er checkt zur Kompilierzeit, dass alles korrekt ist und am Ende steht da nur noch eine ArrayList result;

Trotzdem sehr verwirrend, dass der Compiler, das nicht auch bei der Variablen-Definition irgendwie zulässt. Die nötige Info das durchzulassen wäre ja genauso möglich wie bei Methoden. Hat man das nicht erlaubt, weil dann die Syntax von Java noch komplizierter geworden wäre?

Ich muss schon sagen, wer heute mit Java started hat ganz schön viel an Syntax zu lernen um jeden Sourcecode bis Java17 problemlos lesen zu können.
 
Jein ... es gibt was Besseres.
Seit einigen Versionen kannst du mit dem Schlüsselwort var arbeiten.
Vorteil: Die gesamte Definition der Variable steht RECHTS von der Zuweisung.
Nachteil: Du musst wissen, was du tust. Änderst du die Quelle der Daten, so ändert sich der Typ der Variable und alle späteren Aufrufe könnten dann Compilerfehler auslösen.

Java:
//Alt
String result = new Supplier<String>{/*Hier MUSS ein String geliefert werden, da die Variable den Typ String hat*/}.get();
//Neu
var result = new Supplier<>{/*Hier wird ein eindeutiger Datentyp geliefert, die Variable kriegt den gleichen Typ*/}.get();
Guter Tip. Ich sitze noch bei Java8 fest, aber ich werde es mir mal anschauen.
 
Das Argument ArrayList:new ist noch nicht mal typisiert. Und trotzdem schafft es der Kompiler irgendwie zu erkennen, dass 'result' vom Typ ArrayList<String> ist.
Das hat folgenden Grund. Es gibt eigentlich keine ArrayLists eines bestimmten Typs, also ArrayList<String>
ArrayList<Object> und ArrayList<String> sind identisch, dazwischen besteht kein Unterschied.

Die Generics sind dazu da, um dir beim Programmieren zu helfen, sonst müsstest du ständig hin- und hercasten. Außerdem können so offensichtliche Zuweisungsfehler vermieden werden.

"ArrayList::new" entspricht "() -> new ArrayList<>()".
Die Verwendung von ArrayList::new funktioniert also grundsätzlich immer, der Rest des Aufrufs muss halt zusammenpassen.
 
Zuletzt bearbeitet:
Nur der Vollständigkeit halber:
Das hier funktioniert in Java durchaus
Java:
HashMap<String, List<?>> variable = ...;
//oder auch
HashMap<String, Collection<T>> variable =...;
//sofern T vorher definiert wurde.
 
Wenn man sich überlegt, was denn der Sinn hinter Generics ist, dann ist klar, dass dies nur Sinn macht bei Dingen, die universell von außen nutzbar sind. Dies ist bei Klassen oder Methoden möglich. Das Beispiel ArrayList ist ja genannt worden und man will halt eine ArrayList für bestimmte Elemente haben.

Das ist in sofern wichtig, als das man hier noch nicht wissen kann, was da später gespeichert werden soll. Und unter dem Strich wird es am Ende ein universeller Typ wie Object (oder eben etwas anderes, wenn das Generic spezifiziert wurde).

Bei einer Variablen, die nur intern verwendet wird, macht dies einfach keinen Sinn. Wenn Du da eine beliebige Referenz speichern können willst, dann nimmst Du halt Object. Wenn da das gewünschte etwas wäre wie ein T extends Comparable<T> dann wäre die Variable einfach ein Comparable. Du würdest da schlicht nichts gewinnen, wenn da auch irgendwelche Generics möglich wären.

Das var Schlüsselwort geht da auch in eine ganz andere Richtung. Und ich würde dringend davon abraten, dies nicht zu sehr zu nutzen. Hintergrund ist hier einfach, dass dies keine funktionelle Erweiterung ist und Dir somit eine neue tolle Funktionalität bietet wie Generics. Statt dessen führt es leicht dazu, dass Typen verschleiert werden und dann im Nachhinein Probleme auftauchen. Ein einfaches kleines Beispiel:

Java:
var myList = new ArrayList<String>();
wäre eine gültige Syntax in Java. Dies ist gleichbedeutend zu

Java:
ArrayList<String> myList = new ArrayList<String>();
(wobei das zweite <String> optional ist und eher nur <> geschrieben wird.)

Das Problem kommt, wenn dieser Variable im Anschluss neue Listen zugewiesen werden sollen. Man ist halt auf genau diese ArrayList angewiesen. In der objektorientierten Softwareentwicklung gibt es eine kleine Regel: Man soll gegen Interfaces entwickeln. Daher wäre hier halt als Code sinnvoll:
Java:
List<String> myList = new ArrayList<String>();

Damit kann ich myList nun beliebige Implementationen von List zuweisen in Ihrem Gültigkeitsbereich.

Das einfach nur einmal als Anregung um hier nicht in irgendwas hinein zu stolpern, das "cool" aussieht und dann später ggf. zu Problemen führt. Aber ehe da ein Aufschrei kommt: Es spricht nichts gegen die Verwendung von var wenn man sauber entwickelt. Dann kann es ein super Weg sein, um Code kürzer zu schreiben. Und wenn man sauber entwickelt, dann sind die Methoden kurz und übersichtlich und einer Variablen wird nichts neues zugewiesen das ein anderen Typ haben könnte. Und wenn, ist es so übersichtlich, dass es einfach zu beheben ist.
Meine Empfehlung richtet sich also speziell an Anfänger, die hier eben eher weniger auf Clean Code achten. Und wo dieses Entwickeln gegen Interfaces etwas ist, das diese noch bewusst machen sollten - incl. der Nennung des Interfaces!

Aber das ist einfach nur meine Sichtweise.
 
Falls man innerhalb des selben Blocks (und nur dort ist "var" gültig) nochmal was Anderes zuweisen will mit einem potentiell anderen Datentyp dann ist es natürlich notwendig, eine übergeordnete Klasse oder ein Interface anzugeben.
Ansonsten kann man damit hervorragend arbeiten.

Der Compilier macht aus dem hier:
Java:
var item = useLabel? new JLabel()  : new JButton();
auch ganz brav ein JComponent Objekt. Aus allen möglichen Rückgabewerten wird die erste gemeinsame übergeordnete Klasse ermittelt.
Was ich mal testen muss, wäre, ob er noch zusätzlich angegebene Interfaces erkennen würde.
also "JComponent implements Hassunichgesehen", falls die Klassen beider Rückgabewerte noch explizit dieses Interface implementieren würden.
 
FloatArray<GENERICS> <T> mit 2 Nachkommastellen erzwingen !!!

Java:
import java.text.DecimalFormat;

public class DiverseExamples
{       
    public static<T> void printArrayFloat(T[] array)
    {
        for(T element : array)
        {
            // FloatArray mit 2 Nachkommastellen erzwingen !!!
            DecimalFormat df = new DecimalFormat("#####0.00");
            System.out.println(df.format(element));                 
        }       
        System.out.println();
    }
    
    
    // Main:
    public static void main(String[] args)
    {           
        Float[] floatArray = { 1.341000000f, 9.22888f, 1.044f, 17.500f, 220.5000f };
        
        // FloatArray mit 2 Nachkommastellen erzwingen !!!
        printArrayFloat(floatArray);
        
        // FloatArray mit 2 Nachkommastellen erzwingen !!!
        float number = 17.5000000f;
        String str = String.format("%.02f", number);       
        System.out.println(str);       
    }
}
 
Verstehe nicht, was das mit irgendwas hier zu tun hat.
Dein Generic Typparameter ist völlig nutzlos und deine printArrayFloat Methode ist 100% äquivalent zu:
Java:
public static void printArrayFloat(Object[] array) {
  for (Object element : array) {
    // FloatArray mit 2 Nachkommastellen erzwingen !!!
    DecimalFormat df = new DecimalFormat("#####0.00");
    System.out.println(df.format(element));
  }
  System.out.println();
}
weil Java Arrays covariant sind, und du somit ein Float[] auch zu einem Object[] zuweisen kannst.
 

Zurück
Oben