Class and Objects with a Civil Engineering Application

In object-oriented programming (OOP), classes and objects are fundamental concepts used to model real-world entities. A class acts as a blueprint for creating objects, which are instances of the class. This approach can be particularly useful in civil engineering, where complex systems and structures can be modelled, manipulated, and analysed through software. By leveraging OOP principles, engineers can create more modular, scalable, and maintainable code for simulations, structural analysis, design, and more.

Learning Objectives

  • Understand the concept of classes and objects within the context of civil engineering applications.
  • Learn how to design and implement classes to model civil engineering concepts such as forces, structures, and materials.
  • Gain proficiency in defining methods within classes to perform operations like vector addition, structural analysis, and material property calculations.
  • Develop an understanding of how to instantiate objects from classes and use them in engineering simulations and analyses.

Outline

  • Introduction to Classes and Objects in Engineering Context
    • Definition and significance
    • Real-world applications in civil engineering
  • Designing Classes for Civil Engineering Applications
    • Modelling forces, vectors, and structures
    • Incorporating engineering properties and behaviours
  • Implementing Methods for Engineering Calculations
    • Vector operations for force analysis
    • Calculations for structural integrity and material properties
  • Instantiating Objects and Conducting Simulations
    • Creating instances for simulation scenarios
    • Analysing and interpreting simulation results

Introducing Interface

An interface in Java is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Interfaces cannot contain instance fields or constructor methods. The main purpose of an interface is to specify a set of methods that a particular class must implement. Interfaces are used to achieve abstraction and multiple inheritance in Java.

Characteristics of an Interface:

  • Abstraction: An interface provides a way to specify what a class must do without specifying how it does it.
  • Contract: By implementing an interface, a class signs a contract to provide implementations for the methods declared by the interface.
  • Multiple Inheritance: Unlike classes, Java allows a class to implement multiple interfaces. This enables a form of multiple inheritances since a class can inherit the contract from multiple interfaces.

Implementing an Interface and Collection:

A class implements an interface using the implements keyword. All methods declared in the interface must be implemented in the class, unless the class is abstract.

public interface Animal {
    void eat();
    void sleep();
}

public class Dog implements Animal {
    public void eat() {
        // Implementation
    }
    
    public void sleep() {
        // Implementation
    }
}

The Java Collections Framework (JCF)

The Java Collections Framework (JCF) is a set of classes and interfaces that implement commonly reusable collection data structures. Introduced in Java 2 (JDK 1.2), the framework has become a core component of the Java programming language, providing an efficient way to manage groups of objects.

Key Features of the Java Collections Framework:

  • Rich Set of Interfaces and Classes: The JCF provides a wide range of interfaces (like List, Set, Queue, and Map) and classes (such as ArrayList, LinkedList, HashSet, TreeSet, PriorityQueue, HashMap, and TreeMap). These are designed for various data management tasks, such as storing elements in lists, unique element sets, key-value pairs in maps, and more.
  • Unified Architecture: The framework offers a unified architecture for storing and manipulating collections, allowing collections to be manipulated independently of the details of their representation. This unified architecture also makes it easier to pass collections around in your code and integrate different parts of your application or system.
  • Generics Support: With the introduction of generics in Java 5, the Collections Framework has been enhanced to support type-safe collections. This means that you can specify the type of objects that a collection can contain, providing compile-time type checking and reducing the risk of runtime errors.
  • Performance: Many implementations of the Collections Framework are highly optimized for performance. For example, ArrayList provides constant-time positional access and HashMap offers constant-time performance for basic operations, assuming the hash function disperses elements properly among the buckets.
  • Algorithms: The Collections Framework also includes a set of algorithms that are common to many collection manipulations, such as sorting, searching, shuffling, and reversing. These algorithms are polymorphic: they operate on collections and are designed to be as fast as possible.

Core Interfaces of the Java Collections Framework:

  • Collection Interface: The root interface of the framework. It represents a group of objects known as its elements.
  • List Interface: An ordered collection that can contain duplicate elements. It allows positional access and insertion of elements.
  • Set Interface: A collection that cannot contain duplicate elements. It models the mathematical set abstraction.
  • Queue Interface: A collection used for holding elements prior to processing. It typically orders elements in FIFO (first-in-first-out) manner but can be ordered differently depending on the implementation.
  • Map Interface: An object that maps keys to values. A map cannot contain duplicate keys, and each key can map to at most one value.

The Java Collections Framework provides a comprehensive set of tools for handling data in Java applications. Its rich set of interfaces and classes simplifies the development process by offering reusable data structures and algorithms, making it an essential part of Java programming.

