Hallo liebe Freunde,
Habe folgendes Verständnisproblem:
Ich weiß wozu interfaces gut sind und wie sie verwendet werden!
Ich habe hier folgendes Beispiel aus dem V2B Training:
Das Interface Path wird hier als nicht instanziertes Objekt verwendet.
Wieso funktioniert das aber ohne weiteres?
Das Interface ist als folgendes deklariert:
public interface Path extends Comparable<Path>, Iterable<Path>,
Watchable
Es erbt dir Eigenschaften der Interfaces Comparable, Iterable, und
Watchable.
Was bedeuten aber diese Spitzen Klammern daneben?
Die Entscheidende Frage: Wieso lässt sich ein Interface als
Objektvariable nutzen?
Ist da sprinzipiell mit jedem Interface möglich?
AndyS schrieb:> Das Interface ist als folgendes deklariert:>> public interface Path extends Comparable<Path>, Iterable<Path>,> Watchable>> Es erbt dir Eigenschaften der Interfaces Comparable, Iterable, und> Watchable.>> Was bedeuten aber diese Spitzen Klammern daneben?
Das sind Java Generics.
Und das ganze scheint ein Beispiel für das aus C++ bekannte, aber selten
verwendete "curiously recurring template pattern" zu sein.
> Die Entscheidende Frage: Wieso lässt sich ein Interface als> Objektvariable nutzen?
Weil das ein Hauptanwendungsgebiet ist?
> Ist da sprinzipiell mit jedem Interface möglich?
Ja.
Florian schrieb:> Weil das ein Hauptanwendungsgebiet ist?
Hmm, ich dachte Interfaces werden vorwiegend dazu verwendet um bestimmte
Methoden in einer Klasse implementieren zu lassen bzw. wie in abstrakten
Klassen als Grundgerüst einer Klasse zu dienen?
Ich habe im Net noch kein einziges Beispiel bei der Erklärung der
Interfaces gefunden wo der Name des Interfaces vor einem Namen eines
Variablentyps des gleichen ist.
>> Das Interface Path wird hier als nicht instanziertes Objekt verwendet.> Wieso funktioniert das aber ohne weiteres?
Meinst du "Path.get"? Das wird eine statische Methode sein.
Oder was meinst du?
Stefan Tomanek schrieb:> Meinst du "Path.get"? Das wird eine statische Methode sein.> Oder was meinst du?
Nein, Paths.get ist die statische Methode der Klasse Paths.
Ich meine |--> Path verz <--| = ...
Das was Paths.get() zurückgibt hat das Interface, folglich kann man es
hier verwenden. Hat den Vorteil das man nicht wissen muss was für ein
Objekttyp es genau ist, sondern nur fordert dass es das interface hat
(freier als einen festen Typ fordern, gleichzeitig aber sichergestellt
dass das Interface passt)
Versteh ich nicht ganz was du damit meinst.
Gilt nicht prinzipiell, dass mann abstrakte Klassen nicht erstellen
kann. Da Interfaces im Prinzip abstrakte Klassen sind widerspricht sich
das irgendwie. Von der Logik her leuchtet es mir überhaupt nicht ein
wieso ein Interface einen Wert annehmen kann.
>Gilt nicht prinzipiell, dass mann abstrakte Klassen nicht erstellen
kann.
Weder mann noch eine Frau kann abstrakte Klasse instantiieren - sonst
wären sie nicht mehr abstrakt ;) Das wird auch im Beispiel nicht
versucht.
> Die Entscheidende Frage: Wieso lässt sich ein Interface als> Objektvariable nutzen?>> Ist da sprinzipiell mit jedem Interface möglich?
Weil ein Interface - genau wie eine (abstrakte) Klasse - eben ein Typ
ist. Das ist genau Sinn und Zweck der Geschichte.
Path myVariable = Paths.get(strPath);
Man deklariert da eine lokale Variable von dem Typ (Path ) und weist ihr
eine Instanz (dieses Typs) zu, die von der statischen Methode (get)
einer anderen Klasse (Paths) instantiiert und zurückgegeben wird.
Was im Beispiel etwas verwirrend ist ist die String path -Variable, die
mit dem ganzen nur begrenzt zu tun hat und deswegen unglücklich benannt
wurde :) Diese habe ich in strPath umbenannt.
vorbeigeschaut schrieb:> Man deklariert da eine lokale Variable von dem Typ (Path ) und weist ihr> eine Instanz (dieses Typs) zu
Genau das verstehe ich ebeb nicht, denn damit instanziert man ja ein
Interface.
Verstehen würde ich es wenn die Klasse Paths das Interface Path
implementiert und man das ganze so:
Path path = new Paths();
path.set(string_path);
und über path.get() bekomme ich dann den Pfad.
Aber so funktioniert es nicht, kann es auch nicht.
Aber eine Regel sticht aus deiner Erklärung irgendwie hervor:
Wenn ich Interfaces als Objektvariablen nutzen will muss ich diese über
Funktionen initialisieren./?
Objektvariablen sind es aber dann trotzdem ob durch "new" erstellt oder
einer Funktion.
Ich kann ja auch eine Abstrakte Klasse so nicht initialisieren, also
verstehe ich nicht ganz.
Okay... eine nach dem anderen...
> Ich habe im Net noch kein einziges Beispiel bei der Erklärung der> Interfaces gefunden wo der Name des Interfaces vor einem Namen eines> Variablentyps des gleichen ist.
Was dich anscheinend verwirrt, sind die Namen. In Java gibt es eine
Namenskonvention:
Interface/Klassen -> GrOß
Package/Member/Variablen -> kLeIn
Von daher sind die Konstrukte wie:
1
FooBarFactoryfooBarFactory=newFooBarFactory();
2
BarbarFromFoo=fooBarFactory.getBar();
einfach die Norm. Aus OOP-Sicht ist völlig unerheblich, ob Foo ein
Interface oder eine Klasse ist. In beiden Fällen ist das ein Typ und so
darf (und muss!) man ihn für die Variablen- (wie auch für die Parameter-
sowie Rückgabetyp-) Deklarationen verwenden.
1
InterfaceNamevariablenName=...;
2
3
privateInterfaceNamemyMethod(){
4
...
5
}
6
7
publicvoidanotherMethod(InterfaceNamevar){
8
if(var!=null){
9
....
10
}
11
}
Die hartnäckige Pascal-Fans benennen die Interface historisch mit "I"
(was in Java nicht notwendig, aber auch nicht verboten ist), damit man
sie von den Klassen optisch unterscheiden kann, etwa:
1
FooBarFactoryfooBarFactory=newFooBarFactory();
2
IBarbarFromFoo=fooBarFactory.getBar();
3
...
4
barFromFoo.doSomething();
JDK (von wo java.nio.file.* kommen ) hat nie dieser Konvention gefolgt.
Eine weitere Kleinigkeit. Die Bezeichner wie "verzeichnis" sind
spätestens nach der Grundschule Tabu :) Man muss immer an die Kollegen
denken, die den Code evtl. später pflegen sollen... also, in Englisch!
Jetzt zu der eigentlichen Frage. Vergiss mal die abstrakten Klassen,
Methoden etc. und merke folgendes:
Ein Interface wird (von einer Klasse) IMPLEMENTIERT.
Eine konkrete Klasse wird INSTANTIIERT.
Eine Variable wird DEKLARIERT.
Einer bereits deklarierten Variable wird ein Wert vom passenden Typ
ZUGEWIESEN.
Jetzt schaue mal das Beispiel erneut an und versuche zu finden, was wo
gemacht wird.
Oje. So langsam wird das ganze doch kompliziert.
vorbeigeschaut schrieb:> Ein Interface wird (von einer Klasse) IMPLEMENTIERT.> Eine konkrete Klasse wird INSTANTIIERT.> Eine Variable wird DEKLARIERT.
Das weiß ich doch und deswegen verstehe ich nicht wieso ich den
Interface intitialiseren muss.
Das problem ist, dass die Klasse Paths das Interface Path gar nicht
implementiert.
Was noch möglich wäre ist dass diese Methode der Klasse Paths ein Path
Objekttyp zurückgibt, welches dann einfach in die Variable geschrieben
wird. Dann frage ich mich aber wie dieses Path Objekt, dass eigentlich
in der Methode Paths.get() zurückgegeben wird initialisiert wurde wenn
es nur ein Interface ist.
Wahrscheinlich wirst du wieder das hier schreiben:
1
IBarbarFromFoo=fooBarFactory.getBar();
Wie ist aber in der Methode getBar ein barFromFoo Objekt instanziiert
worden wenn es ein Interface ist?
Auf was ich hinaus will ist, dass mir schon einleuchtet, dass ich eine
!Klasse! durch eine Funktion, welche selbes Objekt zurückgibt,
instanzieren kann. Eh klar, denn in der Methode wird ein Objekt des
gleichen mit new instanzieert und dann der neuen Variablen
zurückgegeben.
Mir ist jedoch immer noch nicht klar wie ich in der Methode getBar bzw.
getPath das Interface initialisiere und wie sinnvoll es eigentlich ist
diese einer Interfacevariablen zu übergeben.
Denn ein Interface ist eine Abstrakte Klasse und kann dementsprechend
auch nicht veränderbare Felder, Funktionen etc... haben
Verstehst du meinen Ansatz?
Ich denke ich habe es jetzt begriffen!
In der doc steht es zwar bei der get Funktion nicht explizit dabei, aber
das ist ja der Sinn und Zweck der OO.
Normalerweise kann ich durch die Polymorphie folgendes sagen:
InterfaceName Var = new KlassenNameWelchesDasInterfaceImplementiert();
Var ist demnach ein Objekt vom Typ
KlassenNameWelchesDasInterfaceImplementi- ert()
Was hier passiert sein muss ist, dass das new
KlassenNameWelchesDasInterfaceImplementiert() in der Methode get
aufgerufen wird und als InterfaceName zurückgegeben wird.
Also hat mich hier das hin und her der Polymorphie etwss verwirrt. In
wirklichkeit wird nämlich in der methode get auf eine Klasse
zurückgegriffen welche die Schnittstelle Path implementiert und somit
ist Path eine Objektvariable dieser eigentlich instanziereten Klasse,
welche dem Entwickler unlogischerweise vorenthalten wird.
Meine Frage hierzu: Wieso implementiert man nicht gleich diese
versteckte Klasse in Klassenliste des Packages?
Ein Wirr War für nichts und wieder nichts...
ja, klar. Der Knackpunkt ist, dass die Methode Paths.get("...") den
Rückgabe-Typ absichtlich als Interface deklariert, da sie nicht verraten
möchte, welche genau Klasseninstanz sie erzeugt und zurück gibt :) Für
dich ist das auch egal... solange diese unbekannte Klasse X das besagte
Interface implementiert, kann jede Instanz der Klasse deiner Variable
zugewiesen werden. Ein klassischer Factory Patern.
vorbeigeschaut schrieb:> da sie nicht verraten> möchte, welche genau Klasseninstanz sie erzeugt und zurück gibt :)
meiner Meinung nach Sinnlos. Sowas nenne ich Vergewaltigung der
Interfaces weil das ja nicht der Sinn und Zweck der Sache ist.
Aber schon wieder ein neues Konzept dazugelernt!
Durch dich bin ich auf ddie Lösung gekommen, dafür danke!
AndyS schrieb:> Wieso implementiert man nicht gleich diese> versteckte Klasse in Klassenliste des Packages?
Weil in OOP außer Polymorphie noch die Kapselung gibt :) Die eigentliche
Implementierung für Linux kann völlig anderes als die für Windows
aussehen. Die Funktionalität ist aber (nach außen) stets gleich. So
nimmt man ein Interface und versteckt dahinter die Einzelheiten.
Sinn der Sache ist, dass dein Code dann überall funktioniert. Es wäre
falsch, die Klassen in JDK zugänglich zu machen, da sonst könntest du
z.B. LinuxPath (statt Interface Path) nehmen (=statisch
referenzieren)und sich wundern, warum unter Windows nix funktioniert :)
AndyS schrieb:> Sowas nenne ich Vergewaltigung der> Interfaces weil das ja nicht der Sinn und Zweck der Sache ist.
Sorry, aber dann hast du immer noch nicht annähernd verstanden, was Sinn
der Sache bei den Interfaces ist :)
vorbeigeschaut schrieb:> Weil in OOP außer Polymorphie noch die Kapselung gibt :) Die eigentliche> Implementierung für Linux kann völlig anderes als die für Windows> aussehen. Die Funktionalität ist aber (nach außen) stets gleich. So> nimmt man ein Interface und versteckt dahinter die Einzelheiten.>> Sinn der Sache ist, dass dein Code dann überall funktioniert. Es wäre> falsch, die Klassen in JDK zugänglich zu machen, da sonst könntest du> z.B. LinuxPath (statt Interface Path) nehmen (=statisch> referenzieren)und sich wundern, warum unter Windows nix funktioniert :)
Scheiße, da hast du recht!! Blöd dass ich nicht so überlegt hab.
Meine Annahme war, dass die versteckte Klasse normal zugänglich wäre,
demnach würde es keinen Sinn machen. Aber du hast sicher Recht damit,
dass diese Klasse auf jedem OS anders ausschauen könnte, eh klar!
Passt!
vorbeigeschaut schrieb:> Sorry, aber dann hast du immer noch nicht annähernd verstanden, was Sinn> der Sache bei den Interfaces ist :)
RIchtig, bis zu dem Zeitpunkt nicht, aber jetzt verstehe ich das
Konzept.
Tatsache ist aber, dass ich diese Art Verwendung der Interfaces so
niemals in meinen eigenen Klassen benötige, außer ich benutze inline
Assembler oder ähnliches.
Wieso? Wenn ich richtig überlege bräuchte man nicht einmal für die
portierbarkeit interfaces, da mann ja auch statt ihnen dementsprechende
unterschiedliche unsichtbare Klassen mit identischen Eigrnschaften
erstellen kann.
AndyS schrieb:> Wieso? Wenn ich richtig überlege bräuchte man nicht einmal für die> portierbarkeit interfaces, da mann ja auch statt ihnen dementsprechende> unterschiedliche unsichtbare Klassen mit identischen Eigrnschaften> erstellen kann.
Das hat nix mit Portierbarkeit zu tun (das kann man auch über abstrakte
Klassen lösen) sondern damit, das man eine Bindung an eine Konkrete
Implementierung verhindert.
In deinem Beispiel, kann es z.B. sein das du Path Objekte nicht nur von
der Faktory sondern von ganz woanders bekommst, und derjenige könnte
eine ganz andere Implementierung nutzen als die Pathfactory.
Die konkrete Instanz ist natürlich immer von einer Klasse welches das
interface implementiert.
Auf die weise kann man sehr schön Abhängigkeiten im Code vermeiden und
allgemeien Algorithmen oder Methoden schreiben, schau dir z.B. die
Collection API an, da sieht man das sehr schön das viele Algorithmen in
der Hilfsklasse "Collections" nie auf einem Konkreten Typen arbeiten und
somit auf jede beliebige Implementierung des Interfaces arbeiten können.
Hatten wir hier aber auch schon mal:
Beitrag "Java Interface"
AndyS schrieb:> Wahrscheinlich wirst du wieder das hier schreiben:> IBar barFromFoo = fooBarFactory.getBar();>> Wie ist aber in der Methode getBar ein barFromFoo Objekt instanziiert> worden wenn es ein Interface ist?
Sortier mal deine Gedanken. Es gibt kein "barFromFoo-Objekt" (also
Objekt vom Typ barFromFoo), barFromFoo ist der Name einer Variablen.
Du meinst vermutlich: "Wie ist aber ein IBar-Objekt erzeugt worden,
obwohl IBar ein Interface ist?".
Antwort: Gar nicht. Es ist ein Objekt vom Typ Irgendwas erzeugt worden
(mit class Irgendwas extends IBar). Das kann nun allen Variablen vom Typ
Irgendwas oder auch allen Variablen vom Typ IBar zugewiesen werden.
Wie willst du sonst mehrere "zusammengehörige" Typen in einem
heterogenen Container (wie Java und C++ nunmal normalerweise vorsehen)
halten?
Läubi .. schrieb:> Auf die weise kann man sehr schön Abhängigkeiten im Code vermeiden und> allgemeien Algorithmen oder Methoden schreiben, schau dir z.B. die> Collection API an, da sieht man das sehr schön das viele Algorithmen in> der Hilfsklasse "Collections" nie auf einem Konkreten Typen arbeiten und> somit auf jede beliebige Implementierung des Interfaces arbeiten können.
Aber was macht es dann für einen Unterschied wenn ich in jeder
unabhängigen Methode einer satic Klasse eine Klasse definiere welches
das bestimmte Interface implementiert und dann den gewünschten Wert
zurückgibt, oder übersichtshalber (meiner Meinung nach besser) die
direkte Klasse durch Paths_ path = new Path_(str_path); initialisiere?
Es kann doch keiner abstreiten, dass die zweite Methode übersichtlicher
ist und mehr über sich aussagt.
> Auf die weise kann man sehr schön Abhängigkeiten im Code vermeiden und> allgemeien Algorithmen oder Methoden schreiben
Mit Abhängigkeiten meinst du warscheinlich die Implementierungen der
jeweiligen Interfaces in der Hilfsklasse.
Naja würde man es mit der zweiten Methode lösen bräuchte man
dementsprechend nur eine Implementierung bzw erweiterung, und das ist
doch glaub ich nicht so unübersichtlich oder?
AndyS schrieb:> unabhängigen Methode einer satic Klasse eine Klasse definiere welches> das bestimmte Interface implementiert und dann den gewünschten Wert> zurückgibt
Das tut man auch nicht, das ist nur in deinem Beispiel so und ist
einfach eine Möglichkeit. Hier einfach mal ein einfaches Beispiel am
Fall "Runnable".
Ein Thread benötigt im Konstruktor ein Runnable:
http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread%28java.lang.Runnable%29
Das ist alles was ein Thread wissen muss, nämlich das die Instanz die du
ihm gibt eine Methode run() implementiert.
Der Aufrufer (also du) kann jetzt:
a) eine innere Klasse definieren:
1
Threadthread=newThread(newRunnable(){
2
3
@Override
4
publicvoidrun(){
5
System.out.println("Hello from inner class");
6
}
7
});
8
thread.start();
b) eine Speziell für diesen Fall angelegte Klasse nutzen:
1
classPrimeRunimplementsRunnable{
2
longminPrime;
3
PrimeRun(longminPrime){
4
this.minPrime=minPrime;
5
}
6
7
publicvoidrun(){
8
System.out.println("Calculating primes");
9
}
10
}
11
...
12
Threadthread=newThread(newPrimeRun(100));
13
thread.start();
c) selbst Runnable in deiner Klasse implementieren
1
Threadthread=newThread(this);
2
thread.start();
d) selbst ein Runnable als Parameter erwarten
1
publicvoidexecuteMe(Runnablerunnable){
2
Threadthread=newThread(runnable);
3
thread.start();
4
}
e) ... weitere Möglichkeiten sind denkbar ...
All das Funktioniert, ohne das die Klasse "Thread" wissen muss welche
Konkrete Instanz ihr nun übergeben wird. Natürlich könnte man sich jetzt
das Runnable auch wieder von einer Factory abholen... Was die Factory in
diesem Beispiel überhaupt soll musst du den Ersteller des beispiels
fragen.
Läubi .. schrieb:> Was die Factory in> diesem Beispiel überhaupt soll musst du den Ersteller des beispiels> fragen.
Das ursprüngliche Beispiel kommt höchstwahrscheinlich von JDK7. Dort (in
java.nio) gibt es eine nützliche Utility-Klasse mit einer Menge
statischen Factory-Methoden. Ich nahm an, dass genau das (Factory
Pattern) verwirrt AndyS und konstruierte aus dem Kopf ein etwas
abstrakteres FooBar -Beispiel. Er hat aber (anscheinend - immer noch)
Schwierigkeiten mit dem eigentlichen Interface-Konzept.
vorbeigeschaut schrieb:> Er hat aber (anscheinend - immer noch)> Schwierigkeiten mit dem eigentlichen Interface-Konzept.
Ich weiß, dass es durch die Polymorphie funktioniert, denn Interfaces
sind ja sehr ähnlich wie abstrakte Klassen, und man kann auch eine
Interface Schnittstelle für davon abgeleitete (Implementierende) Klassen
als Objekte definieren, da ja Polymorphie.
Wieso jedoch das ganze umeinander, keine Ahnung. Für mich eig nicht
relevant, da ich ja verstehe wie das Konzept funktioert, jedoch nicht
weiß wieso es angewendet wird...
Läubi .. schrieb:> a) eine innere Klasse definieren:Thread thread = new Thread(new Runnable() {>> @Override> public void run() {> System.out.println("Hello from inner class");> }> });> thread.start();
Ich weiß noch nicht wie innere Klassen funktionieren, kann es mir aber
schon vorausdenken.
Läubi .. schrieb:> b) eine Speziell für diesen Fall angelegte Klasse nutzen:class PrimeRun >implements Runnable {> long minPrime;> PrimeRun(long minPrime) {> this.minPrime = minPrime;> }>> public void run() {> System.out.println("Calculating primes");> }> }> ...> Thread thread = new Thread(new PrimeRun(100));> thread.start();
Ist klar!
Läubi .. schrieb:> All das Funktioniert, ohne das die Klasse "Thread" wissen muss welche> Konkrete Instanz ihr nun übergeben wird.
Das sehe ich anders. In allen varianten musst du nämlich ein neues
Objekt erzeugen in welches die run() Methode überschrieben wird und dann
übergibst du dieses auch.
AndyS schrieb:> Das sehe ich anders. In allen varianten musst du nämlich ein neues> Objekt erzeugen in welches die run() Methode überschrieben wird und dann> übergibst du dieses auch.
Dann siehst du das falsch ;-)
Natürlich muss das konkrete Objekt die Methode implementieren, aber
"Thread" muss die konkrete Klasse nicht kennen.
AndyS schrieb:> dass es durch die Polymorphie funktioniert, denn Interfaces> sind ja sehr ähnlich wie abstrakte Klassen
Interfaces sind was anderes als Abstrakte Klassen, den unterschied kann
man aber nicht einfach durch anstarren eines Beispiels erkenne, sondern
das sind einfach zwei unterschiedliche Konzepte welche in der
praktischen Anwendung sowohl ihre Vor- als auch Nachteile haben.
Ein Interface ist einfach ein Platzhalter für eine Klasse welche dieses
Interface (oder mehrere) implementiert. Deshalb kannst du dieses als
Typen benutzen anstelle der konkreten Klasse.
Es geht dabei auch in erster Linie nur um die Zusicherung von
Eigenschaften, du sagst einfach damit aus:
1
"Das Objekt worauf diese Referenz zeigt hat (bzw. soll) die im Interface vereinbarten Methoden (haben)".
Dies wiederum nutzt der Compiler aus um zur Compilezeit zu prüfen das
alles mit rechten Dingen zugeht.
Du könntest auch überall als Parameter "Object" nutzen und per
reflection irgendwelche Methoden per Namen aufrufen und hoffen das das
konkrete Objekt diese implementiert, das hat sich aber als Unpraktisch
erwiesen ;-)