1

Creating a program that manages advertisements. Every ad will have 2 common attributes 1: ID, 2: Description.
Depending if the ad is for a webpage or for a newspaper for example it will have different costs per page, words or duration it is shown.

I have 3 files

  1. Main.java
  2. Advertisement.java
  3. PrintedAd.java
  4. WebpageAd.java

Advertisement.java

public class Advertisement{

    private int ID;
    private String description;

    // Constructor
    public Advertisement(int ID, String description){
        this.ID = ID;
        this.description = description;
    }
    public int getID() {
        return ID;
    }

PrintedAd.java

public class PrintedAd extends Advertisement{

        private int First;   //first page cost
        private int Middle;  //middle page cost
        private int Last;    //last page cost

    public PrintedAd(int First, int Middle, int Last){
        this.First = First;
        this.Middle = Middle;
        this.Last = Last;
    }
    public int getFirst() {
        return First;
    }

Main.java

public class Main{
    public static void main(String[] args) {
        
        Advertisement ad = new Advertisement(34567, "test");
        System.out.println(ad.getID());

        PrintedAdd ad2 = new PrintedAdd(10,20,30);
        System.out.println(ad2);
    }
}

(had to change var names to make it more readable so if any "dumb" errors appear please understand) Creating 2 different variables from these 2 classes works fine.

Even though the variable ad2 is an ad and needs to have an ID number and a description I am unable to do both the Advertisement and PrintedAd attributes.

What I am asking is if there a way for me to not have to write the variables and the constructor for every child class. If I were to add to every type of ad this it would solve the issue I believe but still asking if there is a way:

    private int ID;
    private String description;

    // Constructor
    public Advertisement(int ID, String description){
        this.ID = ID;
        this.description = description;
    }
Stephen P
  • 14,422
  • 2
  • 43
  • 67
Spiros Skouras
  • 131
  • 1
  • 5

2 Answers2

3

In your code:

PrintedAdd ad2 = new PrintedAdd(10,20,30);

This new PrintedAdd object (also an Advertisement) has null in its two inherited member fields, ID and description. You neglected to invoke a constructor (or setters) to specify those values, so they default to null.

So yes, if you want a subclass to have 5 fields, 2 inherited, and 3 defined on the subclass itself, at run time you need to pass 5 values (primitives or object references). You can pass to a constructor, and/or you can pass via setter methods. Or you could use a dependency injection framework such as Jakarta Contexts and Dependency Injection, Google Guice, or Spring. Otherwise, you get default values in member field; for object references that means null.

In your particular case, your objects are read-only, with getters but no setters. So that eliminates the setters option. And in your case, a dependency injection framework is not appropriate, as that is for providing resources and services, not for individual values such as an ID or description. So, that leaves the constructor option. You should write the subclass to have a constructor taking 5 arguments, for the 2 inherited fields and the 3 subclass-specific fields.

Be aware that a subclass’ constructor can call a superclass’ constructor. See existing Question such as Calling superclass from a subclass constructor in Java.

Here is some example code, and some notes.

  • Your superclass should be marked abstract as you intend only to instantiate the subclasses, never the superclass. You may want to read Constructors in Java Abstract Classes by baeldung. And read Question, Can an abstract class have a constructor?.
  • Since you know all subclasses at compile time, and do not expect new ones to be dynamically added at runtime, you can seal the superclass. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. (Not important for your Question, just a note for modern Java programming.)
  • In Java naming conventions, a member field should be name with an initial lowercase letter. So, first, not First. And id, not ID. All uppercase by convention means a constant.
  • For simplicity, I replaced your multiple member fields on PrintAd to a single LocalDate.
  • Note how it makes sense to pass the id and description in each constructor — there is no other way to discern what the value should be for those fields.

The superclass:

package work.basil.example.advertising;

import java.util.Objects;
import java.util.UUID;

public abstract sealed class Advertisement
        permits PrintAd, WebAd, RadioAd
{
    // Member fields. Read-only, via getter methods.

    private final UUID id;
    private final String description;

    // Constructors

    public Advertisement ( final UUID id ,
                           final String description )
    {
        this.id = id;
        this.description = description;
    }

    // Getters

    public UUID getId ( )
    {
        return id;
    }

    public String getDescription ( )
    {
        return description;
    }

    // `Object` overrides

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass ( ) != o.getClass ( ) ) return false;
        Advertisement that = ( Advertisement ) o;
        return Objects.equals ( id , that.id );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash ( id );
    }

