Klasse mit Array verbinden

Hallo,
ich bin recht neu in der Programmiersprache Java und stelle mich gerade einem Problem.
Ich habe in einer neuen Class eine Variable gespeichert und möchte diese, nun in einer main Methode einem Array zuweisen.
Das Habe ich auch so teilweise geschafft, jedoch bekomme ich ein Error in der Console. Ich weiß leider nicht den Grund,
bzw. weiß ich auch nicht ob mein Gedankengang so richtig war.

Code:
[SPOILER]
Class Player:

public class Player {
	int ID;
}

Class mit Mainmethode:


	public static void main(String[] args) {
		Player[] testvar = new Player[100];
		
		for(int i = 0; i<testvar.length; i++)
		{
			testvar**.ID = i+1;	//Fehler
		}
		
	}

}

Fehlercode:

Exception in thread "main" java.lang.NullPointerException
	at OnPlayerConnect.main(OnPlayerConnect.java:8)

[/SPOILER]

Ich würde mich sehr freuen, falls mir jemand bei diesem Problem helfen könnte.

Hey!
Dein Array ist zwar initialisiert, allerdings sind alle Felder noch auf null.
Du musst also zunächst einmal für jedes Feld einen Player initialisieren, in dem du wie
bei deiner Schleife durch die Felder gehst und eine neue Instanz von Player für das jew. Feld erzeugt.

Du kannst dir auch einen Konstruktor für den Player bauen, in welchem du gleich die ID für den jew. Player
festlegst.

So ein Array musst Du Dir als “Regal” vorstellen.
Du hast also ein Regal gebaut, in das nur “Player” reinpassen, aber in dem Regal sind alle Fächter leer.

Du musst also erst mal in jedes Fach eine Player rein legen.

bye
TT

Ich habe es versucht, bekomme aber immer noch einen Error.
Ich finde gerade nicht den Fehler, denn ich weiße doch im Sinne dem Index ein Player Objekt zu.

	public static void main(String[] args) {
		Player[] testvar = new Player[100];	
		
		for(int i = 0; i<testvar.length; i++)
		{
			testvar** = new Player**;
		}
	}
}

Errorcode:

	Type mismatch: cannot convert from Player[] to Player

	at OnPlayerConnect.main(OnPlayerConnect.java:8)

Guck dir mal genau an, wie du Objekte initialisierst.
(Du benutzt ja bestimmt irgendwas zum Lernen)

Du musst dir in der Player-Klasse einen Konstruktor erstellen.
Die Initialisierung funktioniert dann mit new Player();
Die eckigen Klammern sind meist immer ein Zeichen von Arrays.

Vielleicht etwas weit vorgegriffen und auch erstmal ohne jegliche Erklärung, aber am Ende könnte es ungefähr so aussehen :

Player

