undefined

Access modifiers, method overriding, and implementing abstract methods

Java Core
Level 5 , Lesson 1
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."

Comments (33)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ImDevin Level 15 Old Town United States
14 June 2021
The last example has Cat changed to Object, which to me is widening, not narrowing (this widening/narrowing argument seems to just continue every time this topic is covered). Also, it looks like because of the addition of string statement as an option is the reason that this does not work, not necessarily b/c of the conversion. Another thing to add to the confusion. Oh well, on to tasks. Happy coding! :)
DarthGizka Level 24 Wittenberg Germany
4 June 2021
I took this text to be more evidence that the use of the terms 'narrowing' and 'widening' conversion with regard to object/interfaces types is confusing and inappropriate, but it turns out I was mistaken. It is only confusing but not inappropriate, because the Java specification uses the very same terms. No wonder Java is such a mess! ;-)
Gellert Varga Level 18 Szekesfehervar Hungary
5 March 2021
This lesson should be completely edited. Total confusion. Whoever wrote this lesson would still have to learn how to explain something understandably.
TomL Level 25 Prague Czech Republic
28 January 2021
sorry, but whoever wrote this article, has opposite direction of terminology (narrowing, widening) then the one who wrote lessons before (https://codegym.cc/groups/posts/106-widening-and-narrowing-of-reference-types). Please, correct it - it is so confusing!
Dmitri Level 22 Seversk Russia
8 December 2020
Again widening-narrowing contradiction! After you several times switched what is widening and what is narrowing, please do not use these terms, because I do not know what you mean!
Onur Bal Level 27 Istanbul Turkey
17 September 2020
Can anyone comment on the confusion regarding widening and narrowing? Different people seem to use the terms differently: to some, going up the inheritance chain is called narrowing, and going down it is called widening, while to other it seems to be the dead opposite.
Angel Li Level 18 Fremont United States
6 July 2020
Everything is so mixed up! In 3), it said narrowing is legal. Then he said ""Exactly! Then the big question is why can't we narrow the return value's type when overriding a method?" I think he means why can't we WIDEN? Correct me if I'm wrong, kind of confused right now.
null Level 26 Orlando United States
9 May 2020
Main point: 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.
Johannes Level 27 Centurion Pretoria South-Africa
27 March 2020
this made less sense than Corona, but okay. Will probably understand it with better examples in the exercises that are coming. The problem is that everything start to look and sound the same, if you do as many exercises as I did today ;)
Fadi Alsaidi Level 27 Carrollton TX USA
19 January 2020
The last bit of of explanation in my opinion needs to be written in a different way. This should walk you through the logic of why it will error out step by step. The whole reason the code wont run is because when the method getMyParent(); is being called and referenced in a Cat object if the possibility of the return is the String Object "I'm an orphan", then the String Object is Not a Cat Object and there for we get an error. hope that helps out someone