KotlinCS 124 LogoJava
PrevIndexNext
Kotlin
Java
  • Implementing a Map : 04/26/2024

  • Streams : 04/25/2024

  • Generics : 04/24/2024

  • Hashing : 04/23/2024

  • Binary Search : 04/22/2024

  • MP3: Course Ratings : 04/19/2024

  • Quicksort : 04/18/2024

  • Merge Sort : 04/17/2024

  • Sorting Algorithms : 04/16/2024

  • MP Debugging Part 1 : 04/15/2024

  • MP2: Course Activity : 04/12/2024

  • Practice with Recursion : 04/11/2024

  • MP Debugging Part 0 : 04/10/2024

  • MP2: API Client : 04/09/2024

  • MP2: API Server : 04/08/2024

  • Trees and Recursion : 04/05/2024

  • Trees : 04/04/2024

  • Recursion : 04/03/2024

  • MP1: Filtering and Search : 04/02/2024

  • MP1: Loading and Sorting : 04/01/2024

  • Lists Review and Performance : 03/29/2024

  • Linked Lists : 03/28/2024

  • Algorithms and Lists : 03/27/2024

  • Continuing MP0 : 03/26/2024

  • Getting Started with MP0 : 03/25/2024

  • Lambda Expressions : 03/22/2024

  • Anonymous Classes : 03/21/2024

  • Practice with Interfaces : 03/20/2024

  • Implementing Interfaces : 03/19/2024

  • Using Interfaces : 03/18/2024

  • Working with Exceptions : 03/08/2024

  • Throwing Exceptions : 03/07/2024

  • Catching Exceptions : 03/06/2024

  • References and Polymorphism : 03/05/2024

  • References : 03/04/2024

  • Data Modeling 2 : 03/01/2024

  • Equality and Object Copying : 02/29/2024

  • Polymorphism : 02/28/2024

  • Inheritance : 02/27/2024

  • Data Modeling 1 : 02/26/2024

  • Static : 02/23/2024

  • Encapsulation : 02/22/2024

  • Constructors : 02/21/2024

  • Objects, Continued : 02/20/2024

  • Introduction to Objects : 02/19/2024

  • Compilation and Type Inference : 02/16/2024

  • Practice with Collections : 02/15/2024

  • Maps and Sets : 02/14/2024

  • Lists and Type Parameters : 02/13/2024

  • Imports and Libraries : 02/12/2024

  • Multidimensional Arrays : 02/09/2024

  • Practice with Strings : 02/08/2024

  • null : 02/07/2024

  • Algorithms and Strings : 02/06/2024

  • Strings : 02/05/2024

  • Functions and Algorithms : 02/02/2024

  • Practice with Functions : 02/01/2024

  • More About Functions : 01/31/2024

  • Errors and Debugging : 01/30/2024

  • Functions : 01/29/2024

  • Practice with Loops and Algorithms : 01/26/2024

  • Algorithms : 01/25/2024

  • Loops : 01/24/2024

  • Arrays : 01/23/2024

  • Compound Conditionals : 01/22/2024

  • Conditional Expressions and Statements : 01/19/2024

  • Operations on Variables : 01/18/2024

  • Variables and Types : 01/17/2024

  • Welcome to CS 124 : 01/16/2024

Generics

public class Counter<T> {
private T value;
private int count;
public Counter(T setValue) {
if (setValue == null) {
throw new IllegalArgumentException();
}
value = setValue;
count = 0;
}
public void add(T newValue) {
if (value.equals(newValue)) {
count++;
}
}
public int getCount() {
return count;
}
}
Counter<Integer> counter = new Counter<>(4);
assert counter.getCount() == 0;
counter.add(4);
assert counter.getCount() == 1;
counter.add("test");

In this lesson we’ll examine Java generics. This is an advanced topic, but one that you are bound to encounter as you continue to use Java. Generics also allow the compiler to better check our code—and we always want that. So let’s do this!

Using Generics
Using Generics

We’ve already seen generics at use when using Java’s containers like Lists. We can create a bare List, but using one requires dangerous downcasting:

import java.util.List;
import java.util.ArrayList;
// This List can store any Object
List list = new ArrayList();
list.add("test");
list.add(4);
String s = (String) list.get(1); // Oh no! A runtime error...
System.out.println(s.length());

Instead, by providing a type parameter when we create the list, we can get the compiler to help us:

