Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Junilu Lacar
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • Jeanne Boyarsky
  • Rob Spoor
  • Bear Bibeault
Saloon Keepers:
  • Tim Moores
  • Tim Holloway
  • Piet Souris
  • Carey Brown
  • Stephan van Hulst
Bartenders:
  • Frits Walraven
  • fred rosenberger
  • salvin francis

Which of a Superclass and a Subclass Method is invoked by a Subtype Declared to be of a Supertype?

 
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
One question that I have been wanting ask for some time is what method would be invoked by an instance that is of a subtype, but that has been declared to be of a supertype. The following code below illustrates what I am trying to say here:
In the code above, the instance named in is of the BufferedReader type, but is declared to be the Reader type. The question that I would like to ask is whether, in situations like this, the read() method invoked would be the one implemented in the Reader class or the one implemented in the BufferedReader class.

My own answer to this question, which I am not sure is correct, is that it is the read() method implemented in the Reader class that will be invoked. I take this position because the compiler reports the following error for the statement on line 9, which indicates that the type of the instance named in is Reader and the compiler cannot find a readLine() method implemented in the Reader class:
While the Reader class does not have a readLine() method implemented in the class, the BufferedReader class has such a method implemented in the class. It seems reasonable to conclude that, if the Reader class were to have a readLine() method implemented in the class, it is this readLine() method that would be invoked on line 9, rather than the readLine() method implemented in the BufferedReader class.

What do others think about this?
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
An interesting observation to make is that, on the one hand, the compiler construes the type of the instance named in to be Reader and, on the other hand, the class of this instance is deemed to be BufferedReader, as may be seen by running the following code:
Executing this code outputs the following:
 
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:In the code above, the instance named in is of the BufferedReader type, but is declared to be the Reader type. The question that I would like to ask is whether, in situations like this, the read() method invoked would be the one implemented in the Reader class or the one implemented in the BufferedReader class.



The issue of whether a method has been implemented in a subclass is of secondary importance. The rule, as applied to your code example, is this: the object referred to by the in variable is of type java.io.BufferedReader. You wrote a little piece of code which demonstrates that, but given the code



it's easy to see that the variable in refers to a BufferedReaderobject.

Now, when you consider calling an instance variable like read() on a BufferedReader object, it's BufferedReader's read() method which is called. Remember that it's the object whose method you are calling so the type of a variable which happens to refer to that object is irrelevant.

This is where it becomes important to consider which classes in the inheritance chain have implemented the read() method. The rule so far asserts that it's BufferedReader's version of the method which is called. If BufferedReader includes an implementation of that method then that's what is called. If it doesn't, then it inherits the method from its superclass, which is Reader, and it's this inherited method which is called.

It's possible that Reader might also not have an implementation of that method, but no problem, if it doesn't then it would inherit the method from its superclass and that would be the method that is called. This step is repeated recursively until an implementation of the method is found in the inheritance chain.

Note: all of this refers to instance methods. Static methods follow different rules.
 
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

While the Reader class does not have a readLine() method implemented in the class, the BufferedReader class has such a method implemented in the class. It seems reasonable to conclude that, if the Reader class were to have a readLine() method implemented in the class, it is this readLine() method that would be invoked on line 9, rather than the readLine() method implemented in the BufferedReader class.



Overload resolution happens at compile-time and based solely on the declared type of the reference.

In your example, using a Reader reference variable, you will only be able to call methods declared in Reader or its super-classes and any interfaces it implements.

Some pedantic types call this compile-time polymorphism, everyone else looks at them funny.

Polymorphic behavior / overriding is based, as Paul explained, on the actual type of the object/instance that is referred to, at runtime.
Calls on super-class references, *even from super-class method code that is not overridden*, always get the most specialized behavior of any instance methods automatically.

So no, the declared type of the reference determines which method overloads can be called and picks one.  The actual type of the instance referred to then determines which code will run, at runtime.
This is the magic of polymorphism and if I recall correctly, is beaten to near-death in both the Sybex 815 book and even more so in the Enthuware materials.

As Paul said, "instance methods only", static methods don't exhibit polymorphic behavior, which is based on, well, each instance.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:Now, when you consider calling an instance variable like read() on a BufferedReader object, it's BufferedReader's read() method which is called. Remember that it's the object whose method you are calling so the type of a variable which happens to refer to that object is irrelevant.


Jesse Silverman wrote:So no, the declared type of the reference determines which method overloads can be called and picks one.  The actual type of the instance referred to then determines which code will run, at runtime.


The foregoing quotes are in agreement with my understanding of how the example of polymorphism, in the code I presented above, should be resolved. Despite this understanding, which we all share, there appears to be an additional rule (which Jesse provides in his response and which the compiler clearly follows when compiling the second code example I presented above); I present both rules below, just to be clear and so that a correction may be made if I have gotten something wrong:

(1.) When an instance method of a class gets overridden, a (reference) identifier, of a subclass of the class (i.e., an identifier that has been assigned a reference to an object of a subclass of the class) that is used to access/reference the method, will invoke only the overriding method in the subclass (not the overridden method in the superclass); this rules does not apply when the identifier is declared to be of the class type, as the next rule stipulates and explains.

(2.) When an instance method of a class gets overridden, a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method, will invoke only the overridden method in the class (not the overriding method in the subclass); this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.

