CS F213 Objected Oriented Programming Labsheet 8

Spring 2024

Java Enumerations

The Enum in Java is a data type which contains a fixed set of constants.

It can be used for days of the week (SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, and SATURDAY) , directions (NORTH, SOUTH, EAST, and WEST), season (SPRING, SUMMER, WINTER, and AUTUMN or FALL), colors (RED, YELLOW, BLUE, GREEN, WHITE, and BLACK) etc. According to the Java naming conventions, we should have all constants in capital letters. So, we have enum constants in capital letters.

Java Enums can be thought of as classes which have a fixed set of constants (a variable that does not change). The Java enum constants are static and final implicitly.

Unlike C/C++, enum in Java is more powerful. Here, we can define an enum either inside the class or outside the class. Java Enum internally inherits the Enum class, so it cannot inherit any other class, but it can implement many interfaces. We can have fields, constructors, methods, and main methods in Java enum.

Once you have defined an enumeration, you can create a variable of that type. However, even though enumerations define a class type, you do not instantiate an enum using new. Instead, you declare and use an enumeration variable in much the same way as you do one of the primitive types.

Example

//An enumeration of different seasons
enum Season { 
WINTER, SPRING, SUMMER, FALL
}
class Main{ 
    public static void main(String[] args) {
        Season s; //This declares s as a variable of enumeration type Season

        //Because s is of type Season, the only values that it can be assigned 
        //(or can contain) are those defined by the enumeration. 
        //For example, this assigns s the value SUMMER:
        s=Season.SUMMER;

        //Two enumeration constants can be compared for equality by using the ==
        // relational operator
        if(s==Season.WINTER)

        //An enumeration value can also be used to control a `switch statement`. 
        //here all of the case statements must use constants from the same enum as
        // that used by the switch expression.

        switch(s) {
            case SPRING:
            // ...
            case SUMMER:
            // ... 
        }
    }
}  

The values( ) and valueOf( ) Methods

All enumerations automatically contain two predefined methods: values() and valueOf(). Their general forms are shown here:

public static enum-type [] values() public static enum-type valueOf(String str)

The values() method returns an array that contains a list of the enumeration constants. The valueOf() method returns the enumeration constant whose value corresponds to the string passed in str. In both cases, enum-type is the type of the enumeration. For example, in the case of the Season enumeration shown earlier, the return type of Season.valueOf("SUMMER") is SUMMER.

Example

class Main{  
public enum Season { WINTER, SPRING, SUMMER, FALL }  
public static void main(String[] args) {  
//traversing the enum  
for (Season s : Season.values())  
System.out.println(s); 

System.out.println("Value of WINTER is: "+Season.valueOf("WINTER"));  
System.out.println("Index of WINTER is: "+Season.valueOf("WINTER").ordinal());  
System.out.println("Index of SUMMER is: "+Season.valueOf("SUMMER").ordinal());
}} 

Output

WINTER
SPRING
SUMMER
FALL
Value of WINTER is: WINTER
Index of WINTER is: 0
Index of SUMMER is: 2

The Java compiler internally adds the ordinal() method when it creates an enum. The ordinal() method returns the index of the enum value.

The semicolon (;) at the end of the enum constants are optional.

Initializing specific values to the enum constants

The enum constants have an initial value which starts from 0, 1, 2, 3, and so on. But, we can initialize the specific value to the enum constants by defining fields and constructors. As specified earlier, Enum can have fields, constructors, and methods.

Example

class EnumExample4{  
    enum Season{   
    WINTER(5), SPRING(10), SUMMER(15), FALL(20);   
    
    private int value;  
    private Season(int value){  
        this.value=value;  
        }  
    }  
    public static void main(String args[]){  
        for (Season s : Season.values())  
        System.out.println(s+" "+s.value);  
    }
}

Output

WINTER 5
SPRING 10
SUMMER 15
FALL 20

Constructor of enum type is private. If you don't declare private compiler internally creates private constructor.

You can compare the ordinal value of two constants of the same enumeration by using the compareTo() method. It has this general form: final int compareTo(enum-type e) Here, enum-type is the type of the enumeration, and e is the constant being compared to the invoking constant. Remember, both the invoking constant and e must be of the same enumeration. If the invoking constant has an ordinal value less than e’s, then compareTo() returns a negative value. If the two ordinal values are the same, then zero is returned. If the invoking constant has an ordinal value greater than e’s, then a positive value is returned.

