How to use Java subclasses - java

I have 3 classes, Book, ChildrensBook and Library. ChildrensBook extends the Book class.
ChildrensBook contains the additional variable recommendedAge.
Library contains an array that can include both Book and ChildrensBook objects.
In the class Library I have to create the method int forChildren(int n) that returns how many childrensBook of age less or equal to n are there in array Library.
the problem is that in the library array there are both Books and ChildrensBooks, so I can't access the recommendedAge variable, because it is only inside a children's book. how can I do?
public class Library {
private ArrayList <Book> collection;
public Library(ArrayList <Book> c){
collection=c;
}
public int forChildren(int n) {
int count=0;
for(int i=0;i<collection.size();i++) {
if((collection.get(i).getRecommendedAge)<=n) {
count++;
}
}
return count;
}
public class Book {
private String title;
private String author;
public Book(String title, String author){
this.title=title;
this.author=author;
}
}
public class ChildrensBook extends Book {
private int recommendedAge;
public ChildrensBook(String title,String author,int recommendedAge){
super(title,author);
this.recommendedAge=recommendedAge;
}
public int getRecommendedAge() {
return recommendedAge;
}
}

Basically I see three possible approaches:
Pull the knowledge about recommended ages up to the Book class so that every book has a recommended age although you then need to decide which recommended age a non children's book has.
Decide on iterating over the books if a Book is a children's book or not - this can be achieved using Java's instanceof operator although this is not particularly object oriented.
Add a method similar to isValidForAge for books deciding if a book is valid for a certain given ages or not which every children's book answers based on the recommended age and every non children's book needs to answer based on other criteria.

This is improper class design.
You mixed a concern of books classification (children, adult, science-fiction, humor) which is more like a category or "tag" and the particular behavior (which is suitability for the audience).
You may decide to make a Book class abstract and inherit a concrete AdultBook class from it.
As already mentioned in other answer, you could have an adult book which is still suitable for children. Let's say a biography or an encyclopedia.
Which means, you may want to exercise getRecommendedAge() on every book in your collection.

You can use instanceof operator to check the type of the object. Try the below code:
public class Library {
private ArrayList<Book> collection;
public Library(ArrayList<Book> c) {
collection = c;
}
public int forChildren(int n) {
int count = 0;
for (int i = 0; i < collection.size(); i++) {
if (collection.get(i) instanceof ChildrensBook) {
if (((ChildrensBook) collection.get(i)).getRecommendedAge() <= n) {
count++;
}
}
}
return count;
}
public class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
public class ChildrensBook extends Book {
private int recommendedAge;
public ChildrensBook(String title, String author, int recommendedAge) {
super(title, author);
this.recommendedAge = recommendedAge;
}
public int getRecommendedAge() {
return recommendedAge;
}
}

Related

Apply methods without knowing object type in java

I have two classes in java: Movie and Book (it's a simplified example):
Book can have author:
public class Book {
public String author;
public Book(String a) {
this.author = a;
}
public String getAuthor(){
return author;
}
}
And Movie can have title:
public class Movie {
public String title;
public Movie(String t) {
this.title = t;
}
public String getAuthor(){
return title;
}
}
I'm trying to put all objects in a list like this:
ArrayList myList = new ArrayList();
Book book = new Book("William");
Movie movie = new Movie("Titanic");
myList.add(book);
myList.add(movie);
And afterwards I want to count how many books written by John do I have (or any other specific titles). However I can't apply getAuthor() or getTitle() method since java doesn't know what type of object it is
int counter = 0;
for (int i =0;i<myList.size();i++){
if (myList.get(i).getAuthor().equals("John") ){
counter +=1;
}
I would be able to use if clause, check every time for an object type, and apply different methods for different objects, but this is not viable, since in real-life case I have 20+ classes and it would make code very long and maintainable.
Can someone suggest a solution for this? Thanks in advance!
create an interface
public interface HasAuthor {
String getAuthor();
}
implement this interface in both your classes and use this:
List<HasAuthor> list = new ArrayList<>();
list.add(new Book());
list.add(new Movie());
long count = list.stream().filter(smth -> "John".equals(smth.getAuthor())).count();
You cannot be using ArrayList myList = new ArrayList(); in 2017. The world has moved on from that archaic and error-prone style of programming. Generics were added to the Java programming language in 2004, and since then, any attempt to use a generic class without a generic type argument issues a warning. Which brings me to the next issue:
You cannot be ignoring warnings in 2017. Actually, there was never a good time to be ignoring warnings. Your IDE ought to be issuing warnings when you try to do ArrayList myList = new ArrayList(); heed them.
So, bottom line is, you should not be putting books and movies in the same collection. If you have a book class that has an author, and a movie class that has a director, (I will ignore your example of movies having a title and returning that as "author", because it is nonsensical,) then you can have either an interface or an abstract base class called, say, Item, with a String getAuthor() method, which is implemented (overridden) in both Book and Movie.
Then, your myList will be an ArrayList<Item>, and since Item has a getAuthor() method, you will be able to do myList.get( 0 ).getAuthor() and it will work without having to know whether it is a book or a movie.
First, myList.get(i).getAuthor() == "John" won't work since strings need to be compared via equals() (look up tutorials on why).
Second, you need to know the type of your objects and cast accordingly in order to call a method (you could do without the cast using reflection but please don't try that at home). Thus when iterating over your list you need to check:
for (Object o : myList ) {
if (o instanceof Book && ((Book)o).getAuthor().equals("John") ){
counter +=1;
}
}
However, if you want one list to contain all books and movies you'd better provide a common interface or superclass:
//Make it abstract to not allow instances of this class directly
abstract class PieceOfArt {
private String creator;
public String getCreator() {
return creator;
}
}
class Book extends PieceOfArt {
//Access the creator as the author
//note that I do this just for demonstration purposes, just using getCreator() would be perfectly fine
public String getAuthor() {
return getCreator();
}
}
class Movie extends PieceOfArt {
//Access the creator as the director
//note that I do this just for demonstration purposes, just using getCreator() would be perfectly fine
public String getDirector() {
return getCreator();
}
}
List<PieceOfArt> myList = ...;
for( PieceOfArt p : myList ) {
if( p.getCreator().equals("John") {
...
}
}
Putting objects of different, unrelated types, such as Book and Movie, which don't have a common superclass (besides java.lang.Object) is bad practice.
You could define a common abstract superclass for these types, and then create a List of that type. For example:
public abstract class Product {
private String title;
private String author;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
class Book extends Product {
}
class Movie extends Product {
}
Create a List<Product> and work with that:
List<Product> products = new ArrayList<>();
Book book = new Book();
book.setTitle("Cooking");
book.setAuthor("Bob the Cook");
products.add(book);
Movie movie = new Movie();
movie.setTitle("Romance at sea");
movie.setAuthor("John");
products.add(movie);
int count = 0;
for (Product product : products) {
if (product.getAuthor().equals("John")) {
count++;
}
}
NOTE: Do not make the getAuthor method actually return the title in case of a Movie, that would make your program really confusing.

Find repeated elements in array list

The following code is suppose to take a string for author and title to check if that exact book is in the array list if it is then it would return the number of copies in the array. So far it's only checking if the book is in the array list, but I was wondering if there is any IPA that I can use to find repeated elements in an array list
public class Book{
private title;
private author;
}
public class Library {
private ArrayList<Book>libraryBooks;
public int checkNumCopies(String title,String author){
int numBookCopies = 0;
for(Book b:libraryBooks){
if((b.equals(title))&& (b.equals(author))){
return "Book is in the library";
}
else
return "Book is not in the library";
}
}
With proper implementation of equals and hashcode implementation in Book
class Book {
private String author;
private String title;
//Getters and Setters
//hashCode and equals impl
//toString impl
}
this can be achieved using streams
List<Book> books = Arrays.asList(new Book("book-1", "title-1"), new Book("book-2", "title-2"), new Book("book-3", "title-3"),
new Book("book-1", "title-1"), new Book("book-2", "title-2"));
Map<Book, Long> bookCount = books.stream().collect(Collectors.groupingBy(b -> b, Collectors.counting()));
System.out.println(bookCount);
output
{Book [author=book-3, title=title-3]=1, Book [author=book-2, title=title-2]=2, Book [author=book-1, title=title-1]=2}
Your Book fields are missing types (I assume you wanted Strings), and you have no accessor (or getters) for those fields. Also, I would probably override equals and use that. Something like,
public class Book {
public Book(String title, String author) {
this.title = title;
this.author = author;
}
private String title;
private String author;
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Book) {
Book b = (Book) obj;
return b.title.equals(title) && b.author.equals(author);
}
return false;
}
}
And then to use it, I would do something like
private List<Book> libraryBooks = new ArrayList<>();
public int checkNumCopies(String title, String author) {
Book toFind = new Book(title, author);
int numBookCopies = 0;
for (Book b : libraryBooks) {
if (b.equals(toFind)) {
numBookCopies++;
}
}
return numBookCopies;
}
or in Java 8+, like
Book toFind = new Book(title, author);
return (int) libraryBooks.stream().filter(book -> book.equals(toFind)).count();
Or you could create an equals method on Book and then use a HashSet. Something like
public class Book{
private title;
private author;
//implement hashcode
public boolean equals(Object o) {
if (!(o instanceof Book)){ return false}
Book that = Book.class.cast(o);
// you may want to do a null check here
return this.title.equals(that.title) && this.author.equals(that.author);
}
}
public class Library {
private ArrayList<Book>libraryBooks;
public int checkNumCopies(String title,String author){
return libraryBooks.size() - new HashSet(libraryBooks).size()
}
}
Im not sure entirely on what you need (sorry cant quite understand the question) but hopefully this at least gives you some ideas.
note: I havent compiled this so please excuse any compilation errors.
Here is the solution using java streams API
public class Library {
private ArrayList<Book> libraryBooks;
public long checkNumCopies(String title, String author) {
return libraryBooks
.stream()
.filter(book -> book.getAuthor().equals(author) && book.getTitle().equals(title))
.count();
}
}
I presupposed that there are getter methods in Book class.
If you want to find the number of copies of a book, that would be:
public int checkNumCopies(String title, String author) {
int numBookCopies = 0;
for (Book b : libraryBooks) {
if ((b.title.equals(title)) && (b.author.equals(author))) {
numBookCopies++;
}
}
return numBookCopies;
}
Otherwise, instead of sending title and author individually you can send a book object. That'd be better way :
public int checkNumCopies(Book book) {
int numBookCopies = 0;
for (Book b : libraryBooks) {
if ((b.title.equals(book.title)) && (b.author.equals(book.author))) {
numBookCopies++;
}
}
return numBookCopies;
}
Since you have made the fields private, you need to write getter methods for getting the values from the fields.

How to save a class instance in an ArrayList that is in the class? It has to be done in the constructor (Java)

Given a class like the this:
public class Book {
private String title;
private int pages;
private static ArrayList<Book> ArrayBooks = new ArrayList<Book>();
public Book(String titl, int page){
title=titl;
pages=page;
}
}
How can I keep a reference to every Book in the List, in a way that doesn't require any special calls - ie done automatically by the constructor.
Also, the List should have a max size of 20 books; if the user attempts to create the 21st book, he shouldn't be able to.
Can anyone figure out a way to implement it?
To access the current object, you could use the this reference. So your constructor could look like this:
public Book(String title, int pages) {
this.title = title; // avoid abbreviations like "titl"
this.pages = pages; // or "page" just to avoid same variable names
ArrayBooks.add(this); // add new Book instance into the list
}
To avoid more than 20 entries, you have to check the current amount of entries in the list:
public Book(String title, int pages) {
this.title = title;
this.pages = pages;
(if ArrayBooks.size < 20) {
ArrayBooks.add(this); // add new Book instance into the list
}
}
And I recommend to read the Java naming conventions. So ArrayBooks should be arrayBooks.
Edit
To prohibit more than 20 Book instances, you'll need a factory method that takes care about creating the instances for you:
public static Book create(final String title, final int pages) {
if (ArrayBooks.size() < 20) {
final Book instance = new Book(title, pages);
ArrayBooks.add(instance);
return instance;
}
return null;
}
private Book(String title, int pages) { // "hide" the constructor, so the user can't use this instead of the factory
this.title = title;
this.pages = pages;
}
You could also throw an exception in your constructor, but I personally don't like that. It is up to you, which way you take.
Add this to the List, and throw an exception if the user attempts to create the 21st:
public class Book {
private static final List<Book> books = Collections.synchronizedList(new ArrayList<Book>());
private String title;
private int pages;
public Book(String title, int pages) {
if (books.size() == 20)
throw new IllegalStateException("Maximum number of books reached");
this.title = title;
this.pages = pages;
books.add(this);
}
}
I've added a call to Collections.synchronizedList() to add simple thread safety to prevent the maximum from being exceeded due to race conditions.
Also note the change of the list type to List<Book> (from ArrayList<Book>) - see Liskov substitution principle
I recommend to use list because it's good in some parameter then array.
public Book (String titl, int page) {
title = titl;
pages = page;
Arraylist.add(this); // add new Book in the list
}
For not giving more then 20
public Book (String titl, int page) {
title = titl;
pages = page;
if (Arraylist.size() < 20) {
Arraylist.add(this); // add new Book into the list
}
}
Try to apply first your own logic.

Update subclass given superclass

Problem Description
I have an abstract Paper class that contains common properties of all papers and one or more child classes of paper that add additional information for that type of paper. I then have a HashMap<String, Paper> to store multiple papers.
My application allows the user to update a paper by providing a the pid and then supplying the attributes and values to update. The issue I am having is how do I update the properties on the sub classes when all I have is the super class.
What is the best way/practice to handle this situation?
Class Structure
public abstract class Paper {
String pid;
String title;
String author;
}
public class Publication extends Paper {
int pages;
}
public class PHDThesis extends Paper {
String supervisor;
}
My Current Attempt
This is what I currently have** and it works by using instance of; but I feel there should be a better way to do this.
import java.util.*;
public class App {
public static abstract class Paper {
private String title;
private String author;
public Paper(String title, String author) {
this.title = title;
this.author = author;
}
public void update(String title, String author) {
this.title = title;
this.author = author;
}
}
public static class Publication extends Paper {
private int pages;
public Publication(int pages, String title, String author) {
super(title, author);
this.pages = pages;
}
public void update(String title, String author, int pages) {
super.update(title, author);
this.pages = pages;
}
}
public static class PHDThesis extends Paper {
private String supervisor;
public PHDThesis(String supervisor, String title, String author) {
super(title, author);
this.supervisor = supervisor;
}
public void update(String title, String author, String supervisor) {
super.update(title, author);
this.supervisor = supervisor;
}
}
public static void main(String[] args) {
HashMap<String, Paper> papers = new HashMap<String, Paper>();
papers.put("P001", new PHDThesis("My Super", "My PHD Title", "My Author"));
papers.put("P002", new Publication(22, "My Pub Title", "My Author"));
Paper p = papers.get("P001");
if (p instanceof PHDThesis) {
((PHDThesis)p).update("New Title", "New author", "New Super");
} else if (p instanceof Publication) {
((Publication)p).update("New Title", "New author", 33);
}
}
}
** reduced test code, actual code is much more complex and better laid out.
You can create an object called UpdateBundle with getters for each attribute.
Then the Paper class will have a method update(UpdateBundle) which each child will implement differently.
All you have to do is call that method for each child and they will know how to handle it.
On a separate note, i don't see why the paper class is abstract. You seem to have no abstract methods in it.
public abstract class Paper {
String pid;
String title;
String author;
public void update(PaperUpdateBundle bundle)
{
pid = bundle.getPID();
title = budnle.getTitle();
author = bundle.getAuthor();
}
}
public class Publication extends Paper {
int pages;
public void update(PaperUpdateBundle bundle)
{
super.update(bundle);
pages = bundle.getPages();
}
}
public class PHDThesis {
String supervisor;
public void update(PaperUpdateBundle bundle)
{
super.update(bundle);
supervisor = bundle.getSupervisor();
}
}
public interface PaperUpdateBundle
{
String getPID();
String getTitle();
String getAuthor();
int getPages();
String getSupervisor();
}
Create a method
public void update( Map<String, Object> parameters );
to all Papers and pull the relevant properties from it in the Paper implementations.
In Publication it might look like:
public void update( Map<String, Object> parameters ) {
super.update( parameters );
this.pages = parameters.get( "pages" );
}
The problem with the accepted answer is that it requires you to update all of the properties manually. If the list of properties changes, you have to change the update() method or things will get out of sync. In my experience this happens frequently. And then you've got to spend a lot of time trying to track down the bug.
A different way (I won't call it a "better" way) is to use reflection or some third party library to copy the fields. There are some tradeoffs, though. The advantage is that your code requires a lot less work and will (probably) have fewer bugs. The downside is that your code will be slower, less flexible, and lack compile-time checks.
I have sometimes used Jackson's ObjectMapper.convertValue() to do this. You can find other ways to do it here: Copy all values from fields in one class to another through reflection.

Creating an ArrayList and adding items

Im learning Java and having a problem with ArrayList.
Firstly I have a class called Item, with which I create various item objects.
Then I have a class Catalogue which is an array list and should hold a list of the item objects I create.
At the moment I can manually add the items to the catalogue by invoking an addItem method on the Catalogue object and manually entering the name of the item object I want to add (item1 item2 item3 etc)
But I wanted to know if there is a way to add the items to the ArrayList automatically each time I create an item object?
I should mention, my list needs to hold an infinite amount of items, so I have not specified a size in my code.
Any help would be greatly appreciated :)
Thanks
import java.util.ArrayList;
public class Catalogue
{
private ArrayList<Item> catalogue;
public Catalogue ()
{
catalogue = new ArrayList<Item>();
}
public void addAnItem(Item item)
{
catalogue.add(item);
}
}
Use the Catalogue as an Item factory:
public class Catalogue
{
...
public Item createItem()
{
Item item = new Item();
catalogue.add(item);
return item;
}
...
}
Another approach: Make Catalogue singleton and let the items add themselves.
One way you could do this, is if you passed the Catalogue into the constructor of the Item class, and once the item is set up, add the item to the catalogue at that point.
It may look something like this
public Item(Catalogue catalogue) {
// set up item here
// finally add item to the catalogue
catalogue.addAnItem(this);
}
I have put some comments at Matten and Codemwnci's answers, and here is an explanation of them.
Codemwnci suggests that you should not be able to construct an Item without setting its catalogue.
public class Item {
public Item(Catalog catalog) {
// set up item here
// finally add item to the catalog
catalog.addAnItem(this);
}
}
This explicit constructor removes the implicit default (no-arg) constructor, and you cannot construct an Item without it having a valid, non-null catalog.
If you have various types of items, with (slightly) different behaviour, you might be better served with Matten's answer (although slightly changed here).
As an example I'm using a Book (which is your Item). My Book has a title, author, textAtTheBack, and weight.
interface Book {
String getTitle();
String getAuthor();
String getTextAtTheBack();
Long getWeight(); // in grams, can be very heavy!
}
public class Catalog {
private ArrayList<Book> catalogue;
public Book createPaperback(final String title, final String author,
final String tatb, final Long weight) {
Book b = new Book() {
String getTitle() { return title; }
String getAuthor() {return author; }
String getTextAtTheBack() {return tatb;}
Long getWeight() {return weight;}
}
catalogue.add(b);
return b;
}
public Book createEBook(final String title, final String author,
final String tatb) {
Book b = new Book() {
String getTitle() { return title; }
String getAuthor() {return author; }
String getTextAtTheBack() {return tatb;}
Long getWeight() {return 0;} // Yep - no weight!
}
catalogue.add(b);
return b;
}
}
Alternatively, you could have different catalogues:
public abstract class Catalogue {
private final List<Book> books = new ArrayList<Book>;
public abstract Book (final String title, final String author,
final String tatb, final Long weight);
/** Find the book with the given title (not null) in the current catalogue.
* #return the book, or null if not found.
*/
public void findBook(String title) {
for (Book b : books) {
if (b.getTitle().equalsIgnoreCase(title)) {
return b;
}
}
return null;
}
protected void addBookToCatalogue(Book b) {
books.add(b);
}
}
public class EbookCatalogue extends Catalogue {
public Book (final String title, final String author,
final String tatb, final Long weight) {
Book b = new Book() {
String getTitle() { return title; }
String getAuthor() {return author; }
String getTextAtTheBack() {return tatb;}
Long getWeight() {return 0;} // ignore weight
}
addBookToCatalogue(b);
return b;
}
}
In the rest of the program you can have multiple catalogues, each with a slightly different type of Book, but the program need not know that.
I think in this case the simple Constructor of codemwnci is best, but there alternative solutions if your situation warrants a more flexible solution.

Categories

Resources