Vector (Collection):

When referring to the Vector in the context of Java collections (not the mathematical vector concept), it is a collection class that implements the List interface. Vector is part of the Java Collections Framework and represents a dynamic array that can grow or shrink in size.

Characteristics of Vector:

  • Synchronization: Unlike ArrayList, every method in Vector is synchronized, making it thread-safe. This means that multiple threads can access and modify a Vector without causing data inconsistency issues, at the cost of performance.
  • Legacy: Vector was part of the original version of Java (Java 1.0). Although it has been retrofitted to be part of the Collections Framework, it's generally considered legacy code. The ArrayList class is usually preferred for new development because it is unsynchronized and therefore faster in environments where synchronization is not a concern.
  • Resizable Array: Like ArrayList, Vector internally uses an array to store its elements, and the array grows as needed to accommodate new elements.

When to Use Vector:

Vector is used in scenarios where thread-safe operations on a list are required without having to manually synchronise the code. However, due to its performance overhead from synchronization, it is often bypassed in favor of ArrayList or the concurrent collection classes from the java.util.concurrent package (like CopyOnWriteArrayList) when thread safety is a concern.

In modern Java applications, direct use of Vector is rare, and it is primarily seen in legacy systems or specific situations requiring built-in synchronisation.

A problem that implements these concepts

To implement the approach for calculating the dot product of scaling vectors as inspired by R.C. Hibbeler's methodology, focusing on precision and rounding during calculations, you would typically use Java. Here, I'll outline a structure that includes a class for reading vectors from a file, a class for vector operations, and the main application class App.java. We'll ensure all internal calculations are rounded to four decimal places and presented with three decimal places.

File Structure and Content

Assume you have a file named vectors.txt with the following content, representing two vectors:

3,4
5,12

This represents two vectors, (3,4) and (5,12), stored in a CSV (comma-separated values) format.

Given the vectors in \(\hat{i}\) and \(\hat{j}\) notation:

\(\vec{A} = 3\hat{i} + 4\hat{j}\)

\(\vec{B} = 5\hat{i} + 12\hat{j}\)

The dot product of \(\vec{A}\) and \(\vec{B}\) is calculated as follows:

\(\vec{A} \cdot \vec{B} = (3\hat{i} + 4\hat{j}) \cdot (5\hat{i} + 12\hat{j}) = 3 \times 5 + 4 \times 12\)

VectorFileReader.java

This class is responsible for reading vectors from a file and storing them in a suitable data structure, such as an array or a list of Vector objects.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class VectorFileReader {
    public List<Vector> readVectorsFromFile(String filePath) {
        List<Vector> vectors = new ArrayList<>();
        try (Scanner scanner = new Scanner(new File(filePath))) {
            while (scanner.hasNextLine()) {
                String[] parts = scanner.nextLine().split(",");
                double x = Double.parseDouble(parts[0]);
                double y = Double.parseDouble(parts[1]);
                vectors.add(new Vector(x, y));
            }
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + filePath);
        }
        return vectors;
    }
}

Vector.java