Although equals( ) can compare an enumeration constant to any other object, those two objects will be equal only if they both refer to the same constant, within the same enumeration. Simply having ordinal values in common will not cause equals( ) to return true if the two constants are from different enumerations.

Type Wrappers

The wrapper classes in Java are used to convert primitive types (int, char, float, etc) into corresponding objects.

Each of the 8 primitive types has corresponding wrapper classes.

Primitive Type
Wrapper Class

byte

Byte

boolean

Boolean

char

Character

double

Double

float

Float

int

Integer

long

Long

short

Short

Convert Primitive Type to Wrapper Objects

We can also use the valueOf() method to convert primitive types into corresponding objects.

Example

class Main {
  public static void main(String[] args) {
    int a = 5;
    double b = 5.65;
    Integer aObj = Integer.valueOf(a);
    Double bObj = Double.valueOf(b);
    //However, the Java compiler can directly convert the primitive types
    // into corresponding objects. This process is known as auto-boxing which
    //we will study in the next topic
    Integer aObj2 = a;
    Double bObj2 = b;
}

Advantages of Wrapper Classe

  • In Java, sometimes we might need to use objects instead of primitive data types. For example, while working with collections. In such cases, wrapper classes help us to use primitive data types as objects.

// error
ArrayList<int> list = new ArrayList<>();
// runs perfectly
ArrayList<Integer> list = new ArrayList<>();
  • We can store the null value in wrapper objects. For example,

// generates an error
int a = null;
// runs perfectly
Integer a = null;

Autoboxing and Auto-unboxing

Autoboxing is the process by which a primitive type is automatically encapsulated (boxed) into its equivalent type wrapper whenever an object of that type is needed. There is no need to explicitly construct an object. Auto-unboxing is the process by which the value of a boxed object is automatically extracted (unboxed) from a type wrapper when its value is needed.

class Main {
 public static void main(String[] args) {
 Integer iOb = 100; // autobox an int
 int i = iOb; // auto-unbox
 System.out.println(i + " " + iOb); // displays 100 100
 }
}

Output

100 100

Autoboxing/unboxing might occur when an argument is passed to a method, or when a value is returned by a method

class Main {
 // Take an Integer parameter and return an int value;
 static int m(Integer v) {
 return v ; // auto-unbox to int
 }
 public static void main(String[] args) {
 // Pass an int to m() and assign the return value to an Integer. 
 //Here, the argument 100 is autoboxed into an Integer. The return value
 // is also autoboxed into an Integer.
 Integer iOb = m(100);
 System.out.println(iOb);
 iOb++;//// The following automatically unboxes iOb, performs the increment,
 // and then reboxes the result back into iOb.
 System.out.println("After ++iOb: " + iOb);
 }
}

Ouput

100
After ++iOb: 101

In the program, notice that m() specifies an Integer parameter and returns an int result. Inside main(), m() is passed the value 100. Because m() is expecting an Integer, this value is automatically boxed. Then, m() returns the int equivalent of its argument. This causes v to be auto-unboxed. Next, this int value is assigned to iOb in main(), which causes the int return value to be autoboxed. ++iOb works like this: iOb is unboxed, the value is incremented, and the result is reboxed.

Advantages of using Autoboxing/unboxing

Autoboxing and Auto-unboxing has a great advantage while working with Java collections.

Example

import java.util.ArrayList;

class Main {
   public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<>();
      //autoboxing
      list.add(5);
      list.add(6);
      System.out.println("ArrayList: " + list);
      // unboxing
      int a = list.get(0);
      System.out.println("Value at index 0: " + a);
   }
}

Output

ArrayList: [5, 6]
Value at index 0: 5

Annotations

Java Annotation is a tag that represents the metadata i.e. attached with class, interface, methods or fields to indicate some additional information which can be used by java compiler and JVM.

Annotations in Java are used to provide additional information, so it is an alternative option for XML and Java marker interfaces.

Custom Annotations

General syntax is:

[Access Specifier] @interface<AnnotationName> {         
  DataType <Method Name>() [default value];
}

Here is the declaration for an annotation called MyAnno:

// A simple annotation type.
@interface MyAnno {
 String str();
 int val();
}

All annotations consist solely of method declarations. However, you don’t provide bodies for these methods. Instead, Java implements these methods. Moreover, the methods act much like fields, as you will see.

