Object-Oriented Programming Fundamentals: Classes, Objects, Methods, and Strings

Class Declarations and Object Instantiation

  • Public Class Rules and File Naming:

    • Every class declaration prefixed with the keyword public must be saved in a source code file having the exact same name as the class, followed by the .java file extension (e.g., GradeBook must be saved as GradeBook.java).

    • The keyword public is an access modifier that makes the class accessible to code outside its file.

  • Execution and Driver Classes:

    • Executable Java applications require a main method, which is automatically called by the Java Virtual Machine (JVM) upon starting the program.

    • A class lacking a main method cannot be directly executed as an application; attempting to execute it results in a runtime error: java.lang.NoSuchMethodError: main.

    • To test classes designed without a main method, a separate driver class containing a main method is declared to instantiate objects and invoke their methods.

  • Static vs. Instance Methods:

    • A static method (such as main) can be invoked without first instantiating an object of the class in which it is declared.

    • Instance methods belong to objects; invoking an instance method requires creating an object of the class and executing the call via a reference variable.

  • Class Instance Creation Expressions:

    • Keyword new creates a new object instance of the specified class and requests memory from the operating system.

    • The syntax requires parentheses after the class name (e.g., new GradeBook()), which triggers a call to the class's constructor to perform initial data setup.

    • Declaring a variable of a class type creates a reference variable capable of holding the location of a class object in memory.

  • Method Invocation Syntax:

    • Instance methods are called using the reference variable name, followed by the dot separator (.), the method name, and parentheses enclosing any required arguments (e.g., myGradeBook.displayMessage()).

  • GradeBook Class Example (Basic Declaration):

// Fig. 3.1: GradeBook.java
// Class declaration with one method.
public class GradeBook
{
   // display a welcome message to the GradeBook user
   public void displayMessage()
   {
      System.out.println( "Welcome to the Grade Book!" );
   } // end method displayMessage
} // end class GradeBook
  • GradeBookTest Driver Class Example:

// Fig. 3.2: GradeBookTest.java
// Creating a GradeBook object and calling its displayMessage method.
public class GradeBookTest
{
   // main method begins program execution
   public static void main( String[] args )
   {
      // create a GradeBook object and assign it to myGradeBook
      GradeBook myGradeBook = new GradeBook();

      // call myGradeBook's displayMessage method
      myGradeBook.displayMessage();
   } // end main
} // end class GradeBookTest

Method Structure, Parameters, and Scanner Input

  • Method Header Breakdown:

    • A method header consists of the access modifier (public), return type (void or a specific data type), method identifier, and a parameter list contained within parentheses.

    • Method naming convention: lowerCamelCase (starts with a lowercase letter, and subsequent combined words begin with an uppercase letter).

    • The void return type indicates that the method performs a task but returns no data to its caller upon completion.

    • Empty parentheses () signify that the method requires no additional information to complete its operation.

  • Parameters and Arguments:

    • Parameters are variables declared in a comma-separated parameter list within the method header. Each parameter declaration must explicitly specify both a data type and an identifier.

    • Arguments are the concrete values supplied to a method when it is called.

    • Requirement: The number of arguments in a method call must exactly match the number of parameters in the method header, and the argument data types must be consistent with the specified parameter types.

  • Reading Text Input with Scanner.nextLine():

    • The nextLine() method of the Scanner class reads characters typed at the command line until a newline character is encountered (when the user presses Enter).

    • It returns a String containing the full line of characters typed, excluding the newline character itself (which is discarded).

  • GradeBook Class with Method Parameter Example:

// Fig. 3.4: GradeBook.java
// Class declaration with a method that has a parameter.
public class GradeBook
{
   // display a welcome message to the GradeBook user
   public void displayMessage( String courseName )
   {
      System.out.printf( "Welcome to the grade book for
%s!
", 
         courseName );
   } // end method displayMessage
} // end class GradeBook
  • Passing Arguments via Scanner Example:

// Fig. 3.5: GradeBookTest.java
// Create GradeBook object and pass a String to its displayMessage method.
import java.util.Scanner; // program uses Scanner