The example of polymorphism, in the code I presented above, has provided the motivation for me to come up with terms like the syntactic and semantic type of a reference identifier. The syntactic type of the reference identifier named in would be Reader, while its semantic type would be BufferedReader (hence, when the compiler both construes and reports that the type of this identifier is Reader, it is referring to the syntactic, rather than the semantic, type of the identifier).
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:

Jesse Silverman wrote:So no, the declared type of the reference determines which method overloads can be called and picks one.  The actual type of the instance referred to then determines which code will run, at runtime.


The foregoing quotes are in agreement with my understanding of how the example of polymorphism, in the code I presented above, should be resolved. Despite this understanding, which we all share, there appears to be an additional rule (which Jesse provides in his response and which the compiler clearly follows when compiling the second code example I presented above); I present both rules below, just to be clear and so that a correction may be made if I have gotten something wrong:

(1.) When an instance method of a class gets overridden, a (reference) identifier, of a subclass of the class (i.e., an identifier that has been assigned a reference to an object of a subclass of the class) that is used to access/reference the method, will invoke only the overriding method in the subclass (not the overridden method in the superclass); this rules does not apply when the identifier is declared to be of the class type, as the next rule stipulates and explains.


It might be easier to use the words super-class and sub-class thru-out, as class seems to be ambiguous here.
I am going to attempt to re-write your quote without using the term class, which seems ambiguous in these sentences, and then critique that.
Forward error correction, if I don't get your sentence exactly right, my critique is worthless:
When an instance method of a super-class gets overridden, a (reference) identifier with a compile-time type of the overriding sub-class (i.e., an identifier that has been assigned a reference to an object of a subclass of the class) that is used to access/reference the method, will invoke only the overriding method in the subclass (not the overridden method in the superclass); this rule does not apply when the identifier is declared to be of the superclass type, as the next rule stipulates and explains.

While that is my translation/clarification of what you said, it is not quite correct.  Having a reference with a declared type of a sub-class may get you access to additional overloads for some methods (which are only defined in the sub-class), and may also get you access to additional methods that are defined only in the sub-class, that part is true.  But as long as a method can be found in the super-class, that will be used for the compile-time overload resolution process, even if the declared reference type of the reference variable is that of the super-class.  BUT WE ARE NOT DONE YET IN MOST CASES...until runtime!

I think it is very important to realize that the whole trick of polymorphism happens in two phases.
Compile-time: Both the compile-time type checking that your instance method call is legal and valid, and possibly choosing between several overloads, is based solely on the declared type of the reference, with no attention to the actual runtime type of the object instance assigned to it.  Now, you can only assign an instance of that class itself, or any of its subclasses to that reference, any attempt to do otherwise will not compile, but at compile-time all this process does is associate the reference variable with at most one of a possible set of overloads of that method name in the base class.  If the compiler can't find one, either because none exist that will match by say, promoting an int parameter to a long, or auto-boxing or unboxing, or because there are two "equally good" competing ones (example given afterwards) the compilation will fail.  Otherwise, at compilation time we have established which overload gets called, including in the trivial case where there is only one method of that name in the super-class and we call it with the exact parameters that were declared.  The compiler does not know or care whether an overriding subclass method or a superclass method will be called at runtime, unless the superclass method was marked final, in which case it can make some optimizations.  This is the magic of polymorphism, and applies to any instance methods, even calls made inside the super-class with the implicit this pointer.  The phenomenon is also called "late binding" because no decision is yet made at compile-time, only at runtime.

[Side Note:]
If we made a stupid API because we aren't very wise like this:
long add(int a, long b)
overloaded with:
long add(long a, int b)
and we call it like this:
int i = 5, j = 10;
ref.add(i, j);

At compile time the poor compiler can't pick which one it should do, it is too much responsibility, so we will get a failure.
jshell> long add(int i, long l) { return i + l; }
|  modified method add(int,long)

jshell> long add(long l, int i) { return l + i; }
|  created method add(long,int)

jshell> add(5, 10)
|  Error:
|  reference to add is ambiguous
|    both method add(long,int) in  and method add(int,long) in  match
|  add(5, 10)
|  ^-^


I am attempting to make the same changes to your language here, and answering that.  Same disclaimer applies, I may be misinterpreting the ambiguous use of the term class in your rule.

Nyeng Gyang wrote:
(2.) When an instance method of a super-class gets overridden, a (reference) identifier, which is declared to be of the super-class type and that is used to access/reference the method, will invoke only the overridden method in the super-class (not the overriding method in the subclass); this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.


No.  At compile time, the overload resolution (which may be totally trivial or involve complex application of several rules if there are many overloads for a super-class method of that name) takes places based on the the static, compile-time, declared type of the reference variable.  That was completed in Step 1 as described and actually is finished at compile time!  Only method overloads that exist in the super-class are considered for that compile-time process we already discussed in the response on the first rule.  Compile-time is done, we have our byte code, and the only question is whether a super-class method or some sub-class method of the identical signature will be called...
Now, at runtime, we finally get to see which method gets called, super-class or sub-class.  The suspense is killing me.
okay, now at runtime we execute the call to the method.  Due to the magic of "late-binding" "dynamic polymorphism", "dynamic dispatch" or any number of other terms describing the same thing, the OBJECT INSTANCE ITSELF resolves which code gets executed!  So the declared, static, compile-time, syntactic type of the reference identifier may be to a Reader, but despite that, if it is now referring to a BufferedReader, the BufferedReader's override gets called regardless, because at runtime it consults the object instance upon calling any instance methods (unless they have been marked final in the superclass, an important special case).  If we are in a loop, each time thru the loop maybe a superclass method gets called, the next time one subclass, the next time a method from a different subclass, then maybe a superclass method, all dependent on what that same reference happens to have assigned to it at the moment it executes!


