Lecture from: 15.11.2024 | Video: Videos ETHZ
Java’s Built-in Lists: java.util.LinkedList
and java.util.ArrayList
Recall from last time…
Java provides implementations of common data structures in the java.util
package.
1. java.util.LinkedList
- Doubly Linked List: Java’s
LinkedList
is a doubly linked list, meaning each node has references to both the next and the previous node. This allows for efficient traversal in both directions. - Methods: Offers methods like
addFirst()
,addLast()
,removeFirst()
,removeLast()
,getFirst()
,getLast()
, etc.
Example Usage:
2. java.util.ArrayList
- Dynamic Array:
ArrayList
uses a dynamic array (a resizable array) internally. It provides fast random access (accessing elements by index) but can be less efficient for insertions or deletions, especially at the beginning of the list.
Example Usage:
Java Collections Framework
The java.util.Collections
class provides utility methods for working with collections (including lists), like sorting, searching, finding min/max, etc. We will learn more about generics and collections in a later lecture.
Wrapper Classes
Java collections (like LinkedList
and ArrayList
) can only store objects, not primitive types (like int
, double
, boolean
). To store primitive types in collections, we use wrapper classes.
- Wrapper Classes: Each primitive type has a corresponding wrapper class:
Integer
forint
,Double
fordouble
,Boolean
forboolean
, etc. These wrapper classes are part of thejava.lang
package.
Boxing and Unboxing
- Boxing: Converting a primitive value to its corresponding wrapper object.
- Unboxing: Converting a wrapper object back to its primitive value.
Example:
Autoboxing and Auto-unboxing
Java automatically performs boxing and unboxing in many situations, which simplifies the code.
Example:
Important Note: No Implicit Type Conversions with Wrapper Classes
Autoboxing and unboxing only work between a primitive type and its corresponding wrapper class. There are no implicit type conversions between different wrapper classes (or from one wrapper class to the primitive type of another.) For example:
Inheritance in Java
Inheritance is a powerful mechanism in object-oriented programming that allows you to create new classes (subclasses or derived classes) based on existing classes (superclasses or base classes). The subclass inherits the attributes and methods of the superclass, promoting code reuse and establishing a hierarchical relationship between classes. This “is-a” relationship is fundamental to inheritance. A subclass is a specialized type of its superclass.
Motivating Example: FastLinkedList
Imagine we want to create a FastLinkedList
that improves upon our previous LinkedList
by adding a direct reference to the last element (back
). This avoids traversing the whole list when adding elements to the end. Instead of rewriting all the LinkedList
code, we can use inheritance to reuse the existing functionality and extend it with the new back
reference.
Problem: Adding to the end of a regular linked list is inefficient because it requires traversing the entire list to find the last node.
Solution: FastLinkedList
will inherit from LinkedList
and add a back
pointer for direct access to the last element.
The Power of Inheritance: Inheritance enables code reuse. We don’t have to copy and paste the LinkedList
code; we inherit it and add the specific enhancements needed for FastLinkedList
.
Motivation: Input Streams in Java
Another motivational example is how Java handles input streams. Different sources of input (keyboard, file, microphone) require different handling. However, they share common functionalities (reading data). Java uses inheritance to create a hierarchy of input stream classes:
InputStream
(abstract base class): Defines the common interface for reading data.FileInputStream
,AudioInputStream
, etc. (subclasses): Implement the specifics for reading from files, audio sources, etc.
This allows you to write code that works with any input stream, regardless of the specific source, thanks to polymorphism (we’ll get to this later).
This demonstrates the flexibility and code reuse enabled by inheritance and polymorphism. The readData
method can handle various input types without modification because they all inherit from InputStream
.
Basic Syntax and Concepts
In Java, inheritance is implemented using the extends
keyword.
extends
Keyword: Indicates thatSubclass
inherits fromSuperclass
.- Inheritance and “is-a” Relationship:
Subclass
is a specialized type ofSuperclass
. - Inherited Members:
Subclass
automatically inherits all the non-private members (attributes and methods) ofSuperclass
. - Adding New Members:
Subclass
can add its own unique attributes and methods. - Code Reuse: Inheritance promotes code reuse by avoiding code duplication.
Classic Example: Dog and Cat
Let’s illustrate with the classic “Animal, Dog, Cat” example.
Dog
andCat
inheritname
,age
,eat()
, andsleep()
fromAnimal
.Dog
addsbark()
, andCat
addsmeow()
.
This structure avoids redundant code (defining name
, age
, eat()
, and sleep()
in both Dog
and Cat
).
Inheritance Hierarchies and Chains
- Inheritance Hierarchy: A tree-like structure formed by classes and their subclasses. The root of the hierarchy is usually a general class (like
Animal
), and subclasses become more specialized as you go down the tree. Java supports single inheritance: a class can only directly inherit from one superclass. However, you can create inheritance chains:
Animal
/ \
Dog Cat
|
Husky
In this example, Husky
inherits from Dog
, which inherits from Animal
. So, Husky
implicitly inherits from Animal
as well.
- Terminology:
- The inheriting classes (e.g.,
Dog
,Cat
,Husky
) are called subclasses or subtypes. - The classes being inherited from (e.g.,
Animal
,Dog
) are superclasses or supertypes.
- The inheriting classes (e.g.,
Overriding Methods
- Overriding: A subclass can override (redefine) an inherited method to provide a specialized implementation. The overriding method in the subclass must have the same signature (method name, parameters, and return type) as the method in the superclass.
@Override
Annotation: (Optional, but recommended.) Indicates that a method is intended to override a superclass method. It helps catch errors if the signature doesn’t match. (More here: https://stackoverflow.com/questions/94361/when-do-you-use-javas-override-annotation-and-why)
Example:
When eat()
is called on a Cat
object, the overridden version in Cat
is executed. When called on an Animal
or a Dog
object, their respective versions are called. This is a fundamental example of polymorphism. (More details on polymorphism in a later part.)
Constructors and Default Constructors
Recall how constructors work:
- Default Constructor: If you don’t define any constructors for a class, Java provides a default constructor (no arguments). It initializes instance variables to their default values (0 for numbers,
false
for booleans,null
for objects). - User-Defined Constructors: If you define any constructor, the default constructor is not automatically provided. You have to explicitly define a no-argument constructor if you need one.
Inheritance and Default Constructors
- Subclass Default Constructors: If a subclass doesn’t define any constructors, it gets a default constructor. This default constructor implicitly calls the superclass’s no-argument constructor. This ensures that the superclass part of the subclass object is properly initialized.
- Super Class with User-defined constructor: However, if the superclass only has constructors with parameters (and no no-argument constructor), and the subclass doesn’t define any constructors, the subclass will not compile. This is because subclass default constructors implicitly look for the superclass’s no argument constructor to call and if such one doesn’t exist, an error is thrown. To fix this, you must explicitly write a constructor for B, which calls the appropriate constructor from A using
super()
Example
super()
Keyword
-
Calling Superclass Constructors: To call a specific superclass constructor from a subclass constructor, use
super(arguments)
. This must be the first statement in the subclass constructor. -
Implicit
super()
: If you don’t explicitly callsuper()
in a subclass constructor, Java automatically inserts a call to the superclass’s no-argument constructor (if one exists). If you’ve explicitly defined a constructor with parameters in the super class but no no-argument constructor exists, the code does not compile. In other words, the compiler only implicitly addssuper();
to your sub class’s constructors if the super class has a no-argument constructor. -
Constructor Chaining in Inheritance: When a subclass object is created, constructors are called in a chain, starting from the top of the inheritance hierarchy (the most general superclass) down to the specific subclass.
Example: (Extending the Dog/Animal example)
Visibility Modifiers
Recall the visibility modifiers in Java:
public
: Accessible from anywhere.protected
: Accessible within the same package and by subclasses (even if in a different package).- (package-private, no modifier): Accessible only within the same package.
private
: Accessible only within the same class.
Inheritance and Visibility
-
Inheriting Members: A subclass inherits all the non-private members of its superclass.
private
members are not directly accessible in the subclass. -
private
Members and Encapsulation:private
members are part of the superclass’s internal implementation and are hidden from subclasses. This supports encapsulation. The subclass should not be dependent on the superclass’s private implementation details.
Example: (Illustrating how private
members aren’t accessible)
Continue here: 17 Visibility Modifiers, Sub Typing, Typecasts, Dynamic Binding, Polymorphism