public class GradeBookTest
{
   // main method begins program execution
   public static void main( String[] args )
   {
      // create Scanner to obtain input from command window
      Scanner input = new Scanner( System.in );

      // create a GradeBook object and assign it to myGradeBook
      GradeBook myGradeBook = new GradeBook();

      // prompt for and input course name
      System.out.println( "Please enter the course name:" );
      String nameOfCourse = input.nextLine(); // read a line of text
      System.out.println(); // outputs a blank line

      // call myGradeBook's displayMessage method
      // and pass nameOfCourse as an argument
      myGradeBook.displayMessage( nameOfCourse );
   } // end main
} // end class GradeBookTest

Packages and Import Declarations

  • Package Structures:

    • Classes in Java are grouped logically into packages.

    • The java.lang package contains essential system classes, including System and String. This package is implicitly imported into every Java program, allowing immediate access to its classes without an import statement.

    • Classes not located in java.lang (such as java.util.Scanner) must be explicitly imported using an import declaration before they can be referenced.

  • The Default Package:

    • Classes compiled in the exact same directory on disk without an explicit package statement automatically belong to the default package.

    • Classes in the same default package can reference one another directly without needing import statements.

Instance Variables, Encapsulation, and Accessor/Mutator Methods

  • Instance Variables (Fields):

    • Instance variables are declared inside a class declaration but outside the body boundaries of any method declarations.

    • Every object instance created from a class allocates and maintains its own distinct copy of these variables in memory.

  • Encapsulation and Data Hiding:

    • Declaring instance variables with the private access modifier enforces data hiding (information hiding).

    • private variables and methods are accessible only to methods declared within that specific class.

    • Encapsulation prevents external client code from modifying an object's internal state directly or accidentally.

  • Set Methods (Mutators) and Get Methods (Accessors):

    • Classes provide public methods to allow controlled external reading and writing of private fields.

    • A set method (mutator) receives an argument, validates it if necessary, and assigns it to the instance variable. Its return type is usually void.

    • A get method (accessor) reads the value of an instance variable and returns it to the caller. Its return type matches the field's data type.

  • Field Default Values:

    • Unlike local variables declared inside method bodies (which must be manually initialized before use), instance variables are automatically assigned default initial values upon object creation.

    • Reference types (such as String) default to null.

    • Numeric primitive types default to 0 or 0.0.

    • Boolean primitive types default to false.

  • Internal Method Calls:

    • Methods contained within the same class can invoke each other directly using only the method name, without using dot notation or reference variables.

  • GradeBook Class with Instance Variable, Getters, and Setters:

GradeBook UML Class Diagram
// Fig. 3.7: GradeBook.java
// GradeBook class that contains a courseName instance variable
// and methods to set and get its value.
public class GradeBook
{
   private String courseName; // course name for this GradeBook

   // method to set the course name
   public void setCourseName( String name )
   {
      courseName = name; // store the course name
   } // end method setCourseName

   // method to retrieve the course name
   public String getCourseName()
   {
      return courseName;
   } // end method getCourseName

   // display a welcome message to the GradeBook user
   public void displayMessage()
   {
      // calls getCourseName to get the name of 
      // the course this GradeBook represents
      System.out.printf( "Welcome to the grade book for
%s!
", 
         getCourseName() );
   } // end method displayMessage
} // end class GradeBook
  • Manipulating GradeBook Instance Variable Example:

// Fig. 3.8: GradeBookTest.java
// Creating and manipulating a GradeBook object.
import java.util.Scanner;

public class GradeBookTest
{
   public static void main( String[] args )
   {
      Scanner input = new Scanner( System.in );
      GradeBook myGradeBook = new GradeBook();

      // display initial value of courseName (outputs null)
      System.out.printf( "Initial course name is: %s

", 
         myGradeBook.getCourseName() );

      // prompt for and read course name
      System.out.println( "Please enter the course name:" );
      String theName = input.nextLine();
      myGradeBook.setCourseName( theName );
      System.out.println();

      // display welcome message including updated course name
      myGradeBook.displayMessage();
   } // end main
} // end class GradeBookTest