{
	private int id; // "ID" wäre eine Konstante und somit "public static final" zu deklarieren
			// normale Member werden grundsätzlich lowerCamelCase geschrieben
			// zu mal eine Konstante hier auch was ganz anderes bewirken würde
	public Player(final int id) // Konstruktor mit ID-Parameter
	{
		this.id=id; // Kopieren des Wertes im Parameter in die Member-Variable
	}
	public int getID() { return id; }
}```

MainClass
```public class MainClass
{
	private Player[] players; // Member; das Array in main() wäre sicher nicht im Sinne von dem was am Ende bei rauskommen soll
	public static final int MAX_PLAYERS=100; // Größe und damit Gesamtzahl der möglichen Player-Objekte
	public static void main(String... args)
	{
		players=new Player[MAX_PLAYERS];
		for(int i=0; i<players.length; i++)
		{
			players**=new Player(i+1);
		}
		
		// lässt sich auch verkürzt so schreiben
		// for(int i=0; i<players.length; i++) // bzw auch for(int i=0; i<MAX_PLAYERS; ...
		// 	players**=new ...
		// da nur ein Statement im Loop kann man die Klammern auch weglassen
		// als Anfänger sollte man aber immer die Block-Klammern schreiben da man so Fehler vermeiden kann
		// sowas kommt dann nachher einfach mit der Zeit und Erfahrung
	}
}```



*gnarf* das Forum macht meine ganze Formatierung kaputt ...

Ich hatte eigentlich vor in der Player Class Variablen von den Spielern einzutragen um diese anschließend wieder auszulesen. Der Code mit dem Konstruktor entfremdet irgendwie den Sinn des Ganzen…

[quote=Zero]Der Code mit dem Konstruktor entfremdet irgendwie den Sinn des Ganzen…[/quote]Ich würde ehr behaupten er gibt der Sache einen Schubs in die richtige Richtung.

Selbst wenn dein Player als “Data Transfehr Object” geplant ist gibt es darin Eigenschaften, die sich während des Spiels ändern werden, beispielsweise der Punktestand, und solche, die sich nie änderen. Zu Letzterem gehöt die ID des Spielers. (Oder macht es in irgendeinem Szenario Sinn die ID eines Spielers zu ändern?)

Eigenschaften die sich nicht ändern werde in Java mit dem final-Schlüsselwort geschützt und können dann nur noch entweder “inline” bei ihrerer Deklaration oder im Konstruktor gesetzt werden.

BTW:
DTOs haben ihren Sinn bei der Kommunitation zwischen verschiedenen Schichten. Innerhalb der Fach-Logik führen sie zur Verletzung des Grundprinzips der OOP: Datenkapselung.

bye
TT

Dann solltest du dir unbedingt noch mal die Grundlagen der objekt-orientierten Programmierung geben, denn eines der Grundkonzepte ist die Datenkapselung.

Um es zu erklären : für deine Main-Klasse ist es völlig egal wie die ID intern im Player organisiert wird. Es interessiert lediglich das es ein int ist der per Konstruktor bei der Objekt-Erstellung initialisiert wird und über einen Getter abgefragt werden kann.
Man könnte den Bogen sogar noch sehr viel weiter überspannen mit einem Interface, der ServiceLoader-API und einer Factory, dann muss man nämlich nicht mal mehr den konkreten Namen einer Klasse kennen um damit zu arbeiten. Das wäre dann z.B. sinnvoll wenn du menschliche Spieler und Bots einbinden willst oder für die Bots alles extra schreiben zu müssen.
In deinem Fall wäre sogar Constructor-Chaining möglich, abhängig wie du es umsetzen willst.

Dein Code aus dem ersten Post

player.ID=xxx;
//...```
ist in der objekt-orientierten Programmierung ein NO-GO !
Denn dadurch würdest du
1) eine statische Abhängigkeit zwischen der MainClass und Player erzeugen (was genau das Gegenteil von Datenkapselung wäre)
2) die Kontrolle über die Member-Variable verlieren (Stichwort Getter und Setter)
3) eine "private" Eigenschaft "nach außen" offenlegen

Gucken wir uns doch mal an was genau du willst : du möchtest eine Klasse "Player" haben die verschiedene Attribute (private Member) speichert mit denen du was anstellen kannst.
Grundsätzlich gehören alle solche "Attribute" als "private"-Member deklariert, haben also alle grundsätzlich den Modifier "private". Zusätzlich bietet die Klasse entsprechende getXXX() und setXXX() Methoden an um die Werte zu lesen und ggf ändern zu können.
Warum macht man das und warum ist das so wichtig ?

Nun, stell dir mal vor dein Player hätte ein Member "hitpoints" und du würdest diese von außen direkt manipulieren. Was passiert wenn dieser Wert auf 0 fällt oder sogar negativ wird ? Wenn du von außen gemütlich "player.ID--;" schreibst fällt das weder auf noch kannst du ein Flag wie "isPlayerAlive" togglen.

Der korrekte weg ist dies nun über einen Setter zu lösen : setHitPoints(int hp).
Diese Methode hat dann die Aufgabe zu ermitteln ob der übergebene Wert überhaupt noch in der Range liegt (0 bis max HP) und einen Überlauf zu verhindern. Außerdem wird bei Erreichen von 0 automatisch ein Member gesetzt das anzeigt das dieser Player "dead" ist, was dann wiederum mit einem Getter isPlayerDead() abgefragt werden kann.


