Fragen zur Java-Zertifizierung, Deklarationen und Zugriffskontrolle

Frage

Analysiere die Quellcodezeile

if ( 5 & 7 > 0 ) System.out.println( "Hullahupp-Reifen" );

  1. Diese Zeile lässt sich nicht kompilieren
  2. Diese Zeile lässt sich kompilieren, aber nichts erscheint auf der Standardausgabe
  3. Dieser Code lässt sich kompilieren und das Wort "Hullahupp-Reifen" erscheint in der Standard-Ausgabe.

Innerhalb vom Schleifen und Verzweigungen muss der Typ boolean sein. 5&7 ergeben zwar einen Wert, der verglichen mit 0 wahr oder falsch sein kann, jedoch ist die richtige Antwort A. Das Problem sind die unterschiedlichen Vorrangsregeln für Operationszeichen. Hier bindet 7>0 stärker als 5&7. So wird der Versuch unternommen 5 mit einem Wahrheits-Wert Und zu verknüpfen. Richtig wäre (5&7)>0.

Frage

Welche der Zeilen erzeugt ein Array mit fünf leeren Strings?

  1. String a [] = new String [5];
  2. for (int i = 0; i < 5; a[i++] = "") ;
  3. String a [] = {"", "", "", "", ""};
  4. String a [5];
  5. String [5] a;
  6. String [] a = new String [5];
    for ( int i = 0; i < 5; a[i++] = null ) ;

In Antwort A werden keine Strings erzeugt, sondern nur ein Feld mit 5 Referenzen. Somit ist A falsch. In B werden fünf leere Stings zugewiesen, ebenso in Antwort C, daher sind beide korrekt. Diese Strings sind natürlich nicht individuell, sondern die alle fünf Referenzen werden auf den gleichen Leerstring zeigen. Antwort D und E sind falsche Deklarationen für ein Feld. Antwort F ist falsch, da die Referenz auf Null zeigt, nicht aber auf einen leeren String "".

Frage

Welche Aussage beschreibt am besten die folgende Programmzeile?

String[][] s = new String[10][];

  1. Diese Zeile ist illegal.
  2. Es ist ein zweidimensionales Feld mit 10 Zeilen und 10 Spalten.
  3. Es ist ein Feld von 10 Feldern.
  4. Jedes Element in s ist mit "" initialisiert.
  5. Jedes Element in s ist nicht initialisiert und muss daher vor der Nutzung initialisiert werden.

Diese Zeile kann normal kompiliert werden, daher ist A falsch. C ist dagegen richtig, denn die Programmzeile schafft Platz für ein Array mit 10 Einträgen in einer Dimension. Wie groß die andere Dimension ist, bleibt offen. Daher ist B falsch. Da die andere Dimension in ihrer Größe noch nicht definiert ist, ist ein Zugriff auf ein Element noch ungültig. Daher kann jedes Element auch noch nicht mit einem Leerstring initialisiert sein. D ist also falsch und E richtig.

Frage

Welche Felddeklaration ist korrekt?

  1. String temp[] = new String { "u" "l" "l" "i" };
  2. String temp[] = { "u " "l" "l" "i" };
  3. String temp = { "a", "b", "c" };
  4. String temp[] = { "a", "b", "c" };

Antwort A, B und C erzeugen einen Übersetzungsfehler und D ist korrekt. Bei A und B fehlen für die Feldelemente das trennende Komma. Dann wäre B korrekt. Da A der Versuch einer Deklaration eines anonymen Feldes ist, muss noch hinter dem Typ String Klammern stehen. Korrigierte man A in String temp[] = new String[] { "u", "l", "l", "i" } ließe es sich kompilieren. Bei C fehlen ebenfalls die Klammern, da die geschweiften Klammern Elemente eines Feldes bezeichnen und nicht zu einem String zusammengesetzt werden können.

Frage

Was passiert, wenn folgendes Programm kompiliert wird?

1: public class Clazz {
2:  public static void main( String args[] ) {
3:    int feld[] = new int[]{1,2,3};
4:    System.out.println( feld[1] );
5:  }
6:}
  1. 1
  2. 2
  3. Übersetzungsfehler, da feld vor der Benutzung nicht definiert wurde.
  4. Übersetzungsfehler, da die Feldgröße festgelegt werden muss.

B ist die korrekte Antwort, da wir in Zeile 3 ein anonymes Feld mit den Elementen 1, 2 und 3 anlegen. An der Stelle 1 befindet sich die Zahl 2, da in Java genauso wie in C(++) der Feldindex bei Null beginnt. Da Felder in Java besondere Objekte sind (sogenannte Array-Objekte innerhalb der virtuellen Maschine) weist die Zuweisung in Zeile 2 dem feld Objekt eine gültige Referenz zu. Demnach ist C falsch. Die Feldgröße kann in Java nur durch die spezielle Form des new-Operator festgelegt werden, nicht durch reine Deklaration des Feldes. Also ist auch D falsch.

Frage

Was passiert, wenn folgendes Programm kompiliert wird?

public class Clazz {
  public static void main( String args[] ){
    int feld[] = new int[5];
    System.out.println( feld[0] );
  }
}
  1. Übersetzungsfehler, da feld vor der Benutzung nicht definiert wurde.
  2. null
  3. 0
  4. 5

Das Programm definiert eine Feld und weist der Referenz ein Feld der Größe von 5 int zu. Das Feld ist dabei mit 0 Werten initialisiert. Demnach ist der Zugriff auf feld[0] bis feld[4] erlaubt und jeder der Einträge ist 0. C ist die einzige richtige Antwort.

Fragen zur Java-Zertifizierung, Operatoren und Zuweisungen

Frage

Was ist die Ausgabe, wenn folgende Anweisungen ausgeführt werden?

Boolean b1 = new Boolean( true );
Boolean b2 = new Boolean( true );
Object obj1 = (Object)b1;
Object obj2 = (Object)b2;

if ( obj1 == obj2 )
  if ( obj1.equals(obj2) )     System.out.println( "a" );
  else
    System.out.println( "b" );
else
  if ( obj1.equals(obj2) )     System.out.println( "c" );
  else     System.out.println( "d" );
  1. a
  2. b
  3. c
  4. d

Die Ausgabe des Programms ist „c“, daher ist Antwort C korrekt. Da die Objektreferenz obj1 und obj2 nicht gleich sind, da sie auf verschiedene Boolean Objekte zeigen, kann die Ausgabe nicht „a“ oder „b“ sein. Da jedoch die equals() Methode den Wert (true) vergleicht, und beide Objekte den gleichen Wert haben, ist die Ausgabe „c“.

Frage

Analysiere die beiden Quellcodezeilen:

float f = 3.2;
int i = f;
  1. Dieses Programmstück wird nicht kompiliert
  2. Dieses Programmstück lässt sich kompiliert und i erhält der Wert 3
  3. Die zweite Zeile würde sich kompilieren lassen, wenn dort stände:

    int i = (byte) f;

  4. Die erste Zeile ließe sich kompilieren, wenn wir schreiben würden:

    float f = 3.2f;

Das Programm liefert beim Kompilieren eine Fehlermeldung, die besagt, dass der Datentyp float nicht mit double kompatibel ist. Der Datentyp double umfasst einen größeren Zahlenbereich und 3.2 ist als Literal automatisch ein double. Also ist A korrekt und B falsch. Der Fehler ist in der ersten Zeile ließe sich beheben, wenn man hinter dem 3.2 ein f hängt, wie es Lösung D vorschlägt. Somit ist D korrekt. Unabhängig von der erste Zeile ist auch mit der Typumwandlung auch die zweite Zeile richtig.