Primitive Types vs. Reference Types

  • Primitive Types:

    • Java defines eight primitive data types: boolean, byte, char, short, int, long, float, and double.

    • Primitive-type variables store exactly one raw value of their declared type at any given moment directly in memory.

  • Reference Types:

    • All non-primitive data types are reference types (e.g., class types, array types).

    • A reference-type variable stores the memory address location pointing to where an object resides.

    • Reference-type instance variables default to null when uninitialized, signifying that the variable points to no location in memory.

    • Calling a method on a reference variable is formally termed sending a message to the target object.

Constructors and Custom Initialization

  • Constructor Syntax and Properties:

    • A constructor is a specialized block invoked automatically during object allocation via new to initialize the object's instance variables.

    • Must have the exact same identifier name as the class in which it is declared.

    • Cannot return any value and must not declare a return type (not even void).

    • Normally declared with public visibility.

  • Default Constructor Mechanics:

    • If a class contains no explicit constructor declarations, the Java compiler automatically generates a parameterless default constructor.

    • The default constructor initializes all instance variables to their default primitive or reference values.

    • Critical Rule: If a programmer explicitly declares any constructor (parameterized or parameterless) in a class, the compiler will not generate a default constructor. In that scenario, calling a parameterless constructor without explicitly defining one causes a compilation error.

  • GradeBook Class with Constructor Example:

GradeBook with Constructor UML
// Fig. 3.10: GradeBook.java
// GradeBook class with a constructor to initialize the course name.
public class GradeBook
{
   private String courseName; // course name for this GradeBook

   // constructor initializes courseName with String argument
   public GradeBook( String name )
   {
      courseName = name; // initializes courseName
   } // end constructor

   public void setCourseName( String name )
   {
      courseName = name;
   } // end method setCourseName

   public String getCourseName()
   {
      return courseName;
   } // end method getCourseName

   public void displayMessage()
   {
      System.out.printf( "Welcome to the grade book for
%s!
", 
         getCourseName() );
   } // end method displayMessage
} // end class GradeBook
  • Instantiating Objects using Constructors Example:

// Fig. 3.11: GradeBookTest.java
// GradeBook constructor used to specify the course name at the 
// time each GradeBook object is created.
public class GradeBookTest
{
   public static void main( String[] args )
   {
      // create GradeBook objects with specific initial course names
      GradeBook gradeBook1 = new GradeBook( 
         "CS101 Introduction to Java Programming" );
      GradeBook gradeBook2 = new GradeBook( 
         "CS102 Data Structures in Java" );

      // display initial value of courseName for each GradeBook
      System.out.printf( "gradeBook1 course name is: %s
",
         gradeBook1.getCourseName() );
      System.out.printf( "gradeBook2 course name is: %s
",
         gradeBook2.getCourseName() );
   } // end main
} // end class GradeBookTest

Floating-Point Numbers and Type double

  • Precision Characteristics:

    • Floating-point numbers contain decimal digits (e.g., 7.337.33, 0.09750.0975 , 1000.123451000.12345).

    • float: Represents single-precision floating-point numbers requiring 32 bits of memory, providing up to 77 significant decimal digits.

    • double: Represents double-precision floating-point numbers requiring 64 bits of memory (twice float), providing up to 1515 significant decimal digits.

  • Formatted Output (System.out.printf):

    • The %f format specifier outputs values of type float or double.

    • Precision syntax %.2f specifies formatting the output to exactly two decimal places (rounded to the hundredths position).

  • Scanner Operations:

    • Method Scanner.nextDouble() parses and returns a double value typed by the user.

  • Account Class Example (Input Validation and Floating-Point Math):

// Fig. 3.13: Account.java
// Account class with a constructor to validate and
// initialize instance variable balance of type double.
public class Account
{
   private double balance; // instance variable that stores the balance

   // constructor
   public Account( double initialBalance )
   {
      // validate that initialBalance is greater than 0.0;
      // if it is not, balance is initialized to the default value 0.0
      if ( initialBalance > 0.0 )
         balance = initialBalance;
   } // end Account constructor

   // credit (add) an amount to the account
   public void credit( double amount )
   {
      balance = balance + amount; // add amount to balance
   } // end method credit