Nyeng Gyang wrote:
The example of polymorphism, in the code I presented above, has provided the motivation for me to come up with terms like the syntactic and semantic type of a reference identifier. The syntactic type of the reference identifier named in would be Reader, while its semantic type would be BufferedReader (hence, when the compiler both construes and reports that the type of this identifier is Reader, it is referring to the syntactic, rather than the semantic, type of the identifier).



Yes, but for example, instanceof will go and look at the actual object type that it is pointing to.
Note this experience in JShell:
jshell> String s
s ==> null

jshell> s instanceof String
$5 ==> false

jshell> s = "Aha!"
s ==> "Aha!"

jshell> s instanceof String
$7 ==> true


Or maybe this one will make it even clearer:
jshell> Number n
n ==> null

jshell> n instanceof Number
$9 ==> false

jshell> n = 15
n ==> 15

jshell> n instanceof Number
$11 ==> true

jshell> n instanceof Integer
$12 ==> true

jshell> n = 3.14159286
n ==> 3.14159286

jshell> n instanceof Integer
$14 ==> false

jshell> n instanceof Double
$15 ==> true


So overload resolution is done based on the declared, static, compile-time type of the reference.

Like instanceof, calling the actual specific instance method that will run is first known only at runtime, because it looks at the actual runtime type of the object that the super-class reference is pointing to.

This seems so complicated, why even do this?

Because I designed the super-class and a bunch of code to work with it and went away, and never even found out about you and the sub-class you made.
You come along without my knowledge and with your well-designed sub-class all my original code still works just fine with objects of your new sub-class that I never heard of.
That is so cool that it is all worth it.
 
Paul Clapham
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
You said "Just to be clear" and then followed up with a couple of long statements which are far from clear. However I'm going to reiterate my statement (also reiterated by Jesse) that it's the type of the object which determines which class's version of a method will be invoked at runtime. The type of a variable which contains a reference to that object has no influence on that at all.

So I think that trying to write long and verbose rules which try to explain what happens when the type of the variable differs or doesn't differ from the type of the object is not worth the trouble, since the type of the variable is irrelevant anyway. I initially thought that your (2) was incorrect, and then after re-reading it carefully I thought that it was correct after all, and eventually I thought that "the reference assigned to the identifier is for an object of the class or an object of the subclass" was a concept which was too hard for me to understand. But in the end it doesn't matter to me because I find the simple rule sufficient for my needs.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
So, I did not read through all the content you posted; I only scanned through this content that you posted in response, so do accept my apologies if your response has actually answered the question I ask in this post I am making.

Jesse Silverman wrote:So the declared, static, compile-time, syntactic type of the reference identifier may be to a Reader, but despite that, if it is now referring to a BufferedReader, the BufferedReader's override gets called regardless, because at runtime it consults the object instance upon calling any instance methods (unless they have been marked final in the superclass, an important special case).


While you make the foregoing remark, I would like you to note that the compiler tries to invoke a readLine() method implemented in the Reader class, rather than the readLine() method implemented in the BufferedReader class. If the second rule I provided above is not applicable, why doesn't the compiler simply look for the readLine() method implemented in the BufferedReader class and execute that method, rather than looking for a readLine() method implemented in the Reader class and reporting the error that it is not able to find such a method?

Furthermore, is it not reasonable to surmise that, when the compiler both construes and reports that the type of the identifier named in is Reader, it is referring to what I have come to describe as the syntactic, rather than what I have come to describe as the semantic, type of the identifier?
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:You said "Just to be clear" and then followed up with a couple of long statements which are far from clear.


My apologies for not having been adequately clear.

Paul Clapham wrote:However I'm going to reiterate my statement (also reiterated by Jesse) that it's the type of the object which determines which class's version of a method will be invoked at runtime. The type of a variable which contains a reference to that object has no influence on that at all.


Paul Clapham wrote:So I think that trying to write long and verbose rules which try to explain what happens when the type of the variable differs or doesn't differ from the type of the object is not worth the trouble, since the type of the variable is irrelevant anyway.


Why, then, doesn't the compiler simply look for the readLine() method implemented in the BufferedReader class and execute that method, rather than looking for a readLine() method implemented in the Reader class and reporting the error that it is not able to find such a method? (I ask this question because the compiler tries to invoke a readLine() method implemented in the Reader class, rather than the readLine() method implemented in the BufferedReader class).

Paul Clapham wrote:... and eventually I thought that "the reference assigned to the identifier is for an object of the class or an object of the subclass" was a concept which was too hard for me to understand.


The following is an example of what I mean by my remark, which you have found unclear:
The "class" and the "subclass" here are BufferedReader and LineNumberReader, respectively. On line 1, the reference that is assigned to the identifier reader is for an object of BufferedReader (which is the "class," in this case) and, on line 2, the reference that is assigned to the identifier reader is for an object of LineNumberReader (which is the "subclass," in this case). That second rule I provided simply states that, in both cases (on lines 1 and 2 above), when the identifier reader is used to invoke a method, the compiler looks for an implementation of the method in the BufferedReader class, rather than an implementation of the method in the LineNumberReader class.
 
