Now that we have instance variables in our classes, we will try to instantiate a Book. It wouldn’t make sense to instantiate a CISItem because that’s just a generalization. We want the actual Book!
As with any Object Instantiation we should use a constructor. We’ll need lots of information for this book. Let’s take a look:
The name
The current location
The price
The description
A word count
The date published
The author
The ISBN
The edition
The title
That’s a lot to put in a constructor, but we can try:
public Book(String name, String location, int price, String description, int wordCount, String datePublished, String author, String ISBN, String edition, String title) { }
Whew, now we’ll try to do the following:
public Book(String name, String location,
int price, String description,
int wordCount, String datePublished,
String author, String ISBN, String edition, String title)
{
this.ISBN = ISBN;
this.edition = edition;
this.title = title;
}
This will work just fine, as long as your instance variables are named the same as the ones in the example above. In the Book class, we have direct access to the ISBN and edition title. But what about the variables that we inhereted from ReadingItem? Can we access those?
public Book(String name, String location,
int price, String description,
int wordCount, String datePublished,
String author, String ISBN, String edition, String title)
{
this.ISBN = ISBN;
this.edition = edition;
this.title = title;
this.wordCount = wordCount;
this.datepublished = datePublished;
this.author = author;
}
We will get the error : ‘wordCount’ has private access in ‘ReadingItem’ . IntelliJ will then ask you if you want to create a field called wordCount, but we already have that field in ReadingItem, why can’t we just access it if we’re inheriting?!
Yes, we are inheriting that variable, but that variable is still private, so only the ReadingItem class can access it. How would we instantiate that variable if we don’t have access to it? Enter super().
First lets make sure that reading item has a constructor like such:
public class ReadingItem extends CISItem
{
private int wordCount;
private String datePublished;
private String author;
public ReadingItem(int wordCount, String datePublished, String author)
{
this.wordCount = wordCount;
this.datePublished = datePublished;
this.author = author;
}
}
We can now use this constructor to instantiate the wordCount, datePublished and author instance variables. Our Book constructor will now look like this:
public Book(String name, String location,
int price, String description,
int wordCount, String datePublished,
String author, String ISBN, String edition, String title)
{
super(wordCount, datePublished, author);
this.ISBN = ISBN;
this.edition = edition;
this.title = title;
}
Notice that the word super means, “Use the constructor for the super class”. The super class, in this case ReadingItem, is in charge of instantiating its own private instance variables. We’re still missing a few variables to be instantiated though. How to we assign initial values to name, location and price?
Side note:
If a class has no constructor in Java, the compiler will add a no-argument constructor. A no-argument constructor is one that doesn’t have any parameters, for example public Person()
.
If a subclass has no call to a superclass constructor using super
as the first line in a subclass constructor then the compiler will automatically add a super()
call as the first line in a constructor. So, be sure to provide no-argument constructors in parent classes or be sure to use an explicit call to super()
as the first line in the constructors of subclasses.
Regardless of whether the superclass constructor is called implicitly or explicitly, the process of calling superclass constructors continues until the Object constructor is called since every class inherits from the Object class.
A subclass inherits all public methods from its superclass, and these methods remain public in the subclass. But, we also usually add more methods or instance variables to the subclass. Sometimes, we want to modify existing inherited methods. This is called overriding methods.
Overriding an inherited method means providing a public method in a subclass with the same method signature (method name, parameter type list, and return type) as a public method in the superclass. The method in the subclass will be called instead of the method in the superclass. One common method that is overridden is the toString() method.
Start by adding a toString() method to your CISItem class. This method can return anything that you would like. If you let Android Studio/IntelliJ to autocomplete it you’ll notice that it will look like this:
@Override
public String toString()
{
return "CISItem{" +
"name='" + name + '\'' +
", location='" + location + '\'' +
", price=" + price +
", description='" + description + '\'' +
'}';
}
The @Override above the method is called a Decorator. Decorators allow the compiler to know that the method below the decorator is meant to do something special. In this case the toString() method will override the parent’s toString(). But wait, CISItem doesn’t have a parent? It does! All classes are subclasses of the parent class Object. Where is Object, though? That’s a detail that we will avoid for now, just know that Java really needs all classes to be subclasses of Object.
The toString method above returns a serialized version of this CISItem class. Which is nice, but what about getting a serialized version of the Book class? If we inherit toString() from CISItem, we’ll miss a lot of information. In this method, we are only return some of the information that a Book class holds. Let’s override this inherited method in Book and create a toString() of our own. If you let Android Studio autocomplete you’ll end up with a toString that looks like this:
@Override
public String toString()
{
return "Book{" +
"ISBN='" + ISBN + '\'' +
", edition='" + edition + '\'' +
", title='" + title + '\'' +
'}';
}
But we’re missing all of the parent class information. Instead we would want to do this:
@Override
public String toString()
{
return "Book{" +
"name=" + this.name +
"ISBN='" + ISBN + '\'' +
", edition='" + edition + '\'' +
", title='" + title + '\'' +
'}';
}
We tried adding the name for this book but we’re told that we can’t access name because it’s private. So we need to use a getter/accessor method. Create one in CISItem for name and then try this in Book:
@Override
public String toString()
{
return "Book{" +
"name=" + getName() +
"ISBN='" + ISBN + '\'' +
", edition='" + edition + '\'' +
", title='" + title + '\'' +
'}';
}
Why is it that we can use getName() in the Book class if there is no method called getName() in this class? There is a getName() in the parent class. Why don’t we have to do CISItem.getname()?
Make another method in CISItem called printName()
public void printInfo() { System.out.println(this.name); }
Notice that after we make this method, our book class will be able to use it as well, because we have inherited this method from the parent! BUT, what if we wanted to have our own printInfo() method in Book? Maybe we want to print more than just the name, maybe we want to print out the title as well. we would override the parent printInfo() method in Book and create NEW functionality for a method of the same name. Try this in Book:
@Override
public void printInfo()
{
super.printInfo();
System.out.println(this.title);
}
Notice that first we call the super class’ printInfo() and then we print out our own info. Magic.