Equals-Methode auf Class-Object

  • Themenstarter Themenstarter erster
  • Beginndatum Beginndatum
E

erster

Gast
Malzzeit,


folgende Frage:
Jedes von Object erbende Objekt besitzt ja bekanntermaßen die Funktion .getClass(). Was genau spuckt diese eigtl. aus, eine neue Instant der Laufzeitklasse oder eine Referenz auf die Klasse selbst (wie auch immer das gehen mag)? Das wäre nämlich wichtig zu wissen, um zu erfahren, ob zwei Attribute (siehe folgender Code) denselben Datentypen besitzen

Java:
AttributeMetaData<Integer> am1 = new AttributeMetaData<Integer>("myInt");
		AttributeMetaData<Double> am2 = new AttributeMetaData<Double>("myDouble");
		AttributeMetaData<Integer> am3 = new AttributeMetaData<Integer>("MyInt");
		
		System.out.println(am1.equals(am2));
		System.out.println(am1.equals(am3));
		System.out.println(am2.equals(am1));
		System.out.println(am2.equals(am3));
		System.out.println(am3.equals(am1));
		System.out.println(am3.equals(am2));
		System.out.println(am1.getClass());


Im Endeffekt soll herauskommen, dass zumindest am1 und am3 einer Gleichheitsmethode genügen, damit ich feststellen kann, ob das Attribute mit den Metadaten AttributeMetaData bereits vorhanden ist. Da
Code:
am1.getClass()
mir jedoch nur class AttributeMetaData ausspuckt (und nicht den aktuellen Datentypen des Templates AM), kann ich damit nichts anfangen.

Wie genau sähe also ein Gleichheitstest aus, wenn vom Template AM Datentyp UND Name identisch sein sollen? Das folgende jedenfalls schonmal nicht:
Java:
public boolean equals(AttributeMetaData a) {
		if (this.getClass().equals(a.getClass()) && this.name.equals(a.name)) return true;
		return false;
	}
 
Generics existieren zur Laufzeit nicht mehr (Stichwort Type Erasure). Wenn du den Typen zur Laufzeit noch brauchst dann musst du dem Konstruktor ein Class<T>-Objekt übergeben und intern speichern.
 
> eine neue Instant der Laufzeitklasse oder eine Referenz auf die Klasse selbst
wäre kinderleicht zu testen statt zu fragen

> this.name.equals(a.name)
wird bei "myInt" und "MyInt" doch wohl eh nichts gleiches bringen
 
wird bei "myInt" und "MyInt" doch wohl eh nichts gleiches bringen

Ups, Schreibfehler, natürlich müssten beide MyInt heißen...
Dass dann eine Gleichheit rauskommt, ist mir schon klar, schließlich sind das nur Strings... Aber ich will ja auch den Typen des Templates vergleichen und nicht nur den Namen...

@Marco: deine beiden Links arbeite ich gerade durch, ist aber hartes Holz...
@andiv Das is schonmal ´ne Idee... Das heißt, es sollte einen Konstruktor geben, der
Code:
public AM<T> (Class<T> c)
heißt? Und wie ruf ich den auf?
Code:
AM<Integer> = new AM<Integer>(Integer.class)
?
 
so in etwa,

es kann aber schon gleich überlegt werden ob dir das Generics überhaupt etwas bringt?
wie verwendst du die Klasse? MetaData und Attribute sind beides Stichwort für relativ allgemeine Daten, die in Listen gehalten,
automatisch per Schleife abgearbeitet werden, dabei kann Generics normalerweise nicht genutzt werden, wie man an der equal-Methode bereits sieht,

vielleicht solltest du (nur) die Klassen, oder besser noch eine schöne neue Enum AttributeType, speichern und danach vergleichen/ switchen wenn nötig usw.
 
Verdammt... ich hatte befüchtet, dass das in einer Sackgasse endet...


Mein erster Versuch gin schon in die Richtung de Enum, ich hatte also eine Klasse AttributeMetaData mit de rMethode
Code:
int getDataType(String key)
und eine Klasse Attribute, welche als Eigenschaft eine Liste von AMs beinhaltet. Ich hatte nur gehofft, dass ich mit dem Template beim Einfügen eines neuen key-Value-Paares in einen Datensatz das Überprüfen auf Zulässigkeit einfacher hinbekomme (weil dann die Datentypen ja gleich vom Compiler geprüft werden...). Wie ´s scheint, scheitert das jedoch bereits daran, dass der Datentyp eines Templates ja eh erst zur Laufzeut bekannt ist, wonach der Compiler an de Stelle nicht viel machen kann... schade eigtl^^.


