Java Umsetzung von Funktions-Pointern?

3 Antworten

Habe versucht, dass Auto und Haus ein Interface implementieren und sie damit eine gleichnamige Methode  create(int i) haben.

OK.

Dann ruf die Methode halt auf. Du musst natürlich den Typparameter einschränken: T darf nicht irgendeine Klasse sein, sondern muss das besagte Interface implementieren.

https://docs.oracle.com/javase/tutorial/java/generics/bounded.html

Wo du da einen Funktionszeiger zu brauchen glaubst, verstehe ich gerade nicht. Du kannst natürlich auch ein entsprechend typisiertes Lambda (Functional Interface) ausdrücklich als Argument der Methode mitgeben. Das wäre das Analogon zum Funktionszeiger. Dieses Muster findet sich überall in der Standardbibliothek seit Java 8, teils auch davor. Gutes Beispiel: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Collector.html

paulfragtgf 
Fragesteller
 28.07.2022, 18:27

Wow, es hat geklappt!

Ich sehe zum ersten Mal, dass man

 <T extends ?>

schreiben kann. Genau das hat mir gefehlt. Ich wusste nicht, dass es möglich ist, T wirklich in solch einer Form einzuschränken.

Meine Lösung sieht jetzt für dieses Beispiel wie folgt aus:

public interface Basis {
  public <T> T create(int i);
}

Haus und Auto implementieren es und überschreiben/implementieren die Methode create, hier das Beispiel für Auto:

