-1

I am reviewing for a test and got to this review exercise. I have the answers but cannot for the life of me wrap my head around this. Could anyone explain what's going on?

interface I1 {
 public void f();
}
class A implements I1 {
 public void f() {
  //implementation
 }
 public static void g(){
  //implementation
 }
}
class B extends A {
 public void f() {
//implementation
 }
 public static void g(){
 //implementation
 }
}

Suppose

I1 i1=new B();

I1 i2=new A();

A a1= new A();

A a2=new B();

A b1= (A) new B();

Without writing any code, explain in words the effect of each of the following method calls. Just say things like "This will call f method implemented in A" etc.

b1.f();

b1.g();

i1.f();

a1.g();

i2.f();

a2.g();

Brian W
  • 31
  • 2
  • 10
  • 1
    To find this out yourself, you can simply put a print statement inside each implementation, saying something like "Method f implemented in A", which you can use to find out what's happening. – Nathan Feb 16 '15 at 03:15

1 Answers1

1

Analysis of g method

Note that f is an instance method, while g is a static method. Static methods are associated with class.

Hence, because you declare b1, a1 and a2 as A, calling b1.g(), a1.g() and a2.g() will all call A.g().

Here's another good SO question on Java static methods: Why doesn't the compiler complain when I try to override a static method?

Analysis of f method

Java instance methods are by default virtual methods, which can be overridden by sub classes. So what f() actually does depends on what the instance is (that is, depends on the type following the new keyword).

b1 is an instance of B, so b1.f() will call the f method defined on B, that is, B.f().

i1 is also an instance of B, so i1.f() will also call the f method defined on B, that is, B.f().

i2 is an instance of A, so i2.f() will call the f method defined on A, that is, A.f().

Then what about a2.f()? As I stated above, a2 is an instance of B (you create it with the new keyword), so a2.f() will call B.f().

More on method overridding

You might wonder why they work like that? Why a2.f() is not designed to call A.f() in Java? Here's why:

Assume you have a module which will do complex calculation against a list of records. These records might be retrieved from database, or from web api provided by third party rest api. How to make your module as extensible as possible?

Here's your module code:

public class MyModule {
    public void doComplexWork() {
        
        // Where to get the records?
        List<Record> records = ???;
        
        // Do complex computation here
    }
}

An extensible way to do this is to define an interface (or abstract class) as a contract of 'record retrieving':

public interface IRecordRetriever {
    List<Record> retrieveRecords();
}

Then you implement two IRecordRetrievers, one to retrieve records from database, while the other one retrieve records from rest api.

public class DatabaseRecordRetriever implements IRecordRetriever {
    @Override
    public List<Record> retrieveRecords() {
        // Connect database and fetch database records here
    }
}

public class RestApiRecordRetriever implements IRecordRetriever {
    @Override
    public List<Record> retrieveRecords() {
        // Access the rest api to fetch records here
    } 
}

Now let's change our module code a little bit:

public class MyModule {
    
    private IRecordRetriever _recordRetriever;
    
    // Inject IRecordRetriever from contructor
    public MyModule(IRecordRetriever retriever) {
        _recordRetriever = retriever;
    }
    
    public void doComplexWork() {
        
        // Where to get the records?
        // From the IRecordRetriever
        List<Record> records = _recordRetriever.retrieveRecords();
        
        // Do complex computation here
    }
}

Now our MyModule is extensible now. Because no matter where to retrieve the records, no code in MyModule need to be changed. If now you retrieve records from database, but later you decide to change to retrieve records from rest api, still nothing in MyModule need to be changed! You just need to change the runtime IRecordRetriever instance passed to MyModule constructor.

Why can we achieve this extensibility? It's because of the method overriding. We declare the private field _recordRetriever as IRecordRetriever, so the actual behavior depends on the actual runtime instance (created by the new keyword). So to change record retrieving behavior we only need to change the runtime IRecordRetriever instance passed to the MyModule constructor. If method overriding is not available, that is:

IRecordRetriever retriever = new DatabaseRecordRetriever(); retriever.retrieveRecords();

If the call to retriever.retrieveRecords() is not calling the retrieveRecords() method defined on DatabaseRecordRetriever, but calling the retrieveRecords() method defined on IRecordRetriever, we are not able to achieve the extensibility. (Of course, IRecordRetriever is an interface, so you are not able to call IRecordRetriever.retrieveRecords(), but this also holds for virtual methods).

What I mentioned above also hold for other object oriented programming languages.

The way I pass IRecordRetriever to MyModule through constructor is called Constructor Dependency Injection. You can read more materials on object oriented programming, method overriding and GoF design patterns.

Community
  • 1
  • 1
Mouhong Lin
  • 4,402
  • 4
  • 33
  • 48