-13

I use a library with an interface, IFC, that is implemented by three classes, A, B and C.

I want to make my own implementation of the method M, that is defined in IFC, and implemented in A, B and C, that should override the corresponding methods in A, B and C.

Is that possible?

In Java, you implement, not extend, an interface and you can only extend one class. This leads me to the conclusion that what I want to do is not possible.

Instead I need to create:

class MyA extends A {

    myM () {
        // Do my stuff
        super.M();
    }

And then two identical classes for B and C, each with an identical myM-method, but this feels really clumsy and ugly. Is there another way?

10 Rep
  • 2,217
  • 7
  • 19
  • 33
  • Why do you call `super.M();`? Is it necessary? – akuzminykh Dec 06 '20 at 01:06
  • Yes. The reason I want my own method is that I need to perform a check every time I call M in A, B and C - which I do a lot. –  Dec 06 '20 at 01:10
  • 2
    I don't see an alternative to extending A, B and C. Anyways, if `//do my stuff` is the same for all three classes, then you could write a class D that implements `//do my stuff` and reference it in `MyA` and so on. This will at least centralize the implementation of `//do my stuff` and reduce redundancy. – akuzminykh Dec 06 '20 at 01:23
  • 1
    Maybe you want to use a proxy/adapter that overrides the one method and delegates all the others to an underlying instance. You can then use that to wrap instances of `A`, `B`, and `C`. – Thilo Dec 06 '20 at 03:03
  • @akuzminykh Ok, thank you. ´do my stuff´ is just two lines of code so I won't save much by extracting it. But I guess it looks better ;-) –  Dec 06 '20 at 10:56
  • @Thilo Can you provide an example? Thank you. –  Dec 06 '20 at 10:56
  • @EmLi You'd write a class that has M and a field for IFC. In M you'd do your stuff and then call M of IFC, where IFC can be A, B or C. So you'd wrap all instances of A, B and C in such a class, which will avoid extending A, B and C. But as far as I understand your question, this si not what you want? – akuzminykh Dec 07 '20 at 01:26
  • 1
    This question is being [discussed on Meta](https://meta.stackoverflow.com/questions/403490/this-question-is-not-unclear). – Cody Gray - on strike Dec 09 '20 at 18:12
  • Can you add your use case to your Question? What you've mentioned in comments here. The situation that one wants to add default functionality to multiple classes isn't uncommon. Some times it's as simple as adding a logging call to the "same" method. – Scratte Dec 09 '20 at 18:54
  • 1
    looks like an x-y-problem: for some reason you didn't elaborate on, you want to change the specification of existing interfaces/classes (that is IFC, A, B, C) - which can't be done, as you know ;) There might be options (f.i. instrumentation or wrappers as suggested by @Scratte), depending on what you _really_ want to achieve .. and now we are back to missing details ;). Basically, you have to rethink and re-evaluate your requirements and then come back with the missing details. – kleopatra Dec 10 '20 at 11:35
  • @kleopatra Note that the use cases for this post could be exactly as stated. It may look like an X-Y problem, but how do you know it is? What if they just initialize the classes and only want to add come functionality to the method M? That's what they stated in their post. Why does it have to be more complicated in order to be valid for Stack Overflow? – Scratte Dec 10 '20 at 12:19
  • @Scratte _What if they just initialize the classes and only want to add come functionality to the method M_ which is already answered in the question: _what I want to do is not possible_ :) Back to what they _really_ want, that is more details .. the center of this argument (which shouldn't have arisen, had the question be changed to include them as requested by more than one user ;) – kleopatra Dec 10 '20 at 13:43
  • @kleopatra How do you read that? It's a Question "Is that possible?" and "Is there another way?" The "what I want to do is not possible" is just a guess, but they want to know, so they ask. Is this all about phrasing? – Scratte Dec 10 '20 at 13:50
  • @Scratte no, it's not about phrasing only - the simple answer is exactly what the asker already knows: NO. From thereon (assuming the question is _what can I do to achieve the impossible nevertheless_) the answer is: depends on what you really want, why _exactly_ did you run into that dead end? Without further details answers are playing guessing games, might hit the one or other nail .. or not. – kleopatra Dec 10 '20 at 13:57
  • @kleopatra I see. I disagree. There are different ways, as you mentioned in your first comment, so it's not impossible to make it happen, hence "NO" isn't really true :) And the simpler the use case, the better, if you ask me. I must admit that I do not care for their specific use case at all. I care for the Question and the possibility of answers to it. "When I have this situation, what are my option?" – Scratte Dec 10 '20 at 14:05

2 Answers2

2

Using this simplified implementation of the library, using method() instead of M():

interface IFC {
  void method();
}

class A implements IFC {
  public void method() {
    System.out.println("method in A");
  };
}

As akuzminykh mentions in their comment

You'd write a class that has M and a field for IFC. In M you'd do your stuff and then call M of IFC, where IFC can be A, B or C. So you'd wrap all instances of A, B and C in such a class, which will avoid extending A, B and C.

The advantage of this is that you only have to account for it when initializing your classes by wrapping them: IFC wrapIFC = new IFCWrapper(new A());. Once that's done, you don't need to handle the variable wrapIFC any differently than you would any variable initialized with new A():