public class Auto implements Basis{	
    @Override
	public Auto create(int i) { //Warning
		return new Auto(i);
}

Und so sieht die generische Methode aus:

private <T extends Basis> List<T> getFromResponse(Response response, T t){
  List<T> list = new ArrayList<>();
  for(int i=0; i < response.array.length; i++){
    list.add(t.create(i));
  }
  return list;
}

Ich habe dennoch eine weitere Frage, weil ich dazu nun Warnings erhalte:

"Type safety: The return type Auto for create(int) from the type Auto needs unchecked conversion to confirm to T from the type Basis".

So deutlich das auch formuliert ist, ich verstehe nicht, wie ich das Problem lösen muss. Kannst du mir hier weiterhelfen? Ich habe bereits versucht, den return type auf Basis zu ändern. Die Warnung bleibt aber bestehen.

Vielen Dank auf jeden Fall schon mal, dass es klappt. Habs auch getestet, die Listen füllen sich korrekt ohne Exception!

0
schnfz  28.07.2022, 18:40
@paulfragtgf

Die Warnung wird hier recht gut erklärt: https://stackoverflow.com/a/23646117

Im Wesentlichen liegt es daran, dass du nicht den Typ (Klasse, Interface) parametrisiert hast, sondern nur eine Methode.

In diesem Fall würde ich dir aber sowieso das Muster von zB Collector nahelegen, den ich oben referenziert habe. Da gibst du dem Aufruf einen Supplier mit, der eine Instanz der gewünschten Klasse liefert. Das kann dann auch nur eine Referenz auf new sein. Einfach, explizit, gut lesbar und du gibst nicht sinnlose Instanzen mit.

1
paulfragtgf 
Fragesteller
 28.07.2022, 18:55
@schnfz

Hm, habe ich gelesen, aber ich bin wohl zu blöd dafür. Ich habe die Klasse Basis nun wie folgt verändert:

public interface Basis {
  public <T extends Basis> Basis create(int i);
}

Entsprechend muss ich in der generischen Methode das create(i) aber explizit casten mit (T), bevor es zur Liste hinzugefügt wird.

Damit verschwinden die Warnings aus Auto und Haus auch, aber dafür gibts nun eine in der generischen Methode - gleiches Problem.

"Type safety: Unchecked cast from Basis to T

Ich mache ansonsten Warnings hin und gut ist.

0
ralphdieter  31.07.2022, 09:56
@paulfragtgf

Die Warnungen sind berechtigt:

Mit dem Interface Basis erzwingst Du nur, dass create() irgendwas Basis-mäßiges produziert. Dass diese Methode in Auto und Haus jeweils die eigene Klasse zurückgeben, weiß der Compiler nicht, und ich sehe keinen einfachen Weg, es ihm beizubringen.

Mit folgender Klasse würde auch alles compilieren:

public class Boot implements Basis{	
    @Override
	public Auto create(int i) {
		return new Auto(i);
}

Damit wird list.add(t.create(i)) eine Boot-Liste mit Auto-Objekten befüllen. Aber beim ersten Zugriff auf ein "Boot" aus dieser Liste fliegt (hoffentlich) eine Exception.

0
Ich bräuchte eig. einen Funktionszeiger - gibts in Java aber nicht.

Java mogelt sich mit java.util.function durch:

import java.util.function.IntFunction;
...
private static <T> List<T> getFromResponse(Response response,
                                           IntFunction<T> create){ 
  List<T> list = new ArrayList<>();
  for(int i=0; i < response.array.length; i++){
    list.add(create.apply(i));
  }
  return list;
}
...
  List<Auto> = getFromResponse(resp, Auto::create);

Dazu brauchst Du in Auto eine Funktion static Auto Auto.create(int). Willst Du einen Konstruktor Auto(int) übergeben, schreibe Auto::new.

https://stackoverflow.com/questions/44912/java-delegates

Ggf. schreibst Du ein eigenes generisches Interface dafür und implementierst es dann manuell in einer Klasse oder mit einer anonymen Klasse.

Woher ich das weiß:Berufserfahrung – C#.NET Senior Softwareentwickler
paulfragtgf 
Fragesteller
 28.07.2022, 17:55

Oh man, ich blicke nicht mehr durch. Das lohnt sich aber auch einfach keineswegs, um 5-10 Zeilen doppelten Code zu verhindern. Dann lasse ich lieber den doppelten Code drin, so unschön ich das auch finde.

Vielen Dank trotzdem für deinen Hilfeversuch. Ich weiß aber immer mehr, wieso C# mir besser gefällt.

0
schnfz  28.07.2022, 18:21
@paulfragtgf

Die SO-Frage und die Antworten sind uralt und komplett überholt.

1
Palladin007  28.07.2022, 20:14
@schnfz

Ändert nichts daran, dass es immer noch stimmt.
Oder hat Java mittlerweile Delegaten oder ein vergleichbares Konzept?Sogar hinter Lambda steckt auch nur ein Interface und eine anonym generierte Klasse.

Es ist also nicht falsch, sich damit auseinander zu setzen, auch wenn die Antworten uralt sind.

@paulfragtgf

Die Frage, ob man doppelten Code vermeiden sollte, ist leider nicht so einfach, wie es immer erzählt wird.

Was die Komplexität angeht, würde ich sagen: Doch mach es so, schon allein für den Lern-Effekt ;)

Aus Architektur-Sicht:

Nötig ist es nicht, der hier doppelte Code enthält keine relevanten Informationen, die es wert sind, sie an einer Stelle zu halten. Außerdem darfst Du nicht vergessen, dass solche ausgelagerten "Utils-Methoden" schnell mehr Probleme verursachen können, als sie beheben, sowas sollte man also mit Vorsicht genießen und wenn dann in kontrollierten Rahmen (= nicht öffentlich) einsetzen

Allerdings sieht das für mich aus, wie ein simples Select von LINQ in .NET. Soweit ich das sehe, scheint es für Java ein ähnliches Konzept unter dem Namen "Stream" zu geben. Schau dir das doch mal an, vielleicht kannst Du dir damit beide Methoden komplett sparen.

Aber bedenke auch, dass es durchaus kontraproduktiv sein kann, die Streams an der einen Stelle zu nutzen, wenn sie sonst nirgendwo anders gebraucht werden.

0