Frage

Welche der folgenden Anweisungen sind korrekt?

  1. 128 >> 1 ergibt 64
  2. 128 >>> 1 ergibt 64
  3. 128 >> 1 ergibt –64
  4. 128 >>> 1 ergibt –64

Antworten A und B sind korrekt.

Frage

Welche der folgenden Anweisungen ergeben den Rückgabewert true?

  1. „ulli“ == „ulli“
  2. „ulli“.equals(„ulli“)
  3. „ulli“ = „ulli“
  4. „ulli“.equals(new Button(„ulli“))

Da sich die virtuelle Maschine gleiche Strings in einem Konstanten-Pool hält, ist Antwort A korrekt. Bei B wird ein Zeichen-zu-Zeichen Vergleich unternommen. Da beide Strings identisch sind, ist auch hier das Ergebnis true, also ist auch Antwort B wahr. Man sollte sich nur bewusst sein, dass bei A kein inhaltlicher Vergleich stattfindet. Antwort C ergibt einen Compilerfehler, da ein einfaches Gleichheitszeichen eine Zuweisung definiert. Auf der linken Seite darf aber kein Literal stehen. Somit kann C nicht richtig sein. Die equals Methode, die auf der String Klasse definiert ist, überschreibt die equals() Methode der Klasse Object. Sie erlaubt aber als Parameter nur String Objekte. Es ist nicht so, dass bei nicht-Sting Objekten die toString() Methoden aufgerufen wird. Der Rückgabewert ist false. Also ist auch D falsch.

Frage

Welche der Anweisungen führt nicht zu einem Kompilierfehler?

  1. „ulli“ + “ plus “ + “ plus“
  2. „ulli“ + 29
  3. 345 + 45
  4. 5 + 5.5

Keine der Anweisungen führen zu einem Laufzeitfehler. Also sind Antwort A, B, C und D korrekt.

Frage

Welche der folgenden logischen Operatoren nennen sich „short circuit“?

  1. &
  2. ||
  3. &&
  4. |

B und C sind die kurzen Operatoren, die dann gelten, wenn der entsprechende Teil gilt.

Frage

Welchen Wert enthält die Variable temp nach der Ausführung der beiden Zeilen?

long temp = (int) 3.9;
temp %= 2;
  1. 0
  2. 1
  3. 2
  4. 3
  5. 4

Das Ergebnis ist 1, also die Antwort B wahr. Die Typumwandlung schneidet den Nachkommateil von 3.9 ab und das Ergebnis 3 Modulo 2 ist 1.

Frage

Welche Bildschirmausgabe erzeugt die folgende Anweisung?

System.out.println( 4 | 7 );

  1. 0
  2. 4
  3. 5
  4. 6
  5. 7

Die binäre Repräsentation der Zahl 4 ist 100 und von 7 ist 111. Beide mit Oder verknüpft gibt 111 und somit wiederum 7. Also ist Antwort E korrekt.

Fragen zur Java-Zertifizierung, Flusskontrolle und Ausnahmebehandlung

Frage

Was wird ausgegeben, wenn die folgende Methode aufgerufen wird?

void test() {
one:
two:
  for ( int i = 0; i < 3; i++ ) {   three:
    for ( int j = 10; j < 30; j++ ) {      System.out.println( i + j );
      if ( i > 2 )
      continue one;
    }
  }
}
  1. 10 und 20
  2. 11 und 21
  3. 12 und 22
  4. 13 und 23
  5. 30, 31, 32, 33

Frage

Analysiere den folgenden Programmtext:

1: void schleife() {
2:   int x = 0;
3: one:
4:   while ( x < 100 ) {
5:   two:
6:     System.out.println( ++x );
7:     if ( x > 3 )
8:     break two;
9:   }
0: }
  1. Der Code lässt sich nicht kompilieren.
  2. Die Funktion schreibt die Zahl 0 in die Standardausgabe.
  3. Die Funktion schreibt die Zahl 1 und 2 in die Standardausgabe.
  4. Die Funktion schreibt die Zahl 3 in die Standardausgabe.
  5. Die Funktion schreibt die Zahl 4 in die Standardausgabe.
  6. Die Funktion schreibt die Zahlen 5 bis 9 in die Standardausgabe.
  7. Die Funktion schreibt die Zahl 10 in die Standardausgabe.

Der Programmcode kann nicht kompiliert werden, da Zeile 8 eine ungültige Sprungadresse enthält. Daher ist Antwort A korrekt. Die break Anweisung dient immer dazu, aus einer Schleife zu entkommen und nicht aus einer if Anweisung. Würde der Sprung nach two: gelingen, wäre man aber immer noch in der Schleife. Eine gültige Anweisung für Zeile 8 ist break one;

Frage

Betrachte den folgenden switch Block. (Zwei Antworten.)

char mychar = 'c';
switch ( mychar )
{   default:
  case 'a': System.out.println( "a" ); break;
  case 'b': System.out.println( "b" ); break;
}

Welche der folgenden Antworten ist richtig?

  1. Der switch Block ist illegal, weil nur Ganzzahlen in der switch Anweisung benutzt werden können.
  2. Mit der switch Anweisung ist alles in Ordnung.
  3. Der switch Block ist illegal, da die default Anweisung zum Schluss kommen muss.
  4. Wenn das Programmstück läuft, wird nichts auf die Standardausgabe geschrieben.
  5. Wenn das Programmstück ausgeführt wird, erscheint der Buchstabe "a" auf dem Bildschirm.

Der Ausdruck in einer switch Anweisung muss sich auf ein 32-Bit Wert konvertieren lassen. Also sind auch die kleineren Datentypen byte, char, short und int erlaubt. Im Beispiel ist ein Buchstabe ein char, der auf ein int konvertiert wird. Dies ist korrekt, also ist Antwort A falsch. Da die Kompilation somit in Ordnung ist, ist Antwort B korrekt. Die Reihenfolge, wann default auftaucht ist egal, somit ist C falsch. Daher folgt auch erst die richtige Ausgabe. Denn der Buchstaben ‚c‘ passt nicht auf ‚a‘ und ‚b‘, somit kümmerst sich der default Teil um die Eingabe. Da hinter default jedoch kein break steht läuft der Programmcode direkt in die Ausgabe "a" hinein. Also ist ebenfalls E korrekt und D falsch.

Frage

Welche der folgenden Aussagen über try, catch und finally sind wahr?

  1. Einem try Block muss immer ein catch Block folgen.
  2. Einem try Block kann entweder ein catch Block, ein finally Block oder beides folgen.
  3. Ein catch Block muss immer mit einem try Block verbunden sein.
  4. Ein finally kann nicht ohne ein try Block auftauchen.
  5. Keine dieser Aussagen sind wahr.

Einem try Block muss entweder ein finally ein catch oder beides folgen. Also ist A falsch und B richtig. Wenn man ein catch oder ein finally schreibt, so ist dies immer mit einem try verbunden, daher ist auch C und D richtig. Da mindestes eine Antwort richtig ist, ist E falsch.

Frage

1: public class Clazz {
2:   public static void main( String args[] ) {
3:     String s = null;
4:     if ( s != null & s.length() > 0 );
5:     if ( s != null && s.length() > 0 );
6:     if ( s == null | s.length() > 0 );
7:     if ( s == null || s.length() > 0 );
8:   }
9: }

Welche der Aussagen ist wahr?

  1. Zeile 4 erzeugt eine NullPointerException
  2. Zeile 5 erzeugt eine NullPointerException
  3. Zeile 6 erzeugt eine NullPointerException
  4. Zeile 7 erzeugt eine NullPointerException

