Image of Austin Gatlin's face
August 24, 2017 (last updated March 6, 2022)

Prelude to Java


This was ported from an old Medium account. I have only made minor edits, and any mistakes from when it was first written might still remain.

This is an introduction to Java for programmers that are not familiar with types, interfaces, etc. If you are new to programming, or coming to Java from a dynamic programming language background. This could be a helpful introduction to some concepts in Java.


The fundamental problem when learning something new is unknown unknowns. When you don’t know what you should know, things can get difficult. In what follows, I introduce some unknown unknowns I wish I knew as I began my Java adventures.

The topics I will address are as follows:

  • Java is not a scripting language
  • Types
  • Interfaces
  • Annotations
  • Generics
  • Use an IDE

Java is not a scripting language

"Scripting language" is not well defined, but whatever it is, Java ain't it. If you want to write a one line "hello, world" Java script… use Javascript. (Not a coincidence, by the way.)

An example of a scripting language is Ruby. You can print "hello, world" from a terse, one-line file. You can even do it in a short line of Bash. Java is not so simple. First, download the SDK from Oracle. Choose Java SE (the SE stands for Standard Edition). Next, write a file called

class Hi {
    public static void main(String[] args) {
        System.out.println("hello, world");
  • Public means the main method is accessible anywhere.
  • Static means the main method is a class method.
  • Void means the main method does not return a value.

Next, compile it. Run javac (the c stands for compiler). It generates class files from java files. In our case, the file contains a single class: Hi. Thus, javac will generate Hi.class. Once we have our class file, we can use the java command to execute it. Run java Hi and greet the world.


Types are a way to identify objects you’re working with. For example, Pizza is the type of myPizza in the follow code:

Pizza myPizza = new Pizza("pepperoni");

This implies the existence of a Pizza class, which could look as follows:

public class Pizza {
    private String topping; public Pizza(String topping) {
        this.topping = topping;
  • String is the type of the parameter topping.

Explicitly typed languages like Java can perform robust type checking at compile time to determine if your code is coherent. It will not catch all problems, but it does increase reliability at runtime when compared to languages that lack type-checking compilation steps.


Java interfaces specify some or all of the behavior of an object. For example, we could define a PizzaMaker interface that specifies a bakePizza behavior:

interface PizzaMaker {
    Pizza bakePizza(Pizza pizza)

Notice that this does not define how the bakePizza function is implemented. That is a job for the class implementing this interface. What the PizzaMaker interface does is state that something claiming to implement PizzaMaker must implement some function called bakePizza. For example, we could have a Chef class that implements PizzaMaker:

public class Chef implements PizzaMaker {
    public Pizza bakePizza(Pizza pizza) {
        [implementation code goes here]

(@Override is an annotation. We will discuss them later.)

If Chef did not define a bakePizza function, while still claiming to implement PizzaMaker, the compiler would error (or your IDE would notice the problem and warn you).

Interfaces seemed cumbersome to me at first, but I have since come to appreciate the rigidity and specificity they provide.

Here’s an example of the utility of interfaces. Let’s say we have a PizzaRestaurantManager object that is responsible for daily dinner preparations. On Saturdays a famous pizza chef bakes pizzas for the guests, while on every other day a bus boy just throws some frozen pizzas in the oven. Maybe on first approach you'd write a prepareDinner function that looked something like this:

class PizzaRestaurantManager {
    public void prepareDinner(
        DayOfWeek day,
        Chef chef,
        BusBoy busBoy
    ) {
        if (day == DayOfWeek.Saturday) {
        } else {

This code works, but it is violating the O and I in SOLID. You can refactor this code, leveraging interfaces to simplify the code. Let's use the PizzaMaker interface we defined earlier, and have both the Chef class and BusBoy class implement it. We can then refactor the code as follows:

class PizzaRestaurantManager {
    public void prepareDinner(PizzaMaker pizzaMaker) {

When you use this code, you can pass in Chef, or BusBoy, or any PizzaMaker-implementing class.

(As an aside, it should be noted that you cannot pass in a non-PizzaMaker object, even if it has a bakePizza method defined on it. The compiler will not allow it.)


Java annotations are syntactic metadata. For example, the @Override we saw earlier. The @ sign is what designates an annotation. Annotations can inform the compiler to perform additional actions, like checking for non-nullity: @NonNull String string;. They can generate code on your behalf @Getter(lazy=true) private final String firstName;. They can augment behavior during runtime as well, such as handle requests: @Controller class APIController {...}.

Custom annotations are used widely by frameworks and packages. The first two annotations used in the above paragraph came from project Lombok, and the last one came from the Spring framework. Java SE 8 also has many annotations out of the box.

A resource for learning more about annotations is the Java tutorial on annotations.


A generic is a type variable. Let’s imagine we want a list of waiters for our pizza restaurant, and the ability to add a new waiter to the list. We could implement a specific List interface:

public interface WaiterList {
    boolean add(Waiter waiter);

Or we could implement a more generic interface that can hold a list of any type, not just Waiter:

public interface List<E> {
    boolean add(E e);

The list is a generic type. More specifically, a generic interface. The <> wraps the type parameter section, inside of which is the type parameter E. (There can be multiple type parameters, e.g., Pair <A, B>.) The add function takes an object of type E, represented by the parameter e, and adds it to the list.

The generic list interface allows us to reuse it across multiple types. To use it, we replace E with the type we want a list of:

Waiter waiter1 = new Waiter("Alice");
Waiter waiter2 = new Waiter("Bob");
List<Waiter> waiters = new ArrayList();

Generics DRY up code, increasing maintainability and reliability.

A resource for learning more about generics is the Java tutorial on generics.

Use an IDE

Not all languages are alike. Different languages warrant different development environments.

I think some of my Java confusions stemmed from my Vim-loving resistance to IDEs. I didn’t want to learn some bloated editor just to type a line of Java. So, instead, I drowned for a day in bloated Vim plugins just to type a line of Java.

In the languages I knew prior to Java, inline method documentation that is triple the length of methods themselves, and million method classes were code smells. In Java, it’s not (or else the whole language smells). An IDE will help you navigate it all.

Java is manifestly typed and nominally typed. Big words aside, it means everything is typed, i.e., explicit typing.

The downside of explicit typing is you have the misfortune of one day writing something like this:

Map<String, Pair<Int, List<Optional<Set<Double>>>>> map;

The upside of explicit typing is the awesome powers IDEs have as a result.


As a parting gift, here’s something you will likely use in your future Java adventures: Arrays.asList().

Recall that List is an interface. ArrayList is a class that implements List. A helper class for ArrayList is Arrays. A helpful method in it is asList. It is a generic method:

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);

The T... notation denotes a vararg called a. The diamond notation <> in ArrayList<> represents type inference. Java will infer the type so it does not need to be specified (the type inferred is T).

You can use Arrays.asList to create lists of variable size:

List<Waiter> waiters = Arrays.asList(waiter1, waiter2, waiter3);

I hope you enjoyed this prelude to Java, and I wish you luck in your future Java adventures!