charAt-Methode liefert falsche Unicode-Werte

Finrod Amandil

Neues Mitglied
Sehr geehrte Community,



Um was es geht:
Viele komplizierte Sachen, doch ich glaube, die helfen, das Ganze besser zu verstehen...

Im Rahmen meiner gymnasialen Abschlussarbeit baue und programmiere ich eine Lego-Mindstorms-Roboter in Java.
Dieser Roboter soll Texte in einer Schrift schreiben können, die sich Tengwar nennt (für den Fantasy-Fan: Ja, das ist die Schrift der Elben vom Herrn der Ringe). Diese Schrift ist ziemlich unbekannt und somit nicht in der Unicode-Tabelle enthalten; es gibt aber Fonts, um diese Schrift darstellen zu können. Ein Schriftzug mit einem solchen Font ist der Input für mein Java-Programm.

Da, wie gesagt, diese Zeichen nicht in der Unicode-Tabelle enthalten sind und die Tastaturbelegung nicht mit der "unsrigen" normalen übereinstimmt, ist der eigentliche Input ein ziemlich abstrakter String. Der Schriftzug für "Lego" etwa wird in dieser Form eingelesen: "jx$`N".

Ich möchte nun eine switch-Struktur, bei dem für jedes Zeichen ein anderer case durchgeführt werden soll. Da die switch-Anweisung bekanntlich nicht mit Strings funktioniert, möchte ich mit der charAt-Methode einen zeichenspezifischen Integer-Wert bekommen.


Programmcode eines Testprogramms, das den Unicode-Wert eines (jeweils nur 1 Zeichen langen) Strings ausgeben soll:
Java:
import java.io.*;

public class Unicode {
    public static void main(String[] args) {
        String eingabe = null;

        //Einlesen
        BufferedReader reader;
	reader = new BufferedReader(new InputStreamReader(System.in));
	System.out.print("Input: ");
	try{
            eingabe = reader.readLine(); 
	}
        catch (IOException ioe){
            System.out.println("Fehler beim Einlesen.");
	}
        // Einlesen fertig

        int n = eingabe.charAt(0);
        System.out.println(n);
    }
}

(Es geht hier hauptsächlich um die Zeile 19)

Mein Problem:
Das Ganze klappt ja ganz ordentlich, bei den einen Zeichen zumindest... Doch bei vielen Zeichen kommt immer derselbe Output, nämlich 65533 (Einer der hintersten möglichen Unicode-Werte).

Fehlerhafte Zeichen sind u.a. ª µ † und viele andere...

Wär echt froh, wenn mir da jemand helfen könnte, eventuell auch mit einer ganz anderen Variante?

Mit freundlichen Grüssen,
Finrod Amandil


EDIT:

Folgende Zeichen funzen nicht (inkl. zu erwartender Unicode-Wert)
ª (170)
¯ (175)
µ (181)
ƒ (131)
… (133)
„ (132)
† (134)
Ô (212)
Õ (213)
und weitere
 
Zuletzt bearbeitet:
Vielen Dank, funktioniert super so!

Dass man switch-Anweisungen auch mit Strings verwenden kann find ich super; da kann aber sogar meine Informatiklehrerin noch was lernen (von der hab ichs nämlich, dass es nich geht) 😉

Also nochmals recht herzlichen Dank für die schnelle Hilfe!
lg Finrod Amandil
 
das ist ja auch soweit richtig ... denn auch java 7 und alle weiteren versionen werden nie native switch w/ string können ...
was aber seit java 7 geht ist das du im source switch w/ string stehen haben kannst und der compiler daraus automatisch ein normales int-switch baut ... und dafür wird die methode "hashCode()" missbraucht ...


wenn du also mit einem java7+ compiler diesen source durchlaufen lässt :
Java:
String string=new String("abc");
switch(string)
{
case "abc":
//...
break;
case "def":
//...
break;
default:
//...
}
macht der compiler daraus eigentlich nur
Java:
String string="abc";
switch(string.hashCode())
{
case 1234:
//...
break;
case 5678:
//...
break;
default:
//...
}
das bekommt man raus wenn man sich solchen code mal durch n de-compiler ansieht ...