Antwort A und C sind korrekt und Zeile 4 und 6 erzeugen eine NullPointerException. Man muss wissen, dass & und | beide Seiten auswerten. Ist also die Referenz null, so ist auch s.length() ungültig. In Zeile 4 und 6 kommen aber diese Auswertungen vor. && wertet den rechten Ausdruck nur dann aus, wenn der linke wahr ist. Also wird in Zeile 5 keine Länge abgefragt. || wertet nur dann die rechte Seite aus, wenn die linke Seite falsch ist. s == null ist aber wahr und die Länge muss nicht ausgewertet werden.

Frage

Welche Methoden kann man für das Kommentar //XX einsetzen?

class Base{
  public void method( int i ) { }
}
public class Extension extends Base {
  public static void main( String args[] ) { }
  //XX
}
  1. void method( int i ) throws Exception {}
  2. void method( long i ) throws Exception {}
  3. void method( long i ) {}
  4. public void method( int i ) throws Exception {}

Eine Methode kann in einer Unterklasse überschieben oder überladen werden. Überladene Funktionen haben den selben Namen wie eine andere Funktion in der Klasse oder der Basisklasse, jedoch verschiedene Parameter. Da in Antwort B und C die Methode mit einem long definiert ist, hat sie mit der in der Klasse Base definierten Methode außer den Namen nichts gemeinsam. So überlädt B und C method(int) aus Base und fügt sie der Klasse Extension zu. B und C sind korrekte Antworten und führen zu keinen Übersetzungsfehler. Anders dagegen A und D. Beide führen zu Compilerfehlern, da sie versuchen die Methode aus der Basisklasse zu überschreiben und eine Exception hinzuzufügen. Auch die Methode in Base müsste die Ausnahme schmeißen.

Fragen zur Java-Zertifizierung, Ein- Ausgabe mit dem Paket java.io

Frage

Sie schreiben ein Programm, um den Text einer 8 Bit Datei, die den Zeichensatz nach ISO 8859-8 benutzt, in einem TextArea Objekt darzustellen. Die lokale Enkodierung ist auf 8859-8 gesetzt. Wie sieht der Programmcode aus, um die erste Zeile dieser Datei zu lesen?
Sie haben dabei Zugriff auf 3 Variablen. myfile ist der Name der Datei, aus der Sie lesen wollen, stream ist ein InputStream Objekt, der dem mit dieser Datei verknüpft ist und s ist ein String Objekt.

  1. InputStreamReader reader = new InputStreamReader( stream, „8859-8“ );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  2. InputStreamReader reader = new InputStreamReader( stream );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  3. InputStreamReader reader = new InputStreamReader( myfile, „8859-8“ );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  4. FileReader reader = new FileReader( myfile );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();

Frage

Was schreibt das folgende Programm auf die Konsole wenn die Datei „Hallo.txt“ im aktuellen Verzeichnis nicht existiert?

import java.io.*;
public class Leser {
  public static void main( String args[] ) {
    Leser l = new Leser();
    System.out.println( l.ließ() );
  }
  public int ließ() {
    try {
      FileInputStream dis = new FileInputStream("Hallo.txt");
    }
    catch ( FileNotFoundException fnfe ) {
      System.out.println( "Keine solche Datei " );
      return -1;
    }
    catch ( IOException ioe ) { }
    finally { System.out.println( "Schlusslicht " ); }
    return 0;
  }
}
  1. Keine solche Datei
  2. Keine solche Datei -1
  3. Keine solche Datei Schlusslicht -1
  4. 0

Ist die Datei nicht vorhanden, so wird eine FileNotFoundException (Spezialisierung von IOException) geschmissen. Da wir diese zuerst abfragen gibt das Programm die erste Meldung aus. Nun muss nicht noch weiter nach catch Klauseln gesucht werden und der finally Block wird ausgeführt. Dieser gibt noch die zweite Ausgabe aus. Vor dem Abarbeiten wird aber der Rückgabewert -1 gesichert und dann nach finallly in der main() Methode ausgegeben. Daher ist C die einzig richtige Antwort.

Fragen zur Java-Zertifizierung, Grundlegende Spracheigenschaften

Frage

In welcher Reihenfolge können die folgenden Klassen in der Quelldatei gesetzt werden, ohne dass Fehler oder Warnungen zur Übersetzungszeit auftreten.

// A
 import java.applet.*;
// B
 class Helper { }
// C
 package myclasses;
// D
 public class MyApplet extends java.applet.Applet { }
  1. A, B, C, D
  2. A, C, B, D
  3. C, A, B, D
  4. C, A, D, B
  5. C, B, A, D

Befindet sich eine Klasse in einem Paket, so muss in der ersten Zeile die package Deklaration stehen. So sind Antworten A, B falsch. Anschließend müssen die Import Anweisungen folgen. So ist auch E falsch. Antworten C und D sind korrekt. Bei D würde aber ein extends Applet reichen.

Frage

Was ist die korrekte Reihenfolge für Importanweisungen, Klassen und Pakete, wenn diese in einer einzigen Datei vorkommen?

  1. Paketanweisung, Importanweisung, Klasse
  2. Klasse, Importanweisung, Paketanweisung
  3. Importanweisung, Paketanweisung, Klassen
  4. Paketanweisung, Klassen, Importanweisung

Die korrekte Antwort ist A.

Frage

Was sind gültige Schlüsselwörter in Java?

  1. sizeof
  2. abstract
  3. native
  4. NULL
  5. BOOLEAN
  6. goto

sizeof ist ein Operator in C(++), der in Java keine Bedeutung hat. Er kann daher ein kein gültiges Schlüsselwort. A ist flasch. abstract und native sind reservierte Schlüsselwörter, daher sind Antwort B und C richtig. NULL ist ein Makro unter C(++), welches in Java nicht existiert. Nur null ist ein eingebautes Schlüsselwort. Auch BOOLEAN ist kein Schlüsselwort, da Java Groß/Kleinschreibung trennt. So sind D und E keine Schlüsselwörter. Obwohl es in Java kein goto gibt (jedoch Multibreak continue, was im Prinzip das gleiche ist), ist das Schlüsselwort reserviert und man kann es nicht als Bezeichner wählen. Auch F ist richtig.

Frage

Welche der folgenden Bezeichner ist illegal?

  1. #_pound
  2. _underscore
  3. 4Interface
  4. Interface5
  5. _5_

Bezeichner dürfen nur mit einem Unterstrich oder einem Buchstaben beginnen. Demnach sind A und C illegale Bezeichner. Die anderen sind gültige Namen, es sind B, D und E korrekt.

Frage

Welche der folgenden Deklarationen für die main() Methode in einer Konsole Applikation ist nicht korrekt?

  1. public static void main()
  2. public static void main( String[] string )
  3. public static void main( String args )
  4. public static void main( String[] args )
  5. static void main( String[] args )

Die korrekte Signatur ist public static void main( String [] ). Also B und D korrekt, da es auf die Namensgebung nicht ankommt. Da die main() Funktion immer als Argument ein String Feld hat, ist A und C falsch. E ist im Prinzip richtig, jedoch wird mit public die main() Funktion auch außerhalb des Paketes sichtbar. Also ist auch E falsch.

Frage

Wie lautet die Spezifikation für den Paramter der pulic static void main Methode?

  1. String args []
  2. String [] args
  3. Strings args []
  4. String args

Als Parameter wird ein Feld von Strings erwartet. Also ist D falsch. Da die Klammern bei der Deklaration eines Feldes vor oder hinter dem Bezeichner stehen können und der Name des Feldes unerheblich ist, sind A, B und C richtige Antworten.