Paul Clapham
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:Why, then, doesn't the compiler simply look for the readLine() method implemented in the BufferedReader class and execute that method, rather than looking for a readLine() method implemented in the Reader class and reporting the error that it is not able to find such a method? (I ask this question because the compiler tries to invoke a readLine() method implemented in the Reader class, rather than the readLine() method implemented in the BufferedReader class).



You'll remember that Jesse's brief description said something like "The compiler uses the variable's type to find out what methods are available, then at runtime the JVM uses the object's type to find out which version of the method to invoke".

We were discussing the second half of that description, namely which version of the method (subclass or superclass) is invoked at runtime. Now you ask about what happens at compile time, which is a separate issue. And in your post you also suggest that "the compiler tries to invoke..."; this is a misconception. The compiler does not try to invoke anything. It's the JVM which invokes methods. I suspect that misconception might be the reason you find yourself confused here.

Let me just remind you about how the compiler does it works: Suppose you have this code:



and later this code:



After the compiler sees the first line of code, it could infer that the in variable refers to an object of type BufferedReader. But when it gets to the second line of code it will not draw that inference because the in variable could be reassigned to refer to some other Reader object at runtime, after the first line but before the second line. So when the compiler is dealing with the second line, all it knows is that in will refer to an object of type Reader at runtime. That object may or may not be a BufferedReader. And therefore the compiler cannot allow that variable (i.e. the object referred to by that variable) to call readLine, which is supported by BufferedReader but not by Reader.
 
Paul Clapham
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
I could also mention that you're building those rules based on the idea that the person reading the code can know the type of the variable and also the type of the object it refers to. But that isn't the case. Consider this code:



This code calls a method which returns an object of type Reader, presumably based on the parameters passed to it. But the reader can't tell whether that's actually a Reader or whether it's one of the many subtypes of Reader, and neither can the compiler. This means that your proposed concepts of "syntactic" and "semantic" type are not all that useful to a person trying to understand the code.
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
Nyeng:

There are at least two ways you could be confused about this question.
One is being short on OOPS experience.
The other is having too much experience in other languages that implement OOPS in a somewhat different way that maps poorly to Java when you try to bring your knowledge over.
I am not sure which one is going on.
I know you care a lot about details and getting things right, so if I had to guess it is either one of those coupled with perhaps too much time elapsed since you read about inheritance and polymorphism in the first Sybex book (or first half of the CSG)?

You might want to review that material, or perhaps this link:
https://coderanch.com/forums/shingle/redirect/955

I will go ahead and answer anyway, but do consider a rewind-and-review of the underlying concepts where they were treated earlier in your studies.
We are studying a LOT of material and it is possible for our mastery of the details to evaporate to some extent with time and a focus on newer material.

Nyeng Gyang wrote:...
Why, then, doesn't the compiler simply look for the readLine() method implemented in the BufferedReader class and execute that method, rather than looking for a readLine() method implemented in the Reader class and reporting the error that it is not able to find such a method? (I ask this question because the compiler tries to invoke a readLine() method implemented in the Reader class, rather than the readLine() method implemented in the BufferedReader class).



I wrote a lot, because the answer covered a high percentage of "What is object-orientation in Java?"

In C++, dynamic binding and runtime polymorphism is strictly "Opt-In" for both historical reasons and obsession with efficiency.
In Java, it is opt-out.  If you declare a method in your class final then and only then can the compiler decide the exact code that will later be called at runtime.
If an instance method is NOT final, then the compiler says (for instance)

I know that some method on some object of class Reader or some subclass will be getting called, with this method signature and return type.  Which one exactly?? I will figure it out when we get there!



So the answer is, you can't call sub-class-only methods using the super-class reference variable, because the compiler DOES NOT KNOW OR CARE THAT THE REFERENCE MIGHT BE POINTING TO SOME SUBCLASS OBJECT OF A TYPE THAT IT DOES NOT EVEN KNOW ABOUT!  The way Polymorphism works, it might actually turn out to be a SuperCallifFrajilisticExpiallidociousReader, not just a BufferedReader.  The instance can, and often does, come from some method that is not defined in that source even.  You can even write a factory method that returns a random subclass when called, or an anonymous subclass, or a random anonymous subclass!!  This is all wonderfully dynamic, extensible and magical, and gets used to great effect, but what both Paul and I are saying is that "At compile-time, the poor compiler has NO IDEA what actual object type it might be pointing to, except that it is either the reference type or some subclass of it.  In fact, if the reference type is to an Interface, which it often is, then it knows that the actual object is of some concrete class or another that implements the interface in question (or the assignment never would have worked!) but no more than that.  It doesn't know, it can't know, it doesn't need to know, and does not guess.  Some people around these parts might say "In some cases it CAN tell and may hard-code something as an optimization" but most of those who would know will not have the patience to read this far thru this particular thread

Paul Clapham wrote:... and eventually I thought that "the reference assigned to the identifier is for an object of the class or an object of the subclass" was a concept which was too hard for me to understand.


The following is an example of what I mean by my remark, which you have found unclear:
The "class" and the "subclass" here are BufferedReader and LineNumberReader, respectively. On line 1, the reference that is assigned to the identifier reader is for an object of BufferedReader (which is the "class," in this case) and, on line 2, the reference that is assigned to the identifier reader is for an object of LineNumberReader (which is the "subclass," in this case). That second rule I provided simply states that, in both cases (on lines 1 and 2 above), when the identifier reader is used to invoke a method, the compiler looks for an implementation of the method in the BufferedReader class, rather than an implementation of the method in the LineNumberReader class.