Also nur nochmal zum Zusammenfassen:

ein Datensatz (nennt sich hier Row) besitzt
eine HasMap<String key, Object value> values sowie
eine ArrayList<String key, int dataType> dataType.

Jedes mal, wenn ich nun in meine Row ein neues Attribut belege, muss ich diese beiden Sets also konsistent halten, sprich mich um deren Management selbst kümmern...

Was nun allerdings bei Class.equals(Class) herauskommt, wüsst ich immernoch gerne^^
 
eine ArrayList<String key, int dataType> dataType.
Ups, ich meine natürlich eine HashMap und keine ArrayList^^.


Btw.: ich glaube, nachdem ich nun unter dem Pseudonym schon einige Beiteäge geschrieben hab, werd ich mich mal hier im Forum anmelden 😀
 
> Was nun allerdings bei Class.equals(Class) herauskommt, wüsst ich immernoch gerne^^

teste es doch einfach, du kannst die Class-Objekte sogar mit == vergleichen, haben Enum-Qualität
 
So geht´s nun endlich 😀

Java:
AttributeMetaData am1 = new AttributeMetaData("MyInt", Integer.class);
		AttributeMetaData am2 = new AttributeMetaData("myDouble", Double.class);
		AttributeMetaData am3 = new AttributeMetaData("MyInt", Integer.class);
		
		System.out.println(am1.equals(am2));
		System.out.println(am1.equals(am3));
		System.out.println(am2.equals(am1));
		System.out.println(am2.equals(am3));
		System.out.println(am3.equals(am1));
		System.out.println(am3.equals(am2));
		System.out.println(am1.getClass());


Java:
public class AttributeMetaData {

	public String name;
	private Class<?> c;
	
	public AttributeMetaData() {}
	public AttributeMetaData(String name, Class<?> c) {
		this.name = name;
		this.c = c;
	}
	
	public Class<?> getDataType() {return this.c;}
	public boolean equals(AttributeMetaData a) {
		if (this.getDataType() == a.getDataType() && this.name.equals(a.name)) return true;
		return false;
	}
}

Die Idee mit der Angabe der Klasse im Konstruktor hat dann schlussendlich doch den entscheidenden Hinweis enthalten...

Vielen Dank für die umfangreiche Unterstützung
 
übrigens überschreibst du damit derzeit nicht die Object-Methode equals(Object),
für Listen-contains und sonstige allgemeine Verwendungen ist deine equals-Methode bisher nicht geeignet

(und bevor jemand anders schreien muss: wenn das richtige equals geändert wird, dann sollte nach Richtlinien auch die hashCode-Methode passend implementiert sein, a.equals(b) ==> Hashcode gleich)
 
@TO So sollte es aussehen:

Java:
  @Override
  public boolean equals(Object o) {
        if(!(o instanceof AttributeMetaData)){ // impliziert o == null
            return false;
        }
        AttributeMetaData a = (AttributeMetaData)o;
        if(!this.c.equals(a.c)){
            return false;
        }
        if(!this.name.equals(a.name)){
            return false;
        }
        return true;
  }

  @Override
  public int hashCode(){
        return c.hashCode() + name.hashCode();
  }

Du kannst die hashCode()-Methode noch besser implementieren, indem du zB Primzahlen mit reinrechnest. Wichtig ist nur, dass gilt:

Code:
a1.equals(a2) => a1.hashCode == a2.hashCode()

Andersrum muss es nicht gelten, sollte es aber für möglichst gute Performanz bei Hash-gestützten Algorithmen (wie sie zB diverse Methoden von Collections nutzen).

PS: Getter sollten sich immer an folgende Namenskonvention halten:

Java:
private <type> varName;

public <type> getVarName(){ return varName; }

Also entweder du nennst deine Instanz-Varialbe "dataType", oder du nennst den Getter "getC".
 
Hab mich jetzt mal hie im Forum angemeldet... wie ihr spätestens jetzt sehen dürftet^^

Das mit dem Overrdie ist mir auch schon aufgefallen, habe mich schon gewundert, warum Eclipse bei der @Override-Annotation meckert... Werde die Signatur der Methode mal anpassen...

Zu den HashCode-Methoden muss ich gestehen: uch hab bis heute nicht kapiert, wofür die überhauot verwendet werden... muss ich mich mal schlau machen. Die HashMap hab ich nur verwendet, weil ich nach einer Liste mit Schlüssel-Werte-Paaren gesucht hab, die (im Gegensatz zur HashTable) Duplikate zulässt und null-Werte.