Frage

Was enthält das erste Erste Element des Stringfeldes, das der public static void main Methode übergeben wird.

  1. Der Name des Programms.
  2. Die Anzahl der Argumente.
  3. Das erste Argument falls verfügbar.

In Java gibt es keine zusammengebunden Programme, sondern nur Klassen. Daher ist A falsch. Die Anzahl der Argumente ergibt sich dem Attribut length der Feldvariablen. Es bleibt C als richtige Antwort.

Frage

Was ist das Ergebnis des folgenden Programmes:

public class Alter {
 public static void main( String args [] ) {
  int age;
  age = age + 1;
  System.out.println( "Das Alter ist " + age);
  }
 }
  1. Kompiliert und läuft ohne Ausgabe.
  2. Kompiliert und die Ausgabe ist „Das Alter ist 1“.
  3. Kompiliert aber erzeugt einen Laufzeitfehler.
  4. Kompiliert nicht.

Das Programm kann nicht kompiliert werden, da auf die lokale Variable age vor dem Lesezugriff kein Schreibzugriff stattfand. Da lokale Variablen aber vor der Benutzung initialisiert werden müssen, meldet der Compiler einen Fehler.

Frage

Was ist das Ergebnis des folgenden Programmcodes?

public class Alter {
 static int age;
 public static void main( String args [] ) {
  age = age + 1;
  System.out.println( "Das Alter ist " + age );
 }
}
  1. Kompiliert und läuft ohne Ausgabe
  2. Kompiliert und liefert die Ausgabe „Das Alter ist 1 „
  3. Kompiliert aber erzeugt einen Laufzeitfehler.
  4. Kompiliert nicht.

Das Programm kompiliert und schreibt das Alter 1 auf den Schirm. Die int Objektvariablen wird mit Null initialisiert. Es ist egal, ob sie dabei static ist oder nicht. Nach dem einmaligen Erhöhen wird sie auf 1 gesetzt. Also ist B die richtige Antwort.

Frage

Welche der folgenden Anweisungen ist korrekt, um ein Zeichenliteral mit dem Wert a zu erzeugen.

  1. ‚a‘
  2. „a“
  3. new Character(a)
  4. \000a

Nur Antwort A ist korrekt. Ein Zeichenliteral muss in einfachen Hochkommas stehen. Nur Zeichenkette, also String-Objekte, stehen in doppelten Anführungszeichen. String Objekte sind aber Objekte und keine primitiven Datentypen wie ein char. Antwort C ist falsch, da dort versucht wird ein Character Objekt zu konstruieren. Der new Operator erzeugt allerdings ein Objekt und kein Literal. Außerdem muss Buchstabe a in Hochkomma stehen. Sonst würde die Zeile auf eine char Variable a zurückgreifen. Das Zeichen \0000a ist die Hexadezimalschreibweise und steht für die Zahl 10. Also ist auch D falsch.

Frage

Wie ist der Zahlenbereich eines byte?

  1. 0 bis 65.535
  2. –128 bis 127
  3. –32.768 bis 32.767
  4. –256 bis 255

Ein byte hat die Datengröße von 8 Bit. So sind Antwort A und C schon falsch. Mit 8 Bit kann man den Zahlenbereich von 0 bis 255 abdecken. Ein Vorzeichen kostet aber noch ein zusätzliches Bit. So ist auch D falsch, da wir hier 9 Bit bräuchten. Da alle primitiven Datentypen außer char mit einem vorzeichenbehaftet sind, bleibt als richtige Antwort B.

Frage

Welche der Anweisungen ist illegal?

  1. int i = 32;
  2. float f = 45.0;
  3. double d = 45.0;

Die erste Anweisung ist korrekt, da 32 im Bereich eines int liegt. Auch Anweisung C ist korrekt. Nur bei B ist das Literal ein double. Dies kann nicht ohne möglichen Verlust in ein float kopiert werden. Also ist Antwort B korrekt. Um die Zeile zu korrigieren schreiben wie float f = 4.0f.

Eine Sich-Selbst-Implementierung

Der Zusammenhang zwischen inneren Klassen und äußeren Klassen und wie sie sich gegenseitig implementieren können.

Eine Klasse kann entweder von einer Klasse erben oder eine Schnittstelle erweitern. Es ergibt sich ein Sonderfall, wenn wir eine Schnittstelle implementieren, die innerhalb der eigenen Klasse liegt, die die Schnittstelle deklariert. Das sieht etwa so aus:

class Outer implements Outer.InterfaceInner
{
  interface InterfaceInner
  {
  void knexDirDeineWelt();
  }

  public void knexDirDeineWelt()
  {
  }
}

Prinzipiell spricht erst einmal nichts gegen diese Implementierung. Innere Klassen, wie InterfaceInner eine ist, werden auf eine extra Klassendateien abgebildet, da es innerer Klassen beziehungsweise Schnittstellen sowieso nicht für die Laufzeitumgebung gibt. In unserem Fall könnte der Compiler die Datei Outer$InterfaceInner erzeugen. Im nächsten Schritt würde dann Outerdiese Schnittstelle erweitern und wie im Beispiel eine Methode überschreiben.

So schön dies auch aussieht: Es funktioniert nicht. Frühe Compiler erlaubten diese Schreibeweise, aber sie führt zu zirkulären Abhängigkeiten.

cyclic inheritance involving Outer

Wenn InterfaceInner zuerst übersetzt würde und dann Outer, wäre es noch zu verstehen, doch das Problem machen zum Beispiel Deklarationen in der inneren Klasse, die abhängig sind von der Äußeren. Wir könnten etwa den Rückgabewert von knexDirDeineWelt() ändern, dass es ein Outer Objekt zurückliefert.

class Outer implements Outer.InterfaceInner
{
  interface InterfaceInner
  {
    Outer knexDirDeineWelt();
  }

  public void knexDirDeineWelt()
  {
  }
}

Jetzt sehen wir: Ohne InterfaceInner kein Outer, da dies knexDirDeineWelt() vorschreibt und ohne Outer kein InterfaceInner, da sonst der Rückgabewert nicht bekannt ist. Mitunter wäre das Problem noch lösbar, aber hier lässt der Compiler lieber die Finger von.

Innere Klasse vor der äußeren

Dass es nicht unmöglich ist, eine innere Klasse von der äußeren Abzuleiten zeigt folgendes Beispiel:

interface I
{
  void boo();
  interface J extends I
  {
  }

  J foo();
}

Es ist problemlos möglich, dass die innere Schnittstelle die äußere erweitert. Im Gegensatz zum vorherigen Beispiel ist in diesem Fall die Problematik genau umgekehrt. Es ist auch möglich, dass eine innere Klasse eine äußere erweitert.

class O
{
  class I extends O
  {
    void bar()
    {
    }
  }

  void bar() { }
}

Selbst definierte Cursor in AWT/Swing

Bei einem Cursor sind wir nicht allein auf die vordefinierten Muster angewiesen. Leicht lässt sich aus einer Grafik ein eigener Cursor definieren. Dazu bietet das Toolkit die Methode createCustomCursor() an. Als Argument geben wir ein Image-Objekt, einen Hotspot und einen Namen an. Der Hotspot definiert eine Art Nullpunkt, der die Spitze angibt. Zeigt etwa der Standardcursor mit einem Pfeil nicht wie üblich nach oben, sondern nach unten, so gibt der untere Punkt den Nullpunkt an. Der Name ist nur nötig, wenn Java-Accessibility genutzt wird, also eine Möglichkeit gegeben ist, den Cursor zum Beispiel ohne Maus anzusprechen.

