Aber ich (bin antiautoritär erzogen

und ) würde dann erstmal fragen, wie diese Regeln begründet sind. Sie wirken zumindest sehr spezifisch und ausgefeilt und schwer zu überprüfen, und u.U. auch schwer einzuhalten: Angenommen man hat schon zwei, drei Konstruktoren, und jetzt kommt in die Klasse auf einmal noch eine Variable, die final sein könnte/sollte - dann würde auf einmal die Regel greifen:
Wenn mindestens eine finale Instanzvariable existiert, die ihren Wert von einem Parameter eines Konstruktors bekommen muss, so darf der Konstruktor nur Parameter zur Initialisierung dieser finalen Instanzvariablen besitzen.
Die Abhilfen (die anderen Konstruktoren wegwerfen - oder "die Variable einfach nicht-final machen"

- stelle ich mir da schwierig vor).
Ich erkläre meine Überlegungen dazu gerne, und sollte sich am Ende herausstellen, dass ich eigentlich dumm wie Brot bin, habe ich wenigstens was gelernt.
Diese Regeln dienen mir selbst vorrangig als Muster für Klassen und haben sich bei mir so herauskristallisiert, weil mich das gleiche Problem nervte wie den TO. Diese Regeln sind nicht in Stein gemeißelt, geben mir aber eine gute Orientierung und sorgen für Übersichtlichkeit.
Zu Fall 1: Man kann sich viel Ärger ersparen, wenn man die final fields schon beim Entwurf ein für allemal festlegt (zumindest wäre es erstrebenswert). hashCode(), equals() und ggf. compareTo() lassen sich so leicht implementieren bzw. generieren und funktionieren im Zusammenhang mit Collections dann auch wie erwartet.
Dieser Fall entstand, wie vielleicht schon zu erahnen war, in Anlehnung an die Idee der unveränderlichen (immutable) Objekte. Der Konstruktor bekommt damit ausschließlich(!) die Aufgabe, die unveränderlichen bzw. zur Identifizierung in Collections notwendigen final fields zu initialisieren. Würden die Parameter für non-final fields neben denen für final fields auftauchen, erscheinen sie als "wichtiger", als sie in dem Augenblick sind. ("Wichtig" bzw. "unwichtig" bezieht sich auf den Zeitpunkt, an dem Werte zur Verfügung stehen müssen. Non-final fields können auch später festgelegt werden, sie sind also vergleichsweise unwichtig.)
Der Witz an der Sache ist ja, dass ein Konstruktor nur aufgerufen werden kann, wenn für
alle Parameter brauchbare Werte existieren. Wenn also der Konstruktor final fields initialisieren muss, wäre ohne Einhaltung dieser Regel der Aufrufer möglicherweise dazu verdonnert, Werte für non-final fields anzugeben, obwohl diese noch gar nicht vorliegen (weil z.B. ein anderes Objekt erst sinnvolle Werte dafür liefern kann,
nachdem es von unserem gerade erzeugten Foo erfahren hat).
Zwar gibt es so etwas wie Überladung, aber ich finde, es kann schnell unübersichtlich werden, wenn in einer einzigen Parameterliste "wichtige" und "unwichtige" Parameter nebeneinander auftauchen.
Eindämmen könnte man das Problem damit, dass grundsätzlich
alle von außen festlegbaren final fields entsprechende Parameter bekommen. Dann wäre ersichtlich, dass Parameter, die nicht in allen Konstruktoren auftauchen, nur für non-final fields da sind. Daraus ergibt sich aber wieder ein Problem: Wenn es viele final fields gibt, weisen die Konstruktoren entsprechend lange Parameterlisten auf (siehe oben das Problem: es müssen genügend Werte vorliegen!), und das dient nicht gerade der Übersichtlichkeit.
Wenn nun tatsächlich der Fall eintreten sollte, dass final fields hinzukommen oder entfernt werden (was schon schlimm genug sein dürfte), müssten
alle Parameterlisten der Konstruktoren wieder angepasst werden, was dazu führte, dass kein einziger Konstruktoraufruf mehr stimmen würde. Hier als "Lösung" wieder zu überladen, birgt zum einen das Risiko, schneller als sonst Kollisionen wegen plötzlich gleich aussehender Signaturen heraufzubeschwören. Zum anderen hat es zur Folge, dass dann Konstruktoren mit vielen quasi-optionalen Parametern zur Verfügung stehen, von denen
einige "wichtig" und
einige wieder "unwichtig" sind. Zumindest ich würde dann nicht mehr wirklich durchblicken, wie ich welchen Parameter bewerten muss.
Deshalb lautet meine Devise: Wenn ein Konstruktor Parameter für final fields bereitstellt, dann sollten alle Konstruktoren auch
nur Parameter für final fields haben. Das sagt mir dann als Aufrufer, dass alle Parameter gleich "wichtig" sind. Und dann klappt das auch wieder mit dem Überladen.
Zu
Fall 2 und
Fall 3 habe ich gerade keine Lust.

Die Kurzfassung: Fall 2 ist eine Abschwächung von Fall 1 (es würde sich dann eher um "semi-final" fields handeln) mit dem Hintergrund, den bereits für ein Objekt reservierten Speicher wiederverwenden zu können. Fall 3 hat mit den anderen nicht mehr viel zu tun. Schreibzugriffe auf fields durch einen Konstruktor erfolgen eben genauso, als hätte man manuell die Setter aufgerufen, wodurch Schreibzugriffe von außen vereinheitlicht sind (das ist bei den anderen Fällen bereits implizit so).
Ich hoffe, das ist jetzt etwas klarer geworden.
Ark