CodeGym /Java Course /Module 2. Java Core /Access modifiers, method overriding, and implementing abs...

Access modifiers, method overriding, and implementing abstract methods

Module 2. Java Core
Level 2 , Lesson 2
Available

"I'm going to tell you about «access modifiers». I told about them once before, but repetition is a pillar of learning."

You can control the access (visibility) that other classes have to the methods and variables of your class. An access modifier answers the question «Who can access this method/variable?». You can specify only one modifier for each method or variable.

1) «public» modifier.

A variable, method, or class marked with the public modifier can be accessed from anywhere in the program. This is the highest degree of openness: there are no restrictions.

2) «private» modifier.

A variable, method, or class marked with the private modifier can only be accessed in the class where it's declared. The marked method or variable is hidden from all other classes. This is the highest degree of privacy: accessible only by your class. Such methods are not inherited and cannot be overridden. Additionally, they cannot be accessed in a descendent class.

3) «Default modifier».

If a variable or method is not marked with any modifier, then it is considered to be marked with the "default" modifier. Variables and methods with this modifier are visible to all classes in the package where they are declared, and only to those classes. This modifier is also called "package" or "package private" access, hinting at the fact that access to variables and methods is open to the entire package that contains the class.

4) «protected» modifier.

This level of access is slightly broader than package. A variable, method, or class marked with the protected modifier can be accessed from its package (like "package"), and from all inherited classes.

This table explains it all:

Type of visibility Keyword Access
Your class Your package Descendent All classes
Private private Yes No No No
Package (no modifier) Yes Yes No No
Protected protected Yes Yes Yes No
Public public Yes Yes Yes Yes

There's a way to easily remember this table. Imagine that you're writing a will. You're dividing all your things into four categories. Who gets to use your things?

Who has access Modifier Example
Just me private Personal journal
Family (no modifier) Family photos
Family and heirs protected Family estate
Everybody public Memoirs

"It's a lot like imagining that classes in the same package are part of one family."

"I also want to tell you some interesting nuances about overriding methods."

1) Implicit implementation of an abstract method.

Let's say you have the following code:

Code
class Cat
{
 public String getName()
 {
  return "Oscar";
 }
}

And you decided to create a Tiger class that inherits this class, and add an interface to the new class

Code
class Cat
{
 public String getName()
 {
   return "Oscar";
 }
}
interface HasName
{
 String getName();
 int getWeight();
}
class Tiger extends Cat implements HasName
{
 public int getWeight()
 {
  return 115;
 }

}

If you just implement all the missing methods that IntelliJ IDEA tells you to implement, later you might end up spending a long time searching for a bug.

It turns out that the Tiger class has a getName method inherited from Cat, which will be taken as the implementation of the getName method for the HasName interface.

"I don't see anything terrible about that."

"It's not too bad, it a likely place for mistakes to creep in."

But it can be even worse:

Code
interface HasWeight
{
 int getValue();
}
interface HasSize
{
 int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
 public int getValue()
 {
  return 115;
 }
}

It turns out that you can't always inherit from multiple interfaces. More precisely, you can inherit them, but you can't implement them correctly. Look at the example. Both interfaces require that you implement the getValue() method, but it's not clear what it should return: the weight or the size? This is quite unpleasant to have to deal with.

"I agree. You want to implement a method, but you can't. You've already inherited a method with the same name from the base class. It's broken."

"But there's good news."

2) Expanding visibility. When you inherit a type, you can expand the visibility of a method. This is how it looks:

Java code Description
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
We've expanded the method's visibility from protected to public.
Code Why this is «legal»
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
Everything's great. Here we don't even know that the visibility has been extended in a descendant class.
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
Here we call the method whose visibility has been extended.

If this weren't possible, we could always declare a method in Tiger:
public String getPublicName()
{
super.getName(); //call the protected method
}

In other words, we're not talking about any security violation.

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
If all the conditions necessary to call a method in a base class (Cat) are satisfied, then they are certainly satisfied for calling the method on the descendent type (Tiger) . Because the restrictions on the method call were weak, not strong.

"I'm not sure I completely understood, but I'll remember that this is possible."

3) Narrowing the return type.

In an overridden method, we can change the return type to a narrowed reference type.

Java code Description
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Tiger getMyParent()
 {
  return (Tiger) this.parent;
 }
}
We overrode the method getMyParent, and now it returns a Tiger object.
Code Why this is «legal»
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Everything's great. Here we don't even know that the getMyParent method's return type has been widened in the descendant class.

How the «old code» worked and works.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
Here we call the method whose return type has been narrowed.

If this weren't possible, we could always declare a method in Tiger:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

In other words, there are no security violations and/or type casting violations.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
And everything works fine here, though we widened the variables' type to the base class (Cat).