Beispiel: Weise einer java.awt.Component den Cursor mit der Grafik cursor.gif und dem Hotspot auf (10,10) zu.

Cursor c = getToolkit().createCustomCursor(
new ImageIcon( "cursor.gif" ).getImage(),
new Point(10,10), "Cursor" );
component.setCursor( c );

Hinweis Animierte Cursor bietet die Java-Umgebung nicht an. Wir könnten selbstständig in einem Thread immer wieder mit setCursor() unterschiedliche Cursor setzen, um etwa eine drehende Sanduhr oder eine rotierende Festplatte zu bekommen.

Da grafische Oberflächen in der Regel keine Cursor beliebiger Auflösung und Farbanzahl zulassen, lässt sich das Toolkit auch über diese Größen erfragen. Die Methode getBestCursorSize() liefert die mögliche Größe des Cursors zurück. Es ist sinnvoll, diese Methode vorher aufzurufen, um ein passendes Bild auszuwählen. Ähnlich wie bei den Icons in der Titelleiste werden die Grafiken sonst skaliert, was nicht unbedingt schön aussehen muss. Gleiches gilt für die Farben. Nicht alle Systeme erlauben beliebig viele Farben für den Cursor. Die maximale Farbanzahl liefert die Funktion getMaximumCursorColors(). Notfalls wird der Cursor auf die Farbanzahl heruntergerechnet.

Tipp: Unterstützt die Plattform Cursor beliebiger Größe, so lässt sich dadurch einfach eine Bubble-Help realisieren, die nicht rechteckig ist. An Stelle des Cursors wird eine Grafik mit dem Cursor zusammen mit einer Hilfe angezeigt. Das Betriebssystem verwaltet den Cursor, und wir müssen den Hintergrund nicht sichern und mit der Hilfe verknüpfen.

abstract class java.awt.Toolkit

  • Cursor createCustomCursor( Image cursor, Point hotSpot, String name )

    throws IndexOutOfBoundsException

    Erzeugt ein neues Cursor-Objekt. Liegt der Hotspot außerhalb der Grenzen der Grafik, wird eine IndexOutOfBoundsException ausgelöst.

  • Dimension getBestCursorSize( int preferredWidth, int preferredHeight )

    Liefert die unterstützte Cursor-Größe, die den gewünschten Ausmaßen am nächsten liegt. Oft werden die Argumente ignoriert, wenn die Umgebung keine beliebige Cursor-Größe zulässt. Erlaubt das System überhaupt keine selbst definierten Cursor, erhalten wir ein Objekt der Dimension (0,0).
  • int getMaximumCursorColors()

    Liefert das Maximum an Farben, das das Toolkit für Cursor unterstützt. Der Rückgabewert ist null, wenn selbst definierte Cursor nicht gestattet sind.

public abstract class java.awt.Component implements ImageObserver, MenuContainer, Serializable

  • void setCursor( Cursor cursor )

    Weise einen neuen Cursor zu.

Rechte (Permissions) und Mengenbeziehungen

Für jede geladene Klassen gilt eine Sammlung von Rechten, die für diese Klasse vergeben wurden. In der Regel wurden sie per Policy-Datei vergeben, doch natürlich sind auch andere Möglichkeiten denkbar. Diese Sammlung selbst wird in einem PermissionCollection-Objekt gespeichert, welches einer ProtectionDomain zugeordnet ist.

ProtectionDomain domain = ListPermissions.class.getProtectionDomain();
PermissionCollection permissonColl = Policy.getPolicy().getPermissions( domain );

Dem PermissionCollection-Objekt lässt sich mit einer Enumeration die gespeicherten Permissions rauskitzeln. Ein System.out liefert ebenso eine schöne Ausgabe, etwa für das eigene Programm ListPermissions ohne Sicherheitsmanager:

java.security.Permissions@c21495 (
(java.util.PropertyPermission java.specification.vendor read)
(java.util.PropertyPermission java.vm.specification.vendor read)
(java.util.PropertyPermission path.separator read)
(java.util.PropertyPermission java.vm.name read)
(java.util.PropertyPermission java.class.version read)
(java.util.PropertyPermission os.name read)
(java.util.PropertyPermission java.vendor.url read)
(java.util.PropertyPermission java.vendor read)
(java.util.PropertyPermission java.vm.vendor read)
(java.util.PropertyPermission file.separator read)
(java.util.PropertyPermission os.version read)
(java.util.PropertyPermission java.vm.version read)
(java.util.PropertyPermission java.version read)
(java.util.PropertyPermission line.separator read)
(java.util.PropertyPermission java.vm.specification.version read)
(java.util.PropertyPermission java.specification.name read)
(java.util.PropertyPermission java.vm.specification.name read)
(java.util.PropertyPermission java.specification.version read)
(java.util.PropertyPermission os.arch read)
(java.lang.RuntimePermission exitVM)
(java.lang.RuntimePermission stopThread)
(java.net.SocketPermission localhost:1024- listen,resolve)
(java.io.FilePermission \D:\JavaBook\programme\24_Sicherheitskonzepte\- read)
)

Die Rechte sind natürlich genau diejenigen, die vom System bereitgestellt worden sind. Sie beziehen sich genau auf unsere Klasse ListPermissions. Für die Systemklassen, wie java.lang.Object oderString gelten keine Einschränkungen. Ersetzen wie ListPermissions durch Objekt, so würde ein System.out genau die alles-erlaubende Permission ergeben:

(java.security.AllPermission <all permissions> <all actions>)

Schließt eine Permission eine andere Permission ein?

Permission-Objekte definieren selbst nicht nur Rechte für spezielle Eigenschaften (Lesen in einem speziellen Verzeichnis), sondern ziehen auch Recht für andere Eigenschaften nach sich (Lesen aller Dateien ab einem Verzeichnis). Wird etwa für ein Verzeichnis das Recht auf Lesen und Schreiben gesetzt, dann impliziert dies auch das Lesen. Die Fähigkeit, dass ein Recht ein anderes bedingt, ist eine Fähigkeit der Permission-Objekte. Die Klasse bietet eine implies()-Funktion, die testet, ob eine Permission eine andere Permission einschließt.

Beispiel: Wir wollen zwei FilePermission-Objetke anlegen, wobei das erste (perm1) das zweite (perm2) einschließt.

import java.io.FilePermission;
import java.security.Permission;

public class PermissionImplies
{
  public static void main( String args[] )
  {
    Permission perm1 = new FilePermission( "c:\\windows\\*", "read,write" );
    Permission perm2 = new FilePermission( "c:\\windows\\fonts", "read" );
    
    if ( perm1.implies(perm2) )
      System.out.println( perm1 + " implies " + perm2 );
    
    if ( !perm2.implies( new FilePermission("c:\\windows\\fonts", "write") ) )
      System.out.println( perm1 + " not implies " + perm2 );
  }
}

Die Ausgabe zeigt, dass diese Rechte vom System korrekt unterstützt werden.

(java.io.FilePermission c:\windows\* read,write) implies (java.io.FilePermission c:\windows\fonts read)
(java.io.FilePermission c:\windows\* read,write) not implies (java.io.FilePermission c:\windows\fonts read)

Wie heißt die Klasse mit der Methode main()?

In C(++) ist das erste Element des Felds der Funktion main(int argc, char **argv) der Name des Programms. Das ist in Java anders. Die Methode enthält als ersten Parameter nicht den Namen der Klasse beziehungsweise des Programms, sondern einfach den ersten Parameter – sofern einer auf der Kommandozeile übergeben wurde. Auf einem kleinen Umweg ist das auch für manche Klassen möglich.