class IFCWrapper implements IFC {
  IFC ifc;

  IFCWrapper(IFC ifc) {
    this.ifc = ifc;
  }

  public void method() {
    always();
    ifc.method();
  }

  void always() {
    System.out.println("wrapper method");
  }
}

You could also create a separate independent (abstract?) class or interface, that you call instead of calling method() passing in your instances. The downside is that you'll have to remember to call this every time you'd want to call method() on your instance. I picked the option to pass in any extra stuff I want for this example using a Runnable:

class IFCExt {
  static Runnable defaultRun = () -> System.out.println("default extra");
  static void method(Runnable toRun, IFC ifc) {
    toRun.run();
    ifc.method(); 
  }
}

Example run of the two variants:

public class Test {
  public static void main(String[] args) { 
    IFC wrapIFC = new IFCWrapper(new A());
    wrapIFC.method();

    System.out.println();

    IFC myIFC = new A();
    IFCExt.method(IFCExt.defaultRun, myIFC);
    IFCExt.method(() -> System.out.println("anything extra"), myIFC); // anything you'd want
  }
}

prints:

wrapper method
method in A

default extra
method in A
anything extra
method in A
Scratte
  • 3,056
  • 6
  • 19
  • 26
  • 1
    Good example, however, this solution has problems depending on the use case: What if A, B and C are supposed to be used beyond just calling M? What if A, B and C have not just M but also M1, M2 and M3? What if A has M4 but B doesn't? What if you pass instances of A, B and C to other methods of the library? You'd need to write much code beyond the simple usage of a single method M. Will it work? Yes. Will it be nice? Depending on the use case, it will be terrible and you would've been much happier by simply extending A, B and C. – akuzminykh Dec 10 '20 at 10:38
  • @akuzminykh I agree that adopted solutions depend on the use cases. Interesting case where A has an M4, but B doesn't. In such a case one can't call M4 if it's initialized with the variable type as the interface though. On the question of passing them to other methods of the library, then the [other Answer](https://stackoverflow.com/a/65227155) by [code11](https://stackoverflow.com/users/1456253/code11) becomes a very attractive solution especially if one wants it to work in the library internally. If not I agree with you on just extending the classes as proposed in the Question itself :) – Scratte Dec 10 '20 at 11:05
  • 1
    Glad that you see the problems too .. And this is where the "Needs details or clarity" comes into place. ;) But as I said in my [comment on meta](https://meta.stackoverflow.com/questions/403490/why-is-my-question-about-extending-java-classes-that-implement-an-interface-uncl#comment813354_403527): Not enough to close this question IMO, but definitely understandable to do so. – akuzminykh Dec 10 '20 at 11:13
  • 1
    @akuzminykh _Not enough to close this question IMO_ .. definitely arguable - but the mere gymnastics in the answers is a good indicator that there are far too few details to provide a good answer, IMO :) There must be a very compelling reason (beyond the _feels clumsy and ugly_ from the question, which is debatable also :) to invest into complexity. – kleopatra Dec 10 '20 at 11:48
  • @kleopatra I don't really agree with that. It's always the case that when one wants a problem solved to go with the best solution for one's own particular use case. Neither Questions nor Answers on Stack Overflow ever really fit exactly for any of my project. The general problem isn't, in my opinion, lacking details. – Scratte Dec 10 '20 at 11:50
  • 1
    This is a great example of how the proxy pattern can be used. The ramifications of this at scale may not be apparent at first glance though. Essentially you will have to make an abstraction layer above whatever API the library is providing. All instances of returned or passed A,B,C must be wrapped or unwrapped. In a large library this may mean wrapping hundreds of functions. Even worse, this abstraction layer must be maintained. If the library developer adds a new function that returns an A,B,C, then it too must be wrapped. – code11 Dec 10 '20 at 14:44
1

I have run into a situation like this before. While proxy/adapter is all well and good if you're just going to use A, B and C external to the library, you might want to replace M() as called from A,B,C when used internally as well.

The example I have run into is a legacy library using a non-threadsafe collection that you just want to switch out. I will assume the situation is like mine, and that you only have access to a jar, but no source and that you want to change internal usage of M() as well. In this situation, achieving this through regular java code is, as many pointed out, impossible.

However, if the code is on your computer, there are two techniques outside of the boundaries of normal java development you can use. Their utilization may have an outsized impact on build complexity and maintenance of the code you are writing.

One possible solution, if you truly want to replace every instance of M(), without making your own classes, is to swap the methods at runtime.

There are libraries that claim to be able to do this. This question goes over this technique in some detail, and highlights one library, HotSwapAgent that can do this.

Another solution is to decompile the library's jar, change the offending method and recompile it. Depending on the library's use of other dependencies and build complexity, this may be non-trivial. You are then also in a situation where you must maintain that new altered jar. If the library updates, you must repeat the process.

code11
  • 1,986
  • 5
  • 29
  • 37
  • The *internally* part of this Answer seems to be a good selling point. Instead of working around the issue, just "hack" it in :) – Scratte Dec 10 '20 at 15:23