Sehe ich im Prinzip genauso. Habe vorher in C/C++ programmiert (C programmiere ich nach wie vor "beruflich") und insofern finde ich zunächst mal die ähnliche Grammatik sehr angenehm. Übrigens - neben dem Fehlen gelungener IDEs/GUI-Builder - aus meiner Sicht ein wesentlicher Punkt gegen Python, Ruby usw., deren Whitespace-Semantik mich fertigmacht (da kann ich ja auch gleich Whitespace oder Brainfuck programmieren, siehe
http://www.99-bottles-of-beer.net).
An C stört mich nicht mal so sehr die fehlende Möglichkeit, objektorientiert zu programmieren, sondern der sehr hohe Aufwand, der für dynamische Strings/Listen/Arrays notwendig ist. In C++ benutzen die einen die STL, die anderen (bislang) die MFC-Äquivalente, meist ohne sich darüber bewußt zu sein, daß dann selbst Konsolenprogramme MS-spezifisch werden. Zudem fehlt die konsequente Unicode-Unterstützung von Java.
Außerdem fehlt der MFC die Möglichkeit von Layouts: bei Fensteranwendungen muß man alles "von Hand" programmieren, was wiederum dazu führt, daß 90% aller mit dem VisualStudio zusammengeklickten Mini-Anwendungen dialogbasiert sind. Nicht davon zu sprechen, daß es einem die MFC-Wizards sehr erschweren, Anwendung und Oberfläche klar zu trennen, um eine spätere Portierung auf eine andere Plattform zu erleichtern. Ich habe es auch immer als sehr negativ empfunden, daß man sich bei C/C++ unter Win32 gleich zu Beginn des Projekts dafür entscheiden muß, ob man eine Konsolenanwendung oder eine im Fenster programmieren will. Das geht sogar soweit, daß einer Fensteranwendung die main-Funktion fehlt (wie übrigens auch in Java bei Applets, was ich auch unschön finde, wo es aber einen gewissen Sinn hat). Da habe ich mir immer meinen Amiga zurückgewünscht. Aber siehe da: Java bietet mir wieder die lange vermißte Freiheit, in einem Konsolenprogramm ein Fenster zu öffnen.
Ein weiterer massiver Nachteil von C/C++ ist die Plattformabhängigkeit aller Integertypen (char, short, int, long). Wenn man wirklich plattformunabhängig programmieren will, erhöht das den Aufwand immens. Glücklicherweise wurde dieses Design nicht in Java übernommen. Das Fehlen von vorzeichenlosen Integertypen in Java habe ich - als alter C-Hase - zunächst als extreme und zudem völlig unnötige Einschränkung empfunden. Immerhin wird der darstellbare Zahlenbereich halbiert und einige Operationen auf unsigned Bytes o.ä. ziemlich verkompliziert. Der Vorteil ist allerdings, daß damit die (in C sehr fehlerträchtige) Vergleichsproblematik signed/unsigned gelöst wurde. IMHO wäre es aber zielführender gewesen, einfach wie in Lint den Vergleich signed/unsigned mit einer Compilerwarnung zu bestrafen und einen expliziten Cast zu verlangen. Bin mir nach wie vor nicht sicher, wie negativ ich diese Problematik werten soll.
Mal abgesehen von ein paar anderen Unschönheiten in Java (Wrapper, C-Switch/Case, Inkonsistenz Basistyp/Objekt, starres und inkonsistentes Parameterhandling), die mich aber im "Alltag" nicht übermäßig stören, ärgert mich vor allem, daß ein wesentliches Problem von C/C++ eher noch verschlimmert wurde:
Wenn man in C Elemente in einem Struct zusammenfaßt und davon per sizeof/memcopy oder per "=" eine Kopie anlegt, weiß man, daß diese Kopie immer "flach" ist. Die Funktionsweise ist immer klar und man muß halt aufpassen. In C++ sollte jede Klasse den "="-Operator so überladen, daß eine unabhängige Kopie entsteht. Darauf kann man sich natürlich nicht verlassen - in jedem Fall besteht immer die Möglichkeit einer performanten flachen Kopie.
Nun weiß ich nicht, ob es nur mir so geht, aber 90% der Fehler, an denen ich länger als ein paar Minuten rumsuche, entstehen durch ungewollte Abhängigkeiten bei flachen Kopien. Insofern hatte ich irgendwie gehofft, daß Java sich dieser Sache annimmt. Ich gehe gar nicht näher darauf ein, daß der Zuweisungsoperator nur bei Basistypen wie erwartet funktioniert, gäbe es eine Standard-Zuweisungsmethode (sowas wie Object.copy). Die gibt es aber schonmal nicht, stattdessen nur Object.clone(). Das macht es oft unmöglich, bereits allokierte Variablen weiterzubenutzen und man muß auf Kosten der Performance neu allokieren. Ich übergehe auch mal den unschönen Aspekt, daß das geklonte Objekt immer vom Typ Object ist. Besonders übel ist aber, daß einige Standardklassen gar keine Clone-Methode implementieren und alle (?) restlichen bloß eine flache Kopie anlegen.
Durch das Fehlen einer Copy-Methode wird man also nicht nur zu Neu-Allokation gezwungen, man bekommt per Clone (falls überhaupt) auch noch eine flache Kopie, bei der Fehler durch ungewollte Querverbindungen quasi vorprogrammiert sind. Im schlimmsten Fall bekommt man nicht mal eine flache Kopie (z.B. BigInteger) per Clone und kann sich überlegen, wie man über Umwege und bei meiser Performance irgendwie eine Kopie bekommt.
Im Licht dieser Problematik erscheint es übrigens plötzlich auch positiv, daß Strings "immutable" sind, was ich zunächst eigentlich als Design-Sünde empfunden habe. Wenigstens ist man aber so sicher, daß Strings in Listen usw. nicht nachträglich geändert werden können. Ich halte das aber nach wie vor für kein überzeigendes Design. Besser wäre es, Strings wären nicht immutable und tiefe Kopien wären dafür möglich.