I can see something you are confused about which is probably better fixed by re-reading the primary material you studied inheritance and polymorphism from than by re-reading my posts, tho I think once you have everything clear you will (I hope) see what I said is correct.  If nothing else, let my responses make clear that you need to keep entirely clear what parts happen at compile time and what parts happen only at runtime.

I am saying this because THE COMPILER IS NOT LOOKING FOR AN IMPLEMENTATION OF THE METHOD ANYWHERE, THIS IS NOT C++ TRYING TO INLINE CODE FOUND IN A HEADER FILE...

The compiler makes dang sure it is legal to call a method of that name with those parameters on the declared, static, compile-time type of the reference.
It may have to pick between more than one overload to figure this out, or it might trivially see you are calling method() with no arguments and it is done.

Ignoring various special cases and optimizations involving final classes or methods (which are important later after you have all of this totally clear and remembered) the decision about which IMPLEMENTATION to call, e.g. superclass, subclass1, subclass2, subsubsubclassN only happens at runtime.  Paul will say that the declared type of the reference has nothing to do with it.  Well, if your code is running it compiled, and in Java (but not other languages!!) it is impossible to get a non-null pointer to the wrong type in your reference.  Only an assignment of an instance of the same class, a valid subclass, or concrete implementing class in the case of an interface will even compile.

You might want to review upcasting and downcasting as well.

When you say:
SuperClass ref = subClassRef;
that always compiles and always works at runtime and is called upcasting.

When you say instead:
SubClass ref = (SubClass)superClassRef;

that will always compile as long as either of them is a sub-class of the other one (or a class that implements the appropriate interface)
but will yield a ClassCastException at runtime if the object referred to by superClassRef is not actually of SubClass type or some sub-type.