   // return the account balance
   public double getBalance()
   {
      return balance;
   } // end method getBalance
} // end class Account
  • AccountTest Application Example:

// Fig. 3.14: AccountTest.java
// Inputting and outputting floating-point numbers with Account objects.
import java.util.Scanner;

public class AccountTest
{
   public static void main( String[] args )
   {
      Account account1 = new Account( 50.00 ); // create Account object
      Account account2 = new Account( -7.53 ); // create Account object

      // display initial balance of each object
      System.out.printf( "account1 balance: $%.2f
", account1.getBalance() );
      System.out.printf( "account2 balance: $%.2f

", account2.getBalance() );

      Scanner input = new Scanner( System.in );
      double depositAmount; // deposit amount read from user

      System.out.print( "Enter deposit amount for account1: " );
      depositAmount = input.nextDouble(); // obtain user input
      System.out.printf( "
adding %.2f to account1 balance

", depositAmount );
      account1.credit( depositAmount ); // add to account1 balance

      // display balances
      System.out.printf( "account1 balance: $%.2f
", account1.getBalance() );
      System.out.printf( "account2 balance: $%.2f

", account2.getBalance() );

      System.out.print( "Enter deposit amount for account2: " );
      depositAmount = input.nextDouble(); // obtain user input
      System.out.printf( "
adding %.2f to account2 balance

", depositAmount );
      account2.credit( depositAmount ); // add to account2 balance

      // display balances
      System.out.printf( "account1 balance: $%.2f
", account1.getBalance() );
      System.out.printf( "account2 balance: $%.2f
", account2.getBalance() );
   } // end main
} // end class AccountTest

Copy Constructors

  • Concept and Purpose:

    • A copy constructor creates a new object as an exact duplicate of an existing object of the same class.

    • It serves as a simple alternative to the default Java cloning mechanism.

  • Inefficient Object Copying Syntax:

    • Manually extracting every field via getters to construct a new instance is verbose:

    • Student s2 = new Student(s1.getId(), s1.getfName(), s1.getlName());

  • Copy Constructor Syntax and Implementation:

    • A copy constructor takes a reference to an object of the same class type as its single parameter:

public Student( Student other )
{
   this.id = other.id;
   this.fName = other.fName;
   this.lName = other.lName;
}
  • Instantiation using Copy Constructor:

    • Student s2 = new Student( s1 );

  • Deep Copy Requirement:

    • Copy constructors must perform a deep copy for mutable reference fields to ensure the new object and original object are fully detached, avoiding shared memory references.

Standard Object Methods: toString() and equals()

  • The toString() Method:

    • Formats and returns a String representation containing the data state of an object.

    • Replaces custom message output methods to standardize object-to-string conversions.

public String toString()
{
   return String.format( "id= %d, First Name = %s Last Name= %s", id, fName, lName );
}
  • Implicit Execution: When an object reference is passed to System.out.println(s1), the compiler automatically calls s1.toString(). Therefore, System.out.println(s1.toString()) and System.out.println(s1) produce identical output.

    • The equals() Method:

  • Compares two objects for logical data equality and returns a boolean result (true or false).

public boolean equals( Student other )
{
   return id == other.id && 
          fName.equals( other.fName ) && 
          lName.equals( other.lName );
}
  • Execution syntax: s1.equals(s2).

Class Design Exercises

  • Student Class Specifications:

    • Private Attributes: id: int, fName: String, lName: String.

    • Constructors:

    • Student(int)

    • Student(int, String, String)

    • Public Methods: getId(): int, setId(int): void, getfName(): String, setfName(String): void, getlName(): String, setlName(String): void, DisplayMessage(): String (or toString()).

Student Class UML Diagram
  • Date Class Specifications:

    • Private Attributes: day: int, month: int, year: int.

    • Constructors:

    • Date(Date) (Copy Constructor)

    • Date(int)

    • Date(int, int)

    • Date(int, int, int)

    • Public Methods: getDay(): int, setDay(int): void, getMonth(): int, setMonth(int): void, getYear(): int, setYear(int): void, toString(): String, equals(Date): boolean.

Date Class UML Diagram