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

  1. Attribute
  2. Methoden
  3. OOP und Klassen

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

  1. if else
  2. for loops
  3. while schleifen
  4. do while schleifen
  5. scanner

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:

  1. Wir erstellen eine Laufvariable i mit int i = 0;
  2. wir überprüfen, ob i kleiner ist als x (unsere Stopp-zahl. z.B. 10) mit i < x;
  3. 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 mit i++ 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:

TypStandardwert
byte0
short0
int0
long0
float0.0
double0.0
booleanfalse
char0 (nicht das Zeichen 0)
Objektnull (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}.

Lexikon