Can an overriding method have a different access specifier from that in the base class?
Asked Answered
A

6

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?

Allegorize answered 12/3, 2016 at 10:2 Comment(1)
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)Woollen
F
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.

Foxworth answered 12/3, 2016 at 10:8 Comment(14)
shouldn't I then only use the interfaces and drop the abstract class ?Allegorize
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()?Mooneye
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.Aikido
@Mooneye 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.Foxworth
@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.Mooneye
@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.Aikido
@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 throws).Mooneye
@Tersosauros: If you're extending an existing class, you must provide all its methods, or you'd violate LSP.Absorbing
@Absorbing Yes exactly! So, as I said in my first comment: class C will always have a method(), because B implements it.Mooneye
@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.Absorbing
@Absorbing 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?Mooneye
@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.Absorbing
@Absorbing 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, this would still be possible (evil, but possible).Mooneye
You can make overridden method finalWoollen
S
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()");
    }
}
Spoils answered 12/3, 2016 at 10:6 Comment(2)
if I would make method in A protected and in C protected as well it would work and access from outside the package or subclass would'nt be possible ?Allegorize
Yes, but he wants it private.Spoils
T
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!

Ta answered 12/3, 2016 at 10:20 Comment(7)
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.Conferva
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.Ta
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()"...Conferva
...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).Conferva
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?Ta
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.Conferva
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.Conferva
A
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.

Aikido answered 12/3, 2016 at 11:3 Comment(0)
A
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.

Agosto answered 12/3, 2016 at 10:5 Comment(0)
A
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.

Amylaceous answered 12/3, 2016 at 10:12 Comment(1)
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.Aikido

© 2022 - 2024 — McMap. All rights reserved.