Um noch ein anderes Beispiel zu geben : wenn du in einer Controller-Klasse direkt gegen player.id gelinkt hast (wie gesagt : "ID" würde von anderen Programmieren als Konstante missverstanden werden) und du änderst es irgendwann mal in z.B. uid (für unique - einzigartig) und re-compilest nur diese Klasse bekommst du zur Runtime ein UnresolveCompilationError weil im Code des Controllers noch "id" steht, in der Player-Klasse aber plötzlich "uid". Das passt dann nicht mehr zusammen und deine gesamte Anwendung crasht. Glücklicherweise ist sowas durch eine entsprechende Fehlermeldung recht leicht rauszubekommen, lässt sich aber durch korrektes Programmieren vermeiden.
Verwendest du hingegen einen Konstruktor dem diese ID übergeben wird (und dieser prüft dann selbst auf die "Einzigartigkeit") und einem getter getID() um diese abzufragen ist es dem Controller völlig egal was du intern mit diesem Wert anstellst da keine direkte Verbindung zwischen beiden besteht.



Man könnte das jetzt noch sehr viel ausführlicher erklären, aber das würde erstens den Rahmen eines solchen Threads sprengen, gehört zweitens wie gesagt zu den OOP-Grundlagen (wir helfen auch gerne beim Lernen dieser, aber ein bisschen Eigeninitiative sollte man schon mitbringen) und drittens sollte man erstmal klein anfangen bevor man sich gleich ein zu großes Projekt vornimmt.
Sowas kommt halt alles mit der Zeit und Erfahrung. Wenn du das erstmal drauf hast und danach auch entsprechend "saubere" Grundkonzepte aufstellst wirst du dich später selbst fragen : "Was hab ich mir denn dabei eigenlich gedacht ?".

Danke an alle, ich habe glaube mal das Prinzip verstanden.
Ich hätte noch eine Frage, wenn ich zum bsp. in der Class „a“ einen Player erstelle und ihm Variablen usw. zuweise und nun möchte ich dann über Class „b“ Daten über diesen Spieler abrufen, wie geht das dann genau?

[SPOILER]
Player Class:
Wäre nett wenn jemand mal drüber schaut ob alles so ok ist. :slight_smile:

package Player;

public class Player {
	private float health;
	private boolean alive, reg, con;
	private int MAX_PLAYERS = 151, id;
	private boolean[] varid = new boolean[MAX_PLAYERS];
	private String name;
	
	Player(String s)
	{
		if(this.id >0) return;
		this.SetPlayerName(s);
		for(int i = 1; i<MAX_PLAYERS; i++){
			if(varid** == false){
				this.id = i;
				System.out.println("Dem Spieler '"+ this.name + "' wurde die ID " + this.id +" zugewiesen.");
				break;
			}
		}
	}
	
	public boolean SetPlayerName(String n)
	{
		if(n.length()==0) return false;
		this.name = n;
		return true;
		
	}
	
	public int GetID(){
		return this.id;
	}
	
	public boolean IsPlayerConnected(){
		return this.con;
	}
	
	public boolean IsPlayerReg(){
		return this.reg;
	}
	
	public void SetPlayerHealth(float h)
	{
		if(h < 0.0 && this.alive == true) {
			this.health = (float) 0.0;
			this.alive = false;
			return;
		}
		if(h > 0.0) {
			this.health = h;
			this.alive = true;
		}
	}
	
	public float GetPlayerHealth() {
		return this.health;
	}

	public boolean IsPlayerAlive() {
		return this.alive;
	}
}