Der zu einer Klasse gehörende Klassenlader lässt sich mit dem Class-Objekt erfragen. Mit der Methode getResource() erhalten wir von einem Klassennamen ein URL-Objekt zurück, das (unter gewissen Voraussetzungen) die Position der Klassendatei im Dateisystem anzeigt. Das folgende Programmbeispiel zeigt, wie wir von einer Klasse den vollständigen Dateipfad zurückbekommen.

package com.tutego.insel.lang;

import java.net.*;

public class FindClassLocation
{
 static String findLocation( Class<?> clazz )
 {
  ClassLoader loader = clazz.getClassLoader();

  if ( loader == null )
   return null;

  URL url = loader.getResource( clazz.getName().replace('.', '/' ) + ".class" );

  return ( url != null ) ? url.toString() : null;
 }

 public static void main( String[] args ) throws Exception
 {
  Class<?> c = Class.forName( "com.tutego.insel.lang.FindClassLocation" );

  System.out.println( "Class: " + c.getName() );
  System.out.println( "Filename: " + findLocation(c) );
 }
}

Unter meinem Dateisystem liefert das Programm die Ausgabe:

Class: com.tutego.insel.lang.FindClassLocation
Filename: file:/S:/Insel/programme/09_Funktionsbibliothek/com/tutego/insel/lang/FindClassLocation.class

Achtung! Die Lösung funktioniert natürlich nur unter gewissen Voraussetzungen. Es geht nur für Klassen, die in kein Jar-Archiv eingebunden sind und nicht den Standardbibliotheken entstammen. Auch ist eine Dateiangabe unmöglich, wenn wir etwa einen eigenen Klassenlader schreiben, der die Klassen aus einer Datenbank bezieht; dann gibt es keinen Pfad mehr.

getResourceAsStream()

Benötigen wir den Ort einer Klasse, um mit dieser Information auf weitere Dateien im Verzeichnis zuzugreifen, geht es mit der Class.getResourceAsStream(String) einfacher. Diese Methode dient insbesondere dazu, Ressourcen wie Bilder oder Musik aus einem Jar-Archiv auszulesen. Auch der ClassLoader bietet die Methode getResourceAsStream(String) an. Diese Methoden funktionieren ebenfalls für Klassen aus Jar-Archiven, wenn die Ressource im Archiv liegt.

Durch Null-Cursor Flackern des Mauszeigers bei Animationen vermeiden

Einige Betriebssysteme haben bei Java-Animationen das Problem, dass der Mauszeiger unruhig flackert. Zur Lösung kann man einen Cursor ohne Pixel an die Stelle der Grafik setzen.

Es ist Sache der grafischen Oberfläche, den Mauszeiger mit dem Hintergrund zu verbinden. Um ein unruhiges Bild zu vermeiden, greifen wir zu einem Trick und schalten den Mauszeiger einfach ab. Dazu soll createCustomCursor() einen neuen Cursor mit transparentem Image-Objekt erzeugen. Da wir kein leeres transparentes GIF-Bild nutzen wollen, legen wir einfach mit der Klasse BufferedImage ein Bild im Speicher an. Das Argument muss dabei TYPE_INT_ARGB sein, sonst ist das Bild nicht transparent. Damit ist die Arbeit getan, der letzte Schritt besteht darin, den Cursor mit setCursor() einer Komponente zuzuweisen. Im Fall einer Animation wäre das zum Beispiel ein JComponent, im folgenden Beispiel wird das eine Schaltfläche sein:

import javax.swing.*;
import java.awt.*;
import java.awt.image.*;

public class NullCursor
{
 public static void main( String[] args )
 {
  JFrame f = new JFrame();
  f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

  JButton b = new JButton( "Kein Cursor" );
  f.add( b, BorderLayout.PAGE_START );

  b.setCursor( Toolkit.getDefaultToolkit().createCustomCursor(
    new BufferedImage( 16, 16, BufferedImage.TYPE_INT_ARGB ),
    new Point(0,0), "" ) );

  f.setSize( 200, 200 );
  f.setVisible( true );
 }
}

Algorithmen für Rastergrafik, 1. Linien zeichnen mit Bresenham

Der Bildschirm ist in eine Reihe von Punkten zerlegt, die alle mit verschiedenen Intensität oder Farbe gesetzt sind. Die Rastergrafik versucht, die geometrischen Beschreibungen von Objekten auf die Pixel des Bildschirmes abzubilden. Diese Umrechnung muss schnell und in einigen Fällen speichersparend sein. In den Letzen Jahren wurden eine ganze Reihe von Algorithmen entwickelt, die mittlerweile in Hardware Einzug gehalten hat. Die modernen Grafikkarten unterstützen neben grafischen Operatio­nen wie Pixel und Farbe mischen auch Funktionen zum Zeichen von Linien, Kreisen, Polygonen, gefüllten Flächen und weiteres. Daneben sind dreidimensionale Koordinatentransformationen in Hardware gegossen wie das Abbilden einer Bitmap auf eine Fläche, das Erzeugen von Nebel (engl. Fog­ging) oder auch perspektivische Korrektionen (engl. Perspective Correction). Natürlich überlassen wir das Zeichen der Linien der Grafikkarte und die dreidimensionalen Transformationen der 2D-Biblio­thek. Java ist allerdings eine einfache Sprache und die Arbeitsweise kann somit schnell erklärt werden. Notfalls können wir Funktionen, die nicht in der Grafikbibliothek vorliegen, nachbilden.

Linien zeichnen

Linien sind ein einfachen Beispiel dafür, wie Punkte auf dem Raster verteilt werden. Beachten wir dazu die Grafik. Wie zeichnen eine Linie von den Koordinaten (x1,y1) zum Punkt (x2,y2).

Um nun die Punkte auf dem physikalischen Bildschirm zu setzen, müssen die Punkte so verteilt wer­den, dass sie möglichst nah an der wirklichen Linien liegen. Gehen wir die X-Achse von xl nach x2 ab, so müssen wir die Y-Punkte wählen. Bei der Wahl können wir folgende Entscheidung fällen: An jedem Punkt müssen wir einen Pixel wählen, nämlich diesen, der den geringsten Abstand zum Pixelraster besitzt. Ist die Linie horizontal, so ist der Abstand zwischen wirklicher Linie und Rasterpunkt immer Null und die Wahl ist einfach.

Ein Algorithmus kann nun wie folgt arbeiten: Es ist der Abstand zwischen der wirklichen Linie und einem Pixel auf der Y-Achse gleich einem Fehlerwert. Es ist der Punkt auf der Y-Achse auszuwählen,
der zur wirklichen Linie den kleinsten Fehlerwert aufweist. Einen schnellen Algorithmus, der genau mit diesem Fehlerwert arbeitet, wurde zuerst von j. E. Bresenham (1965) veröffentlicht. Bresenham
betrachtet Linien im ersten kartesischen Oktanten, die Steigung der Linie ist also kleiner Eins, formal: 0 < Δy / Δx < 1. Die Rasterung ist von links nach rechts. Betrachten wir nun ein Programmsegment in
Pseudocode das, welches eine Linie mit Steigung kleiner Eins zeichnet:

Δx = x2 - x1
Δy = y2 - y1
error = - Δx / 2;
setPixel( x1, y1 );
while( x < x2 ) {
 error = error + Δy;
 if ( error >= 0 ) {
  y = y + 1;
  error = error - Δx;
 }
 x = x + 1;
 setPixel( x, y );
}