    @Override
    public String toString ( )
    {
        return "Advertisement[" +
                "id=" + id + ", " +
                "description=" + description + ']';
    }

}

Each of the subclasses, with a third one RadioAd just for fun:

package work.basil.example.advertising;

import java.time.LocalDate;
import java.util.UUID;

public final class PrintAd extends Advertisement
{
    private final LocalDate runDate;

    public PrintAd ( final UUID id , final String description , final LocalDate runDate )
    {
        super ( id , description );
        this.runDate = runDate;
    }

    public LocalDate getRunDate ( )
    {
        return runDate;
    }
}
package work.basil.example.advertising;

import java.net.URL;
import java.util.UUID;

public final class WebAd extends Advertisement
{
    private final URL url;

    public WebAd ( final UUID id , final String description , final URL url )
    {
        super ( id , description );
        this.url = url;
    }

    public URL getUrl ( )
    {
        return url;
    }
}
package work.basil.example.advertising;

import java.time.LocalTime;
import java.util.UUID;

public final class RadioAd extends Advertisement
{
    private final LocalTime airTime;

    public RadioAd ( final UUID id , final String description , final LocalTime airTime )
    {
        super ( id , description );
        this.airTime = airTime;
    }

    public LocalTime getAirTime ( )
    {
        return this.airTime;
    }
}

And an app to exercise those business logic classes.

package work.basil.example.advertising;

import java.net.MalformedURLException;
import java.net.URI;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.UUID;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App ( );
        app.demo ( );
    }

    private void demo ( )
    {
        List < Advertisement > ads = null;
        try
        {
            ads =
                    List.of (
                            new PrintAd (
                                    UUID.fromString ( "a71330e7-131f-4d10-beac-4e8ffc21887f" ) ,
                                    "Example print ad." ,
                                    LocalDate.now ( )
                            ) ,
                            new WebAd (
                                    UUID.fromString ( "8aafa2cf-03dc-452b-a84d-c61d75964955" ) ,
                                    "Example web ad." ,
                                    URI.create ( "https://www.DailyBugle.com/ads" ).toURL ( )
                            ) ,
                            new RadioAd (
                                    UUID.fromString ( "e21b187c-01e2-44da-9a26-5debb9146cf6" ) ,
                                    "Example radio ad." ,
                                    LocalTime.NOON
                            )
                    );
        }
        catch ( MalformedURLException e ) { throw new RuntimeException ( e ); }

        System.out.println ( "ads = " + ads );
    }
}

When run:

ads = [Advertisement[id=a71330e7-131f-4d10-beac-4e8ffc21887f, description=Example print ad.], Advertisement[id=8aafa2cf-03dc-452b-a84d-c61d75964955, description=Example web ad.], Advertisement[id=e21b187c-01e2-44da-9a26-5debb9146cf6, description=Example radio ad.]]


To learn more, see the The Java Tutorials provided by Oracle Corp free of cost:

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
-1

One way to complete this task is to create a static field within the Advertisement class.

Then, within your Advertisement constructor, increment this field by 1, and assign it to your ID field.

Consider the following.

public class Advertisement {
    private static int universalId = 0;

    private int ID;
    private String description;

    protected Advertisement() {
        ID = universalId++;
    }

    public Advertisement(String description) {
        this();
        this.description = description;
    }

    public int getID() {
        return ID;
    }
}

Then, for your sub-classes, within their constructors', make a call to the super constructor, as follows.

public class PrintedAdd extends Advertisement {
    private int First;   //first page cost
    private int Middle;  //middle page cost
    private int Last;    //last page cost

    public PrintedAdd(int First, int Middle, int Last) {
        super();
        this.First = First;
        this.Middle = Middle;
        this.Last = Last;
    }

    public int getFirst() {
        return First;
    }
}

Thus, making a call to ad.getID() should produce 0, and making a call to ad2.getID() should produce 1.

Reilas
  • 3,297
  • 2
  • 4
  • 17
  • 1
    making `ID` static will not help getting an ID for each instance - it will just be a count of how many were created. `ad.getID()` will return the same value as `ad2.getID()` - they are both returning the value of the *static* ID, a class variable, **not** an instance variable – user16320675 May 17 '23 at 20:47
  • 1
    ( and static initialization block is an *overkill* in this scenario IMHO ) – user16320675 May 17 '23 at 20:57
  • @user16320675, I had the correct idea, except I wrote the code incorrectly. I changed it to what I was intending to recommend. – Reilas May 17 '23 at 22:44