2

If I have a concrete class Foo, and I want to add an "alias" called Bar which extends Foo but does not override any methods - will Java let me do this?

Edit: Context - the goal is to ultimately rename Foo to Bar, because Foo was poorly named; however, Foo is depended on by another project and I can't change both Foo and FooCaller in the same commit. To avoid breaking FooCaller, I'll make three changes: (1) add Bar, (2) change FooCaller to BarCaller (same behavior), (3) remove Foo altogether (when there are no more FooCallers).

Are there any gotchas here, or will Bar behave identically to Foo?

mfsiega
  • 2,852
  • 19
  • 22
  • 2
    I believe you need to pay attention to constructors, but there is no obligation to override any methods of a parent class unless the parent is abstract. – KevinO Apr 18 '16 at 16:17
  • 2
    Would you be using `Foo` or `Bar` as a generic type parameter, e.g. `List`? – rgettman Apr 18 '16 at 16:18
  • @rgettman nope just an alias for a gradual refactor – mfsiega Apr 18 '16 at 16:24
  • 1
    There shouldn't be any functional difference between `Foo` & `Bar`. But, I guess you don't mind the design confusion that it may cause (why does `Bar` extend `Foo` if it doesn't add any additional functionality). – Sarath Chandra Apr 18 '16 at 16:25

2 Answers2

1

Bar will behave as Foo so long as you use the proper visibility. Private methods and properties in Foo will be inaccessible to Bar, and protected ones will be assumed to be private in Bar.

The example below will output "Foo" to your console.

class Example
{
    public static void main (String[] args)
    {
        Bar b = new Bar();
        System.out.println(b.getName());
    }
}

class Foo {
    protected String name; // Will be private in Bar

    public Foo() {
        this.name = "Foo";
    }

    public String getName() {
        return this.name;
    }
}

class Bar extends Foo {

}
Keenan
  • 179
  • 1
  • 9
1

Yes, Java will let you subclass a non-final concrete class just for the purposes of aliasing the original class.

public class Foo {  /* implementation */ }

public class Bar extends Foo {}

That would be the minimal implementation, assuming that there is only a default constructor in Foo. If there are constructors that take arguments in Foo, then you must provide a constructor in Bar for every constructor you plan to call to create your Bar instances, e.g.:

public class Foo {
    public Foo(String baz) { /* constructor implementation */ }
}

public class Bar extends Foo {
    public Bar(String baz) { super(baz); }
}

All non-private methods in Foo will be inherited by Bar.

The only place it might not work is if you intend to use Bar in place of Foo as a generic type parameter.

For example, if you replace a List<Foo> with a List<Bar>, then your List can no longer hold any Foo instances, or instances of any other class that might extend Foo.

Additionally, because Java's generics are invariant, a List<Bar> is not a subtype of List<Foo>, even if a Bar is a Foo. See Is List a subclass of List? Why aren't Java's generics implicitly polymorphic? for details on why that is the case. Also, you may be able to get around it if you use List<? extends Foo> so that you can use a List<Foo> or a List<Bar>, but then you won't be able to add anything but null.

You state that you won't be using it in generics, but it's worth keeping in mind if your requirements are expanded.

In conclusion, yes, the Java compiler will let you do it, but with a limited upside -- using Bar instead of Foo as an alias -- and a potential downside, I see no reason to use this aliasing process.

Community
  • 1
  • 1
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • Also, code relying on getClass() may behave differently. – user140547 Apr 18 '16 at 16:38
  • The generics issue is a good point. In my particular case, the goal is to ultimately rename Foo to Bar, because Foo was poorly named; however, Foo is depended on by another project and I can't change both Foo and FooCaller in the same commit. To avoid breaking FooCaller, I'll make three changes: (1) add Bar, (2) change FooCaller to BarCaller (same behavior), (3) remove Foo altogether. – mfsiega Apr 18 '16 at 16:45
  • @mfrankli that is good context to include in your question. I suggest renaming Foo to Bar, then creating a new Foo that extends Bar for backwards compatibility. – dsh Apr 18 '16 at 16:57