An annotation cannot include an extends clause. However, all annotation types automatically extend the Annotation interface. Thus, Annotation is a super-interface of all annotations. It is declared within the java.lang.annotation package.

Any type of declaration can have an annotation associated with it. For example, classes, methods, fields, parameters, and enum constants can be annotated. Even an annotation can be annotated. When you apply an annotation, you give values to its members. For example,

// Annotate a method.
@MyAnno(str = "Annotation Example", val = 100)
public static void myMeth() { // ...

Using Default Values

You can give annotation members default values that will be used if no value is specified when the annotation is applied. A default value is specified by adding a default clause to a member’s declaration. Here, value must be of a type compatible with type.

// An annotation type declaration that includes defaults.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
 String str() default "Testing";
 int val() default 9000;
}
 //Now following are the four ways that @MyAnno can be used:

@MyAnno() // both str and val default
@MyAnno(str = "some string") // val defaults
@MyAnno(val = someInteger) // str defaults
@MyAnno(str = "Testing", val = 100) // no defaults

Built-In Annotations

There are several built-in annotations in Java. Some annotations are applied to Java code and some to other annotations.

Built-In Java Annotations used in Java code

  • @Override : Thid annotation assures that the subclass method is overriding the parent class method. If it is not so, compile time error occurs. Sometimes, we do the silly mistake such as spelling mistakes etc. So, it is better to mark @Override annotation that provides assurity that method is overridden.

class Animal{  
void eatSomething(){System.out.println("eating something");}  
}  
class Dog extends Animal{  
@Override  
void eatsomething(){System.out.println("eating foods");}//should be eatSomething
// so this gives compile time error
}  
  • @SuppressWarnings : is used to suppress warnings issued by the compiler.

import java.util.*;  
class TestAnnotation2{  
@SuppressWarnings("unchecked")  
public static void main(String args[]){  
ArrayList list=new ArrayList();  
list.add("sonoo");
}
//Now no warning at compile time. If you remove the @SuppressWarnings("unchecked")
// annotation, it will show warning at compile time because we are using non-generic
// collection.
  • @Deprecated : It marks that this method is deprecated so compiler prints warning. It informs user that it may be removed in the future versions. So, it is better not to use such methods.

Built-In Java Annotations used in other annotations

  • @Target : is used to specify at which type, the annotation is used.

//Example to specify annotation for a class, methods or fields
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})  
@interface MyAnnotation{  
int value1();  
String value2();  
}
  • @Retention : is used to specify to what level annotation will be available.

RetentionPolicy
Availability

RetentionPolicy.SOURCE

Refers to the source code, discarded during compilation. It will not be available in the compiled class.

RetentionPolicy.CLASS

Refers to the .class file, available to the Java compiler but not to the JVM. It is included in the class file.

RetentionPolicy.RUNTIME

Refers to the runtime, available to the Java compiler and JVM.

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
@interface MyAnnotation{  
int value1();  
String value2();  
} 
  • @Inherited : By default, annotations are not inherited to subclasses. The @Inherited annotation marks the annotation to be inherited to subclasses.

  • @Documented : Marks the annotation for inclusion in the documentation.

Use of Annotations

  • Compiler instructions - Annotations can be used for giving instructions to the compiler, detect errors or suppress warnings. The built-in annotations @Deprecated, @Override, @SuppressWarnings are used for these purposes.

  • Compile-time instructions - Compile-time instructions provided by these annotations help the software build tools to generate code, XML files and many more.

  • Runtime instructions - Some annotations can be defined to give instructions to the program at runtime. These annotations are accessed using Java Reflection.

Exercises

Exercise 1

You are tasked with implementing a Weekly scheduler that allows multiple tasks to be executed concurrently on different days of the week. The days of the week are represented by an enum named Week, and each day can have multiple tasks associated with it.

Implement a Java program using threads and the Week enum to schedule and execute tasks concurrently. Ensure that the tasks are performed on the specified days of the week. Additionally, allow users to input the tasks they want to schedule for each day.

Week.java : enum to contain all days of a week. Task.java : implements Runnable and represents a task to be executed on a specific day. WeeklyScheduler : responsible for scheduling tasks for different days and executing them concurrently.Threads are used to run tasks concurrently on the specified days of the week. Main.java: Users are prompted to input tasks for each day, and the tasks are scheduled accordingly.

Sample Input

