Java Basics
Auf diesen Seiten habe ich nochmal kleine Erklärungen zu allen Themen geschrieben.
Vielleicht hilft euch ja nochmal eine alternative Erklärung.
Ich möchte niemanden weiter verwirren und hier können auch Falschinformationen dabei sein. Nutzten zum Lernen auf eigene Gefahr! Das ist keine Alternative zum Lernen :P
Viel Spaß beim Java lernen, falls ihr Fragen habt, schreibt uns einfach an
~ Flo :)
Java Basics
Attribute
In Java können wir durch Attribute Werte in dem RAM abspeichern und darauf zugreifen. Hier sind einige Beispiele, wie man Attribute deklariert:
String name;
int alter;
double literWasser;
Wie man im Beispiel sehen kann, werden Attribute mit Datentyp + name + ;
deklariert.
Diesen Attributen wurden Standardwerte zugeordnet (siehe Standardwerte).
Um Attributen neue Werte zu geben, benutzt man den Zuweisungsoperator.
Dem Attribut links wird der Wert rechts zugeswiesen:
name = "Florian";
alter = 16;
literWasser = 5.2;
Man kann die Attribute auch direkt bei der Deklaration initialisieren, was für erhöhte Lesbarkeit sorgt:
String name = "Florian";
int alter = 16;
double literWasser = 5.2;
Der Name eines Attributes kann sich nicht ändern.
Methoden
Klassen können neben Attributen, Methoden enthalten. Methoden sind Codestücke, die von anderen Methoden aus aufgerufen werden können. Beim Aufrufen einer Methode können wir ihr auch Werte übergeben:
private int addieren(int a, int b) {
int c = a + b;
return c;
}
Jede Methode hat eine Methodensignatur. Diese besteht aus der ersten Zeile der Methode, in welcher Zugriffsmodifikatoren, Rückgabetyp, Name der Methode und Parameter angegeben werden. Darunter ist der Methodenrumpf (alles in den geschweiften Klammern), welcher den Code enthält, der beim Aufrufen der Methode ausgeführt wird.
Die Beispielmethode oben nimmt 2 Integer Werte an und gibt einen Integerwert zurück.
Sie kann mit addieren(1, 2)
von der gleichen Klasse aus aufgerufen werden.
Falls das Ergebnis gespeichert werden soll, muss es einer Variable oder einem Attribut
zugewiesen werden: int summe = objekt.addieren(1, 2)
(siehe Attribute).
Bei manchen Methoden gibt es kein Ergebnis, also geben sie nichts zurück.
Diese haben dann void
als Rückgabetyp.
Beispielsweise die Main Methode public static void main(String[] args) {}
gibt nichts
zurück.
Eine Methode ist immer Teil einer Klasse. Die Zugriffsmodifikator bestimmt die Sichtbarkeit. Zugriffsmodifikatoren werden im Nächsten Abschnitt OOP und Zugriffsmodifikatoren erklärt.
Objekt Orientierung und Zugriffsmodifikatoren
Nachdem Attribute und Methoden verstanden sind, sind Klassen an der Reihe.
Der Datentyp String
ist zum Beispiel eine Klasse.
Klassen werden immer großgeschrieben.
So wird zum Beispiel String
großgeschrieben und int
klein, weil int
ein primitiver
Datentyp ist.
Klassen sind Behälter für Attribute und Methoden.
Ein Objekt ist eine Ausprägung einer Klasse mit Attributwerten.
Eine Klasse ist ein Weg, Attribute und Methoden zu bündeln und abzukapseln.
Das bündeln kann die Programmierung vereinfachen und übersichtlicher machen.
Hier eine Beispielklasse "Konto":
class Konto {
private int kontoNummer;
private String name;
public Konto(int kontoNummer, String name) {
this.kontoNummer = kontoNummer;
this.name = name;
}
public int getKontonummer() {
return this.kontonummer;
}
}
Hier kommt auch das Prinzip von "Sichtbarkeit" ins Spiel.
Die Attribute "kontoNummer" und "name" sind private
.
Das heißt, sie sind nur in dieser Klasse sichtbar.
Nicht außerhalb unseres "Bündels"
Unter den Attributwerten ist ein Beispiel Konstruktor.
Ein Konstruktor ist einfach eine besondere Methode, die wir nutzen, um unser Objekt zu erstellen.
Momentan ist unsere Klasse einfach ein Plan.
Der Konstruktor erstellt ein Objekt, wo alle Attributwerte und Methoden drin sind.
In die Objekte können wir nicht reingucken, nur in die Klassen!
Danach kommt eine "getMethode" das ist eine einfache Methode, die einen privaten Wert zurückgibt. Diese ist public
, da wir sie außerhalb unseres Objektes nutzen wollen.
Hier ein Beispiel, wo ich ein Objekt namens konto1 erstelle und danach benutzte:
Konto konto1 = new Konto(1, "Florian");
konto1.getKontonummer() //rückgabe: 1
Der Konstruktor wird mit new KLASSENNAME();
aufgerufen.
Hierbei ist zu beachten, dass wir das Konto genau wie einen normalen String oder integer
abspeichern. (String name = "Florian"
).
Hier ist auch zu sehen, das wir die public Methode "getKontonummer" über einen Punkt
aufrufen.
Das Objekt konto1
ist unser Bündel, wir haben nur noch Zugriff auf die public
Attribute und Methoden.
Schleifen
if else
Falls wir code schreiben wollen, der von einer Bedingung abhängig ist, nutzten wir if
und else
boolean test = true;
if (test) {
System.out.println("<test> ist true!");
} else {
System.out.println("<test> ist false!");
}
In der ersten Zeile sehen wir nach if
direkt die Bedingung in Klammern. Hier, der boolean "test". Auch hier wird alles in geschwungenen Klassen ausgeführt. Bei dem Beispiel oben ist auch "else" zu sehen. Das wird ausgeführt, falls die erste Bedingung nicht wahr ist, (false), dann wird else ausgeführt.
Wichtig: else ist optional. Wir können auch nur eine
if
Bedingung haben. If kann alleine stehen, aber else steht niemals ohne if davor!
Wir können auch mehrere Bedingungen aneinander reihen:
int kekse = 2;
if (kekse == 0) {
System.out.println("keine Kekse mehr da :(");
} else if (kekse == 1) {
System.out.println("Ein Keks ist noch da! :)");
} else {
System.out.println("Genug Kekse für mich! :>");
}
Hier benutzte ich einfach else if (Bedingung)
.
for loops
wenn wir eine Aktion x-mal ausführen wollen, benutzten wir eine for Schleife.
for (int i = 0; i < x; i++) {
System.out.println(i); // 0, 1, 2, 3 ... x-1
}
In der ersten Zeile der for
Schleife ist die Bedingung (int i = 0; i < x; i++)
zu sehen. Hier passieren 3 Sachen:
- Wir erstellen eine Laufvariable i mit
int i = 0;
- wir überprüfen, ob i kleiner ist als x (unsere Stopp-zahl. z.B. 10) mit
i < x;
- Falls die Bedingung in Schritt 2 wahr ist (
i < x
) wird alles in den geschwungenen Klammern ausgeführt (hier:System.out.println(i);
). Danach wird 1 zu i miti++
addiert und die Schleife springt zu Schritt 2. Falls die Bedingung in Schritt 2 falsch ist, wird die Schleife abgebrochen.
While schleifen
While schleifen nutzen wir, wenn wir etwas so lange ausführen wollen, bis eine Bedingung wahr ist.
boolean hunger = true;
while(hunger) {
essen();
}
Die Methode essen()
wird so lange aufgerufen, bis hunger false
ist.
Wichtig: While schleifen können unendlich laufen, wenn man nicht aufpasst. z.B., wenn die Bedingung
true
ist und sich nie ändert.
Mehr Informationen ist in unserer Präsentation auf Teams.
do while Schleifen
do while Schleifen sind ähnlich wie while Schleifen, aber der code wird erst ausgeführt, und danach die Bedingung überprüft:
boolean hunger = false;
do {
essen(); // "essen" wird mindestens 1 mal ausgeführt, auch wenn hunger == false ist.
} while (hunger);
Scanner
der Scanner ist eine Klasse, die benutzt werden kann, um Daten vom User einzulesen. Dabei müssen wir erst das Objekt erstellen.
Scanner eingabe = new Scanner(System.in);
Für die Arbeit am Montag müssen wir nicht ein Scanner Objekt erstellen können. Uns wird das Scanner-Objekt zur Verfügung gestellt. (hier:
eingabe
)
private String username = scanner.nextLine();
private int alter = scanner.nextInt();
scanner.nextLine(); // muss hier stehen, da nach nextInt(); nicht der Buffer gelöscht wird.
private String lieblingsfarbe = scanner.nextLine();
Klassenbeziehungen
die folgenden Beispielklassen wurden nur vereinfacht implementiert. Echte Klassen haben weitere Attribute, andere Zugriffsmodifikator und Konstruktoren. Auch Namen ändern sich. Hier ist eine gute quelle, Klassenbeziehungen uni-Leipzig
Aggregation
bei einer Aggregation existieren beide Klassen unabhängig, eine ist jedoch teil der anderen. Das Kann in Java so implementiert werden. Hier wird ein Auto als Hauptklasse (dem Ganzen) und eine "Reifen" Klasse als teil Klasse benutzt:
class Reifen {
}
class Auto {
private Reifen r1;
public Auto(Reifen reifen) {
this.r1 = reifen;
}
}
Im Konstruktor des Autos wird der Reifen übergeben. Der Reifen existiert außerhalb des Autos, ist aber Teil von diesem.
Die Autoklasse kann so instanziiert werden: new Auto(einReifenObjekt);
Komposition
die Komposition ist eine "verstärkte" Aggregation. Hier ist die teil Klasse fester Bestandteil der Hauptklasse. (dem Ganzen) hier zum Beispiel ein Haus als ganzes und ein Raum als teil. Der Raum kann nicht ohne Haus existieren.
class Raum {
}
class Haus {
private Raum raum;
public Haus() {
this.raum = new Raum();
}
}
Das Raum-Objekt wird innerhalb des Konstruktors der Haus-Klasse erzeugt. So muss kein Objekt übergeben werden, und jedes Haus erstellt automatisch ein Raum. Ein Haus kann instanziiert werden durch dem Konstruktor: new Haus();
.
Generalisierung und Spezialisierung
Generalisierung und Spezialisierung sind 2 Wege, um Klassen zu vereinfachen.
Beispielsweise haben wir eine Küche
Klasse und eine Wohnzimmer
Klasse.
Beides sind Klassen, die wir Zusammenfassen könnten. Schließlich sind ja beides Räume.
So könnte eine "Raum" Klasse aussehen:
class Raum {
boolean fenster;
int tueren;
}
Bisher noch nichts Neues.
Wir wollen aber nicht, das ein "Raum" Objekt erstellt werden kann. Diese Klasse soll ja nur als Vereinfachung der Unterklassen [Kueche
und Wohnzimmer
] dienen. Deshalb machen wir die Klasse abstract
.
abstract class Raum {...}
Jetzt haben wir eine abstrakte Klasse um Räume darzustellen. Bisher haben wir aber noch gar nichts mit unseren Unterklassen gemacht. Wir müssen dem Java Compiler sagen, das die Kueche
und Wohnzimmer
Klassen die "Raum" Klasse erweitern. Das geht mit dem extends
Modifikator:
class Kueche extends Raum {...}
class Wohnzimmer extends Raum {...}
Das war die Generalisierung. Wir haben 2 Unterklassen und vereinfachen die in einer Oberklasse. Generalisierung und Spezialisierung sind das Gleiche. Nur aus einer anderen Sicht. Bei der Spezialisierung hätten wir eine Oberklasse und wollen die "spezialisieren". Es ist genau der gleiche code.
Abstrakte Klassen
wir können eine Klasse den abstract
Modifizierer geben, damit kein Objekt von ihr erstellt werden kann. Stellt euch eine abstrakte Klasse wie eine Art "Vorlage Klasse" vor. Eine andere Klasse kann mit extends
von der abstrakten Klasse abstammen (mehr dazu in "Klassenbeziehungen"). Dabei können wir einen Konstruktor mittels super()
aufrufen, auf protected
Attributen zugreifen und Methoden überschreiben. Hier ist ein Beispiel:
public abstract class VorlageKlasse {
protected int testInt;
public abstract void testVoid();
public VorlageKlasse(int testInt) {
this.testInt = testInt;
}
}
Das ist das komplizierteste, was wir machen müssen. Viele Sachen, die hier vorkommen, wurden in anderen Beiträgen erklärt. Es macht also Sinn, mindestens die folgenden: Definitionen, Generalisierung 2 und Klassenbeziehungen erstmal durchzulesen.
In der ersten Zeile sehen wir abstract class VorlageKlasse {
hiermit deklarieren wir eine neue abstrakte Klasse namens VorlageKlasse
.
In der nächsten Zeile sehen wir einen protected
integer namens testInt. protected
ist ähnlich wie private
. Das heißt, wenn eine Klasse unserer VorlageKlasse abstammt, hat diese Klasse (und ihr Objekt) zugriff auf diesen Integer. Von außen, kann man den Integer aber nicht erreichen. Das sieht man gleich am Beispiel am besten. Dazu könnt ihr mit NetBeans / IntelliJ-Idea etwas herumspielen. So bekommt man dafür ein Gefühl.
Nach dem protected integer, kommt abstract void testVoid();
. Das ist eine abstrakte Methode. Besser gesagt, es ist die Signatur der abstrakten Methode. Abstrakte Methoden haben keinen Methodenrumpf wie normale Methoden. Das ist so, weil diese Methode von unserer abstammenden Klasse implementiert werden soll. Wie bereits gesagt ist unsere abstrakte Klasse nicht mehr als eine "Vorlage". Das Gleiche gilt für diese Methode. Sie ist eine Vorlage. Wie die implementiert werden soll, sieht man gleich im Beispiel.
Nach unseren Attributen kommt noch unser Konstruktor. Der dürfte allen bekannt vorkommen. Hier hat sich nichts geändert. Da wir aber kein Objekt von einer abstrakten Klasse erstellen können, wird dieser mithilfe von super()
in unserer abstammenden Klasse ausgeführt.
Hier ist ein Beispiel, wie eine abstammende Klasse aussehen könnte;
public class abstammendeKlasse extends VorlageKlasse { //normale Klasse stammt der abstrakten VorlageKlasse ab.
private String testString; //normales Attribut
public abstammendeKlasse(int testInt, String testString){ //normaler Konstruktor der abstammendeKlasse
super(testInt); //wir rufen den Konstruktor der Oberklasse "VorlageKlasse" auf
this.testString = testString;
}
public void testVoid() { //Diese Methode ist die implementation der abstrakten Methode in VorlageKlasse. Diese hier MUSS implementiert werden!
//beispiel leere Methode
}
public int getTestInt() { //getter für den protected wert der VorlageKlasse
return this.testInt; //wir haben in der abstammendeKlasse nirgendswo testInt deklariert. Wir können auf ihn trotzdem zugreifen, da er in der Oberklasse "VorlageKlasse" als protected deklariert wurde.
}
}
Generalisierung und Spezialisierung Beispiel 2
Generalisierung:
PKW, LKW → Fahrzeug
//Oberklasse:
abstract class Fahrzeug {
protected String farbe; //protected -> verfügbar in unterklassen
public Fahrzeug(String farbe) { //Konstruktor der Klasse "Fahrzeug"
this.farbe = farbe;
}
}
//Unterklassen:
class LKW extends Fahrzeug {
private double maxGewicht;
public LKW(String farbe, double maxGewicht) { //Konstruktor der Klasse LKW
super(farbe); //ruft den Konstruktor der Klasse "Fahrzeug" auf
this.maxGewicht = maxGewicht;
}
}
class PKW extends Fahrzeug {
private int tueren;
public PKW(String farbe, int tueren) { //Konstruktor der Klasse PKW
super(farbe); //ruft den Konstruktor der Klasse "Fahrzeug" auf
this.tueren = tueren;
}
}
Spezialisierung:
Haus → Schule, Krankenhaus
//Oberklasse
abstract class Haus {
protected boolean fenster; //protected -> verfügbar in unterklassen
public Haus(boolean fenster) {
this.fenster = fenster;
}
}
//Unterklassen
class Schule extends Haus {
private int tueren;
public Schule(boolean fenster, int tueren) {
super(fenster);
this.tueren = tueren;
}
}
class Krankenhaus extends Haus {
private int betten;
public Krankenhaus(boolean fenster, int betten) {
super(fenster);
this.betten = betten;
}
}
Beides ist das Gleiche. Spezialisierung kann auch als Generalisierung angesehen werden. Die Implementation ist identisch.
Spezialisierung → Eine Klasse, die in mehrere spezialisiert wird.
Generalisierung → mehrere Klassen die in eine vereinfacht/zusammengefasst werden (diese Klasse hat dann die gemeinsamen Attribute und Methoden)
Generalisierung und Spezialisierung
Generalisierung → 2 Klassen werden "vereinfacht" und stammen nun einer Oberklasse ab, die die gemeinsamen Attribute beinhaltet
Spezialisierung → Eine Oberklasse wird "spezialisiert" mehrere Unterklassen stammen dieser ab und beinhalten neue Attribute. Letztendlich ist es eine Frage der Perspektive, Generalisierung und Spezialisierung werden identisch implementiert und in UML notiert.
Implizite Vererbung
"vermutende Vererbung" Alle Klassen in Java stammen von anderen Klassen ab. Auch unwissend. Falls eine Klasse nicht explizit einer anderen abstammt, stammt sie dem Objekt in Java ab.
Explizite Vererbung
bei der expliziten Vererbung wird genau definiert, wovon die
Klasse abstammt. Dies wird durch den extends
Modifizierer getan.
Abstrakte Klasse
eine abstrakte Klasse ist eine, die nicht instanziiert werden kann. Sie wird von anderen Klassen benutzt, die dieser abstammen. Abstrakte Klassen werden genutzt, um eine Art Hierarchie von Abstraktion zu schaffen, wobei diese Klasse eine Art Platzhalter darstellt.
super() Methode
die super
Methode wird benutzt, um den Konstruktor der Oberklasse aufzurufen.
Falls super in einem Konstruktor einer Unterklasse verwendet
wird, muss dies in der ersten Zeile nach der Signatur erfolgen.
Standardwerte
Verschiedene Typen haben verschiedene Standardwerte, die genutzt werden, wenn man ihnen keinen konkreten Wert zugewiesen hat. Hier eine Tabelle mit den Standardwerten:
Typ | Standardwert |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0 |
double | 0.0 |
boolean | false |
char | 0 (nicht das Zeichen 0) |
Objekt | null (Zeiger auf nichts) |
Erstellung
Ein leeres Array, bei dem der Speicherplatz mit dem Standardwert des Typen initialisiert wird, erstellt man so:
Typ[] einArray = new Typ[10];
Das obere Array hat die Länge 10.
Wenn der Typ zum Beispiel int
wäre, hätte jedes Element des Arrays nun den Wert 0
.
Alternative 1
Alternativ kann man direkt Werte für das zu erstellende Array angeben:
einArray = new int[]{1, 2, 3};
Das obere Array hat die Länge 3.
Alternative 2
Bei der Deklaration einer Variable kann bei Alternative 1 das new Typ[]
weggelassen werden:
int[] einAnderesArray = {5, 10, 15};
Das obere Array hat die Länge 3.
Länge
Um im Nachhin ein auf die Länge des Arrays zuzugreifen kann man auf das Attribut length
des Arrays zugreifen:
int[] einArray = new int[7];
int laenge = einArray.length; // 7
Die Länge des Arrays kann nicht im Nachhinein verändert werden.
Zugriff auf Elemente
Auf die Elemente des Arrays kann so zugegriffen werden:
int[] einArray = new int[]{1, 2, 3};
int erstesElement = einArray[0];
Das erste Element hat einen Index von 0
, das zweite einen von 1
, usw.
Veränderung der Elemente
Um Elemente in dem Array zu verändern muss bei dem Zugriff ein neuer Wert gesetzt werden:
int[] einArray = new int[]{1, 4, 3};
einArray[1] = 2;
einArray
enspricht nun {1, 2, 3}
.