```[/SPOILER]

Zwei Sachen, es gibt immer nur zwei Sachen.

A erhält Referenz zu B und ruft darauf Methoden auf
A erstellt B und ruft darauf Methoden auf
oder C ist in Wirklichkeit A und erhält Referenz zu B, also C ‘registriert’ sozusagen B, und in B kann dann die action stattfinden.

Der Code hat einige Probleme. Zuerst einmal die Namenskonventionen: Methoden werden in Java klein geschrieben.

Dann: Ein Setter gibt normalerweise keinen Wert zurück. Wird ein illegaler Wert übergeben, wirft man einen Fehler (falls du das schon hattest). Und bevor du auf die Länge testest, solltest du vorher schauen, ob der String nicht null ist (was beim Zugriff zur allseits beliebten NullPointerException führen würde).

public boolean SetPlayerName(String n)
{
    if(n == null || n.isEmpty()) {
       throw new IllegalArgumentException("Name darf nicht null oder leer sein");
    }
    this.name = n;
}

Dann der Konstruktor: Erst einmal kann man if(varid** == false) besser als if(! varid**) schreiben.

Außerdem ist if(this.id >0) immer falsch, weil numerische Instanzvariablen mit 0 vorbelegt werden.

Dann ist auch die Logik der ID-Vergabe nicht in Ordnung: Wenn du wirklich ein Array für die ID-Verteilung verwenden willst, musst das Array zum einen statisch sein (sonst hast du für jeden Player ein eigenes), zum andern musst du varid** auch auf true setzen, damit die entsprechende ID dann als “vergeben” gekennzeichnet ist. Allerdings sehe ich nicht so richtig, warum ein einfacher Zähler (natürlich auch statisch) hier nicht ausreichen sollte:

private static int counter = 0;
...
   Player(String s)
   {
        this.SetPlayerName(s);
        this.id = counter;
        counter = counter + 1;  // oder kürzer: counter++;
    }

@Landei : Wenn ich meine for Schleife mit 1 beginne, statt mit der 0, dann wäre es die Zeile:
if(this.id >0) doch richtig, denn jede nicht ausgefüllte ID wäre invalid.

Und was bewirkt throw new IllegalArgumentException("Name darf nicht null oder leer sein");

Danke außerdem noch für die Hilfe :slight_smile:

package Player;

public class Player {
	//private float health;
	//private boolean alive, reg, con; // macht man nicht
        //private int MAX_PLAYERS = 151, id; // macht man nicht
	//private boolean[] varid = new boolean[MAX_PLAYERS];
	//private String name;

	private final int MAX_PLAYERS = 151; // sollte konstant sein
	private boolean[] varid = new boolean[MAX_PLAYERS]; // arraybelegung doppelt gespeichert


        // die hier gehören in eine eigene Klasse, die EINEN Spieler darstellt
        private id;
        private String name;
        private float health;
        private boolean alive;
        private boolean registered; // unterschied registered und connected?
        private boolean connected; // keine kurzen Namen verwenden




        // unlogisch: jeder Player enthält eine komplette Liste von Playern??
	
	Player(String s) // Konstruktor normalerweise public
	{
		if(this.id >0) return; // immer???
		this.SetPlayerName(s); // warum mit setter??? Rückgabewert nicht verwendet, wozu das ganze?
		for(int i = 1; i<MAX_PLAYERS; i++){ // arrayindex beginnt bei 0?
			if(varid** == false){ // if(!varid**)??
				this.id = i;
				System.out.println("Dem Spieler '"+ this.name + "' wurde die ID " + this.id +" zugewiesen.");
				break;
			}
		}
	}


	
	public boolean SetPlayerName(String n)
	{
		if(n.length()==0) return false; // null abfragen? ersetze null durch ""
		this.name = n;
		return true;
		
	}
	
	public int GetID(){
		return this.id;
	}
	
	public boolean IsPlayerConnected(){
		return this.con;
	}
	
	public boolean IsPlayerReg(){
		return this.reg;
	}
	
	public void SetPlayerHealth(float h)
	{
		if(h < 0.0 && this.alive == true) {
			this.health = (float) 0.0;
			this.alive = false;
			return;
		}
		if(h > 0.0) {
			this.health = h;
			this.alive = true;
		}
                // besser ersetzen durch else klausel, leichter lesbar
		if(h > 0.0) {
                        this.health = h;
                        this.alive = true;
		}else{
                        this.health = 0.0f;
                        this.alive = false;
                }
	}
	
	public float GetPlayerHealth() { // klein gschreiben getHealth
		return this.health;
	}

	public boolean IsPlayerAlive() { // klein schreiben isAlive
		return this.alive;
	}
}

Hauptproblem: Vermischung von Spieler und Spielerliste, kann NIE so funktionieren, Fehler in der Logik

du brauchst eine Klasse Player und eine seperate Klasse für die Liste der Spieler, anders geht das nicht

*** Edit ***

package com.beispiel;

public class Player {
    private final int id;
    private final String name;
    private float health;
    private boolean registered;
    private boolean connected;
    
    public Player(int a_id, String a_name, float a_health){
    	this.id = a_id;
    	this.name= null==a_name ? "" : a_name;
    	this.health = a_health;
    }
    
    public Player(int a_id, String a_name){
    	this(a_id,a_name,100f); // default-health 100
    }

	public int getId() {
		return id;
	}

	public float getHealth() {
		return health;
	}

	public void setHealth(float health) {
		this.health = health;
	}

	public boolean isAlive() {
		return this.health > 0;
	}
    //TODO: was genau bedeuten  id, registered und connected?   equals/hashcode? 
}

[QUOTE=Zero]@Landei : Wenn ich meine for Schleife mit 1 beginne, statt mit der 0, dann wäre es die Zeile:
if(this.id >0) doch richtig, denn jede nicht ausgefüllte ID wäre invalid.[/quote]

Diese Zeile steht als allererstes in deinem Konstruktor, id bekommt weder bei der Definition noch sonstwo vorher einen Wert zugewiesen - deshalb kann an dieser Stelle nichts anderes als 0 drinstehen. Wenn das Player-Objekt fertig instantiiert (also der Konstruktor „durchgelaufen“) ist, sieht das natürlich anders aus. Aber Reihenfolge ist nun mal wichtig - oder rennst du mit den Socken über deinen Schuhen rum?

Und was bewirkt throw new IllegalArgumentException("Name darf nicht null oder leer sein");

Danke außerdem noch für die Hilfe :slight_smile:

Es wird eine Ausnahme „geworfen“, eine Art Notsignal. IllegalArgumentException ist eine ganz normale Klasse, die sagt, was an der Stelle „schiefgelaufen“ ist. Die Abarbeitung der aktuellen Methode wird durch throw sofort beendet, auch die Arbeitung der diese aufrufenden Methode u.s.w. bis zur main-Methode, und dann zeigt die Java Runtime den Fehler und und dessen Fehlermeldung auf der Konsole an (so man denn eine offen hat). Natürlich ist das nicht immer das Verhalten, das man haben will, deshalb kann man an irgendeiner Stelle der Aufrufkette die Ausnahme auch „auffangen“ und darauf irgendwie vernünftig reagieren (eine Fehlermeldung anzeigen, einen Standardwert setzen, die Aktion nochmal versuchen - sowas in der Art).

Es gibt gute Gründe, das über Ausnahmen und nicht über spezielle Rückgabewerte zu lösen: Der Aufrufer selbst kann entscheiden, ob und wo er auf eine bestimmte Ausnahme reagiert (und so z.B. in größeren Systemen die Fehlerbehandlung besser zentralisieren kann), es kann auch leicht zwischen verschiedenen Fehlerarten unterschieden werden, es können abschließende „Aufräumaktionen“ definiert werden, die auch im Fehlerfall ausgeführt werden (finally) und vor allem kann der Fehler nicht versehentlich ignoriert werden (man kann ihn natürlich gezielt ignorieren, wenn man das will).

Die Details: Galileo Computing :: Java ist auch eine Insel – 6 Exceptions

@Bleiglanz :
Warum genau soll ich den gewisse Variablen, die dem selben Typ entsprechen nicht in eine Zeile schreiben, dass er scheint mir seltsam, dass man sowas nicht macht.

[QUOTE=Zero]@Bleiglanz :
Warum genau soll ich den gewisse Variablen, die dem selben Typ entsprechen nicht in eine Zeile schreiben, dass er scheint mir seltsam, dass man sowas nicht macht.[/QUOTE]
Das ist natürlich nur eine Geschmacksfrage (kein muss), aber in Java sieht man das eben eher selten. Ich persönlich finde es lesbarer, klarer.

The standard Java convention is to recommend rather than require one declaration per line.

[QUOTE=Zero]@Bleiglanz :
Warum genau soll ich den gewisse Variablen, die dem selben Typ entsprechen nicht in eine Zeile schreiben, dass er scheint mir seltsam, dass man sowas nicht macht.[/QUOTE]

Mehrere Gründe:

  • Es kann sein, dass man vorn den Typ ändert und damit hinten eine Variable in der Liste “erwischt”, die eigentlich so bleiben sollte
  • Es kommen manchmal Wertzuweisungen dazu, dann geht das umkopieren los, oder es wird unübersichtlich: List<String> list1 = new ArrayList<>(), list2 = new LinkedList<>();
  • Bei Arrays kann es auch lustig werden: String[] x,y[][],z[]; ist völlig legal, aber nicht besonders lesbar

Ich würde sagen, dass man das nicht so dogmatisch sehen sollte. Wenn ich einen Vektor mit x,y und z habe, die Variablen also auch logisch zusammengehören (ich aber kein Array verwenden will, z.B. bei der Grafikprogrammierung), kann man sie auf eine Zeile packen. Wenn sich dort etwas ändern sollte (etwa der Typ von double auf float), dann wird das auf alle drei Variablen zutreffen. Bei einer Adresse sähe es schon anders aus: Für die PLZ ist String vielleicht in Ordnung, aber später bekommt sie vielleicht ihre eigene Klasse mit entsprechender Prüfung. Ob man Vorname und Name bzw. Straße und Hausnummer einzeln haben will, oder besser zu zweikomponentigen Typen zusammenfasst, kann sich auch ändern. Der Typ des Landes kann sich von String zu einem Enum verändern u.s.w.

[QUOTE=Zero]@Bleiglanz :
Warum genau soll ich den gewisse Variablen, die dem selben Typ entsprechen nicht in eine Zeile schreiben, dass er scheint mir seltsam, dass man sowas nicht macht.[/QUOTE]

Naja, es ist auch nichts falsch dran, und jeder hat halt seinen eigenen Stil, aber es hat sich in Java nun mal so eingebürgert das man jedes Member in eine extra Zeile schreibt.
Das hat hauptsächlich was mit der Leserlichkeit und dem Lesefluss zu tun. Und es macht es halt einfacher wenn alle “den gleichen Dialekt” sprechen (siehe zum Vergleich die verschiedenen Dialekte der deutschen Sprache).
Man findet sich halt als geübter Entwickler der sich gewisse “Quasi-Standards” angeeignet hat in fremden Code besser zurecht wenn dieser den gleichen Regel folgt anstatt sich immer erst wieder an jede Schreibweise gewöhnen zu müssen.

Klar, wenn du für dich selbst was schreibst kannst du es ruhig machen, aber spätestens wenn du in einem Team programmierst wirst du feststellen das es für alle angenehmer ist. Daher solltest du es dir gleich “richtig” angewöhnen (wobei “richtig” hier der falsche Ausdruck wäre, denn bei sowas gibt es halt kein “richtig” oder “falsch”, dafür aber halt “best / common practice”).

Wenn man schon mehrere Variablen in eine Zeile schreibt ist das meist ein Zeichen dafür, dass eine Klasse eh zu viele davon hat. Ich empfinde das auch als nachlässigen und schlechten Stil. Man kann sie z.B. auch nicht einzeln dokumentieren mit einem Java-Doc und man kann sie leicht übersehen und wie schon gesagt ist eine Änderung des Typs leicht fehleranfällig.

Wenn Variablen sprechende namen haben - und das sollten sie unbedingt - macht es auch wenig Sinn, sie hintereinander zu schreiben bei der Deklaration.