This class models a mathematical vector and includes methods for vector operations, including the dot product. Calculations are rounded as specified.

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Vector {
    private double x;
    private double y;

    public Vector(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double dotProduct(Vector other) {
        double product = round(this.x * other.x + this.y * other.y, 4);
        return product;
    }

    // Utility method for rounding
    private double round(double value, int places) {
        if (places < 0) throw new IllegalArgumentException();
        
        BigDecimal bd = new BigDecimal(Double.toString(value));
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    // Presentation method to round to 3 places for display
    public String toRoundedString() {
        return "(" + round(this.x, 3) + ", " + round(this.y, 3) + ")";
    }
}

App.java

The entry point of the application, App.java, uses VectorFileReader to read vectors from a file, performs the dot product operation, and prints the result with the required precision.

import java.util.List;

public class App {
    public static void main(String[] args) {
        VectorFileReader reader = new VectorFileReader();
        List<Vector> vectors = reader.readVectorsFromFile("vectors.txt");
        
        if (vectors.size() >= 2) {
            Vector v1 = vectors.get(0);
            Vector v2 = vectors.get(1);
            double dotProduct = v1.dotProduct(v2);
            
            System.out.println("Vector 1: " + v1.toRoundedString());
            System.out.println("Vector 2: " + v2.toRoundedString());
            System.out.printf("Dot Product: %.3f\n", dotProduct);
        } else {
            System.err.println("Insufficient vectors in file.");
        }
    }
}

This setup emphasises precision in mathematical calculations, adhering to the rounding requirements for both internal calculations and presentation. The VectorFileReader class separates the concern of file reading from the vector operations, adhering to good software design principles.

Class Diagram

The class diagram shows the relationships and interactions between classes within the application, focusing on attributes and methods relevant to calculating the dot product.

classDiagram
    class Vector2D {
      -double x
      -double y
      +Vector2D(double x, double y)
      +dotProduct(Vector2D other) double
      +toString() String
    }
    
    class Vector2DFileReader {
      +readVectorsFromFile(String filePath) List
    }
    
    class App {
      +void main(String[] args)
    }
    
    Vector2DFileReader --> Vector2D : reads and creates
    App --> Vector2DFileReader : uses
    App --> Vector2D : calculates dot product

In this diagram:

  • Vector2D represents a 2D vector with methods for calculating the dot product and converting the vector to a string.
  • Vector2DFileReader is responsible for reading vector data from a file and creating Vector2D objects.
  • App is the main class that uses Vector2DFileReader to read vectors and then performs the dot product calculation on the vectors.

Flow Chart

The flow chart describes the sequential steps involved in the dot product calculation process, from start to finish.

flowchart TD
    A[Start] --> B{Read vectors from file}
    B --> C[Create Vector2D objects]
    C --> D{Are there at least two vectors?}
    D -->|Yes| E[Calculate dot product of first two vectors]
    D -->|No| F[Error: Not enough vectors]
    E --> G[Display result]
    F --> H[End]
    G --> H[End]

This flowchart outlines the application's process:

  1. Start the application.
  2. Read vectors from a file, where each line represents a vector in 2D space.
  3. Create Vector2D objects for each vector read from the file.
  4. Check if there are at least two vectors available for the dot product calculation.
  5. If yes, calculate the dot product of the first two vectors.
  6. If not, display an error message indicating the lack of sufficient vectors.
  7. Display the result of the dot product calculation.
  8. End the application.

These diagrams effectively convey the structure and process flow of the application designed to calculate the dot product of 2D vectors, emphasising the separation of concerns and the steps involved in the calculation.

Following is a Java application that performs the cross product of vectors. The cross product is a binary operation on two vectors in three-dimensional space, resulting in a vector perpendicular to both of the original vectors. It's widely used in physics, engineering, and computer graphics for calculations involving torque, rotational force, and the orientation of 3D models.

The application will consist of several classes to handle different aspects of the operation, such as reading vectors from a file, performing the cross product calculation, and the main application class for execution. We'll ensure all calculations are rounded to four decimal places internally and presented with three decimal places.

In the context of explaining how a List<Vector> data structure works, each Vector represents a 2D vector with x and y components. Given the vectors in the vectors.txt file they are (3,4) and (5,12), the List data structure contains a List of objects of class Vectors, which has two properties x and y. The same can be visualised that a List is a container with individual cells which are in a sequence where data can be stored. Now, because we used List<Vector>, each list will contain an x and y pair, which can be visualised as a box within each box of the list where a list element is stored.

Here's a more detailed visualisation using Mermaid, reflecting the specific vectors mentioned:

flowchart TB
    list[List] --> vector1[(Vector: 3,4)]
    list --> vector2[(Vector: 5,12)]
    
    subgraph vector1
        x1[x: 3]
        y1[y: 4]
    end
    
    subgraph vector2
        x2[x: 5]
        y2[y: 12]
    end
    
    class vector1,vector2 vector;
    
    %% Styling
    classDef list fill:#f9f,stroke:#333,stroke-width:4px;
    classDef vector fill:#bbf,stroke:#333,stroke-width:2px;

This diagram now includes:

  • A List named list, representing the container that holds vectors read from the file.
  • Two vectors, vector1 and vector2, which correspond to the vectors (3,4) and (5,12) found in the vectors.txt file. These vectors are represented as subgraphs to illustrate their internal structure:
    • vector1 contains x: 3 and y: 4
    • vector2 contains x: 5 and y: 12

Frequently Asked Questions on the above Construct

In the Java code in the class file VectorFileReader.java, <Vector> does not refer to a built-in data type but rather to the Vector class defined in the Vector.java code snippet. The use of <Vector> in List<Vector> leverages Java's generics system to specify that the List will contain elements of type Vector, as defined by the Vector.java class you outlined. Here's a breakdown to clarify common questions:

  1. Is <Vector> a Built-in Data Type?
    • No, <Vector> in the context of List<Vector> refers to the custom class Vector that you've defined in your code. Java has a built-in Vector class (java.util.Vector), which is a part of the Collections framework and represents a growable array of objects. However, when you use <Vector> in List<Vector>, Java looks for a class named Vector in the current package or among the imported classes. Since you've defined a Vector class that represents a mathematical vector and haven't imported java.util.Vector, Java uses your custom Vector class.
  2. Why is <> Used Around Vector?
    • The <> symbols are used to specify the type of elements the List will contain, a feature known as generics. Generics were introduced in Java 5 to provide compile-time type checking and eliminate the risk of ClassCastException that was common when using collections before generics. By specifying <Vector> within <>, you tell the compiler that the List named vectors will only hold Vector objects, allowing for safer, more readable code. It helps ensure that you only add instances of Vector to the List and avoid errors related to handling incorrect types.
  3. Purpose of Generics (<>)
    • Generics improve the type safety of your code, making it easier to read and maintain. They enable classes, interfaces, and methods to operate on types specified by the programmer, reducing runtime errors by catching incorrect types at compile time. For example, without generics, you would need to cast objects retrieved from a collection, which could lead to runtime errors if the object was not of the expected type.

<Vector> specifies that the List data structure will contain elements of your custom Vector class. The <> syntax is part of Java's generics system, providing compile-time type safety for collections and other generic types.

Problem Statement

Calculate the cross product of two three-dimensional vectors to determine the vector perpendicular to the plane containing the original vectors. This can be applied to calculate the torque of a force applied at a point or to find the normal vector of a plane in 3D space.

File Structure

Assume the vectors are stored in a file named vectors3D.txt with the following format:

3,4,5
2,8,7

This file represents two vectors:

\(\vec{A} = 3\hat{i} + 4\hat{j} + 5\hat{k}\)

and

\(\vec{B} = 2\hat{i} + 8\hat{j} + 7\hat{k}\)

Vector3DFileReader.java

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

public class Vector3DFileReader {
    public List<Vector3D> readVectorsFromFile(String filePath) {
        List<Vector3D> vectors = new ArrayList<>();
        try (Scanner scanner = new Scanner(new File(filePath))) {
            while (scanner.hasNextLine()) {
                String[] parts = scanner.nextLine().split(",");
                double x = Double.parseDouble(parts[0]);
                double y = Double.parseDouble(parts[1]);
                double z = Double.parseDouble(parts[2]);
                vectors.add(new Vector3D(x, y, z));
            }
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + filePath);
        }
        return vectors;
    }
}

Vector3D.java

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Vector3D {
    private double x, y, z;

    public Vector3D(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vector3D crossProduct(Vector3D other) {
        double newX = round(this.y * other.z - this.z * other.y, 4);
        double newY = round(this.z * other.x - this.x * other.z, 4);
        double newZ = round(this.x * other.y - this.y * other.x, 4);
        return new Vector3D(newX, newY, newZ);
    }

    private double round(double value, int places) {
        if (places < 0) throw new IllegalArgumentException();
        
        BigDecimal bd = new BigDecimal(Double.toString(value));
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    @Override
    public String toString() {
        return String.format("(%.3f, %.3f, %.3f)", round(x, 3), round(y, 3), round(z, 3));
    }
}

App.java

import java.util.List;

public class App {
    public static void main(String[] args) {
        Vector3DFileReader reader = new Vector3DFileReader();
        List<Vector3D> vectors = reader.readVectorsFromFile("vectors3D.txt");
        
        if (vectors.size() >= 2) {
            Vector3D v1 = vectors.get(0);
            Vector3D v2 = vectors.get(1);
            Vector3D crossProduct = v1.crossProduct(v2);
            
            System.out.println("Vector 1: " + v1);
            System.out.println("Vector 2: " + v2);
            System.out.println("Cross Product: " + crossProduct);
        } else {
            System.err.println("Insufficient vectors in file.");
        }
    }
}

This application structure provides a clear separation of concerns, with different classes handling file reading, vector operations, and application logic. The rounding rules ensure precision in calculations and presentation, adhering to the high standards of engineering applications.

The following diagrams illustrate the design and workflow of the Java application for calculating the cross product of vectors, including class structure and process flow.

Class Diagram

The class diagram represents the relationship between the classes in the application, highlighting the attributes and methods of each class.

classDiagram

    class Vector3D {
      -double x
      -double y
      -double z
      +Vector3D(double x, double y, double z)
      +crossProduct(Vector3D other) Vector3D
      +toString() String
    }
    
    class Vector3DFileReader {
      +readVectorsFromFile(String filePath) List
    }
    
    class App {
      +void main(String[] args)
    }
    
    Vector3DFileReader --> Vector3D : reads and stores
    App --> Vector3DFileReader : uses
    App --> Vector3D : operates on

Flow Chart

The flow chart illustrates the process flow of the application, from reading vector data from a file to calculating the cross product.

flowchart TD
    A[Start] --> B{Read vectors from file}
    B --> C[Create Vector3D objects]
    C --> D{Are there at least two vectors?}
    D -->|Yes| E[Calculate cross product of first two vectors]
    D -->|No| F[Error: Not enough vectors]
    E --> G[Display result]
    F --> H[End]
    G --> H[End]

These diagrams convey the structured approach to solving the problem of calculating the cross product of vectors, using a well-defined class structure and a clear process flow.

A little more about the @Override

The @Override annotation in Java is a marker annotation that is used to indicate that a method is intended to override a method declared in a superclass or implement an abstract method defined in an interface. Its primary purpose is to increase the readability and maintainability of the code by making the developer's intention clear. Additionally, it provides a compile-time safety check, ensuring that the method does indeed override or implement a method from a superclass or interface.

The @Override and When to Use @Override:

In this example, the @Override annotation before the speak method in the Dog class signals that speak overrides the method from its superclass, Animal. If the speak method in Animal were to be renamed, removed, or have its parameters changed, the compiler would generate an error for the @Override annotated speak method in Dog, alerting the developer to the issue.

  • Overriding Superclass Methods: Always use @Override when you override a concrete method of a superclass to ensure you have correctly overridden the method.
  • Implementing Interface Methods: Use @Override when implementing methods from an interface. Although not strictly required (since interfaces define abstract methods), it's a good practice for clarity and consistency.
  • Abstract Methods in Abstract Classes: When a subclass implements an abstract method from an abstract class, using @Override is also considered good practice for the reasons mentioned above.

The @Override annotation is a powerful feature in Java that aids in ensuring code correctness, readability, and maintainability. It serves as an explicit declaration of the programmer's intent to override or implement a method, providing compile-time verification of this intention. Adopting its use in appropriate contexts is considered a best practice in Java programming.

Key Points about @Override:

  • Compile-time Check: When a method is annotated with @Override, the compiler checks if there is a method in the superclass or interface that matches the annotated method. If no such method exists, the compiler issues an error. This check helps catch common errors, such as misspelling the method name or mismatching the parameters.
  • Enhances Readability: The @Override annotation makes it clear to anyone reading the code that the method is overriding a method from its superclass or implementing an abstract method from an interface. This clarity is particularly beneficial in complex class hierarchies.
  • Avoids Runtime Errors: By catching mismatches at compile time, the @Override annotation helps prevent certain types of runtime errors that could occur if an intended override method does not correctly override a superclass method.

Usage Example:

Consider a simple class hierarchy where a class Animal has a method speak(), and a subclass Dog overrides this method.

class Animal {
    void speak() {
        System.out.println("This animal speaks");
    }
}

class Dog extends Animal {
    @Override
    void speak() {
        System.out.println("Bark");
    }
}

Some Important Aspects of the Code

In the Vector3DFileReader.java class, an ArrayList is used for a few important reasons:

  1. Dynamic Array: ArrayList is a resizable array implementation of the List interface in Java. Unlike regular arrays in Java, which have a fixed size, ArrayLists can dynamically adjust their capacity when elements are added or removed. This makes ArrayList a convenient choice for situations where the number of elements is not known in advance, such as when reading data from a file.
  2. Ease of Use: ArrayList provides methods for common operations such as adding, removing, and searching for elements, which simplifies code and improves readability. For instance, in Vector3DFileReader.java, elements (vectors in this case) can be easily added to the ArrayList without worrying about the underlying array's capacity.
  3. API Compatibility: ArrayList is part of the Java Collections Framework, which means it integrates well with other collection types in Java. It's often a preferred choice when working with APIs that expect instances of List or its superinterfaces, such as Collection or Iterable.

As mentioned earlier, the <> syntax is related to Java Generics. Java Generics allows to specify the type of elements stored in a collection, providing stronger type checks at compile time and eliminating the need for casting when retrieving elements. For example, in Vector3DFileReader.java, the use of List<Vector3D> indicates that the ArrayList will store elements of type Vector3D. This ensures type safety, as only instances of Vector3D (or its subclasses) can be added to the ArrayList, and when retrieving an element, it is automatically typed as Vector3D without needing to cast it.