1z0-808 Oracle Java SE 8 Programmer – Inheritance Part 2
- Overloading Methods
Now we know that whenever we instantiate an object that we have not only the methods that were defined in that class available to us, but we also have the methods available that were defined in any of the ancestors, anything that it inherits from, provided that the access modifiers allow us to call those methods. And because of that, Java enforces a few rules. Java wants to make sure that when we try to call a method that it’s able to pick the exact method and only one method that would answer that request. And if there’s any chance of an ambiguity where it’s like, well, I’m not sure which of the methods I should choose, it won’t even let you compile with those kind of ambiguities. So there’s rules that we have to follow and one of the rules we can use is called overloading methods. This is something that allows us to use the same name of a method as another one. And I’ll explain the rule in just a second, but let’s see how it’s used.
So for example, we got print line and we’ve been calling print line all over the place with our code. We have been passing in strings, we’ve been passing in objects, like my dates passing in INTs. And it’s not like there was just this one big print line method that said I can accept any number of arguments and we’ll figure out what we want to do with them instead. It says in fact there’s a number of print line methods that have been created, each one that takes a different parameter.
So when we call print line and we pass in a float, it calls a special method that knows what to deal with floats. And when we pass in an int, it calls the print line that knows how to deal with INTs. That’s method overloading. So it’s easy to use. But how do we take advantage of that ourselves when we’re making our own classes? So the general rule is this that whenever you create a method, the signature must be unique. There is one exception to this rule. It’s called overriding methods.
And we’ll look at that exception in the next lecture. So for now, let’s just accept that the signatures must be unique for every method that we create. So what is the signature? The signature is the name of the method and then the parameter type in order. That’s it. Nothing else in the method is considered part of the signature. Other parts might be considered. In other cases they’ll refer to it as an extended signature. But when we just talk about the basic signature, it’s the name of the method and then the number of parameters. Number and type of parameters. So that has to be unique. Now let’s apply that to print line. So if everything is using the same name print line, print line, print line then that means the parameter type in order has to be unique. If it’s not. It just won’t compile. Now, like I said, there is an exception to this rule with overriding.
But we’ll wait to the next lecture to see that exception. Let’s just take a quick look at a couple of examples. Jump to some code here. So I’ve got two Say Hello methods and they both take a string. The one is a string first name and the other is a string nickname. And if I was to call this, I could say, say Hello J. Now you know me as Jason, but let’s say you didn’t know me that well and you’re just not sure if my name is Jason or Jay. So maybe Jay is my nickname, maybe it’s my first name. So my question for you is, as a human being, if you were to route this method call, which of the two methods would you invoke?
Would you invoke the first one or the second one? And again, if you didn’t know that my real name was Jason, you might be stumped. Jay could be a first name. Jay could be a nickname. We’re not sure. So imagine how the Java platform feels. You probably already noticed that there are red lines here. So it’s saying that, no, this isn’t legal. So what we’re looking at is we’re breaking the rules of overloading a method. The signature is not unique. Now you might think, but this is unique. This one says first name and this one says nickname. Well, the Identifier for the parameter, the name of the parameter does not count for the very reasons that you just saw. If I’m a computer, if I’m the Java platform and a string is passed in, all I know is I’m trying to find a say hello that takes a string. I’m not really thinking about this name here, this Identifier that’s for use inside of the method. That’s a human readable thing, not a computer readable thing.
So these types have to be unique. I could, for example, change this to also accept an age. And now look, all the red lines go away. It compiles because the signature is unique. Say hello, String. We’ve got one with a string, one with an int and then one with just a string. So when I call and I just pass in a string, it knows exactly which one to go to. It’s going to go to this method right here. If I want to call the method on top, I’m going to have to pass in here. I’ll make myself 80. I’ll pass in an age. Does that make sense? So we have to have each method be unique. And by the way, the return type doesn’t fall into this at all. So if we had, I’ll get rid of this age again. Now we’ve got a problem. Let’s say I changed this to be this one’s going to return an int. That does not make it unique. The return type is not a part of the signature. But once you follow the rule where everything is unique. So put age here and I’m going to also show you this example. So put into, age and string.
This is still unique because the order parameters matters. This is a say hello that takes a string and then an int. And this is say hello that takes an into and then a string. That’s unique. This one is definitively going to the second method or so the first method, because it’s passing in a string and then an int. This is the method that takes a string and then an int. Now, once you follow the rules of overloading methods, everything else can be unique.
Like now I can change the return type. I could say this one returns an int. Now why is it giving me a problem? Well, because I haven’t returned an into yet. But if I return an into now, the problem goes away. That red line, the compile error, had nothing to do with overloading methods, it was just not returning the value. But you can see that once we have an overloaded method which means the same name but a unique parameter type, anything else about it can be changed. We can change the return type, we can change the modifiers. Later we’ll learn about exceptions, we’ll see how we can change what the method throws and so on. And that’s overloading methods.
- Overriding Methods
The rule in Java is that every method signature needs to be unique with one exception, and that’s with overriding methods. So the general idea of overriding a method is that there is a method that is being inherited and we want to change the functionality. So we don’t like how that method behaves. Let’s change it, let’s look at some code, it will be easier to understand. So we’ve got two new classes. We got a class for a father and we got a class for a son. And father has a method called drive. So if I created a son, son s equals new son. And then I said S drive, what happens? Well, even though Son doesn’t have a method called drive, it will inherit the drive method from the father. So it’ll still work. The velocity will be set to 45, the path will be set to less traveled and then it’ll go. So that’s great. But what if we didn’t want to drive in the same way that the father drives? Well, what we could do is override the method. So in the subclass we would create a method here’s public void drive. It is the exact same method signature. The method signature is the name drive and then no parameters, that’s the method signature. So this has the exact same method signature.
And what will happen is now when we say S drive, the version that’s in the son will override the one that was in the father. What you can think of this is that when we say S drive it’s going to go to Son first and say hey, do you have a method called drive? And the answer is yes. And so it runs it. If it didn’t, it would go up to the father and say father, do you have a drive? And then it would run that one and so on. So in order to successfully override a method, the overriding and the overridden method must have the exact same signature. They have to have the exact same name and the exact same parameters. And so this can’t happen in the same class. You can’t have a class that has the exact same signature. This is a relationship between a superclass and a subclass. If the method names are the same but the parameters are different, well then what you’re doing is you’re just overloading. You’re not overriding the method anymore, you’re overloading the method.
And so then you just follow all the rules of overloading. But once it’s determined that you are in fact overriding a method, there’s a few more rules that have to be followed. First of all, the return type, the return type is going to have to be the exact same type that was declared in the overridden method. Or it can be a subtype of whatever that overridden return type is. Now I’ll show you a little bit more detail of that. It’s called covariant returns. And we have a whole lecture in this section that’s dedicated to that topic, an overriding method cannot be less accessible than the inherited method. So the code that you’re seeing here would not compile. The problem is this drive is overriding the method. In father, it’s overriding because there is an extension, there’s an inheritance relationship. The method signature is exactly the same. But the problem is in father the method is public, which is as accessible as it can get. And then we are using the default modifier. In other words, no modifier, which means that it’s package level that overridden method.
Or sorry, the overriding method is less accessible and therefore that is illegal. We could go the opposite way. If the father’s drive was just void drive and the son’s drive was public void drive, that would be okay because it became more accessible. So in the final section of this class, we’re going to talk about exception handling. And when we do, this final rule about overriding methods will make a lot more sense. The only reason I’m going to mention it now is not that I expect you to understand it at this time, but eventually you’ll probably come back, you’ll do the whole class and you’ll start to come back and watch certain lectures again. And when you’re trying to remind yourself about the rules of overriding methods, I want all of the rules together. So the rule in regard to exceptions is that an overriding method can declare fewer exceptions, but it cannot declare more exceptions unless those exceptions are a subtype of the overridden exception or if it’s a runtime exception.
Like I said, don’t worry about that. If it doesn’t make sense right now, it will once you get to the exceptions chapter and if you happen to come back and review what it means to override a method. Now you’ve got all the rules in one place. One other interesting tidbit is that you can’t actually override static or private methods. It just doesn’t work. You can’t do it.
They can only be hidden. And it’s a little confusing at first to understand this. But what it means is that when we call, for example, a static method, then the static method is going to which static method is going to be called is dependent on the reference variable, not on the actual object type. So it’s resolved at compile time. Now, when we’re dealing with overridden methods, those are actually resolved at runtime. Let me show you some code and you can see the difference between the two. So I’ve got a static method called say hello. And then I’ve got just a regular method called Say hi, I’ve one set in father, one set in son. And then I’ve got a tester that’s going to call this. So what I’m doing here is I’m just showing you and this is actually kind of getting a little ahead of ourselves because in the next section sorry. Yeah. The next section we’re going to talk about polymorphism and this is a form of polymorphism. But what’s happening here is that when I have father F so that’s my reference type. My reference type is father and then I say equals new son.
Now that seems odd, right? But trust me in that it’s legal. That’s not the part I want you to focus on yet. We’ll talk about this structure here in the next section. So it’s legal. So if I say F dot say hello. It’s actually looking at the reference type variable to figure out which one it should call. So it goes to the father’s, say hello and it says static hello from father. Whereas if I say just a normal overridden method say hi. Then it’s resolved at runtime and it’ll figure out that this is actually a son object and so it will use the sons say hi again just like that last rule with exceptions. This might be a little confusing right now. Don’t let this stop you from continuing on with this particular section. We will talk a lot more about these issues in the upcoming section on polymorphism. So what if an instance of the son wants to drive like an instance of a father? We’ve overridden the method. How do we ever call the version that the father has? And we can do that with the super keyword just like we did with the constructors that were chained. We can also use super to just call methods in the super class.
So now I’ve got my drive method and then I got another drive method. It’s called drive like dad and I’m using the super keyword and I just call drive. And what’s going to happen is when you say super instead of well, let’s pretend that super wasn’t there, let’s just say it said drive. What would happen is it would come back into this object sun and say do you have a method called drive? And the answer would be yes. And it would answer it. But now when we say super drive, it doesn’t even look in the sun for that particular method. It instantly goes to the parent class, which in this case is father, and use their version of drive. Now, that doesn’t mean you can keep using super and chain them together to get to some ancestor. Like you can’t say super drive hoping to get to grandfather’s drive method. That will not work. You can only have one super.
- Covariant Returns
When we were talking about overriding methods, I said that the return type can either be the exact same type that you’re overriding, or it can be a covariant return. So I just want to have a quick little lecture here to show you what a covariant return looks like. Imagine I’ve got a class called a grandfather registry. And we’ve got one method who is my ancestor and it’s returning a grandfather type. Now, covariant return is where a subtype would be listed in the place of a supertype. So overridden methods can use the covariant return and it looks like this.
So my father registry extends grandfather registry. And if we look at the who’s my ancestor method, it’s got the same signature. We are overriding the method, but the return type is different. I am now returning here a father. And you can imagine that the grandfather is the supertype of the father class. That’s all it is. That is a covariant return. And that’s legal with overriding methods. Now, the reason that you want to do a covariant return is that it just allows other people when they call a method to get a more specific form of the object. If I was to call father registry, who my ancestor, and it could only return a grandfather, then I’d be restricted to the methods that are available to a grandfather. This way I actually get a father object. And now I have more specific methods that would be available to that type.
- The Three Faces of Final
So the final lecture in this section is on the topic of final. Final is a modifier that we can apply to a number of different things. But in each case it really means just unable to change. So there are three primary places that we’ll use the final modifier. One is in front of a variable and when we do that, it makes the variable a constant, just some sort of value that won’t change. If we use final with a method, then that means that method can’t be overwritten. And if we put final in front of a class, then that means that class is unextendable. You cannot inherit from that class. So let’s start with the constant. We’ll add a final modifier to the variable and we can add this to a number of different variables. It can be local in a method, but it can also be a static or instance variable. Typically speaking, when we add final to a variable, it’s going to be a static variable.
Most often by convention, the constant names are an uppercase, which means that if we have multiple words, we’ll separate them with an underscore. Probably the most confusing part about using a final modifier in front of a variable is what are the rules for initializing the value of that variable? Let’s look at an example. So here I’ve got two different final variables. I’ve got one called less underscore traveled and one called shortest. Now, the less underscore traveled is the most easy to understand. We’ve got explicit initialization. So we’re setting less traveled to be zero because it’s final. Once it’s set to zero, we can’t change that it is going to be zero for the life of the application. Notice that the final is after public static. Just remember that that’s not a rule.
The rule is that modifiers can be in any order you’d like. The other rule, of course, is that all modifiers must be to the left of the type. So final must be to the left of the end, but it doesn’t have to be the most immediate thing to the left of the end. You could have had static public final or final public static. All those would work too. If you look at shortest, that one’s a little bit more confusing. So what’s happening here is that we’ve got our final int shortest. We only have one constructor. So whenever final test is going to be created, we know that constructor will be called and shortest is then set to one. Once shortest is set to one, it cannot change. And this is all legal because it knows that shortest will have some value before it’s used. And since it must be set before it’s used, that means there are some rules about how we set these variables. So for example, if I’m trying to set the final variable outside of some kind of explicit initialization or within a single constructor, we’re probably going to get a compile error here’s an example. If we look back at shortest down at the bottom here, we do have a method that’s called foo and it’s going to set shortest to one. But the problem here is that a method is nothing but potential for activity.
If nobody ever calls the method foo, then this shortest is never going to be set and that’s possible. Or someone might try to access shortest before it has this value assigned. Because of that, the compiler is going to complain and said and just say I can’t help. You cannot assign a value to final variable shortest and it just doesn’t compile when you put the final modifier in front of a method. What you’re saying is that this method cannot be overridden. And so here we’ve got our father and son again. And remember, son was overriding the method drive. If we’ve got final in that version father has, then this won’t even compile it’ll. Say this is illegal, you cannot override a final method. Now, why would we want to do that? I think that’s going to become a little bit more obvious when we start looking at polymorphism. So let’s hold off on that a little bit.
But just know for now that adding final to in front of a method means you cannot override it. And the last version of final is when we put it in front of a class. So here we’ve got public final class father. And what that means is that the class itself cannot be extended. You cannot inherit from this. And once again, this will become more clear when we use polymorphism. Polymorphism is the idea of substituting one type for another. And so imagine that I’ve got some kind of a program that accepts a father or any other type like a son. And so if I want to make sure that I’m only getting a father class and nothing that inherits from father, I can make it final.
And then it’s illegal for someone to try to substitute another object. That sounds a little confusing. Don’t worry, that’s another thing that we’re going to talk about coming up here. In fact, the whole next section is on polymorphism. And so we’ll clear up a lot of these concepts. As a side note, in the Java API, the string class is final. So you can’t inherit from string and you cannot change any of its functionality. Also, Java enums, when we write out all those labels, the constants that are used there are implicitly static and final, even though we don’t write those modifiers on each of them. So if you remember, we had days and days at Sunday, Monday, Tuesday and so on. Each one of those was static and final.
- Inheritance Lab
Up until this lab, all of our orders simply had strings. Now we’re going to start using real objects. We’re going to create a hierarchy of objects at the very top of the structure. We’re going to have good. A good is something that you can buy. And then we’ll have two subclasses, liquid and solid. So you’re going to work with inheritance. You’re going to be chaining to a super constructor and do a lot, lot more more. So the instructions for this lab are in the resource section for the lecture. This is lab number nine, and it’s called lab nine. Inheritance PDF solution is at the end of the lab. Let me know if you have any questions.