Update September 2004: Ein weiterer Beitrag zum Thema Plattformunabhängigkeit und Java findet sich in meinem Weblog. Das Argument, dass Java plattformunabhängig sei, muss immer mehr kritisch hinterfragt werden.
Einführung
Ich gehöre nicht gerade zu den Personen die Java speziell lieben. Schon öfters musste ich das begründen, und es fällt mir nicht leicht jedes mal die ganze Liste von Nachteilen aufzuzählen, weshalb ich das ganze hier mal in einem Dokument zusammengefasst habe.
Ich hasse Java jedoch nicht. Es geht mir nur darum gegen die vielen Leute zu debattieren, welche Java als the right thing ansehen und keine andere Programmiersprache kennen. Denn es ist Fakt, dass die meisten Personen mit denen ich über Java diskutiere, mit keiner anderen Sprache gearbeitet haben sondern ausschliesslich Java kennen. Zudem ist Java im Moment (Stand Februar 2002) die Main-Stream Sprache die bis in jedes Management hoch bekannt ist und zusammen mit der OOP-Technik als Lösung für alle Softwareprobleme angesehen wird.
Fakt ist jedoch, dass es keine Sprache gibt welche für jeden Einsatzbereich geeignet ist, sondern jede Sprache hat ihre Nachteile. Ich persönlich verwende Perl für kleiner Software (auch mit GUI), C++ wenn's was grösseres sein muss, und Java nutze ich eigentlich ganz gerne wenn's um Web-Seiten geht, da ich JSP wirklich stark finde. Auch erledige ich relativ viel mit simplen Shell-Scripts und ab und zu schreitet mal eine andere Sprache ein wenn ich gerade Lust habe. Und genau das wollen viele meiner Diskussionspartner nicht verstehen.
Ich versuche jeden Punkt ausführlich zu erklären und mit Code-Beispielen zu belegen. Wenn mal etwas nicht der Wahrheit entspricht bin ich über Kommentare sehr froh.
Keine Templates
Schon mal einem Anfänger erklärt, wieso er bei folgendem Code einen Cast braucht? Also ich musste das schon ein paar mal und es hat niemandem direkt eingeleuchtet.
Vector animalFarm=new Vector();
animalFarm.add(new String("cat"));
animalFarm.add(new String("dog"));
animalFarm.add(new String("cow"));
String favoriteAnimal = (String)animalFarm.elementAt(1);
Das gleiche sieht mit C++ so aus:
vector<string> animalFarm;
animalFarm.push_back(string("cat"));
animalFarm.push_back(string("dog"));
animalFarm.push_back(string("cow"));
string favoriteAnimal = animalFarm[1];
Da ist kein casting nötig. Und das finde ich an Templating so genial. Gerüchtweise hat das Sun auch eingesehen und wird's in einer zukünftigen Java-Version implementieren.
Kein Überladen von Operatoren
Folgende Statements sind in Java möglich:
String hello = new String("Hello");
String world = new String("World");
String helloWorld = hello + ", " + world;
System.out.println(hello + ", " + world);
während der Code einen Fehler ergibt:
Integer one = new Integer(1);
Integer two = new Integer(2);
Integer three = one+two;
System.out.println(three);
Für eine Addition zweier Integer ist der Code nötig:
Integer one = new Integer(1);
Integer two = new Integer(2);
Integer three = new Integer(one.intValue()+two.intValue());
System.out.println(three);
Ich überlasse es dem Leser, sich ein Urteil zu bilden.
Das Überladen von Operatoren kann das Programmieren einiges intuitiver gestalten. Ein Beispiel aus der Java Class Library ist BigInteger. Eine Addition sieht da so aus:
BigInteger one = new BigInteger("1");
BigInteger two = new BigInteger("2");
BigInteger three = one.add(two);
Dies erfordert es, dass der Anwender immer Zugriff auf die API Dokumentation hat. Für den Anwender der Library wäre es sicherlich einiges einfacher, folgendes zu schreiben:
BigInteger three = one + two;
Keine Enums
Enums erlauben ein fixes Set von erlaubten Werten zu definieren.
Diese können vom Compiler geprüft werden. In Java
wird dies mit public static final Variablen
simuliert. Doch diese können vom Compiler nicht geprüft
werden, wodurch falsche Übergaben von Hand geprüft werden
müssen. Zum Beispiel kann ein C++ Compiler folgende Fehlermeldungen
ausgeben. (Bei g++ braucht's dazu den -Wall Parameter)
warning: enumeration value `NORTH' not handled in switch
warning: label `WEST' defined but not used
Ich will das mal an einem konkreten Beispiel demonstrieren. Erst der C++ Code mit Enums, danach der Java Code mit den Konstanten.
class Plane {
public:
enum directions {
NORTH,
EAST,
SOUTH,
WEST
};
void changeDirection(directions newDir) {
dir = newDir;
}
void move() {
switch(dir) {
case NORTH: y--; break;
case EAST: x++; break;
case SOUTH: y++; break;
case WEST: x--; break;
}
}
private:
directions dir;
int x;
int y;
};
int main(void) {
Plane p;
p.changeDirection(p.WEST);
}
class Plane {
public final static int NORTH=0;
public final static int EAST=1;
public final static int SOUTH=2;
public final static int WEST=3;
public void changeDirection(int newDir) {
if(newDir>=NORTH && newDir<=WEST)
dir = newDir;
else
// Error handling, whatever is suitable
System.exit(99);
}
public void move() {
switch(dir) {
case NORTH: y--; break;
case EAST: x++; break;
case SOUTH: y++; break;
case WEST: x--; break;
}
}
private int y;
private int x;
private int dir;
}
class EnumTest {
public static void main(String[] args) {
Plane p=new Plane();
p.changeDirection(p.WEST);
}
}
Die Java-Lösung hat den grossen Nachteil, dass zur Laufzeit Fehler behandelt werden müssen, die bereits beim kompilieren behandelt werden können. Wie soll das geschehen, was ist ein korrektes Verhalten? Soll es üherbaupt abgehandelt werden? All diese Fragen muss man sich stellen. Und wenn der Fehler behandelt wird: was soll passieren wenn jemand 5 als Parameter übergibt? Nichts? Exception? Absturz? Alles hat seine Vor- und Nachteile und bringt dem Entwickler unnötigen Aufwand und mögliche Fehlerquellen.
Na ich denke der OOP-Ansatz wäre eine eigene Klasse, aber das ist die berüchtigte Kanone. Zudem müssen da die gleichen Fragen zur Fehlerbehandlung gestellt werden und der Compiler hat ebenfalls keine Chance die Fehlermeldung "enumeration value `NORTH' not handled in switch" auszugeben.
Übrigens hat Java einen Variablen-Typ eingebaut, der Enums benötigt (boolean ist ein Enum mit zwei Werten), erlaubt es aber dem Programmierer trotzdem nicht eigene Enums zu definieren.
Plattformunabhängig
Ok, wir machen eine kleine Übung. Bitte zähle alle Plattformen auf, auf welchen Java unterstützt wird. Und jetzt hätte ich gerne eine Liste aller Plattformen die C unterstützen. Danke, wegtreten.
Immer noch da? Ok ein kleines Zitat von www.rolemaker.dk/articles/WhyJavaCanBeUsedForGames:
It is true that there are no Java virtual machines available for the Nintendo 64, Sony Playstation or the Sega Dream cast, but because GCJ (the Java GNU compiler) can run on any platform that has a port of gcc and because gcc ports exist for the consoles you can in effect write efficient GCJ programs for consoles.
Man lasse sich dieses Statement mal auf der Zunge zergehen. Hübsch langsam wie 'ne feine Schweizer Schokolade. Dieser Herr, der übrigens die Verwendung von Java für Spiele fördern möchte, sagt also dass man die Spiele einfach mit GCJ kompilieren sollen und sie laufen auch auf Nintendo 64, der Sony Playstation oder der Sega Dream cast. Es gibt zwei Dinge die man über GCJ wissen sollte:
- Geschrieben in C
- Unterstützt nur JDK 1.1 und nicht die gesamte Class Library
Zum ersten Statement. Damit Java plattformunabhängig ist, verlässt sich Sun also darauf, dass es auf der jeweiligen Plattform einen funktionierenden C Compiler gibt. Dies ist auch für die JVM der Fall, weil eine JVM nicht in Java geschrieben sein kann. Wenn dir dies nicht klar ist, würde ich mir mal Gedanken darüber machen, was für die Ausführung eines Java-Programmes nötig ist. Das ganze kann man dann in einem kleinen Kreis aufzeichnen, den Titel "Teufelskreis" darüber stellen und erst dann weiter lesen.
Weiter zum nächsten Statement. GCJ unterstützt momentan JDK 1.1 und nicht die gesamte Class Libary, besonders schlecht schneidet die Unterstützung von GUI-Klassen ab. Siehe auch die GCJ FAQ, Punkte 2.1 und 2.8.
Hier drin steckt ein viel tieferes Problem als man auf den ersten Blick annehmen könnte. Es weist darauf hin, dass für die Verwendung neuer Java-Features eine aktuelle JVM für die Plattform existieren muss. Wenn diese aus irgendeinem Grund nicht vorhanden ist, muss man entweder das Programm kompatibel halten oder die Plattform nicht mehr unterstützen. Wir erweitern also die Übung von vorhin. Bitte stelle mir a) eine Liste mit allen Plattformen zusammen welche eine JVM für Java 1.3 besitzen und b) erforsche die Java-Version, welche von allen Plattformen die eine JVM besitzen unterstützt wird. Ach und wenn du schon dabei bist, such dir doch mal eine Liste mit den Portierungen von Linux oder NetBSD und vergleiche die beiden Zahlen mit dem Ergebnis der Aufgabe a.
Zum Abschluss noch ein Zitat aus der oben erwähnten GCJ FAQ:
I think it's important to stress that there is a big difference between Java and the many libraries which Java supports. Unfortunately, Sun's promise of "write once, run everywhere" assumes much more than a JVM: you also need the full set of JDK libraries. Considering that new Java APIs come out every week, it's going to be impossible to track everything.
Keine Mehrfachvererbung
Mehrfachvererbung hat viele für und wider. Sun hat sich aufgrund der Nachteile dafür entschlossen, keine Mehrfachvererbung zuzulassen. Ich glaube, dass die meisten Programmierer keine Bevormundung des Compiler-Herstellers nötig haben. Einem C++ Programmierer der Mehrfachvererbung anwendet ist klar, dass dies gefährlich ist, und er waltet auch mit entsprechender Vorsicht. Interfaces sind da nur eine kastrierte Alternative, da sie es erfordern dass der Code jedes mal neu implementiert wird. Das kann wirklich nicht die Lösung sein.
Keine Default-Werte für Methoden-Parameter
Folgender Code ist mit C++, nicht jedoch mit Java möglich. Ich frage hier in aller Öffentlichkeit und bin gerne bereit Antworten entgegenzunehmen: WIESO?
public doIt(int param1=0, int param2=0) {
// do something here
}
Was genau ist der Grund, dass ich das in Java in etwa wie folgt implementieren muss?
public doIt(int param1, int param2) {
// do something here
}
public doIt(int param1) {
doIt(param1, 0)
}
public doIt() {
doIt(0, 0);
}
Mich friert's. Also, was überseh' ich? Danke für jegliche Antworten.
Getter und Setter
Wieso muss für jede private Variable eine getter und eine setter-Methode definiert werden? Das ist eklige Schreibarbeit. Ich werde darauf nicht weitergehen, denn Michael Schwern hat das sehr gut beschrieben.
Credits
Entwürwfe dieses Artikels zirkulierten in einer privaten Mailingliste unter Kollegen. Ich danke besonders folgenden Personen für ihre Mitarbeit: (geordnet nach Nachnamen)
- Christian Oberholzer
- Jonas Schwertfeger
See also
Eine wilde Sammlung von Links zu diesem Thema.
Internal Sun Memo zu den Nachteilen von Java
Why Michael Schwern is not a Java programmer
Java is not type-safe
Google search for "java sucks"
Java Critism in the Google Web Directory
A Java Critique
Why Java can be used for games
Harnessing multiple inheritance
Uses and Abuses of Inheritance
Jamie Zawinski on Java