und genau desswegen kann man auch switch w/ string in allen früheren versionen als Java7 nutzen ... wichtig ist nur das man sich selbst diese mühe mit den hashCode's macht ...

darum hätte man korrekterweise in den update-news von java7 eigentlich nur schreiben dürfen das nun der compiler in der lage ist dies selbst zu einem "normalen" switch umzubauen ...

von daher ist nicht nur das argument "geht ab java7" gültig ... sondern deine lehrerin hatte allgemein unrecht ... denn mit dem gewissen aufwand ging das schon immer ... und ich wette es gab schon in 1.0 leute die das gemacht haben .. das es bis 7.0 gedauert hat bis es endlich im compiler gelandet ist zeigt doch nur wie die entwickler (sun/oracle) mit der java-community zusammenarbeiten : so gut wie gar nicht
 
Lieber Troll, du weißt aber schon, dass Hashcodes bei Strings nicht eindeutig sind?

Und wer [c]String string=new String("abc");[/c] schreibt, frisst auch kleine Kinder...
 
sorry für den wort-witz ... aber jetzt trollst du den troll ...

hast du denn überhaupt schon mal in den source von java.lang.String gesehen ? scheinbar nicht ... denn String überschreibt hashCode EINDEUTIG !

Java:
/** Cache the hash code for the string */
    private int hash; // Default to 0
// ...
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
Oracle v7u7 x64

und zum "new String()" > war lediglich als beispiel für optimierung ... denn wenn du den code richtig gelesen hättest hättest du auch gesehen das ich im zweiten code direkt "string="abc"" geschrieben habe ...



und genau sowas finde ich immer wieder sehr belustigend ... user mit sehr vielen posts und eigentlich auch gutem fachwissen versagen an solch elementaren grundlagen ... gleich ob es daraus resultiert weil man sich nicht mit befasst oder es nicht mal selbst vorher testet ... oder halt einfach mal in den source guckt ...
wenn du also das nächste mal behaupten willst das eine spezielle klasse die auf eine spezielle implementierung von hashCode() und equals() angewiesen ist diese angeblich nicht entsprechend überschreiben würde guck bitte nächste mal vorher in den source ... um dir diese peinlichkeit zu ersparen ...


@btt
was mir noch eingefallen ist : man sollte von swith w/ string eh die finger lassen ...
grund : string verwendet intern ein char-array ... und da char nun mal 16bit groß ist hat man die 4,3 mrd möglichkeiten des 32bit int hashcodes bereits mit 2 unicode-chars komplett ausgefüllt ...
ergo : jeder string mit mindestens 3 chars ist potentiell kollisions gefährdet da es immer mindestens bereits eine 2-unicode-char möglichkeit gibt die den selben hashCode besitzt ...
bei sehr vielen abfragen ist also das kollisions-risiko höher ... und kann so groß werden das es "sicherer" ist mit if(String.equals()) zu arbeiten ... oder man rechnet sich halt vorher die benötigten hashes aus (einfach alles durch den compiler schieben) und sucht nach selbst nch kollisionen ...
 
hast du denn überhaupt schon mal in den source von java.lang.String gesehen ? scheinbar nicht ... denn String überschreibt hashCode EINDEUTIG !
Ja, String überschreibt
Code:
hashCode()
eindeutig, aber die Methode liefert nicht eindeutige Ergebnisse. Was ist daran misszuverstehen?

