When most data scientists think about programming languages, Java rarely tops the list. Python dominates data science workflows, and for good reason — its libraries for machine learning and data analysis are unmatched. But Java plays a bigger role in the data science ecosystem than many realise.
Apache Spark, Hadoop, Kafka, Elasticsearch, and Weka — some of the most widely used tools in enterprise data pipelines — are all built on Java. Understanding how Java works, and specifically how it uses Object-Oriented Programming (OOP), gives data scientists a significant advantage when working with these tools, reading source code, contributing to open-source projects, or building production-ready data applications.
This article breaks down the four core OOP concepts in Java — classes and objects, encapsulation, inheritance, and polymorphism — using examples that make sense for data scientists specifically.
What Is Object-Oriented Programming and Why Does It Matter for Data Science?
Object-Oriented Programming is a way of structuring code around objects rather than functions. An object is a self-contained unit that combines data (called fields or attributes) and behaviour (called methods).
For data scientists coming from Python, this might feel familiar — Python also supports OOP. But Java takes it further by enforcing OOP principles strictly. Every piece of code in Java lives inside a class. There are no standalone functions floating around.
Why does this matter for data science? Because real-world data problems map naturally to objects. A dataset is an object. A machine learning model is an object. A data pipeline stage is an object. Once you see data through the lens of OOP, building reusable and maintainable data workflows becomes much more intuitive.
1. Classes and Objects — The Building Blocks
A class is a blueprint. An object is an instance of that blueprint.
Think of a class as a template for a data record. Imagine you’re working with sensor data from IoT devices. Each sensor has a location, a type, and a reading. In Java, you would define a class to represent this:
public class Sensor {
String location;
String type;
double reading;
public void displayReading() {
System.out.println(type + " at " + location + ": " + reading);
}
}
Now you can create as many sensor objects as you need:
Sensor tempSensor = new Sensor();
tempSensor.location = "Warehouse A";
tempSensor.type = "Temperature";
tempSensor.reading = 23.5;
tempSensor.displayReading();
Output:
Temperature at Warehouse A: 23.5
Every row in your dataset could be an object of this class. This is exactly how Java-based data tools like Apache Spark structure distributed data — as collections of typed objects processed in parallel across clusters.
2. Encapsulation — Protecting Your Data
Encapsulation means hiding the internal state of an object and only exposing it through controlled methods. In Java, this is done using access modifiers:
private
,
public
, and
protected
.
For data scientists, encapsulation is particularly relevant when building data pipelines. Imagine a class that holds a trained machine learning model. You want to allow predictions but prevent anyone from accidentally modifying the model’s internal parameters directly.
public class MLModel {
private double[] weights;
private double bias;
public double predict(double[] features) {
double result = bias;
for (int i = 0; i < features.length; i++) {
result += weights[i] * features[i];
}
return result;
}
public void setWeights(double[] weights) {
this.weights = weights;
}
}
Here,
weights
and
bias
are private — they cannot be accessed directly from outside the class. Anyone using this model can call
predict()
and
setWeights()
, but they cannot accidentally overwrite or corrupt the internal state. This pattern is standard in production ML systems and is exactly how libraries like Weka structure their model classes.
3. Inheritance — Reusing Code Across Model Types
Inheritance allows one class to inherit the fields and methods of another. The class that inherits is called a subclass, and the class it inherits from is the superclass.
This is incredibly useful when you have multiple related model types that share common behaviour. Imagine a data science pipeline with different types of classifiers — all of them need to be trained and evaluated, but they do it differently internally.
public class Classifier {
private String name;
public void evaluate(double[] testData) {
System.out.println("Evaluating " + name + " on test data...");
}
}
public class DecisionTree extends Classifier {
private int maxDepth;
public void train(double[][] trainingData) {
System.out.println("Training Decision Tree with max depth: " + maxDepth);
}
}
public class NaiveBayes extends Classifier {
public void train(double[][] trainingData) {
System.out.println("Training Naive Bayes classifier...");
}
}
Both
DecisionTree
and
NaiveBayes
inherit the
evaluate()
method from
Classifier
— no need to rewrite it. This is exactly how Weka organises its classifier hierarchy: a base
Classifier
class with dozens of algorithm implementations inheriting shared evaluation logic.
The keyword in Java for inheritance is
extends
. A subclass can use all public and protected methods of its superclass, and can add its own behaviour on top.
4. Polymorphism — One Interface, Many Implementations
Polymorphism (from Greek: “many forms”) means that objects of different classes can be treated as objects of the same superclass. The actual method that gets called is determined at runtime based on the real type of the object.
For data scientists, this is powerful when you want to swap out algorithms without rewriting pipeline code:
public class Pipeline {
public static void runExperiment(Classifier model, double[][] data) {
model.train(data);
model.evaluate(new double[]{1.0, 2.0, 3.0});
}
public static void main(String[] args) {
double[][] trainingData = {{1, 2}, {3, 4}, {5, 6}};
Classifier dt = new DecisionTree();
Classifier nb = new NaiveBayes();
runExperiment(dt, trainingData);
runExperiment(nb, trainingData);
}
}
The
runExperiment
method accepts any
Classifier
object — it doesn’t care whether it’s a Decision Tree or Naive Bayes. The right
train()
method is called automatically based on the actual object type. This makes your pipeline extensible: add a new algorithm by creating a new subclass, and the pipeline code stays untouched.
This pattern is the foundation of Weka’s experiment framework and Apache Spark ML’s estimator/transformer architecture.
5. Abstraction — Hiding Complexity Behind Simple Interfaces
Abstraction means exposing only what is necessary and hiding the implementation details. In Java, abstraction is achieved through abstract classes and interfaces.
Imagine a unified interface for loading data from different sources — a CSV file, a database, or a REST API. Each source loads data differently, but the pipeline just needs to call
loadData()
:
public abstract class DataLoader {
public abstract double[][] loadData(String source);
public void preview(String source) {
double[][] data = loadData(source);
System.out.println("Loaded " + data.length + " rows.");
}
}
public class CsvLoader extends DataLoader {
public double[][] loadData(String filePath) {
System.out.println("Reading CSV from: " + filePath);
return new double[][]{{1, 2}, {3, 4}};
}
}
public class DatabaseLoader extends DataLoader {
public double[][] loadData(String query) {
System.out.println("Querying database: " + query);
return new double[][]{{5, 6}, {7, 8}};
}
}
The
preview()
method works with any
DataLoader
— it doesn’t need to know whether the data is coming from a CSV or a database. This is the same pattern used by Apache Spark’s DataFrameReader API, which abstracts over dozens of data sources behind a unified interface.
Putting It All Together: A Data Science Perspective
Here’s a quick summary of how these OOP concepts map to real data science scenarios:
| OOP Concept | Data Science Use Case |
| Classes & Objects | Representing data records, model instances, pipeline stages |
| Encapsulation | Protecting model parameters from accidental modification |
| Inheritance | Sharing evaluation logic across different algorithm types |
| Polymorphism | Swapping algorithms in a pipeline without changing pipeline code |
| Abstraction | Unified data loading interface across CSV, database, and API sources |
Where to Go from Here
Understanding these OOP concepts opens the door to reading and contributing to Java-based data tools that power enterprise data engineering — Spark, Kafka, Hadoop, and Weka all use these patterns extensively in their source code.
If you want to build a solid Java foundation before diving into these frameworks, free structured Java lessons for beginners cover all the OOP concepts discussed here — with practice exercises and built-in tests at every step. It’s a practical starting point for data scientists who want to understand the Java ecosystem from the ground up.
Conclusion
Java’s OOP principles are not just academic concepts — they are the architectural backbone of the most widely used tools in enterprise data science. Understanding classes, encapsulation, inheritance, polymorphism, and abstraction gives data scientists the ability to read framework source code, debug pipeline issues at a deeper level, and build more robust data applications.
Python will remain the primary language for exploratory data analysis and model training. But Java literacy is increasingly becoming a valuable secondary skill for data scientists working in production environments where Spark, Kafka, and Hadoop are part of the daily stack.
The code examples in this article have been simplified for clarity. All examples are compatible with Java 25 LTS.