If you want to make sure you don't throw a fatal runtime exception during execution, you can protect yourself by writing:
if (superClassRef instanceof SubClass) SubClassRef = (SubClass) superClassRef;
else { // assign null or handle some other way }


I am not recommending you write code like that, it is considered poor Java coding style, but it appears there may be a lot of previously studied material you currently need to review.

I sympathize with you, rather than empathizing, if you are frustrated with just how much material is covered on the 819, and how much in total you need to know well and be able to recall and apply quickly.

The stuff you were confused about here is much more basic and fundamental than the issues involved in many other questions you have posted -- there are much easier certification exams with much smaller scope that still absolutely require you to have the basics of inheritance, polymorphism, up-casting and down-casting of reference types and related knowledge clear and solid.
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:I could also mention that you're building those rules based on the idea that the person reading the code can know the type of the variable and also the type of the object it refers to. But that isn't the case. Consider this code:



This code calls a method which returns an object of type Reader, presumably based on the parameters passed to it. But the reader can't tell whether that's actually a Reader or whether it's one of the many subtypes of Reader, and neither can the compiler. This means that your proposed concepts of "syntactic" and "semantic" type are not all that useful to a person trying to understand the code.



Well, if Reader is a final class then both the reader and compiler CAN know this.
But yeah, the whole POINT of
Polymorphism at its heart means to the compiler "I don't know what the object will turn out to be at runtime, but whatever it may be at that point, it is for sure a Reader and can definitely do Reader things, and that is good enough for me as the compiler.  As long as you only ask to do Reader things that all Reader sub-classes can do, it isn't for me to judge or interfere.  The code's gonna do what the code's gonna do if and when it gets to that line at runtime, so whether Reader code or that of some sub-class of Reader that hasn't even been written at the time I am compiling, not my problem, man!  It's all good to me!"
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
Thanks for pointing out that the JVM, and not the compiler, runs the bytecode generated by the compiler, unlike how my choice of words, albeit only in a couple of instances, says. However, take a look at the second rule I provided earlier in this discussion thread, which I provide verbatim as follows:

(2.) When an instance method of a class gets overridden, a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method, will invoke only the overridden method in the class (not the overriding method in the subclass); this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.

Neither this rule nor some of the other discussions I provided rely on a claim or understanding that the compiler runs bytrecode. This wrong wording I employed does not amount to a misconception, as you construe and allege in the following remark you made; rather than this wrong wording being a misconception, it was due to brief moments of absent-mindedness; trust me, even though I am learning things about Java that I did not know before, from studying this study guide, I know enough of Java to know that the compiler generates bytecode and the JVM executes this generated bytecode:

Paul Clapham wrote:... this is a misconception. The compiler does not try to invoke anything. It's the JVM which invokes methods. I suspect that misconception might be the reason you find yourself confused here.



Paul Clapham wrote:We were discussing the second half of that description, namely which version of the method (subclass or superclass) is invoked at runtime.



Paul Clapham wrote:Now you ask about what happens at compile time, which is a separate issue.


The last two of the foregoing quotes, from your response, make me to revise my second rule as follows:

When an instance method of a class gets overridden and a (reference) identifier, which is declared to be of the class type, is used to access/reference the method, the compiler will construe that the overridden method in the class (not the overriding method in the subclass) is the method to be invoked at runtime; this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.

And, yes, just like you said in your other post/response, I built that second rule "based on the idea that the person reading the code can know the type of the variable and also the type of the object it refers to." This rule, as well as my proposed concepts of "syntactic" and "semantic" types, are definitely only for cases in which both the declared type of a reference identifier and the type of the object whose reference is assigned to the identifier are known, to either the compiler or a human reader of the code, at compile time.

I accept responsibility for the misunderstanding caused by my conflation of the following two scenarios, which arise due to the polymorphism in the code I provided: (1) Compile-time determination of the appropriate method that should be invoked during runtime and (2) The method that gets invoked at runtime. With the benefit of hindsight, I should have employed wording and a discussion that precludes this conflation on my part. Thanks for your contributions and answers to my inquiry.
 
Paul Clapham
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:(2.) When an instance method of a class gets overridden, a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method, will invoke only the overridden method in the class (not the overriding method in the subclass); this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.



That still seems wrong to me. Let me try to apply the original example to that.

When an instance method of a class gets overridden... The instance method read() of the class Reader gets overridden.

a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method... An identifier is declared and is used to reference the method: Reader in = new BufferedReader(); followed by int value = in.read();.

will invoke only the overridden method in the class (not the overriding method in the subclass)... The call to the read() is invoked by a BufferedReader object and so it invokes the overriding method in the subclass BufferedReader.

So under this interpretation, the rule is not correct.

 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:

Nyeng Gyang wrote:(2.) When an instance method of a class gets overridden, a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method, will invoke only the overridden method in the class (not the overriding method in the subclass); this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.



That still seems wrong to me. Let me try to apply the original example to that.

When an instance method of a class gets overridden... The instance method read() of the class Reader gets overridden.

a (reference) identifier, which is declared to be of the class type and that is used to access/reference the method... An identifier is declared and is used to reference the method: Reader in = new BufferedReader(); followed by int value = in.read();.

will invoke only the overridden method in the class (not the overriding method in the subclass)... The call to the read() is invoked by a BufferedReader object and so it invokes the overriding method in the subclass BufferedReader.

So under this interpretation, the rule is not correct.



What Nyeng is saying above can happen in C++ if a method is not marked virtual and optionally in C# using a rare arcane feature, but is never what happens in Java.

I had suggested the link:
https://coderanch.com/forums/shingle/redirect/955
(How my dog learned Polymorphism)

But this is also gone over extensively in the Sybex book(s) many chapters back, as well as all the Enthuware materials I had read.

This is one of those times I think he just needs to go back and review that material.
If I recall correctly it would be impossible to get thru the end-of-chapter tests in the appropriate areas successfully while remaining confused about this issue.
 
Paul Clapham
Marshal
Posts: 26697
81
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Jesse Silverman wrote:This is one of those times I think he just needs to go back and review that material.



I'm still of the opinion that generating excessively complicated rules is unhelpful, but on the other hand people have to work through things for themselves to arrive at a proper understanding.
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
All of the stuff that I mentioned (except how C++ or C# work somewhat differently) is in scope for the 819 exam, which from his materials he is apparently preparing for.

I agree that the excessively complex rules aren't the best strategy for everyone, I don't normally use them, but on the other hand, fi someone asked for them, if you know how everything works one should be able to write them out with some thought.  There are loads of questions where you have to answer "will this compile, and if so, what output gets produced?" that require the same knowledge as would be needed to write those things out (which nobody will ask you to do).

Both the Enthuware materials and the Sybex book break down the two-phase process of which things happen at compile-time and what happens only an runtime, there is no way someone could pass the much earlier end-of-chapter stuff without having all that clear in their heads, at least at that time.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:That still seems wrong to me. Let me try to apply the original example to that.


Paul Clapham wrote:So under this interpretation, the rule is not correct.


Why continue the discussion with the old rule 2, which I have replaced with a new one? Recall that I have noticed that our discussion is better conducted if two scenarios, which arise due to the polymorphism in the code I provided, are separated from each other? These two scenarios are as follows:

(1.) The compiler's determination of the appropriate method that should be invoked during runtime. Even though the JVM, and not the compiler, runs the compiled bytecode, the compiler nonetheless fails to successfully compile the code with the polymorphism in this code.

(2.) The method that gets invoked at runtime. This issue has been settled and, just to underscore that the issue has been settled, I wrote the following code to test exactly which method would be invoked and the result of running this code shows that the overriding method (in the subclass) is the method that gets invoked:
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Jesse Silverman wrote:What Nyeng is saying above can happen in C++ if ...


Jesse Silverman wrote:This is one of those times I think he just needs to go back and review that material.


We have gone past this issue. It has been resolved. Please, refer to the previous few posts here to apprise yourself of how the issue has already been resolved.

Jesse Silverman wrote:If I recall correctly it would be impossible to get thru the end-of-chapter tests in the appropriate areas successfully while remaining confused about this issue.


There is no confusion, since the issue has been resolved.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Paul Clapham wrote:I'm still of the opinion that generating excessively complicated rules is unhelpful, but on the other hand people have to work through things for themselves to arrive at a proper understanding.


Practically every aspect of computer programming is the existence and application of rules, whether it is the programmer following these rules as they write code or it is the compiler checking whether a programmer has followed these rules as well as enforcing that a programmer follows the rules.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Jesse Silverman wrote:There are at least two ways you could be confused about this question.


While I truly appreciate and am grateful for your having taken both the time and effort provide responses to my inquiry here. You seem to not have apprised yourself with the resolution that has already been achieved on the issue I raised. Like I have already mentioned in a previous post I made above, kindly check certain posts in this discussion, which have already brought the matter to a resolution. You seem either to not have seen these posts or to have seen them, but not fully apprised yourself with their content.
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
The quote that led us to suspect you still had residual confusion was your re-written, un-numbered version of Rule 2:

When an instance method of a class gets overridden and a (reference) identifier, which is declared to be of the class type, is used to access/reference the method, the compiler will construe that the overridden method in the class (not the overriding method in the subclass) is the method to be invoked at runtime; this rule applies whether the reference assigned to the identifier is for an object of the class or an object of the subclass.



That sounded/sounds like you believed that your example shown most recently, i.e.:



Would yield the output:
Method implemented in MyClass

Which it does not.  If you further updated your understanding/wording of that rule, we either failed to see it, or it did not post.
As it appears you are now no longer confused about this essential point of polymorphism, that is good and to be celebrated, but for those reading the thread in the future, what I repeated above, which was interpreted to be your understanding of the situation, was not correct.

Secondarily, but still importantly, the words:

the compiler will construe that the overridden method in the class (not the overriding method in the subclass) is the method to be invoked at runtime


are neither right nor properly speaking wrong, the compiler doesn't concern itself which implementation of a non-final instance method will be invoked at all, as that decision only gets made at runtime.
We could perhaps say that the compiler "knows that whatever most overridden implementation of the method that corresponds to any given instance assigned to it on a particular execution of that invocation during a given program run will be invoked at runtime, but doesn't particularly care, as long as it has determined a matching method declaration in the declared class of the reference variable.."
This would be an exceptionally verbose way to describe the magic of Runtime Polymorphism, but would be accurate.

EDIT -- I think I see where the problem lies in the interpretation of your re-formulated Rule 2:

When an instance method of a class gets overridden and a (reference) identifier, which is declared to be of the class type, is used to access/reference the method, the compiler will construe that the overridden method in the class (not the overriding method in the subclass) is the method to be invoked at runtime;



If your understanding of what actually occurs in this case is correct, I would change the wording to be consistent with the descriptions of the mechanisms of polymorphism in the materials I have learned it from, at least some of which we have in common.
For the process known as "overload resolution", or more pompously, "compile-time polymorphism", the compiler during compilation finds exactly one overload matching the declared static reference type of the reference variable that is the best fit to be called.  If it can not find any valid match, or if it should find more than one which are "equally good" since none are a perfect match, and it has no rule by which to select one, the compile fails.  Otherwise, the compiler at that moment knows the signature of the method that will be called, and the compilers work is done.

Later at runtime, each time that line runs, the most derived override of that overload chosen previously by the compiler that is associated with that object instance will be the implementation that actually executes.  The actual runtime class of the object may very well not have been known to the compiler at the time the call was compiled, in fact, it may not even have existed yet at that point because it hadn't yet been written.

This is the magic of polymorphism described to death in an apparent attempt to take all the magic away, in hopes of better coding and exam scores.
 
Saloon Keeper
Posts: 13072
281
  • Likes 2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:Practically every aspect of computer programming is the existence and application of rules, whether it is the programmer following these rules as they write code or it is the compiler checking whether a programmer has followed these rules as well as enforcing that a programmer follows the rules.


Paul's point is that it's not helpful to generate additional rules that are overly complex, especially when they're not even correct.


There really is no need to make it so difficult:

1) The compiler determines what overload will be called, based on the formal type of the reference you're calling the method on AND the formal types of the method arguments.

2) Of the overload picked by the compiler, the runtime determines what override it will call, based on the actual type of the reference you're calling the method on.