was mir noch eingefallen ist : man sollte von swith w/ string eh die finger lassen ...
grund : string verwendet intern ein char-array ... und da char nun mal 16bit groß ist hat man die 4,3 mrd möglichkeiten des 32bit int hashcodes bereits mit 2 unicode-chars komplett ausgefüllt ...
ergo : jeder string mit mindestens 3 chars ist potentiell kollisions gefährdet da es immer mindestens bereits eine 2-unicode-char möglichkeit gibt die den selben hashCode besitzt ...
bei sehr vielen abfragen ist also das kollisions-risiko höher ... und kann so groß werden das es "sicherer" ist mit if(String.equals()) zu arbeiten ... oder man rechnet sich halt vorher die benötigten hashes aus (einfach alles durch den compiler schieben) und sucht nach selbst nch kollisionen ...
Totaler Quatsch. Du bist allen Ernstes bisher davon ausgegangen, dass switch auf Strings kaputt ist?

Natürlich wird nicht einfach der Hashcode genommen und damit ist die Sache gegessen. Besonders witzig ist deswegen diese Aussage:
macht der compiler daraus eigentlich nur
Java:
String string="abc";
switch(string.hashCode())
{
case 1234:
//...
break;
case 5678:
//...
break;
default:
//...
}
das bekommt man raus wenn man sich solchen code mal durch n de-compiler ansieht ...
Ja, hättest du das mal getan. Ich bin mir nämlich zu 100 % sicher, dass du das nicht getan hast.

Hier ein Beispiel:
Java:
void switchOnString(String s) {
	switch (s) {
		case "":
			break;
		case "a":
			break;
		case "b":
			break;
		case "c":
			break;
		default:
			break;
	}
}
Dekompiliert mit JAD:
Java:
void switchOnString(String s)
{
	String s1 = s;
	byte byte0 = -1;
	switch(s1.hashCode())
	{
	case 0: // '\0'
		if(s1.equals(""))
			byte0 = 0;
		break;

	case 97: // 'a'
		if(s1.equals("a"))
			byte0 = 1;
		break;

	case 98: // 'b'
		if(s1.equals("b"))
			byte0 = 2;
		break;

	case 99: // 'c'
		if(s1.equals("c"))
			byte0 = 3;
		break;
	}
	switch(byte0)
	{
	case 0: // '\0'
	case 1: // '\001'
	case 2: // '\002'
	case 3: // '\003'
	default:
		return;
	}
}

Hier gibts mehr Infos dazu: http://blogs.oracle.com/darcy/entry/project_coin_string_switch_anatomy
 
dann hast du mich nicht richtig verstanden ...

mit dem wort "eindeutig" meinte ich schon das ein fest definierter string auch IMMER denselben fest definierten hashCode liefert ... ergo : jeder string hat einen eindeutigen hashcode ... was deine widersprüchliche aussage , das hashCode zwar eindeutig überschrieben wird aber nicht immer eindeutig das selbe ergebnis liefern würde , irgendwie in sich schon nicht stimmig und IMO falsch macht ... also scheinst du hier etwas misszuverstehen

den source hab ich gepostet ... und von diesem ausgehend kann man mit nem zettel und nem stift beweisen das ein input "X" halt einen festen output "1234" hat ... und zwar IMMER bei jedem call in jeder VM mit jeder version ...

beweis : string "X"
int hash = 0
int h = 0
h = 31 x h + (char) "X" = 31 x 0 + 0x58 = 88

Java:
public class Test
{
	public static void main(String[] args)
	{
		System.out.println("X".hashCode());
	}
}

ausgabe : 88 ... und das jedes mal ...

um also deinen satz korrigiert zu nutzen :
"Ja, String überschreibt hashCode() eindeutig und liefert eindeutige Ergebnisse."



zu deinem code :

ich bestreite nicht das es kombinationen aus compiler und de-compiler gibt die den von dir geposteten code erzeugen ... wollte mit meinem code jedoch zum ausdruck bringen das mit entsprechender optimierung, sowohl im compiler als auch im de-compiler, ein code entsteht der syntax-geämße dem meinem entspricht ... was deiner ja auch tut ... den rein logisch / syntaktisch ist es ja egal ob man erst den hashCode prüft und darauf hin ein steuer-wert setzt und dann diesen noch mal durch ein switch drückt oder ob man an den entsprechenden stellen gleich den code ausführt ...
außerdem muss das ganze ja nachher im byte-code auch so erzeugt werden das es die VM richtig verarbeiten kann ...