import java.util.List;
import java.util.ArrayList;
// This List can store only Strings
List<String> list = new ArrayList<>();
list.add("test");
list.add(4); // Now the compiler will fail here...
String s = (String) list.get(1);
System.out.println(s.length());

Remember: runtime errors cause things to fail right in the user’s face. This is not good! Compiler errors, in contrast, must be caught in development. So transforming runtime errors to compiler errors helps us produce more correct programs. This is good!

Generifying Our Classes
Generifying Our Classes

OK—so now we know how to use classes that accept type parameters. But how about using the in our own classes? This turns out to not be too hard! Let’s explore together.

To start, let’s design a class that does not support type parameters:

public class LastN {
}

Next, let’s look at how to add a generic type parameter to our example above. This will allow the compiler to ensure that all of the values added to LastN are the same type!

public class LastN {
}

Compiling Generic Classes
Compiling Generic Classes

There are a few important things to understand about how Java compiles generic classes. First, the type parameter is not a variable. You can use them in most places that you would normally use a type, but you can’t assign to them, or use them in a constructor call:

public class Example<E> {
private E value; // This is fine, since E replaces a type
public Example() {
E = String; // But you can't reassign a type parameter
value = new E(); // Also doesn't work, since maybe E doesn't have an empty constructor?
}
}

One useful way to think about what happens when your program is compiled is that the compiler replaces the type parameters with types used in your code. So, given this generic class:

public class Example<E> {
private E value;
public Example(E setValue) {
value = setValue;
}
}
Example<String> example = new Example<>("test");

If I create a Example<String>, it’s almost as if I had written this code:

public class Example {
private String value;
public Example(String setValue) {
value = setValue;
}
}
Example example = new Example("test");

Let’s talk through a few more examples together:

Type Parameter Naming Conventions
Type Parameter Naming Conventions

You can use any name to name your type parameters. But Java has established some conventions. Specifically:

Parameterized Interfaces
Parameterized Interfaces

Just like Java classes, interfaces can also accept type parameters. That includes one interface that we are fairly familiar with by now!

public class Dog {
private String name;
public Dog(String setName) {
name = setName;
}
}

One Big Generic Gotcha
One Big Generic Gotcha

You may have noticed above that when we implemented LastN we used a List rather than an array. That wasn’t an accident! Let’s see why and what problems arise with generic arrays.

public class LastN {
}

Bounded Type Parameters
Bounded Type Parameters

(What follows is bonus bonus material, but very cool!)

Let’s consider another example where we’d like to use generics:

public class Maximum<E> {
private E maximum;
public Maximum(E setMaximum) {
maximum = setMaximum;
}
public void add(E newValue) {
// Change the maximum if newValue is bigger
}
}

Let’s explore how we can complete the example above using another feature of Java’s generics system: bounded type parameters.

public class Maximum<E> {
private E maximum;
public Maximum(E setMaximum) {
maximum = setMaximum;
}
public void add(E newValue) {
// Change the maximum if newValue is bigger
}
}

And There’s More…
And There’s More…

This lesson has barely scratched the surface of what is possible with Java generics. If you want to learn more, this is a good place to start.

Generics in Javadoc
Generics in Javadoc

You regularly see generic type parameters in Javadoc. Now that we’ve discussed a bit about how to use type parameters in our own code, we’re more prepared to understand documentation for classes that use them. Let’s look at one familiar example together and discuss how to identify and interpret these type parameters.

Homework: BinaryTree Balanced

Created By: Geoffrey Challen
/ Version: 2020.11.0

Let's determine if a binary tree is height balanced. A tree is height balanced if the height of any node's two subtrees (right and left) never differ by more than 1.

Provide a public class named BinaryTreeBalanced providing a single public class method named isBalanced. isBalanced accepts a BinaryTree and returns true if the tree is balanced, and false otherwise. If the passed tree is null, you should throw an IllegalArgumentException.

A few hints on this problem:

  • Your main entry point method will probably need to start the recursion using a private helper method, because the main method needs to throw on null which you want to handle as a valid base case in your recursion
  • This helper method will probably have the same arguments as the main method, so you'll need to change something else to make sure that the method signature is different and the compiler can determine which one you want to use
  • You will probably need a second helper method implementing tree height that you call in your primary recursive method

More Practice

Need more practice? Head over to the practice page.