Liegen die Linien in anderen Oktanten, so müssen wir den Algorithmus nur leicht abändern. So sind durch einfache Spiegelungen an Winkelhalbierenden oder Ordinate bzw. Abszisse die Punkte zu verschieben. Liegt die Linie beispielsweise im siebten Oktanten, so rastern wir entlang der Y-Achse und vertauschen dx mit -dx und liegt die Linie im achten Oktanten, so rastern wir wir im ersten Oktanten, nur dass dy mit -dy vertauscht wird.

Bresenhams Linienalgorithmus arbeitet schnell und optimal (was bedeutet, die Fehler sind am kleinsten), weil alle Berechnungen auf Ganzzahlen ausgeführt werden. Dies war in den sechziger Jahren

noch wichtiger als heute, wo eine Fließkommaoperation schon genauso viel kostet wie eine lnteger-Operation. Da der Beweis langwierig — aber für die mathematisch geschulten nicht schwierig — ist, wollen wir an dieser Stelle auf den Formalismus verzichten. Anstelle dessen betrachten den Quellcode einer Klasse, die eine einfaches Linienmuster schreibt. Ein nicht unwesentlicher Teil wird dafür aufgewendet, den Algorithmus für die verschiedenen Lagen im kartesischen Koordinatensystem anzupassen. Da wir später noch andere grafische Objekte zeichnen, dient dieses Programm gleichzeitig als Rahmen.

import java.awt.*;
import java.awt.event.*;

public class GDV extends Frame
{
  private Graphics g;
  static final int CLASSIC = 0;
  static final int BRESENHAM = 1;
  public int lineType = CLASSIC;
  
  public GDV() {
    setSize( 400, 400 );
    addWindowListener( new WindowAdapter() {
      public void windowClosing ( WindowEvent e ) { System.exit(0); }
    });
  }

  /**
   * Draws a line with algorithm of Bresenham
   */
  private void drawBresenhamLine( int x1, int y1, int x2, int y2 )
  {
    int xIncrement = 1,
        yIncrement = 1,
        dy = 2*(y2-y1),
        dx = 2*(x1-x2),
        tmp;
  
    if ( x1 > x2 ) {      // Spiegeln an Y-Achse
      xIncrement = -1;
      dx = -dx;
    }
  
    if ( y1 > y2 ) {      // Spiegeln an X-Achse
      yIncrement= -1;
      dy= -dy;
    }
  
    int e = 2*dy + dx;
    int x = x1;           // Startpunkte setzen
    int y = y1;

    if ( dy < -dx )       // Steigung < 1
    {      
      while( x != (x2+1) )
      {
        e += dy;
        if ( e > 0) 
        {
          e += dx;
          y += yIncrement;
        }
        g.drawLine( x, y, x, y );
        x += xIncrement;
      }
    }
    else // ( dy >= -dx )   Steigung >=1
    {
      // an der Winkelhalbierenden spiegeln
      tmp = -dx;
      dx = -dy;
      dy = tmp;

      e = 2*dy + dx;

      while( y != (y2+1) )
      {
        e += dy;
        if( e > 0 ) {
          e += dx;
          x += xIncrement;
        }
        g.drawLine( x, y, x, y );
        y += yIncrement;
      }
    }
  }
  
  private void line( int x1, int y1, int x2, int y2 ) {
      if ( lineType == BRESENHAM )
          drawBresenhamLine( x1, y1, x2, y2 );
      else
          g.drawLine( x1, y1, x2, y2 );
  }

  public void paint( Graphics g ) {
    this.g = g;

    for ( int i=30; i < 300; i+=20 )
      line( 10+i, 40, 300-i, 100 );
  }
  
  public static void main( String[] args ) {
    GDV line = new GDV();

//    line.lineType = CLASSIC;
    line.lineType = BRESENHAM;

    line.show();
   }
}

Bresenhams Algorithmus arbeitet ohne Frage schnell. Wir können aber noch einige Erweiterungen programmieren: Wie wird eine antialiased Linie, also eine weiche Linie gezeichnet? Es gibt mehrere

Mögichkeiten, wobei aber nicht alle schnell sind. Eine Lösung besteht darin, auf jeden Punkt einen Filter loszulassen. Dies läuft aber auf viele Multiplikationen hinaus. Bekannt geworden ist ein anderer

Ansatz, ein Algoritmus von Gupta—Sproull (1981). Dieser ist in allen bekannten Grafik-Büchern nachzulesen.

Da der Algorithmus von Bresenham sehr schnell ist, wundert es einen, wenn wir noch von einer Geschwindigkeitsoptimierung sprechen. Das ist tatsächlich möglich und wurde von Angel, Edward

