21

Which access modifier, in an abstract class, do I have to use for a method, so the subclasses can decide whether it should be public or not? Is it possible to "override" a modifier in Java or not?

public abstract class A {

    ??? void method();
}

public class B extends A {
    @Override
    public void method(){
        // TODO
    }
}

public class C extends B {
    @Override
    private void method(){
        // TODO
    }
}

I know that there will be a problem with static binding, if someone calls:

// Will work
A foo = new B()
foo.method();

// Compiler ?
A foo = new C();
foo.method();

But maybe there is another way. How I can achieve that?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jam
  • 1,253
  • 1
  • 12
  • 26
  • You can make the overridden final but not private. Also you can make it deprecated and throw unsupported operation exception (guava does that for immutable collections) – Paweł Prażak Mar 13 '16 at 11:29

6 Answers6

12

It is possible to relax the restriction, but not to make it more restrictive:

public abstract class A {
    protected void method();
}

public class B extends A {
    @Override
    public void method(){    // OK
    }
}

public class C extends A {
    @Override
    private void method(){    // not allowed
    }
}

Making the original method private won't work either, since such method isn't visible in subclasses and therefore cannot be overriden.

I would recommend using interfaces to selectively expose or hide the method:

public interface WithMethod {
    // other methods
    void method();
}

public interface WithoutMethod {
    // other methods
    // no 'method()'
}

public abstract class A {
    protected void method();
}

public class B extends A implements WithMethod {
    @Override
    public void method(){
      //TODO
    }
}

public class C extends B implements WithoutMethod {
    // no 'method()'
}

... then only work with the instances through the interfaces.