In your original example, Reader is the formal type of the in variable. Reader has a read() method that takes no parameters, so the compiler raises no error on line 8. At runtime, the actual type of in would be BufferedReader, so the BufferedReader.read() override would be called.

On the other hand, Reader has no readLine() method declared, so the compiler can't determine an overload to be called, so it raises a compiler error. That the actual type would be BufferedReader is of no consequence, because the application never reaches runtime.


Make of this what you want. If you need to rewrite it to a set of rules that only make sense to you, that is fine, but we can't help you with those, because they don't make sense to us.
 
Jesse Silverman
Ranch Foreman
Posts: 627
13
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
Thank you Stephan:

This is useful for me, because there are several different nomenclatures floating around for what should apparently simply be referred to as:
formal type
and
actual type

I used several of these terms I am afraid, possibly mixing and matching.
I do prefer to be using the same terms as colleagues and those I ask questions of.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Jesse Silverman wrote:That sounded/sounds like you believed that your example shown most recently, i.e. ... Would yield the output ...


I actually thought that I was the only one conflating the issues that I have already explained I was conflating and then hoped that we would all advance past this conflation. However, seeing that both you and Paul have continued to be unable to avoid this same conflation, I am led to believe that all of us were actually deeply involved in the conflation, so much such that even you persist in advancing the conflation in your response from which the foregoing quote has been excerpted.

In particular, my updated rule has to do with the method that the compiler would construe should, rather the method that would, be invoked (by the JVM) at runtime. Consequently, no, I did not (and do not now) believe that my code example would yield an output that ends up to not be the output actually yielded. Indeed, like I already explained in my response to Paul earlier, I provided this code, "just to underscore that the issue has been settled," to use the same words I used in that post I made. The other issue is the one captured in the updated rule I provided. My code, to which you referred, has to do with the method that would be invoked at runtime, which is an issue that has already been resolved as well as one of the two issues that need not be conflated.