EDIT:
Also entweder du nennst deine Instanz-Varialbe "dataType", oder du nennst den Getter "getC"
Ja, das is schon klar... der Grund hinter dieser Inkonsequenz war, dass ich mir nicht sicher war, ob ich die Methode, die mir den Datentyp ausspuckt (welche ja nix weiter als die Klasse des Attributes zurückgint), getClass() nennen darf (nach Konvention), ober ob getClass() schlichtweg AttributeMetaData zurückgeben sollte.
 
Zuletzt bearbeitet:
Zu den HashCode-Methoden muss ich gestehen: uch hab bis heute nicht kapiert, wofür die überhauot verwendet werden...
Hashing-Verfahren sind dazu da um aus einer (großen) Menge von Daten möglichst schnell einen beliebigen Datensatz ermitteln zu können, und zwar in Fällen in denen der Speicherplatz für die Abbildung des gesamten Datenraums nicht ausreicht. Grundsätzlich ist die performanteste Lösung ja Random Access. Also ein Array, wo du ja direkt sagen kannst: Datensatz an der Stelle 234. Problem dabei ist, dass du irgendwann ans Limit des Speicherplatzes kommst. Angenommen du willst Farben (im RGBA-Format) abspeichern, und möglichst schnell neue Datensätze einspeichern, löschen und suchen. Ein Array würd das anbieten (konstante Zugriffszeit) , aber du hast einen Datenraum von 256^4 Permutationen, also für jede mögliche Kombination ein Fach. Du kannst aber kein Array dieser Größe anlegen, da der Speicherplatz nicht reicht . Also musst du das Array kleiner machen und und mehrere Datensatz in einem Fach speichern (Also 2-dim Array). Und Hashing beschreibt nun den Vorgang, einen Dantesatz anhand einer gewissen Funktion einem solchen Fach zuzuweisen, also ein Mapping aus dem gesamten theoretisch möglichen Datenraum in den verfügbaren Datenraum. Je weniger Datensätze in einem Fach, desto performanter arbeitet die Datenstruktur. Und welche Werte in welchem Fach landen, und damit wieviel in ein und dem selben Fach sind, das besimmt die Hashfunktion. In Java liefert dir hashCode() den Index für das Fach, in das der Datensatz reinkommt. Die ganzen Collections sind hash-gestüzt, d.h. sie halten intern so ein Array und sie rufen hashCode() auf um rauszufinden wo ein Element ist oder riengehört. Damit das gut klappt muss gelten:

o1.equals(o2) == true => o1.hashCode() == o2.hashCode()

Und idealerweise gilt auch:

o1.equals(o2) != true => o1.hashCode() != o2.hashCode()

Aber letzteres kannst du eben nicht für alle Fälle erreichen, wobei man zB durch Verrechnung mit Fremdfaktoren eine ganz gute Aufteilung erreicht (zB Primzahlen unterrechnen). Aber das klappt eben nicht immer denn sonst hättest du gleich den gesamten Datenraum nehmen können. In so einem Fall liegen mehrere Datensätze in einem Fach und das ganze wird unperformanter.

Hm, das geht leider nicht so schnell richtig zu erklären. Aber zumindest hast du jetzt vllt ne grobe Vorstellung wofür das da ist. Es geht dabei um effiziente Datenstrukturen, d.h . Performance. Ich hab auf meiner Website Java Video Kurs - Java Video Tutorials - Online Java lernen! ein Video wo ich das ganz genau anhand eines Beispiel-Programms erkläre. Bzw 2 (hashCode & Collections). Bevor du umsonst nachkuckst: Der Kurs ist kostenpflichtig!

Ich hoffe du hast Verständnis dass ich das jetzt nicht runtertippe, weil das sind gut 40 Minuten und das wär ein riesen Roman, außerdem muss man das sehen.

Ja, das is schon klar... der Grund hinter dieser Inkonsequenz war, dass ich mir nicht sicher war, ob ich die Methode, die mir den Datentyp ausspuckt (welche ja nix weiter als die Klasse des Attributes zurückgint), getClass() nennen darf (nach Konvention), ober ob getClass() schlichtweg AttributeMetaData zurückgeben sollte.
Wenn du ne Variable vom Java-Typ Class hast ist - so kenne ich es zumindest - die Konvention, diese "clazz" zu nennen (da class ein Keyword ist). Entsprechen heißt der getter dann getClazz().
 