Because of overriding, the correct setMyParent method is called.

And there is nothing to worry about when calling the getMyParent method, because the return value, though of the Tiger class, can still be assigned to the myParent variable of the base class (Cat) without any problems.

Tiger objects can be safely stored both in Tiger variables and Cat variables.

"Yep. Got it. When overriding methods, you have to be aware of how all this works if we pass our objects to code that can only handle the base class and doesn't know anything about our class."

"Exactly! Then the big question is why can't we narrow the return value's type when overriding a method?"

"It's obvious that in this case the code in the base class would stop working:"

Java code Explanation of the problem
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Object getMyParent()
 {
  if (this.parent != null)
   return this.parent;
  else
   return "I'm an orphan";
 }
}
We overloaded the getMyParent method and narrowed the type of its return value.

Everything is fine here.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
Then this code will stop working.

The getMyParent method can return any instance of an Object, because it is actually called on a Tiger object.

And we don't have a check before the assignment. Thus, it is entirely possible that the Cat-type myParent variable will store a String reference.

"Wonderful example, Amigo!"

In Java, before a method is called, there is no check whether the object has such a method. All checks occur at runtime. And a [hypothetical] call to a missing method would most likely cause the program to attempt to execute non-existent bytecode. This would ultimately lead to a fatal error, and the operating system would forcibly close the program.

"Whoa. Now I know."

3
Task
Java Core, level 2, lesson 2
Locked
Whales and cows
In real life you would never confuse a whale and a cow. Sea cows (manatees) are sometimes encountered in Terranian waters, and that's not all you'll see on the planets of the Commonwealth! That said, in programming you can do whatever you want. Correctly write a getter for the Whale class, so that the program displays the phrase, "I'm not a cow. I'm a whale."
3
Task
Java Core, level 2, lesson 2
Locked
Whales, the descendants of cows
In Java, method overriding is a very powerful tool. So we're going to begin to master it immediately through practice. Suppose we have a whale, a descendant of cows. Don't ask us how it happened... let's just say it's a family matter. Override the whale getter so that it doesn't display a message about being a cow.
3
Task
Java Core, level 2, lesson 2
Locked
Render to Caesar the things that are Caesar's
There is no doubt whatsoever that cats and dogs are animals. And their descendants must also be animals. But not just any animals. They should be very specific types of animal - cats and dogs. Don't you agree? In that case, override the getChild method in the Cat and Dog classes, so that cats give birth to cats and dogs to dogs.
3
Task
Java Core, level 2, lesson 2
Locked
Whether it's a bird or a lamp
What do birds and lamps have in common? The fact that birds and lamps can both be objects of their respective classes. And objects can be passed into methods as arguments. In this task, we are going to write a method that determines the type of the object passed to it, and then displays the appropriate message. Cat, Dog, Bird, and Lamp.
3
Task
Java Core, level 2, lesson 2
Locked
Animal identification
Napoleon, a cat breeder, believes that it is vitally important to know how to distinguish between types of animals. Let's help Napoleon out. We'll write a method that determines the class of the object passed to it, and returns one of the following results: "Cow", "Whale", "Dog", or "Unknown Animal".
Comments (45)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Hoist Level 35, San Diego, United States
17 September 2024
ישראל מאיר Level 23, Israel
7 June 2024
Okay, so to summarize this lesson what we learned is: 1. Although in inheritance if you override a method there is a requirement that the signature be the same as the signature in the base class, it is possible to change the access modifier, and the returning type (if it is a base or inherit type of the inherited class) 2. If you change the returning type in an inheriting class, you must make sure that it is no more extended than the base class, otherwise its variable will not be able to hold the type returning from the method. As mentioned before. the use of the terms "narrowing and "widening" in the article each time for a different meaning is confusing.
tomtom6789 Level 32, University Place, United States
3 May 2024
this is confusing
Parsa Level 62, Bangalore, India Expert
20 April 2024
"I'm not sure I completely understood, but I'll remember that this is possible." If you have to add this line, you know you didn't write it properly.
Jakhongir Ruziev Level 23, Tashkent, Uzbekistan
9 March 2023
This lesson is very confusing. I had to read it several times and read additional material.
Surya Gonuguntla Level 22, United States of America, United States
16 February 2023
What the hell was this explanation?
matemate123 Level 50, Kraków, Poland
3 January 2023
I think this lesson could be better explain, it’s confusing.
Anonymous #11016365 Level 39, Detroit, United States
24 August 2022
This article never answers the question of what should be changed in the Tiger class that implements both the HasWeight and HasSize interfaces using one method???
Mary Khan Level 23, Russia, Russian Federation
18 July 2022
The last code example here is not even compiled in Idea.
proegg123 Level 16, Slovakia
9 February 2022
That was crazy and the first time during the whole course I actually understood nothing :D