das jedoch der erzeugte byte-code abwärtskompatibel ist konnte ich durch eine manipulation des class-files prüfen ... (gleiches verfahren wie zum beweis von type-erasure bei generics angewand wird : im class-file die major-version nachträglich runtersetzen) ... und war in der lage diese class sogar korrekt mit 1.4.2 (hab ich noch auf ner cd) korrekt zum laufen zu bekommen ...

also muss ja der compiler am ende doch bytecode erzeugen der so "einfach" ist das dieser selbst von "ur-alt" VMs korrekt ausgeführt werden kann ...
wie es dann der de-compiler wieder dem menschen lesbar zugänglich macht ist eine andere geschichte ... und ich denke das man sich hier wohl wirklich um das ergebnis streiten kann ...

fakt ist : ich habe den code erhalten den ich gepostet habe und du hast den code erhalten den du gepostet hast ... beide gleichen sich logisch (bis auf die tatsache das ich der einfachheit halber fiktive werte genommen habe) ... wo ist also dein problem ? ...

ich habe von ASM keine ahnung und daher bringt es mir auch nichts wenn ich jetzt den java-disassmbler nutzen würde um daraus zu lesen welches von beiden nun wirklich im bytecode steht ... allerdings scheint es das ich einen "intiligenteren" de-compiler nutze der aus diesem doppel-switch halt nur eins macht ... obwohl mit das if(equals()) auf grund der von mir angesprochenen kollisions-gefahr durchaus klar ist und natürlich auch sinn macht ...
allerdings zeigt es ebenfalls das es deutlich besserer code wäre wenn man anstatt switch w/ string gleich if(equals())-else nutzt ... da der compiler ja scheinbar genau das daraus macht ... und das was dir dein de-compiler liefert ist ja nun mal gegen alle grundregeln von clean-code ... auch wenn der eigentliche source ganz sauber aussieht ...
 
Du interpretierst hier Dinge in Aussagen hinein, die so nie gesagt wurden. Mit Eindeutigkeit war hier einfach nur Linkseindeutigkeit (Injektivität) gemeint. Das hätte aus dem Kontext eigentlich ersichtlich sein sollen.

ich bestreite nicht das es kombinationen aus compiler und de-compiler gibt die den von dir geposteten code erzeugen
Der Compiler war der aktuelle
Code:
javac
. Ich habe das dann auch noch mit
Code:
javap
überprüft. Wenn du dem nicht traust, kannst du dir das Kompilat ja selbst Byte für Byte anhand der JVM-Spezifikation anschauen.

wollte mit meinem code jedoch zum ausdruck bringen das mit entsprechender optimierung, sowohl im compiler als auch im de-compiler, ein code entsteht der syntax-geämße dem meinem entspricht
Das wäre dann falscher Code.

Ein aktueller Decompiler liefert dann einfach den Ausgangscode.

das jedoch der erzeugte byte-code abwärtskompatibel ist konnte ich durch eine manipulation des class-files prüfen
Das war schon durch die Nutzung von JAD bewiesen. Dieser unterstützt nämlich Java 5 nicht.

ich habe den code erhalten den ich gepostet habe und du hast den code erhalten den du gepostet hast
Mithilfe welches Compilers und welches Decompilers?

allerdings zeigt es ebenfalls das es deutlich besserer code wäre wenn man anstatt switch w/ string gleich if(equals())-else nutzt ... da der compiler ja scheinbar genau das daraus macht ... und das was dir dein de-compiler liefert ist ja nun mal gegen alle grundregeln von clean-code ... auch wenn der eigentliche source ganz sauber aussieht ...
Wie der Bytecode letztendlich aussieht, ist doch ziemlich egal.
Aus Performanzgründen nutzt man doch auch gern Inlining. Den resultierenden Code hätte man so auch nie geschrieben.
 

Zurück
Oben