Pourquoi est super.super.méthode(); pas permis en Java?
j'ai lu cette question et j'ai pensé que ce serait facilement résolu (non pas qu'il ne soit pas soluble sans) si on pouvait écrire:
@Override
public String toString() {
return super.super.toString();
}
Je ne suis pas sûr qu'il soit utile dans de nombreux cas, mais je me demande pourquoi il n'est pas et si quelque chose comme cela existe dans d'autres langues.
qu'en pensez-vous?
EDIT: Pour clarifier les choses: oui, je sais, c'est impossible à Java et ça ne me manque pas. Ce n'est rien que je m'attendais à travailler et j'ai été surpris d'avoir une erreur de compilation. J'ai juste eu l'idée et, comme pour en discuter.
21 réponses
il viole encapsulation. Tu ne devrais pas pouvoir contourner le comportement de la classe des parents. Il est logique d'être parfois en mesure de contourner le comportement de votre classe propre (en particulier à l'intérieur de la même méthode), mais pas celui de vos parents. Par exemple, supposons que nous ayons une base "collection d'Articles", une sous-classe représentant "une collection d'articles rouges" et une sous-classe de celle représentant "une collection de grands articles rouges". Il est logique d'avoir:
public class Items
{
public void add(Item item) { ... }
}
public class RedItems extends Items
{
@Override
public void add(Item item)
{
if (!item.isRed())
{
throw new NotRedItemException();
}
super.add(item);
}
}
public class BigRedItems extends RedItems
{
@Override
public void add(Item item)
{
if (!item.isBig())
{
throw new NotBigItemException();
}
super.add(item);
}
}
c'est très bien - les RedItems peuvent toujours être sûrs que les articles qu'il contient sont tous rouges. Supposons maintenant que nous étions capables d'appeler super.super.ajouter ():
public class NaughtyItems extends RedItems
{
@Override
public void add(Item item)
{
// I don't care if it's red or not. Take that, RedItems!
super.super.add(item);
}
}
maintenant nous pouvons ajouter ce que nous voulons, et l'invariant dans RedItems
est cassé.
ça a du sens?
je pense que Jon Skeet a la bonne réponse. Je voudrais juste ajouter que vous can accéder aux variables ombrées des superclasses de superclasses en moulant this
:
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t" + x);
System.out.println("super.x=\t\t" + super.x);
System.out.println("((T2)this).x=\t" + ((T2)this).x);
System.out.println("((T1)this).x=\t" + ((T1)this).x);
System.out.println("((I)this).x=\t" + ((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
qui produit la sortie:
x= 3 super.x= 2 ((T2)this).x= 2 ((T1)this).x= 1 ((I)this).x= 0
(exemple tiré du JLS )
cependant, cela ne fonctionne pas pour les appels de méthode parce que les appels de méthode sont déterminés en fonction du type d'exécution de l'objet.
je pense que le code suivant permet d'utiliser super.super...super.méthode() dans la plupart des cas. cents cinquante et une million neuf cent vingt mille neuf cent vingt"
en bref
- créer une instance temporaire de type ancêtre
- copier les valeurs des champs de original objet à temporaire
- invoquer la cible de la méthode sur l'objet temporaire
- copier les valeurs modifiées retour à l'objet original
Utilisation :
public class A {
public void doThat() { ... }
}
public class B extends A {
public void doThat() { /* don't call super.doThat() */ }
}
public class C extends B {
public void doThat() {
Magic.exec(A.class, this, "doThat");
}
}
public class Magic {
public static <Type, ChieldType extends Type> void exec(Class<Type> oneSuperType, ChieldType instance,
String methodOfParentToExec) {
try {
Type type = oneSuperType.newInstance();
shareVars(oneSuperType, instance, type);
oneSuperType.getMethod(methodOfParentToExec).invoke(type);
shareVars(oneSuperType, type, instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static <Type, SourceType extends Type, TargetType extends Type> void shareVars(Class<Type> clazz,
SourceType source, TargetType target) throws IllegalArgumentException, IllegalAccessException {
Class<?> loop = clazz;
do {
for (Field f : loop.getDeclaredFields()) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
f.set(target, f.get(source));
}
loop = loop.getSuperclass();
} while (loop != Object.class);
}
}
Je n'ai pas assez de réputation pour commenter donc je vais ajouter ceci aux autres réponses.
Jon Skeet répond parfaitement, avec un bel exemple. Matt B a un point: toutes les superclasses n'ont pas de supers. Votre code se briserait si vous appeliez un super d'un super qui n'avait pas de super.
la programmation orientée Objet (Java est) est l'ensemble des objets, des fonctions pas. Si vous voulez une programmation axée sur les tâches, choisissez C++ ou autre chose. Si votre objet ne rentre pas dans sa classe super, alors vous devez l'ajouter à la "classe grand-parent", créer une nouvelle classe, ou trouver un autre super il ne rentre dans.
personnellement, J'ai trouvé que cette limitation est l'une des plus grandes forces de Java. Le Code est un peu rigide par rapport aux autres langues que j'ai utilisées, mais je sais toujours à quoi m'attendre. Cela aide avec L'objectif "simple et familier" de Java. Dans mon esprit, un appel à super.super n'est pas simple ou familier. Peut-être les développeurs ressenti la même chose?
il y a de bonnes raisons de faire ça. Vous pouvez avoir une sous-classe qui a une méthode qui est implémentée incorrectement, mais la méthode mère est implémentée correctement. Parce qu'il appartient à une bibliothèque tierce partie, vous pourriez être incapable/peu disposé à changer la source. Dans ce cas, vous voulez créer une sous-classe mais outrepasser une méthode pour appeler le super.super méthode.
comme le montrent d'autres affiches, il est possible de le faire à travers la réflexion, mais il devrait être possible de faire quelque chose comme
(SuperSuperClass this).theMethod ();
je fais face à ce problème en ce moment - la solution rapide est de copier et coller la méthode superclass dans la méthode subsubclass:)
en plus des très bons points que d'autres ont fait, je pense qu'il y a une autre raison: et si la superclasse n'a pas de superclasse?
puisque chaque classe s'étend naturellement (au moins) Object
, super.whatever()
se référera toujours à une méthode dans la superclasse. Mais que faire si votre classe ne s'étend que Object
- à quoi super.super
se référerait alors? Comment gérer ce comportement - une erreur de compilateur, un NullPointer, etc.?
je pense que la raison principale pour laquelle cela n'est pas autorisé est qu'il viole encapsulation, mais cela pourrait être une petite raison aussi.
je pense que si vous écrasez une méthode et que vous voulez toute la version super-classe de celle-ci (comme, disons pour equals
), alors vous voulez virtuellement toujours appeler la version direct superclass d'abord, qu'on appellera sa version superclass à son tour si elle veut.
je pense que cela n'a que rarement un sens (si ce n'est pas du tout. je ne peux pas penser à un cas où il le fait), appeler arbitraire une superclasse " version d'une méthode. Je ne sais pas si c'est possible en Java. Il peut être fait en C++:
this->ReallyTheBase::foo();
à une supposition, parce qu'il n'est pas utilisé que souvent. La seule raison pour laquelle je pouvais voir l'utiliser est si votre parent direct a dépassé certaines fonctionnalités et vous essayez de le restaurer à l'original.
, ce qui me semble contraire aux principes de L'OO, puisque le parent direct de la classe devrait être plus étroitement lié à votre classe que le grand-parent ne l'est.
je mettrais le concierge.super corps de méthode dans une autre méthode ,si possible
class SuperSuperClass {
public String toString() {
return DescribeMe();
}
protected String DescribeMe() {
return "I am super super";
}
}
class SuperClass extends SuperSuperClass {
public String toString() {
return "I am super";
}
}
class ChildClass extends SuperClass {
public String toString() {
return DescribeMe();
}
}
ou si vous ne pouvez pas changer la classe super-super, vous pouvez essayer ceci:
class SuperSuperClass {
public String toString() {
return "I am super super";
}
}
class SuperClass extends SuperSuperClass {
public String toString() {
return DescribeMe(super.toString());
}
protected String DescribeMe(string fromSuper) {
return "I am super";
}
}
class ChildClass extends SuperClass {
protected String DescribeMe(string fromSuper) {
return fromSuper;
}
}
dans les deux cas, le
new ChildClass().toString();
résulte en "je suis super super "
il semblerait qu'il soit possible d'obtenir au moins la classe de la superclasse de la superclasse, mais pas nécessairement l'instance de celle-ci, en utilisant la réflexion; si cela peut être utile, s'il vous plaît considérer le Javadoc à http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass ()
public class A {
@Override
public String toString() {
return "A";
}
}
public class B extends A {
@Override
public String toString() {
return "B";
}
}
public class C extends B {
@Override
public String toString() {
return "C";
}
}
public class D extends C {
@Override
public String toString() {
String result = "";
try {
result = this.getClass().getSuperclass().getSuperclass().getSuperclass().newInstance().toString();
} catch (InstantiationException ex) {
Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
}
return result;
}
}
public class Main {
public static void main(String... args) {
D d = new D();
System.out.println(d);
}
}
exécuter: Un BUILD SUCCESSFUL (total time: 0 seconds)
j'ai eu des situations comme celles-ci lorsque l'architecture doit construire une fonctionnalité commune dans une CustomBaseClass commune qui implémente au nom de plusieurs classes dérivées. Cependant, nous avons besoin de contourner la logique commune de méthode spécifique pour une classe dérivée. Dans de tels cas, nous devons utiliser une super.super.mise en œuvre de methodX.
nous y parvenons en introduisant un membre booléen dans la classe CustomBaseClass, qui peut être utilisé pour différer sélectivement la mise en œuvre et rendement par rapport à la mise en œuvre par défaut lorsque cela est souhaitable.
...
FrameworkBaseClass (....) extends...
{
methodA(...){...}
methodB(...){...}
...
methodX(...)
...
methodN(...){...}
}
/* CustomBaseClass overrides default framework functionality for benefit of several derived classes.*/
CustomBaseClass(...) extends FrameworkBaseClass
{
private boolean skipMethodX=false;
/* implement accessors isSkipMethodX() and setSkipMethodX(boolean)*/
methodA(...){...}
methodB(...){...}
...
methodN(...){...}
methodX(...){
if (isSkipMethodX()) {
setSKipMethodX(false);
super.methodX(...);
return;
}
... //common method logic
}
}
DerivedClass1(...) extends CustomBaseClass
DerivedClass2(...) extends CustomBaseClass
...
DerivedClassN(...) extends CustomBaseClass...
DerivedClassX(...) extends CustomBaseClass...
{
methodX(...){
super.setSKipMethodX(true);
super.methodX(...);
}
}
cependant, avec de bons principes d'architecture suivis dans le cadre aussi bien que dans l'application, nous pourrions éviter de telles situations facilement, en utilisant l'approche hasA, au lieu de l'approche isA. Mais en tout temps, il n'est pas très pratique de s'attendre à une architecture bien conçue, d'où la nécessité de s'écarter des principes de conception solides et d'introduire des hacks comme celui-ci. Juste mes 2 cents...
@Jon Skeet jolie explication. IMO si quelqu'un veut appeler super.super méthode alors on doit vouloir ignorer le comportement du parent immédiat, mais vouloir accéder au comportement du grand parent. Ceci peut être réalisé par exemple. Comme ci-dessous code
public class A {
protected void printClass() {
System.out.println("In A Class");
}
}
public class B extends A {
@Override
protected void printClass() {
if (!(this instanceof C)) {
System.out.println("In B Class");
}
super.printClass();
}
}
public class C extends B {
@Override
protected void printClass() {
System.out.println("In C Class");
super.printClass();
}
}
Voici driver class,
public class Driver {
public static void main(String[] args) {
C c = new C();
c.printClass();
}
}
sortie de ce sera
In C Class
In A Class
Classe B printClass comportement sera ignoré dans ce cas. Je suis pas sûr est-ce un idéal ou une bonne pratique pour atteindre super.super, mais il est encore à travailler.
si vous pensez que vous allez avoir besoin de la superclasse, vous pouvez le référencer dans une variable pour cette classe. Par exemple:
public class Foo
{
public int getNumber()
{
return 0;
}
}
public class SuperFoo extends Foo
{
public static Foo superClass = new Foo();
public int getNumber()
{
return 1;
}
}
public class UltraFoo extends Foo
{
public static void main(String[] args)
{
System.out.println(new UltraFoo.getNumber());
System.out.println(new SuperFoo().getNumber());
System.out.println(new SuperFoo().superClass.getNumber());
}
public int getNumber()
{
return 2;
}
}
doit être imprimé:
2
1
0
IMO, c'est un moyen propre pour atteindre le comportement super.super.sayYourName()
en Java.
public class GrandMa {
public void sayYourName(){
System.out.println("Grandma Fedora");
}
}
public class Mama extends GrandMa {
public void sayYourName(boolean lie){
if(lie){
super.sayYourName();
}else {
System.out.println("Mama Stephanida");
}
}
}
public class Daughter extends Mama {
public void sayYourName(boolean lie){
if(lie){
super.sayYourName(lie);
}else {
System.out.println("Little girl Masha");
}
}
}
public class TestDaughter {
public static void main(String[] args){
Daughter d = new Daughter();
System.out.print("Request to lie: d.sayYourName(true) returns ");
d.sayYourName(true);
System.out.print("Request not to lie: d.sayYourName(false) returns ");
d.sayYourName(false);
}
}
sortie:
Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha
appel de super.super.méthode() de sens que si vous ne pouvez pas modifier le code de la classe de base. Cela se produit souvent lorsque vous étendez une bibliothèque existante.
demandez-vous d'abord pourquoi vous prolongez cette classe? Si la réponse est "parce que je ne peux pas le changer" alors vous pouvez créer le paquet exact et la classe dans votre application, et réécrire la méthode naughty ou créer délégué:
package com.company.application;
public class OneYouWantExtend extends OneThatContainsDesiredMethod {
// one way is to rewrite method() to call super.method() only or
// to doStuff() and then call super.method()
public void method() {
if (isDoStuff()) {
// do stuff
}
super.method();
}
protected abstract boolean isDoStuff();
// second way is to define methodDelegate() that will call hidden super.method()
public void methodDelegate() {
super.method();
}
...
}
public class OneThatContainsDesiredMethod {
public void method() {...}
...
}
par exemple, vous pouvez créer org.springframework.test.cadre.junit4.SpringJUnit4ClassRunner classe dans votre application de sorte que cette classe doit être chargée avant la vraie de jar. Alors réécrire les méthodes ou les constructeurs.
Attention: c'est un hack absolu, et il est fortement déconseillé d'utiliser, mais il fonctionne! À l'aide de cette approche est dangereuse en raison de problèmes possibles avec classe chargeurs. En outre, cela peut causer des problèmes à chaque fois que vous mettrez à jour la bibliothèque qui contient la classe écrasée.
je pense que c'est un problème qui brise le contrat de succession.
En étendant une classe vous obéissez / acceptez son comportement, les caractéristiques
Pendant que vous appelez super.super.method()
, vous voulez briser votre propre accord d'obéissance.
Vous ne pouvez pas cherry pick de la super-classe .
cependant, il peut arriver des situations où vous sentez besoin d'appeler super.super.method()
- habituellement un mauvais signe de conception, dans votre code ou dans le code que vous héritez !
Si les classes super et super super ne peuvent pas être modifiées (certains codes hérités), optez pour la composition plutôt que l'héritage.
La rupture de l'Encapsulation est quand vous @Override certaines méthodes en cassant le code encapsulé.
Les méthodes conçues à ne pas dépasser sont marqués
finale .
dans C# vous pouvez appeler une méthode de n'importe quel ancêtre comme ceci:
public class A
internal virtual void foo()
...
public class B : A
public new void foo()
...
public class C : B
public new void foo() {
(this as A).foo();
}
vous pouvez aussi le faire dans Delphi:
type
A=class
procedure foo;
...
B=class(A)
procedure foo; override;
...
C=class(B)
procedure foo; override;
...
A(objC).foo();
mais en Java vous pouvez faire une telle mise au point que par certains engrenages. Une solution possible est:
class A {
int y=10;
void foo(Class X) throws Exception {
if(X!=A.class)
throw new Exception("Incorrect parameter of "+this.getClass().getName()+".foo("+X.getName()+")");
y++;
System.out.printf("A.foo(%s): y=%d\n",X.getName(),y);
}
void foo() throws Exception {
System.out.printf("A.foo()\n");
this.foo(this.getClass());
}
}
class B extends A {
int y=20;
@Override
void foo(Class X) throws Exception {
if(X==B.class) {
y++;
System.out.printf("B.foo(%s): y=%d\n",X.getName(),y);
} else {
System.out.printf("B.foo(%s) calls B.super.foo(%s)\n",X.getName(),X.getName());
super.foo(X);
}
}
}
class C extends B {
int y=30;
@Override
void foo(Class X) throws Exception {
if(X==C.class) {
y++;
System.out.printf("C.foo(%s): y=%d\n",X.getName(),y);
} else {
System.out.printf("C.foo(%s) calls C.super.foo(%s)\n",X.getName(),X.getName());
super.foo(X);
}
}
void DoIt() {
try {
System.out.printf("DoIt: foo():\n");
foo();
Show();
System.out.printf("DoIt: foo(B):\n");
foo(B.class);
Show();
System.out.printf("DoIt: foo(A):\n");
foo(A.class);
Show();
} catch(Exception e) {
//...
}
}
void Show() {
System.out.printf("Show: A.y=%d, B.y=%d, C.y=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y);
}
}
objC.DoIt () sortie du résultat:
DoIt: foo():
A.foo()
C.foo(C): y=31
Show: A.y=10, B.y=20, C.y=31
DoIt: foo(B):
C.foo(B) calls C.super.foo(B)
B.foo(B): y=21
Show: A.y=10, B.y=21, C.y=31
DoIt: foo(A):
C.foo(A) calls C.super.foo(A)
B.foo(A) calls B.super.foo(A)
A.foo(A): y=11
Show: A.y=11, B.y=21, C.y=31
Regardez ce Github du projet, en particulier la objectHandle variable. Ce projet montre comment appeler réellement et précisément la méthode des grands-parents sur un petit-enfant.
Juste au cas où le lien est cassé, voici le code:
import lombok.val;
import org.junit.Assert;
import org.junit.Test;
import java.lang.invoke.*;
/*
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
Please don't actually do this... :P
*/
public class ImplLookupTest {
private MethodHandles.Lookup getImplLookup() throws NoSuchFieldException, IllegalAccessException {
val field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
field.setAccessible(true);
return (MethodHandles.Lookup) field.get(null);
}
@Test
public void test() throws Throwable {
val lookup = getImplLookup();
val baseHandle = lookup.findSpecial(Base.class, "toString",
MethodType.methodType(String.class),
Sub.class);
val objectHandle = lookup.findSpecial(Object.class, "toString",
MethodType.methodType(String.class),
// Must use Base.class here for this reference to call Object's toString
Base.class);
val sub = new Sub();
Assert.assertEquals("Sub", sub.toString());
Assert.assertEquals("Base", baseHandle.invoke(sub));
Assert.assertEquals(toString(sub), objectHandle.invoke(sub));
}
private static String toString(Object o) {
return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
}
public class Sub extends Base {
@Override
public String toString() {
return "Sub";
}
}
public class Base {
@Override
public String toString() {
return "Base";
}
}
}
Bon Codage!!!!
c'est tout simplement facile à faire. Par exemple:
C sous-classe de B et b sous-classe de A. dans les trois cas, la méthode methodName() est utilisée par exemple.
Public abstract class A {
public void methodName() {
System.out.println("Class A");
}
}
Public class B extends a {
public void methodName() {
super.methodName();
System.out.println("Class B");
}
// Will call the super methodName
public void hackSuper() {
super.methodName();
}
}
public class C extends b {
public static void main(String[] args) {
A a = new C();
a.methodName();
}
@Override
public void methodName() {
/*super.methodName();*/
hackSuper();
System.out.println("Class C");
}
}
Exécuter en classe C Sortie sera: De Classe A Classe C
au lieu de sortie: De Classe A Classe B Classe C
public class SubSubClass extends SubClass {
@Override
public void print() {
super.superPrint();
}
public static void main(String[] args) {
new SubSubClass().print();
}
}
class SuperClass {
public void print() {
System.out.println("Printed in the GrandDad");
}
}
class SubClass extends SuperClass {
public void superPrint() {
super.print();
}
}
sortie: imprimé dans le grand-père