Also ich durfte/musste jetzt im ersten Semester meines Studiums Java lernen und fand C++ doch sympathischer. Finde es aber auch nicht verwunderlich. Kann man schließlich C++, dann sind die für Anfänger vllt. nützlichen Einschränkungen eher Hindernis als Nutzen.
Klar ist Java grundsätzlich relativ leicht zu programmieren und gerade Arrays sind doch deutlich schöner zu verwenden als in C++(komfortables Erstellen von Arrays auch bei nicht zur Kompilierungszeit bekannten Längen und einfache Überprüfung der Länge von Arrays etc.).
Allerdings gab es doch etliche Punkte, die hier ja auch schon häufig genannt wurden, die mich oft gestört haben:
Operatorenüberladung: Operatoren dürfen nicht überladen werden, weil der Code unleserlich würde und man nicht unbedingt intuitiv versteht, was ein Operator tun könnte. Wozu bin ich denn Programmierer? Wenn ich unleserlichen Code schreiben und warten will, dann soll man micht doch lassen. Vor allen finde ich das Argument ziemlich lächerlich. Denn jeder der schon mal mehrere Operationen(+ - * /) mit bspw. BigInteger durchgeführt hat, weiß, dass der Code alles andere als gut lesbar ist, weil sämtliche Funktionen ineinander verschachhtelt werden und es sieht dann einfach schrecklich aus. Und wer garantiert mir denn, dass Funktionen genau das tun, was ihr Name vermuten lässt? Auch niemand, also verstehe ich einfach nicht, warum man Operatoren nicht überladen sollen dürfte.
Mehrfachvererbung: Mehrfachvererbung ist verboten! Achso naja gut so ganz stimmt das ja doch nicht. Denn manchmal wäre Mehrfachvererbung auch ganz sinnvoll. Darum gibt es Interfaces. Interfaces darf man mehrere implementieren. Oberklassen darf es nur eine geben. Dieses Konzept ist für mich auch nicht unbedingt schlüssig gewählt. Das hat nämlich ggf. zur Folge, dass man in Interfaces abstrakte Getter und Setter-Methode implementiert, deren Variablen, die gesetzt werden sollen, erst von den eigentlichen Klassen enthalten sind(schließlich dürfen in Interfaces keine dynamischen Instanzvariablen enthalten sein). Schönes Klassendesign sieht dann auch jeden Fall anders aus.
Referenzsemantik: Vor kurzem laß ich den schönen Satz: "Pointers are evil, we call them references." Trifft finde ich schon ziemlich genau das, was es letztlich ist. Zwar sind Referenzen sehr schön, allerdings muss man bedenken, dass diese Referenzen alle irgendwann vom Garbage-Controller aufgesammelt werden müssen. Und wann der Garbage-Controller sie einsammelt ist ungewiss. Das Problem ist, dass der Garbage-Controller alles andere als schnell ist und gerade in der Spieleentwicklung ist es alles andere als wünschenswert, wenn mitten im Spiel, dieses plötzlich einfriert, weil der Garbage-Collector die Referenzleichen aufräumt. In C++ entscheide ich eben selbst, wann die Variablen gelöscht werden. In C++ hört man dennoch oft, man solle das Schlüsselwort "new" möglichst selten verwenden, da man Pointer, die man erstellt auch wieder aufräumen muss und manchmal dann unklar ist, wo nun der Pointer egtl. gelöscht werden soll bzw. welche Klasse dafür zuständig ist. In Java gilt letztlich aber genau das gleiche, da man eben aufpassen muss, dass man nicht unbedacht Referenzen erstellt, die dann irgendwann vllt. vom Garbage-Controller aufgeräumt werden müssen.
Gleichzeitig hat man in C++ aber die Möglichkeit bei der Variablenerstellung klar anzugeben, ob die Variable eine Referenz auf eine andere sein soll oder eine Kopie angelegt werden soll, was doch sehr viel Komfort bringt. Ich glaube zwar, dass gerade Anfänger deutlich öfter Kopien nutzen als notwendig, aber in Java finde ich es einfach extrem schwierig, wenn man nicht ohne weiteres eine Variable als Kopie anlegen kann. Statt dessen muss die Variable, die kopiert werden soll explizite Funktionen aufrufen, damit sie kopiert wird(ggf. ist noch ein expliziter Cast notwendig) und es ist deutlich unübersichtlicher eine Kopie auszumachen. Stattdessen gibt es für die internen Datentypen eben einfach noch jeweils Klassen, welche dann Referenzsemantik bieten, während die normalen Datentypen Kopien sind.
Ein weiterer Knackpunkt sind noch Generics, die gegenüber Templates doch klar den kürzen ziehen. Was ich allerdings ebenfalls sehr interessant finde ist, dass es schlichtweg keine Möglichkeit gibt bspw. generische Arrays anzulegen. Das kann dann zu Compiler-Warnungen führen. Lösungen dafür gibt es keine außer die Warnung eben zu unterdrücken. Dabei passiert im Code egtl. nie etwas schlimmes und es ist zu jeder Zeit gewährleistet, dass es funktioniert, aber dennoch gibt es eben Warnungen, die man nicht beheben kann. Nicht unbedingt ein Zeichen für gutes Sprachdesign.