Enter task for MONDAY: Study for Mids
Enter task for TUESDAY: Complete Assignment
Enter task for WEDNESDAY: Do Project report
Enter task for THURSDAY: Attend lab
Enter task for FRIDAY: Meeting with Professor
Enter task for SATURDAY: Clean room
Enter task for SUNDAY: Submit report

Sample Output

Task 'Complete Assignment' is being executed on TUESDAY
Task 'Meeting with Professor' is being executed on FRIDAY
Task 'Study for Mids' is being executed on MONDAY
Task 'Do Project report' is being executed on WEDNESDAY
Task 'Submit report' is being executed on SUNDAY
Task 'Clean room' is being executed on SATURDAY
Task 'Attend lab' is being executed on THURSDAY

For your ease, template of Task.java and WeeklyScheduler.java is given below.

public class Task implements Runnable {
    private String taskName;
    private Week day;

    public Task(String taskName, Week day) {
        //.... complete
    }
    @Override
    public void run() {
        System.out.println("Task '" + taskName + "' is being executed on " + day);
    }
}
public class WeeklyScheduler {
    private Thread[7] scheduledTasks;
    public void scheduleTask(String taskName, Week day, int dayIndex) {
            //..... complete
    }
    public void executeScheduledTasks() {
        for (Thread thread : scheduledTasks) {
            //..... start the thread
        }

        // Wait for all threads to complete before proceeding
        for (Thread thread : scheduledTasks) {
            try {
                if (thread != null) {
                    //.... join
                }
            } catch (InterruptedException e) {
                //...... print the exception
            }
        }
    }
}

You can use these and make the rest two classes to get the desired output.

Exercise 2

You are tasked with implementing a Stock price checker that concurrently monitors the prices of multiple stocks. Implement a Java program using multithreading concepts, autoboxing, and unboxing to simulate stock price updates.

Create a Stock class that stores the current price of a stock and a StockMonitor class to monitor and update the stock prices. Each stock's price will be updated by a separate thread.

Stock.java : represents a stock with a symbol and its current price. The price is accessed and updated through synchronized methods to ensure thread safety. StockMonitor.java : extends Thread and simulates the continuous monitoring of a stock's price. It updates the stock price randomly to simulate real-world fluctuations. Main.java : takes user input for 2 stoscks, creates instances of Stock and StockMonitor, starts the monitoring threads, waits for them to complete using the join() method and prints the final price of both stocks.

Sample Input

Enter the symbol for Stock 1: GGLE
Enter the initial price for Stock 1: Rs.3000
Enter the symbol for Stock 2: APPL
Enter the initial price for Stock 2: Rs.2800

Sample Output

Stock APPL price updated: Rs2799.4998421746427
Stock GGLE price updated: Rs2864.267064672649
Stock APPL price updated: Rs2771.484044354885
Stock GGLE price updated: Rs2884.351654021626
Stock APPL price updated: Rs2803.0950069354767
Stock GGLE price updated: Rs2952.615749868918
Stock APPL price updated: Rs2928.4485190927717
Stock GGLE price updated: Rs2997.2741777786678
Stock APPL price updated: Rs2872.779920660959
Stock GGLE price updated: Rs3087.632192562072
Final stock prices:
GGLE: Rs3087.632192562072
APPL: Rs2872.779920660959

For your ease, template of Stock.java and StockMonitor.java is given below.

public class Stock {
    private String symbol;
    private Double price; // Using Double (wrapper class) to allow null and 
    //handle autoboxing/unboxing
    public Stock(String symbol, double initialPrice) {
        this.symbol = symbol;
        //... use Autoboxing: primitive double to Double
    }
    public synchronized double getPrice() {
     //.. return using Unboxing: Double to primitive double
    }
    public synchronized void updatePrice(double newPrice) {
        System.out.println("Stock " + symbol + " price updated: Rs." + newPrice);
         //.. update Autoboxing: primitive double to Double
    }
}
import java.util.Random;
public class StockMonitor extends Thread {
    private Stock stock;
    public StockMonitor(Stock stock) {
        //.... complete
    }
    @Override
    public void run() {
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            //this loop updates prices randomly 5 times
            double newPrice = stock.getPrice() * (1 + (random.nextDouble() - 0.5) * 0.1);
            //.. update new stock price
            try {
            //... Simulate a pause of 1000 between updates
            } catch (InterruptedException e) {
                //.. print the exception
            }
        }
    }
}

You can use these and make the main class to take input of two stocks and get the desired output.

Last updated