Does Polymorphism Apply on Class Attributes in Java

Does polymorphism apply on class attributes in Java?

When you extend a class, methods are overriden, but fields are hidden. Dynamic dispatch works for methods, but not for fields. Why is the language designed so, god knows why.

Attributes and polymorphism

In Java, fields are not overridden, they are hidden. That means Increase.a and SubIncrease.a are separate fields that can be changed and queried separately. Because the type of your variable f is Increase, the expression f.a returns the value of the superclass field. But the add() method is overridden and f.add() calls the subclass method, which modifies the subclass field.

Hiding a field rarely makes sense, so you should avoid it. If you want to have a field with a different default value in a subclass, define it only in the superclass and assign a value to it in the subclass constructor.

Polymorphism and pre validating class attributes

Usually, the data which tells you which objects to create comes from an external source: a file, a socket, another object etc. In your case, you could use a text file. Create the Grade instances passing the values you read to the constructor and then call validate and cost on each.

public class PipeFactory(){
public Pipe CreatePipe( int minGrade, int maxGrade, boolean hasColour0, boolean hasColour1, boolean hasColour2, boolean hasIns, boolean hasReinf ){
if( (minGrade == 1 || maxGrade == 3) /* ... Complete this condition yourself */ )
return new TypeIPipe();
if( (minGrade == 2 || maxGrade == 4 /* ... Complete this condition yourself */ )
return new TypeIIPipe();
//If for other types...
//If no pipe was created, parameters are invalid, so we throw an exception
throw new InvalidArgumentException( "Can't create a pipe with these parameters" );
}
}

How does polymorphism in Java work for this general case (method with parameter)?

In order to get why you get the result B and A twice, you need to know that there are 2 parts to this: compilation and runtime.

Compilation

When encountering the statement a.show(b), the compiler takes these basic steps:

  1. Look at the object that the method is called on (a) and get its declared type. This type is A.
  2. In class A and all of its super types, make a list of all methods that are named show. The compiler will find only show(A). It does not look at any methods in B or C.
  3. From the list of found methods, choose the one that best matches the parameter (b) if any. show(A) will accept b, so this method is chosen.

The same thing will happen for the second call where you pass c. The first two steps are the same, and the third step will again find show(A) since there is only one, and it also matches the parameter c. So, for both of your calls, the rest of the process is the same.

Once the compiler has figured out what method it needs, it will create a byte-code instruction invokevirtual, and put the resolved method show(A) as the one it should call (as shown in Eclipse by opening the .class):

invokevirtual org.example.A.show(org.example.A) : java.lang.String [35]

Runtime

The runtime, when it eventually gets to the invokevirtual needs to do a few steps also.

  1. Get the object on which the method is called (which is already on the stack by then), which is a.
  2. Look at the actual runtime type of this object. Since a = new B(), this type is B.
  3. Look in B and try to find the method show(A). This method is found since B overrides it. If this had not been the case, it would look in the super classes (A and Object) until such a method is found. It is important to note that it only considers show(A) methods, so eg. show(B) from B is never considered.
  4. The runtime will now call method show(A) from B, giving the String B and A as result.

More detail about this is given in the spec for invokevirtual:

If the resolved method is not signature polymorphic (§2.9), then the invokevirtual instruction proceeds as follows.

Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:

If C contains a declaration for an instance method m that overrides (§5.4.5) the resolved method, then m is the method to be invoked, and the lookup procedure terminates.

Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of this lookup procedure.

Otherwise, an AbstractMethodError is raised.

For your example, the objectref is a, its class is B and the resolved method is the one from the invokevirtual (show(A) from A)


tl:dr - Compile-time determines what method to call, runtime determines where to call it from.

What is polymorphism, what is it for, and how is it used?

If you think about the Greek roots of the term, it should become obvious.