und Don Morrison in einem Aufsatz in den “IEEE Computer Graphics and Applications” auf zwei Seiten Ende 1991 beschrieben. Der Hintergrund ist einfach und kann daher kurz mit einer Programmieraufgabe umgesetzt werden: Die Pixelbewegungen wiederholen sich zyklisch. In einer Linie der Steigung 1, ist mit jedem Erhöhen des X-Zählers auch ein Erhöhen des Y-Zählers Verbunden. Eine Gerade mit der Steigung 1/8 setzt vier Punkte auf der Horizontalen, geht dann einen Pixel nach oben und setzt wieder vier Punkte. Können wir den Rhythmus erkennen, dann haben wir es einfach, denn dann müssen uns nur die Zeichentechnik merken und dann immer wieder kopieren. Für parallele Prozesse gibt es nichts schöneres. Wir berechnen dazu den Größten Gemeinsamen Teiler von Δy und -Δx. Ist dieser echt größer als Eins, dann haben wir damit den ersten Punkt, an dem die Berechnungsfolge von vorne beginnt. Der Punkt zeichnet sich durch die Koordinaten (x1 + a/ggT(Δy,  -Δx, y1 + b/ggT(Δy/-yΔx) aus.

Kreditkartennummern in Java testen

Sind Kreditkartennummern korrekt aufgebaut? Wie lassen sich sich Kreditkartennummern generieren?

E-Commerce-Lösungen sind im Internet mittlerweile häufig anzutreffen. Lassen sich für kleine Beträge Sonderlösungen finden, werden für größere Beträge immer noch Kreditkarten verwendet. Grund genug für uns Java-Programmierer, die Nummern der Karten zu testen, um zu überprüfen, ob uns nicht ein Anwender täuschen wollte.

Die Nummer einer Kreditkarte setzt sich nicht willkürlich zusammen. Die Nummern von Karten eines bestimmten Herstellers bestehen aus einer festen Anzahl von meistens 14 bis 16 Ziffern. Als Kennung für einen Hersteller (Visa (Veni, Vidi, VISA: I came, I saw, I did a little shopping.) , MasterCard, American Express) ist jeder Nummer eine zusätzliche Kennung von einer bis vier Ziffern vorangestellt. Die Ziffern der Kartennummer werden durch mathematische Verfahren überprüft. Wir wollen eines dieser Verfahren auch kennen lernen; den so genannten Luhn-Algorithmus. Dieser Algorithmus testet die Korrektheit des Aufbaus einer Nummernfolge. Die letzte Ziffer ist oft eine berechnete Checksummen-Ziffer.

Die folgende Tabelle gibt eine Übersicht über einige Kartenhersteller. Sie führt die Kennung, die Länge der Kartennummer und ein gültiges Beispiel auf:

Hersteller

Anfang

Gesamtlänge

Beispiel

Visa

4

13,16

4111 1111 1111 1111

Master

51,52,53,54,55

16

5500 0000 0000 0004

Diner’s Club11

30,36,38

14

3000 0000 0000 04

American Express12

34,37

15

3400 0000 0000 009

Discover

6011

16

6011 0000 0000 0004

en Route

2014,21

15

2014 0000 0000 009

JCB

3088,3096,3112,3158,3337,3528

16

3088 0000 0000 0009

Neben den Herstellernummern sind auch folgende Nummern von den ausgebenden Ban­ken im Umlauf: Manufacturers Hanover Trust (1033), Citibank (1035), Chemical Bank (1263), Chase Manhattan (1665), Bank of America (4024), Citicorp (4128), New Era Bank (4209), HHBC (4302), Imperial Savings (4310), MBNA (4313), California Federal (4317), Wells Fargo (5282), Citibank (5424), Wells Fargo (5410), Bank of New York (5432), MBNA (6017). Carte Blanche und Diner’s Club haben die gleichen Nummern.

Einen Abend im Februar 1950 vergaß Frank MacNamara seine Brieftasche. Er kam auf die Idee, eine Kredit­karte aus Karton anzubieten. Mit seinen Freunden gründete er am 28.2.1950 den Diner’s Club, der im Grün­derjahr mehr als 10.000 Mitglieder und 1.000 Vertragspartner hatte. So war die erste Kreditkarte geboren. Im Jahre 1958 entschloss sich das internationale Transport-, Reise-, und Finanzierungsunternehmen American Express, eine eigene Karte herauszugeben.

Die Überprüfung mit dem Luhn-Algorithmus

Der Luhn-Algorithmus (auch modulus 10 oder mod 10-Algorithmus genannt) basiert auf dem ANSI-Vorschlag X4.13. Er wurde Ende 1960 von einer Gruppe Mathematiker ausgearbeitet und veröffentlicht. Danach nutzten Kreditkartenhersteller dieses Verfahren zur Prüfung der Kreditkartennummern. Auch die Versichertennummer in Kanada, die Canadian Social Insurance Number (CSIN), wird über das Luhn-Verfahren geprüft.

Der Algorithmus testet, ob die letzte Ziffer der Kreditkartennummer korrekt zu den angegebenen Zahlen passt. Die Testziffer wird von allen Ziffern außer der letzten Ziffer berechnet und anschließend mit der angegebenen Testziffer verglichen. Stimmt sie überein, ist die Karte seitens der Prüfnummer in Ordnung. Wir wollen das Verfahren hier nicht näher vertiefen, sondern einfach den Algorithmus angeben:

class LuhnTest
{
  static boolean luhnTest( String s )
  {
    int len = s.length();

    int digits[] = new int[len];

    for ( int i = 0; i < len; i++ )
    {
      try {
        digits[i] = Integer.parseInt( s.substring(i,i+1) );
      }
      catch ( NumberFormatException e ) {
        System.err.println( e );
        return false;
      }
    }

    int sum=0;

    while ( len > 0 )
    {
      sum += digits[len-1];
      len--;

      if ( len > 0 )
      {
        int digit = 2*digits[len-1];
        sum += ( digit>9) ? digit-9 : digit;

        len--;
      }
    }

    return ( sum%10 == 0 );
  }


  static boolean isVisa( String s )
  {
    if ( ( (s.length() == 16) || (s.length() == 13) ) &&
          (s.charAt(0) == '4') )
      return luhnTest( s );

    return false;
  }

  public static void main( String args[] )
  {
    System.out.println( luhnTest( "4111111111111111" ) );
    System.out.println( luhnTest( "5500000000000004" ) );
    System.out.println( luhnTest( "340000000000009" ) );
    System.out.println( luhnTest( "30000000000004" ) );
    System.out.println( luhnTest( "601100000000000" ) );
    System.out.println( luhnTest( "201400000000009" ) );
    System.out.println( luhnTest( "3088000000000009" ) );
    System.out.println( luhnTest( "9238475098787444" ) );

    System.out.println( isVisa( "4111111111111111" ) );
    System.out.println( isVisa( "5500000000000004" ) );


    // Böse: Visa-Nummer generieren

    char[] c = "4123456789123456".toCharArray();

    while ( !isVisa(new String(c)) )
      c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);

    System.out.println( c );
  }
}

Im Quelltext ist eine zusätzliche Methode eingebaut, die testet, ob die Karte von Visa ist. Dazu müssen wir nur überprüfen, ob die erste Ziffer eine 4 und die gesamte Zahl nach dem Luhn-Verfahren gültig ist. Andere Tests sind genauso einfach durchzuführen. Eine mögliche Erweiterung wäre, die Methode fehlertoleranter zu gestalten, indem Trennzeichen herausgefiltert werden. Dies und die Implementierung der übrigen Tests überlasse ich als Übung den Lesern.

Wir beginnen mit einer vorgegebenen, unsinnigen Kartennummer, deren erste Stelle "4" ist, wie für eine Visa-Karte erforderlich. Anschließend ändern wir in einer Schleife eine zufällig ausgewählte Stelle der Kartennummer (außer der ersten) in eine ebenfalls zufällig bestimmte Ziffer aus dem Bereich "0" bis "9". Das wiederholen wir so lange, bis die abgewandelte Zahl irgendwann passt.

Beispiel Mit diesen Methoden ist es natürlich leicht möglich, Nummern zu erzeugen. Betrachten wir Folgendes:

char c[] = "4123456789123456".toCharArray();
while ( !isVisa(new String(c)) )
  c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);
System.out.println( c );

JDBC LOBs (Large Objects)

Auf der Datenbankseite gibt es zwei Typen für besonders große Daten: BLOB und CLOB. »B« steht für Binary und »C« für Character, also steht ein SQL BLOB für beliebig große Binärdaten und ein CLOB für beliebig große Textdaten.

Die LOBs unterscheiden sich von den anderen Datentypen dadurch, dass der Treiber erst dann die Daten überträgt, wenn sie auch angesprochen werden: Wird eine Zeile mit einer Zahl übertragen, so wird der Treiber auch diese Zahl immer mitschicken. Bei den LOBs sieht das anders aus: Intern steckt dort eine Art Verweis (LOCATION), die mitgeschickt wird, aber nicht die Daten selbst. Durch die Kapselung im Treiber fällt das allerdings nicht auf.

Für den BLOB gibt es in JDBC die Schnittstelle Blob und für CLOB Clob. In beiden Fällen können die großen Daten erfragt, aktualisiert und angelegt werden. Außerdem lässt sich die Länge erfragen und lassen sich ab einer bestimmten Position Daten auslesen.

Einen BLOB besorgen

Einem BLOB/CLOB steht genau, wie es für andere Datentypen entsprechende getXXX()-Funktionen gibt, eine getBlob()/getClob()-Funktion zur Verfügung. Der Unterschied besteht nur darin, dass getInt() direkt ein int zurückgibt, während getBlob() nur eine Referenz auf ein Blob-Objekt liefert, über die im zweiten Schritt die Daten zu beziehen sind.

Beispiel: Eine Tabelle Persons weist eine Spalte Image auf, die eine Grafik einer Person speichert. Die Grafik ist in einer Spalte vom Typ BLOB.

ResultSet rs = stmt.executeQuery( "SELECT * FROM Persons" );
rs.first();
Blob data = rs.getBlob( "Image" );

data bezieht sich jetzt auf das Blob-Objekt. Es enthält noch nicht die Daten. Sie lassen sich zum Beispiel über die Methode data.getBinaryStream() beziehen. Damit lässt sich dieser InputStream toll im Konstruktor von ImageIcon() einsetzen, der aus den Daten dann gleich eine Swing-taugliche Grafik konstruiert. Teile des Datenfeldes werden mit byte[] getBytes(start, ende) angefordert. data.length() liefert die Anzahl der Elemente des LOBs.