Zuletzt bearbeitet:
Das reicht mir als Erklärung erstmal... da ich wahrscheinlich sehr große Datenbestände zu verwalten habe, ist der Hinweis mit den Hashes durchaus brauchbar, also werde ich mir das mit der Hash-Funktion nochmal genauer angucken. :rtfm:

Der Platz sollte bei 256^4 Permutationen (warum überhauot hoch vier, welchen Farbraum gedenkst du zu verwenden? Ahh, habs gefunden, ignorier die Frage^^) aber durchaus reichen, 256^3 sind die bekannten 16,7Mio Farben, , das mal vier macht dann irgendwas um die 40Mrd. Bei 8Bit Kodierung macht das also etwas mehr als 1Bio. Bit (also ungefähr 125Mrd. Byte)... Hmmmmmmm... Das is doch irgendwie etwas viel 😀 na gut, vergesst diesen Absatz einfach *selfconfuse*
 
Der Platz sollte bei 256^4 Permutationen aber durchaus reichen
16GB RAM.. Ts, da hat mein Taschenrechner ja mehr 😀 Aber selbst wenn du das hast: Das Maximum der Anzahl an Fächern für ein Array ist Integer.MAX. Das entspricht etwa 4GB Speicherverbrauch.

Ich geb dir jetzt doch kurz das Bsp aus meinem Kurs, damit du das mal in der Praxis siehst:
Mach dir mal ne Klasse mit 3 Instanz-Variablen x,y,z. Implementiere equals so dass für zwei Instanzen dieser Klasse gilt: equals() liefer true wenn die Werte von x, y und z jeweils gleich sind. Dann implementier hashCode wie folgt:

int hashCode(){
return x+y+z;
}

Dann mach dir in nem Programm mal etwa 100000 Instanzen. Lass dir für x,y und z jeweils zufällige Zahlen erzeugen aber achte darauf dass die Summe jeweils 0 ist! Also:

x = zufälliger int
y = zufälliger int
z = -(x+y);

Dann mach mal n HashSet und kopier alle 100000 Instanzen da rein mit add(), und stopp die Zeit.

Danach änderst du hashCode, zB:

Java:
int hashCode(){
   return 13*x+7*y+(x*z) - 97;
}

Dann start dein Testprogramm erneut und vergleiche die Zeit, die es gebraucht hat. Es wird ungefähr 1000 (!) mal schneller gehen.

Grund: Mit der ersten Hash-Code implementierung gibt's intern nur ein Bucket (Array-Fach), und dort liegen alle 100000 Elemente. Denn hashCode liefert für alle 0. Innerhalb eines Buckets von 100000 Elementen eines rauszusuchen dauert..

Dahingegen mit der zweiten Hashcode-Implementierung gibt's viel mehr verschiedene Buckets. Vllt steckt in dem ein oder anderen mehr als ein Element drin, vllt 2, 3 oder 4. Aber nicht viel mehr. Und ein Element aus 4 zu suchen geht eben 1000 mal schneller als es aus 100000 rauszusuchen.

Das finden des buckets selbst geht nämlich in konstanter Laufzeit (Random Access).

Also die Quintessenz: Je mehr unterschiedliche hash-codes es für per equals ungleiche Elemente gibt, desto besser die Performance. Und das ist ein exponentieller Unterschied. Wenn man sowas bei ner Datenbank-Implementierung verkackt dauert deine Google-Suche Anfrage etwa 2387324987324892394873289432 Jahre. 😉
 
Zuletzt bearbeitet:
Wenn man sowas bei ner Datenbank-Implementierung verkackt dauert deine Google-Suche Anfrage etwa 2387324987324892394873289432 Jahre.
Hab´s ja kapiert, aber trotzdem Danke für die Verdeutlichung 😀

Ich sollte also, wenn davon auszugehen ist, dass von meiner Klasse eine gewisse Anzahl Instanzen erstellt wird und ich in der Klasse die equals-Methode überschreiben will, auch gleich mal die hashMethode auch noch mitspendieren, richtig?
 
Richtig. Gelten muss:

o1.equals(o2) == true => o1.hashCode() == o2.hashCode()

D.h. du musst alle Variablen, die in der equals-Auswertung genutzt werden, auch in die Berechnung des hashCodes einfließen lassen.

Und gelten sollte:

o1.equals(o2) != true => o1.hashCode() != o2.hashCode()

Das ist kein Muss (und erweist sich zT als sehr schwierig) aber für je mehr Instanzen-Kombinationen das zutrifft, desto schneller arbeiten die Collections.
 

Zurück
Oben