Jesse Silverman wrote:EDIT -- I think I see where the problem lies in the interpretation of your re-formulated Rule 2: ...


The part of your response, which follows the foregoing quote of your remarks, perpetuates the conflation of the following subjects/issues (1) The one addressed by my updated rule 2 and (2) The one about the method, including which of multiple overloaded methods, that would be invoked at runtime. Once again, to be clear, that updated rule has to do only with the method that the compiler would construe should be invoked at runtime. If I am repeating myself, this is because you are yet to see and/or acknowledge the conflation, of which I have been talking about.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Stephan van Hulst wrote:Paul's point is that it's not helpful to generate additional rules that are overly complex, especially when they're not even correct.


While everyone would agree that rules ought to be simple, rather than complex, I hope you do realize that Paul persisted at addressing the rule that I had already replaced with a new one. Any further discussion and/or critique of this old rule, by anyone reading this discussion thread, will indeed only muddy the waters and, thereby, not help the discussion. Any and all critiques should be of the new rule, not the old one.

Stephan van Hulst wrote:There really is no need to make it so difficult:

1) The compiler determines what overload will be called, based on the formal type of the reference you're calling the method on AND the formal types of the method arguments.

2) Of the overload picked by the compiler, the runtime determines what override it will call, based on the actual type of the reference you're calling the method on.


If you would take a moment to compare your quoted remarks above with my updated rule, you will definitely see that your remarks pertain to the determination of which of multiple overloaded methods would be invoked at runtime, while the rule pertains to the compile-time determination of which of an overloaded/overridden method and an overloading/overriding method that would be invoked, in the particular case/example of polymorphism in the code I provided. That you have provided your remarks, which I have excerpted in the foregoing quote of yours, demonstrates that you also are yet to avoid the conflation of which I have been speaking; the same one that Paul and Jesse are also yet to be able to avoid.

Stephan van Hulst wrote:On the other hand, Reader has no readLine() method declared, so the compiler can't determine an overload to be called, so it raises a compiler error.


And, what does this compiler error say? It says that the compiler can't find a readLine() method implemented/provisioned in the Reader class, not the BufferedReader class. The compiler tries to find a readLine() method implemented/provisioned in the Reader class, rather than one implemented/provisioned the BufferedReader class, despite the fact that the actual type of the identifier used is BufferedReader, not Reader; clearly, the compiler does this because the formal type of the identifier is Reader. This is the heart/core of what my updated rule covers.

Stephan van Hulst wrote:That the actual type would be BufferedReader is of no consequence, because the application never reaches runtime.


Yes, of course, it is true "that the actual type would be BufferedReader is of no consequence," as you opine, but only if one doesn't care that the compiler reports this error and fails to compile the code. However, since I would like to write code that does not even generate this particular error in the first place, I find my updated rule to be of consequence in this regard.

Stephan van Hulst wrote:If you need to rewrite it to a set of rules that only make sense to you, that is fine, but we can't help you with those, because they don't make sense to us.


I have already noted that my initial conflation of two issues, which ought to have been separated from the onset, is responsible for my inquiry here to not make sense to you folks. This is precisely the reason why, now that I have identified this conflation, it is not useful to perpetuate it, since doing so will only perpetuate my inquiry not making any sense to you. They say that hindsight vision is 20-20; this is true in this case for which I now realize that I should have not only separated the two issues I conflated, but that I should have, in fact, also placed them in entirely different discussion thread questions.

P.S.:

Since the terms "formal" and "actual" types already exist, I guess this means that my suggested "syntactic" and "semantic" types, respectively, are redundant because, unless there is/are some scenario(s) that I am overlooking, I suspect that my suggested terms mean exactly the same thing as their counterparts, which already exist.

 
Stephan van Hulst
Saloon Keeper
Posts: 13072
281
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Nyeng Gyang wrote:If you would take a moment to compare your quoted remarks above with my updated rule


I will not waste my time.

Your "rules" are overly complex, unnecessarily loquacious, and plainly put, a headache to read.

Once again you insist that any confusion in this matter is caused by all except the imperspicuity of your own posts.
 
Nyeng Gyang
Ranch Hand
Posts: 100
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator

Stephan van Hulst wrote:I will not waste my time.


You are, of course, entitled to respond in this way. Your time is provided by you only as you allow it, not as anyone either cajoles, coerces or forces you to.

Stephan van Hulst wrote:Your "rules" are overly complex, unnecessarily loquacious, and plainly put, a headache to read.


Again, you are entitled to this opinion, which I don't happen to share with you. The rule I have come up with will guide me to not write code that will generate that compiler error again. You are liberty to read those of my posts or comments that are adequately clear to you and to ignore those that are not.

Stephan van Hulst wrote:Once again you insist that any confusion in this matter is caused by all except the imperspicuity of your own posts.


This remark by you is either sincerely (inadvertently) erroneous or provably deliberately disingenuous, but I can't say that I know which of these two possibilities is the case.
 
author & internet detective
Posts: 40677
827
Eclipse IDE VI Editor Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Report post to moderator
Locking another thread that has devolved into the merits of discussing the topic.
 
Our first order of business must be this tiny ad:
Thread Boost feature
https://coderanch.com/t/674455/Thread-Boost-feature
    Bookmark Topic Watch Topic
  • New Topic