Jiri Tousek
  • 12,211
  • 5
  • 29
  • 43
  • shouldn't I then only use the interfaces and drop the abstract class ? – jam Mar 12 '16 at 10:35
  • 1
    In your example code, wouldn't an instance of class `C` still *have* a `method()` from class `B`? Since `C extends B`. The adding of `implements WithoutMethod` for class `C` is unable to ***remove** `method()` from class `C`*. Right? So "*only work[ing] with the instances through the interfaces.*" just let's you **pretend** that `C` has no `method()`? – Tersosauros Mar 12 '16 at 12:47
  • 2
    Hmm... An interesting idea, but... This way you can just make `WithMethod` extend `WithoutMethod`. Then class `C` will implement `WithoutMethod` implicitly. As will class `B`, though, which may or may not be what the OP wants in the first place. – Sergei Tachenov Mar 12 '16 at 13:22
  • @Tersosauros You're right, `C` still has the method in that case, but if you only expose it to the user as `WithoutMethod` interface, the user has no access to the method. On smaller projects, making the whole class hierarchy (`A`, `B` and `C`) package-private and only exposing the interfaces can ensure the user doesn't get to code against the actual class. On bigger project, you can separate the API and implementation into two modules (JARs) and only make the API available at compile time. – Jiri Tousek Mar 12 '16 at 13:25
  • @SergeyTachenov Exactly! All sorts of things like this could be done... And even with the JAR separation as @Jiri Tousek suggests, there are still **unsafe** environments (*i.e.* some collections, as well as things like RMI, etc) where *casting/reflection* is required - and then this entire hack (because that's really what it is) will break. Not to mention things like introspection! I think it is **MUCH** better to just throw an `UnsupportedOperationException` or similar, as @Konstantin Yovkov says in the answer below. – Tersosauros Mar 12 '16 at 14:14
  • @Tersosauros, throwing an exception is a good idea only if it's documented in the topmost class introducing that method in the first place. That is, it should be explicitly marked as optional, like some methods in the collections library. Otherwise it's going to be a real pain for anyone catching that exception unexpectedly, no pun intended. – Sergei Tachenov Mar 12 '16 at 14:21
  • @SergeyTachenov True. Obviously you wouldn't want to `throw UnsupportedOperationException` unless you'd explicitly marked the method (*i.e.* `throws UnsupportedOperationException`) **and** documented it (JavaDoc, etc). But, without knowing more of the OPs situation, we can only speculate about the appropriateness of the exception approach. If `A` is some existing class, then this may not be achievable. However, if the OP has total control over the source then altering the `method()` signature in `A` (and down) would enable subclasses "*not to implement*" it (their implementation `throw`s). – Tersosauros Mar 12 '16 at 14:30
  • 1
    @Tersosauros: If you're extending an existing class, you *must* provide all its methods, or you'd violate LSP. – Kevin Mar 12 '16 at 16:55
  • @Kevin Yes exactly! So, as I said in my first comment: class `C` will *always* have a `method()`, because `B` implements it. – Tersosauros Mar 12 '16 at 23:29
  • @Tersosauros: You're missing my point. If you have an *existing class* that provides `method()` and does not throw, you can't throw either, or you're violating LSP. – Kevin Mar 12 '16 at 23:31
  • @Kevin How so? If class `A` had an `abstract method() throws UnsupportedOperationException`, and class `B` sub-classes class `A`, then `B` **must** have an `@Override` `method()`. But that `method()` implementation doesn't *have* to throw UnsupportedOperationException, but it must still be marked as `throws UnsupportedOperationException` - in order to maintain the same method signature as the superclass. Then class `C` can sub-class `B` and have an `@Override` `method()` implementation that **DOES** `throw UnsupportedOperationException`. Nothing in that anywhere would violate LSP? – Tersosauros Mar 13 '16 at 06:50
  • @Tersosauros: I was talking about the opposite situation: A does not throw (and is someone else's class, so you can't just change that), and B wants to throw but can't because of LSP. – Kevin Mar 13 '16 at 06:58
  • @Kevin True, as I said in my earlier comment: "*If `A` is some existing class, then this may not be achievable.*" However, since `UnsupportedOperationException` is [**unchecked**](https://docs.oracle.com/javase/7/docs/api/java/lang/RuntimeException.html), this *would* still be *possible* (evil, but possible). – Tersosauros Mar 13 '16 at 07:37
  • You can make overridden method final – Paweł Prażak Mar 13 '16 at 11:28
8

When overriding methods, you can only change the modifier to a wider one, not vice versa. For example this code would be valid:

public abstract class A {

    protected void method();
}

public class B extends A {
    @Override
    public void method() { }
}

However, if you try to narrow down the visibility, you'd get a compile-time error:

public abstract class A {
    protected void method();
}

public class B extends A {
    @Override
    private void method() {}
}

For your case, I'd suggest to make C not implementing A, as A's abstraction implies that there's a non-private method():

public class C {
    private void method(){
      //TODO
    }
}

Another option is to make the method() implementation in C throwing a RuntimeException:

public class C extends A {

    @Override
    public void method(){
        throw new UnsupportedOperationException("C doesn't support callbacks to method()");
    }
}
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
7

What you are asking for is not possible for very good reasons.

The Liskov substitution principle basically says: a class S is a subclass of another class T only then, when you can replace any occurrence of some "T object" with some "S object" - without noticing.

If you would allow that S is reducing a public method to private, then you can't do that any more. Because all of a sudden, that method that could be called while using some T ... isn't available any more to be called on S.

Long story short: inheritance is not something that simply falls out of the sky. It is a property of classes that you as the programmer are responsible for. In other words: inheritance means more than just writing down "class S extends T" in your source code!

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • The LSP requires that base-class methods exist and have a defined function in all derived classes. It does not require that the function actually be *useful* in all derived classes. For example, it would be legitimate for the contract of a derived class to specify that some virtual base-class property will always return "false" for all instances of that derived class. In such cases, it may be entirely reasonable to hide that member in the derived class, since there would be no reason to invoke it on a reference *known* to identify a derived-class object. – supercat Mar 12 '16 at 19:10
  • You said it yourself: the method must exist in the derived class. Turning it private is the very same thing as removing completely from an outside perspective. And of course, you can do with an "S" whatever S allows for; but that is the whole point of "transparent exchange"; you are accessing the S as T. Thus I am not sure what you are trying to say. – GhostCat Mar 12 '16 at 19:23
  • If recipients of a reference to instance of some class may want to use its "Wizzle()" method if it has one, or else perform some other sequence of methods which can achieve the same effect but less efficiently, and some derivatives will be able to "Wizzle()" and others won't, it may be helpful to have the base class include a "CanWizzle()" property and a "Wizzle()" method, with the contract that if "CanWizzle()" returns true the "Wizzle() " method will return something useful, and if "CanWizzle()" returns false it might not. A derived class which can't "Wizzle()"... – supercat Mar 12 '16 at 19:28
  • ...would have no reason to allow the "Wizzle()" method to be invoked upon references that are known to identify instances of that derived class (since such a call couldn't possibly work). – supercat Mar 12 '16 at 19:32
  • Maybe so. But such discussions do not make such in such general terms. As I would then ask: and if some of your thingies can whistle; and others can not; what is the purpose then of having a common base class? – GhostCat Mar 12 '16 at 19:33
  • As a simple example, consider a base-class representing a sequence of items. Some implementations of such a base class would be able to efficiently access the Nth item for any N; others would not. Suppose the recipient of a base-class reference to such a thing needs many items in the range 50,000 through 50,999. If an item does not support random reads, the consumer would be best off reading 51,000 items, ignoring the first 50,000. If the item does support random reads, however, the consumer would be better off simply reading the items which are of interest. – supercat Mar 12 '16 at 19:37
  • Having the ability to read items in arbitrary sequence be part of the base class, along with a property indicating whether the object can handle such reads, means that client code which knows how to exploit that ability can use it to improve performance, while still being able to work with items that cannot support it. Further, wrapper items can expose to their clients any combination of abilities supported by the wrapped objects--something which would be very difficult or impossible if the type system were the only means of conveying abilities or lack thereof. – supercat Mar 12 '16 at 19:41
4

This is impossible because of the polymorphism. Consider the following. You have the method in class A with some access modifier which is not private. Why not private? Because if it was private, then no other class could even know of its existence. So it has to be something else, and that something else must be accessible from somewhere.

Now let's suppose that you pass an instance of class C to somewhere. But you upcast it to A beforehand, and so you end up having this code somewhere:

void somewhereMethod(A instance) {
    instance.method(); // Ouch! Calling a private method on class C.
}

One nice example how this got broken is QSaveFile in Qt. Unlike Java, C++ actually allows to lower access privileges. So they did just that, forbidding the close() method. What they ended up with is a QIODevice subclass that is not really a QIODevice any more. If you pass a pointer to QSaveFile to some method accepting QIODevice*, they can still call close() because it's public in QIODevice. They “fixed” this by making QSaveFile::close() (which is private) call abort(), so if you do something like that, your program immediately crashes. Not a very nice “solution”, but there is no better one. And it's just an example of bad OO design. That's why Java doesn't allow it.

Edit

Not that I missed that your class is abstract, but I also missed the fact that B extends C, not A. This way what you want to do is completely impossible. If the method is public in B, it will be public in all subclasses too. The only thing you can do is document that it shouldn't be called and maybe override it to throw UnsupportedOperationException. But that would lead to the same problems as with QSaveFile. Remember that users of your class may not even know that it's an instance of C so they won't even have a chance to read its documentation.

Overall it's just a very bad idea OO-wise. Perhaps you should ask another question about the exact problem you're trying to solve with this hierarchy, and maybe you'll get some decent advises on how to do it properly.

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
3

Here is a part of the @Override contract.

The answer is : there isn't any possibility to achieve what you have.

The access level cannot be more restrictive than the overridden method's access level. For example: if the superclass method is declared public then the overridding method in the sub class cannot be either private or protected.

This is not a problem concerning abstract classes only but all classes and methods.

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
3

THEORY:

You have the determined modifiers order:

public <- protected <- default-access X<- private

When you override the method, you can increase, but not decrease the modifier level. For example,

public -> []
protected -> [public]
default-access -> [public, default-access]
private -> []

PRACTICE:

In your case, you cannot turn ??? into some modifier, because private is the lowest modifier and private class members are not inherited.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 4
    This is incorrect, although it's a widespread myth. In fact, the correct order is `public -> protected -> default-access -> private`. You can actually access protected members from other classes in the same package *and* from subclasses in other packages, while default-access only provides the former. Refer to [JLS 8.4.8.3](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8.3). – Sergei Tachenov Mar 12 '16 at 10:49