  • Poly = many: polygon = many-sided, polystyrene = many styrenes (a), polyglot = many languages, and so on.
  • Morph = change or form: morphology = study of biological form, Morpheus = the Greek god of dreams able to take any form.

So polymorphism is the ability (in programming) to present the same interface for differing underlying forms (data types).

For example, in many languages, integers and floats are implicitly polymorphic since you can add, subtract, multiply and so on, irrespective of the fact that the types are different. They're rarely considered as objects in the usual term.

But, in that same way, a class like BigDecimal or Rational or Imaginary can also provide those operations, even though they operate on different data types.

The classic example is the Shape class and all the classes that can inherit from it (square, circle, dodecahedron, irregular polygon, splat and so on).

With polymorphism, each of these classes will have different underlying data. A point shape needs only two co-ordinates (assuming it's in a two-dimensional space of course). A circle needs a center and radius. A square or rectangle needs two co-ordinates for the top left and bottom right corners and (possibly) a rotation. An irregular polygon needs a series of lines.

By making the class responsible for its code as well as its data, you can achieve polymorphism. In this example, every class would have its own Draw() function and the client code could simply do:

shape.Draw()

to get the correct behavior for any shape.

This is in contrast to the old way of doing things in which the code was separate from the data, and you would have had functions such as drawSquare() and drawCircle().

Object orientation, polymorphism and inheritance are all closely-related concepts and they're vital to know. There have been many "silver bullets" during my long career which basically just fizzled out but the OO paradigm has turned out to be a good one. Learn it, understand it, love it - you'll be glad you did :-)


(a) I originally wrote that as a joke but it turned out to be correct and, therefore, not that funny. The monomer styrene happens to be made from carbon and hydrogen, C8H8, and polystyrene is made from groups of that, (C8H8)n.

Perhaps I should have stated that a polyp was many occurrences of the letter p although, now that I've had to explain the joke, even that doesn't seem funny either.

Sometimes, you should just quit while you're behind :-)

Cannot access the unique properties of a concrete class using polymorphism

You should have a look at the visitor pattern. You need to declare an interface ConnectionVisitor and add an method visit for each of your connection class in your hierarchy.

public interface ConnectionVisitor {

public int visit (Connection connection);
public int visit (SqlServerConnection sqlconnection);
public int visit (OracleConnection oracleConnection)
}

Now you need to add an accept method in your base class connection and that accepts a ConnectionVisitor and then calls visit on it. Your new Connection class will look something like

public abstract class Connection {
private int port;
private int ipAddress;

public Connection() {}

public String description() {
return "Generic";
}

public int accept(ConnectionVisitor visitor){
return visitor.visit(this);
}

}

Notice that the accept method does a dual dispatch. It dispatches on the base of the object on which it is called and the parameter that is passed to this method. This is at the heart of visitor pattern.

You can then implement the ConnectionVisitor interface to define any new functionality without changing your base class.

class   DemoVisitor implements ConnectionVisitor{
public int visit(Connection connection){
System.out.println("Visiting Connection");
return 1;
}

public int visit(SqlServerConnection sqlServerConnection){
System.out.println("Visiting SqlServerConnection");
return 1;
}

public int visit(OracleConnection oracleConnection){
System.out.println("Visiting Oracle Connection");
return 1;
}

}

In your TestConnection class you can simply create a new connection object and then call accept method on that object passing a visitor object.

public class TestConnection {
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory("SQLServer");
Connection conn = factory.createConnection();

conn = factory.createConnection();
System.out.println(conn.description());
ConnectionVisitor visitor = new DemoVisitor();
System.out.println(conn.accept(visitor));
}

}

So now any child class specific functionality must not reside in connection class hierarchy instead they must be implemented in new visitors.

Note that this pattern is not going to fit as such in your scenario. One of the limitation of this pattern in that the return type for all the methods in visitor interface must be same. This pattern may or may not fit your needs but it is worth looking into your case as such. You will probably need to modify this pattern to fit your needs. And that is what patterns are all about looking into some common solutions and then modifying those solutions to fit into your problem.



Related Topics



Leave a reply



Submit