Skip to main content

Introduction

This resource is presented as list of topics, functions, and patterns that we’ve encountered so far in CIS 110. It is reference material, and not a definitive textbook.

Please note that this resource is not

Here are a handful of good ways to use this document:

You might notice that some text looks like this. When something is written in this way, it’s information that you might be interested to learn but that you’re not required to know.

Table of Contents:


Incomplete List of Functions & Operations We Have Used


Basics of a Java Program

Writing & Running Code

The main Method & args

This is the entry point to a program. That is, when you run java Apples fuji macintosh pink-lady, you’re doing the following:

Code Blocks

Java programs are essentially sequences of nested code blocks. Code blocks are groups of lines of code that live between an open brace (this character: { ) and its corresponding close brace (this character: }). Note that a block of code does not necessarily end at the first } character following a { character in the case of nested code.
An illustration follows, with each comment specifying which code block that line would belong to. Note the fact that code blocks can nest, and how this is represented here with the decimal notation (block 1.2.1 is the first block inside the second block inside the first block, for example).

public class CodeBlockExample {
  // BLOCK 1
  public static void main(String[] args) {
    // BLOCK 1.1
    for (...) {
      // BLOCK 1.1.1
    }
    // BLOCK 1.1
    while (...) {
      // BLOCK 1.1.2
    }
    // BLOCK 1.1
  }
  // BLOCK 1

  public static int tester() {
    // BLOCK 1.2
    for (...) {
      // BLOCK 1.2.1
    }
    return 0; // BLOCK 1.2
    // BLOCK 1.2
  }
}

Drawing

A complete reference to drawing with PennDraw is already available on the course site. Some key points:

Variables

What A Variable Is

Variables are just names for individual pieces of data in Java. You provide the name and type of a variable to Java, and Java will keep track of that name for you. You can then assign (and reassign) data to the variable for later use. Variables in Java are mutable, meaning that you are able to change the values that the variables contain. This is the big reason why they are so useful!

Declaring a Variable

The syntax for declaring a variable is a two part statement: type name, where type is the data type of the variable and name is the name of the variable. When a variable is declared without having a value assigned to it, the variable will have a default value. The default value depends on the data type that variable, but the result is that whenever you reference the variable after it is declared, it will have some (potentially null value). Once a variable has been declared, it is not possible to declare a variable of the same name in the same scope.
Some examples:

int x;
double y;
String lastName;
char middleInitial;
boolean isFinished;

Assigning a Value to a Variable

This is done with the use of the assignment operator, =. The syntax for assigning a value to a variable is: providing the name of the variable, followed by the assignment operator, followed by an expression that will be the new value of the variable (examples to follow). It is possible to assign a value to a variable at the moment it is declared, although this is not required. This means that it is possible to assign to a variable when or after it is declared, but it is not possible to assign a value to a name of a variable that has never been declared. Variables can also be assigned in terms of themselves, and this is a powerful asset in the case of trying to count or store intermediate results of computation.
Some examples:

int age = 53;
double y;
y = age * 2.5;
String lastName;
lastName = "Fouh";
middleInitial = (char) (middleInitial + 2);
boolean isFinished;
isFinished = !isFinished;

Variable Scopes

The scope of a variable is the set of lines in the Java program where Java will recognize the name of that variable. A variable is generally in scope for the entire code block in which it is defined following its definition, and it is also in scope for all code blocks nested inside after its definition.

public static void main(String[] args) {
  // x is not in scope
  System.out.println("not yet");
  boolean x = true; // x begins scope here
  while (x) {
    // this is a nested code block within the previous one
    x = false; // x is in scope here
    System.out.println("Just once.");
  }
  // we're back in the original block; x is in scope
  System.out.println(x);
} // x's original code block ends.
// x is not in scope here

public static int tester() {
  // x is not in scope here
}

Data Types

What a Data Type Is

In Java, every expression that stores or calculates to a value has a data type. A data type is a formal classification for how Java represents and handles any piece of information. This is a broad and almost philosophical definition. A more practical one looks like this: a data type in Java is a definition of the range that a particular value can belong to, and what operations are valid to perform on that value. Operators and functions in Java specify what data types they can work with. If you are not careful with tracking the data types of the inputs to different functions and operators, you can end up with code that won’t compile.

Primitive Data Types in Java

There are a number of primitive Data Types available in Java. To say that a data type is primitive means that the type is predefined by Java to be included in the language. In this class we focus on studying four primitive data types: boolean, char, int, and double. In addition, we are also heavily interested in the String Data Type which, while not technically a primitive one, can be used in many similar ways to the primitive data types.

Table of Primitives

Data Type Range Default Usage Key Operations (not exhaustive)
int [-32,768, 32,767] 0 integers +,-,*,/,%,>,<,<=,=>,==,!=
double a very negative number up to a very positive number 0.0 numbers, including fractional parts +,-,*,/,%,>,<,<=,=>,==,!=
boolean {true, false} false truth values ==, !=, !, && , ||
char [0, 65535] 0, or '\u0000' single characters of text (letters, numbers, punctuation, spaces) represented as small integers similar to int

Strings

Strings are effectively sequences of chars. The default value of a String is null (more on this later). Strings are how we represent text and language in Java. The operations defined on Strings are numerous, but an important basic one is the + operator, which represents concatenation when applied to one or more Strings.

Strings are immutable in Java, meaning that when you perform some operation on a String, an entirely new String will be returned.

String first = "Maya";
String second = first.toUpperCase(); // Capitalize all letters in string

System.out.println("first is still " + first);
System.out.println("second is " + second);

Running the above code demonstrates that String first was unchanged after the call to toUpperCase():

> first is still Maya
> second is MAYA

Some Important Operators & Tools

Many of the operators available for each data type will prove useful at some point. This section represents a brief overview of some of these, as well as other function tools. For a more complete overview of the behaviors of operations among different types, make sure to look at the exercises at the end of this presentation.

Relational Operators

All of >,<,<=,=>,==,!= are operators that accept two values of primitive data types and return a boolean result. These comparison tools are often essential for defining the control flow of your programs in if statements and in loops.

The multifaceted +

Mod (%)

Another name for mod would be “remainder after division”. Major uses for this operation include constraining ints to wrap around within a certain range, testing for whether a number is even or odd, or checking to see if a number is a multiple of another. Remember that % with one negative argument leads to a negative result.

Casting

Casting is the process of interpreting a value of some data type as a different data type. In this course, when we refer to casting we are referring to the manual process of converting from a type with a wider range of values into a type with a smaller range of values.

int justTheInt = (int) 51.564;
char asLetter = (char) justTheInt;

Although we usually refer to it as “type promotion” in CIS 110, some sources will refer to conversions going in the other direction as casting, too. This is the automatic process of converting a char into an int or an int into a double that Java will do for you. This works automatically when the original type is “smaller” than the new, “larger” type.

Parsing

Parsing is the process of interpreting the value of a String as a representation of a value in some other data type. The primary ways that we use parsing with respect to data types are with the following two examples:

int fromString42 = Integer.parseInt("42");
double fromStringThreePointOneFour = Double.parseDouble("3.14159265");

Math

The basic arithmetic operators represent only a small fraction of the math that you can do in Java. Using the Math library, operations like abs, min, max, trigonometric functions, exponentiation, round, and random are available. There’s a good listing of these at the end of this presentation.

Conditionals

Conditionals are the simplest means we have to influence control flow in our programs. By default, Java programs execute sequentially, with each line being run in the order it’s written. When you add conditionals to your program, you decide among several options for which lines of code to be run next based on the state of some specified values.

When to use a Conditional

if, else if, and else

Control flow can be influenced using if, else if, and else statements.

if (x > y || a == c) {
  System.out.println("Hello");
} else if (x == y) {
  System.out.println("Goodbye");
} else if (x < y) {
  System.out.println("So long");
} else {
  System.out.println("Farewell");
}

// These next if statements are unrelated to the first chain and to each other.
// Either of them will be run when their conditions are true, independent of prior conditionals.
if (u && i) {
  return 10;
}
if (me && u) {
  return 20;
}

Switch Statements

Switch statements are conditionals designed for the purpose of checking the value of some variable or expression against several enumerated outcomes. We do not require you to use a switch statement in this class, although you are certainly welcome to. An example that demonstrates the complete syntax of a switch statement is presented here.

switch (name) {
  case "Harry": case "Eric":
    title = "Professor";
    break;
  case "Jules": case "Michael": case "Gian": case "Ben":
    title = "Head TA";
    break;
  default:
    System.out.println("Name not matched, default case used");
    title = "TA";
    break;
}

This example checks to see if name is the name of a professor, a head TA, or a TA.

Loops

Loops are the way that we implement iteration in Java. They are the tools by which we are able to write programs that perform one task many, many times. It is from this ability to write in repetition (with controlled variation) that the power of computation emerges.

Rules of Iteration in Java

Any loop that we use in Java is controlled by three components:

  1. An initialization of a loop control variable (or perhaps several variables).
  2. A termination condition defined over the control variable(s). When the loop control variable reaches a particular value or state, the termination condition will evaluate to false and the iteration of the loop will stop.
  3. A modification of the loop control variable(s). By modifying the control variable(s), we can eventually reach the state outlined in the termination condition, indicating that the loop should cease repeating.

The While Loop

As outlined in the previous section, a while loop must include an initialization component, a termination condition, and a modification step.

int i = 40; // initialization (i is the loop control variable)
while (i > 0) { // termination condition
  System.out.println(i);
  i--; // modification step
}

boolean hasPressed = false; // initialization (hasPressed is the loop control variable)
while (!hasPressed) { // termination condition
  PennDraw.filledRectangle(...);
  if (PennDraw.mousePressed()) {
    hasPressed = true; // modification
  }
}

Some takeaways from these two examples:

The termination condition is checked before every iteration of the loop, including before the (potential) first iteration.

The For Loop

A for loop must again include an initialization step, a termination condition, and a modification. Unlike a while loop, though, a for loop has space for these steps built into its very syntax.

//  |INITIALIZATION   | TERMINATION COND. | MODIFICATION|
for (String line = "."; line.length() < 10; line += '.') {
  System.out.println(line);
}

//  |INIT.         | TERM. |  MODIF.|
for (double k = 100; k == 1; k /= 10) {
  System.out.println(k);
}

//  |INIT.    | TERM.          | MOD|
for (int i = 0; i < args.length; i++) {
  System.out.println(args[i]);
}

A variable initialized during the intialization step is in scope only in the body of the loop. The termination condition is checked before every iteration of the loop, including before the (potential) first iteration. The modification step is performed after each iteration.

Your Loop of Choice

It is possible to convert a loop of one variety into an equivalent loop of the other variety. This is always true, although it usually is somewhat more natural to use one type in certain circumstances.

It’s often more natural to write a while loop when:

It’s often more natural to write a for loop when:

Perhaps the most salient point here: if your problem can be phrased as “examine every x in y” or “for every element of this array/this string, do something”, this is a good sign that a for loop would fit nicely. These are just general guidelines, and you should keep in mind that you can always make either loop work.

Break and Continue

break is a statement that, when executed in the body of a loop, causes that loop to immediately stop executing. The current iteration is halted, and no further iterations are executed. The control of the program resumes immediately after the body of the loop.

continue is a statement that, when executed in the body of a loop, causes that iteration of the loop to immediately cease. In the case of the for loop, this causes the modification step to be applied. As long as the termination condition has not been met, the next iteration of the loop will commence.

break exits a loop entirely, continue skips to the next iteration.

Arrays

What is an Array?

In Java, an array is an fixed-length, indexed sequence of values that are all of the same type. Each value in this sequence can be modified and overwritten. An array is a data type of its own, and the type of an array is different from the type of the elements it contains. In particular, int x = 4; defines a variable of type int, whereas int[] xs = {3, 4, 5}; defines a variable of type int[] (note the difference in brackets).

int[] ages = new int[320]; // an array of 320 ints
ages = new int[60]; // a new array of 60 ints. It is valid to store a new array in ages.

double[] xPositions = new double[10];

String[] suits = {"spades", "hearts", "diamonds", "clubs"};

Arrays Are Fixed-Length

When you create an array, you must also specify its length. This can be done with implicit initialization (e.g. new char[4]) or explicit (e.g. {4, 3, 2, 1}). In both of these example cases, the arrays have a length of 4. It is not possible to add another element to this array without first creating a new, larger array.

Arrays Are Indexed

Arrays in Java are 0-indexed, meaning that we refer to the first element of an array as being at index 0. Using .length on the end of the array, we have an expression for the length of an array. If we have some array arr, and we say that n is equal to arr.length, then each integer between 0 and n (but not including n) is a valid index for that array.

The syntax for indexing into arrays in Java is to append [index] to the end of a variable containing an array. Taking our example from above with suits:

String[] suits = {"spades", "hearts", "diamonds", "clubs"};

Then the following table represents the indices of suits:

Index Value
suits[0] "spades"
suits[1] "hearts"
suits[2] "diamonds"
suits[3] "clubs"
suits[4], suits[5], … Leads to ArrayIndexOutOfBoundsException
suits[-1], suits[-2], … Leads to ArrayIndexOutOfBoundsException

Arrays Contain Values Of One Type Only

When an array is initialized without explicit values, we use the syntax new type[length], where type represents some valid data type (String, boolean, etc). The array that is created thus has the data type type[], which reads in English as “an array of types” or a “type array”. This new array can only contain values of data type type from now on.
This also means that when initializing arrays with explicit values, those explicit values must all be of the same type.
When declaring a variable that will contain an array, you must declare the variable of the correct type. Refer the previous examples under “What is an Array?”.

Manipulating Arrays

Presented here are a few examples of how to manipulate arrays. These are taken directly from the website of the course’s textbook. All credit to Sedgewick and Wayne. Refer to the booksite’s section on arrays for a fuller listing of patterns related to using arrays.

Initializing entries of an array to random values

int n = 5;
double[] a = new double[n];
for (int i = 0; i < n; i++) {
    a[i] = Math.random();
}

Printing the values of an array

Assumes the existence of some array a, like the one from the first example.

System.out.println("a[]");
System.out.println("-------------------");
for (int i = 0; i < n; i++) {
    System.out.println(a[i]);
}
System.out.println();
System.out.println("a = " + a);
System.out.println();

Finding the maximum value in an array

Assumes the existence of some array a, like the one from the first example.

double max = Double.NEGATIVE_INFINITY;
for (int i = 0; i < n; i++) {
    if (a[i] > max) max = a[i];
}
System.out.println("max = " + max);

Finding the sum & mean of an array of doubles

Assumes the existence of some array a, like the one from the first example.

double sum = 0.0;
for (int i = 0; i < n; i++) {
    sum += a[i];
}
System.out.println("average = " + sum / n);

Functions

Functions (or methods) are the basic building blocks of Java programs. main is a function, Math.abs is a function, PennDraw.filledRectangle is a function, and you can write your own functions. In CIS 110, we use the terms “function” and “method” to refer to the same thing. Both the term “function” and the term “method” have broader and diverging meanings outside of the scope of the course, but in CIS 110 we can use them interchangably.

In JavaScript, it’s not the case that every function is a method although the reverse is basically true. Going by the definitions outlined in, say, Cornell’s CS1130 Course, Java has it so that not every method is a function even though every function is indeed a method. This is vague notation.

Functions in Java are similar to functions in mathematics. Fundamentally, functions are tools that take some inputs, do some work based on the inputs, and produce some output.

Anatomy of a Function

A function has…

  1. a header. A header consists of several pieces:
    1. One or more optional modifiers, like public and static. You should continue to include these words at the beginning of your functions, even though we haven’t covered their meanings yet.
    2. A return type. This specifies the data type that the function is guaranteed to output.
      1. If a function has a return type of int, for example, the function is guaranteed to return an int.
      2. It is possible for a function to have a return type of void, which is a guarantee that the function will not return any data at all.
    3. A name. This specifies how you can refer to this function and use it later.
    4. A list of arguments type & name pairs
      1. Each argument must have its type specified first
      2. The name of the argument in the header puts variables with these names in scope in the body of the function.
  2. a body. The body of a function is a just another block of code where execution starts at the top.
  3. one or more return statements.
    1. return is a keyword in Java that indicates that the function should stop executing.
    2. return is coupled with a value. For example, a full return statement might look like return true;, return x + 4;, or return str.toUpperCase(). In each case, the type of the value being returned must match the return type of the function.
    3. A function with a non-void return type must end with a return. Reaching the end of a function without hitting return will result in an error.
    4. The only exception to these rules are in the case of a function with return type void. In this case, return cannot be paired with a value, since void indicates that no data is returned. In functions with return type void, it is possible to use no return statements at all, and when Java reaches the end of the function body, Java will return implicitly.

To illustrate these rules, we examine a searching function:

/**
 * indexOfValue attempts to find value in an array of chars
 * called list. If we find value, return its index. If we
 * don't find value, return -1 to indicate that value is 
 * not in list.
 */
public static int indexOfValue(char value, char[] list) {
  for (int i = 0; i < list.length; i++) {
    if (list[i] == value) {
      return i;
    }
  }
  return -1
}

Functions and Control Flow

Java programs start executing at main when run from the command line, as we do in CIS 110. Control is passed between functions with function calls, which happen where we write the name of the function followed by a list of input argument values in parentheses.

Considering the example below, we have a function public static int addTwo(int x). We can call addTwo like so: addTwo(4). In this case, 4 is the value that will be bound to the variable x at the start of addTwo’s body.

The execution of the most recent function call must be completed before control is passed back to the point where that call was made. This results in a call stack.

public class Functions {
    public static void main(String[] args) {
        System.out.println("Starting main.");
        int six = addTwo(4);
        System.out.println("four plus two is " + six);
        int eight = addThree(5);
        System.out.println("five plus three is " + eight);
        System.out.println("Leaving main");
    }

    public static int addTwo(int x) {
        System.out.println("Starting addTwo with input " + x);
        System.out.println("Leaving addTwo");
        return x + 2;
    }

    public static int addThree(int x) {
        System.out.println("Starting addThree with input " + x);
        int temp = addTwo(x);
        System.out.println("Leaving addThree");
        return temp + 1;
    }
}

The output:

$ java Functions
Starting main.
Starting addTwo with input 4
Leaving addTwo
four plus two is 6
Starting addThree with input 5
Starting addTwo with input 5
Leaving addTwo
Leaving addThree
five plus three is 8
Leaving main

Observe how this output illustrates the call stack. main is called first. From main, we call addTwo, which returns 6 without calling another function. Then from main, we call addThree, which in turn calls addTwo. addThree’s call to addTwo is resolved before addThree returns the result back to main. Then, main finishes execution without a return statement, which is acceptable because main always has return type void.

Functions and Variable Scope

When a function is called and its body is executed, Java creates a new scope. Generally speaking, the only variables that will be in scope at the start of the function execution will be the input arguments to that function.

public class Scope {
  public static void main(String[] args) {
        int a = 50;
        int b = 100;
        int c = 200;
        System.out.println("before newScope, main's c = " + c);
        newScope(a, b);
        // the variable c declared in newScope is out of scope
        // back here in main.
        System.out.println("after newScope, main's c = " + c);
    }

    public static void newScope(int x, int y) {
        System.out.println("STARTING newScope");
        // x and y are in scope
        System.out.println("x = " + x);
        System.out.println("y = " + y);

        // a, b, and c are not in scope here.
        // we can declare a variable c here
        int c = 40;
        System.out.println("newScope's c = " + c);
        System.out.println("ENDING newScope");
    }
}

Output:

$ java Scope
before newScope, main's c = 200
STARTING newScope
x = 50
y = 100
newScope's c = 40
ENDING newScope
after newScope, main's c = 200

Recursion

The Codio lecture notes for this topic are very detailed, and they’re even interactive! You should refresh yourself on rules about recursion there.

Object Oriented Programming

Object Oriented Programming is a paradigm that views programs as collections of entities called objects that are comprised of and interact with each other.

Contrast this with our previous notion of programming, which is called procedural programming. This just views programs as lines of code to be run, one after the other, with functions representing procedures that implement a specific task. Procedural programming is actually an effective way of thinking about drawing as we did it in the early parts of the semester: it’s not not necessary to view a program that sketches a scene as one made of many objects interacting with each other. Sometimes code is, after all, just a list of instructions.

Objects vs Classes

Objects in Java are representations of real-world entities. Objects have state and behavior, and these are defined by classes. We say that objects are individual instances of a class. Therefore, a class is a blueprint for how to create objects of that underlying type.

Here we have a class called Soda.

public class Soda {
  private String container;
  private String flavor;

  public Soda(String container, String flavor) {
    this.container = container;
    this.flavor = flavor;
  }

  // rest of class omitted for space
}

In another program, SodaFountain.java, we might have the following lines:

Soda sprite = new Soda("glass bottle", "lemon-lime");
Soda coke = new Soda("can", "hard to describe");

In this case, we say that sprite and coke are objects, and that Soda is the class from which they have been instantiated.

Writing Classes to Define Object State & Behavior

Now that we have the difference between a class and an object, it’s time to think about how classes are written. Classes are made up of fields, methods, and (optionally) constructors.

Fields

A class’ fields represent the data that an instance of the class can store. These are effectively variables that belong to instances (objects) of this class. To that end, we sometimes refer to them as instance variables. Fields belong at the top of a class by convention.

public class Tree {
  public int age;
  private String species;
  private double height;
  private double width;

  // rest of class omitted for space
}

In this class Tree, we have four fields. These are: age, species, height, and width. Observe that they have a type associated with them just like any other variable. Additionally, they have a privacy modifier (in this case, all but age are private). By convention, fields will normally be private, and this is explained further in the section on Encapsulation.

A field can be accessed inside of a class definition using its name alone, or with this as a way to specify that you’re referring to the field in the case that multiple variables in the same scope share the same name (this is called shadowing).

age = age + 34;
// if the RHS variable height shadows the field named 
// height, this example stores the value of the local
// variable height in the field called height.
this.height = height 

Outside of the object’s class, only its fields are directly accessible without the use of getter methods. Imagining we have some function generateTree that returns some Tree object, consider the following:

Tree t = generateTree();
System.out.println(t.age); // this compiles
System.out.println(t.width); // this does not

Methods

Methods are the functions of a class and they represent what the object can do and how it can change. These follow the rules of functions as we have discussed before. When viewing methods from the object oriented approach, it’s important to recall that the methods in a class have access to that class’ fields. Continuing our Tree example from before:

public class Tree {
  // ... same field definitions from above

  // estimate volume of trunk using cylinder formula.
  public double estimateTrunkVolume() {
    return Math.PI * (width / 2) * (width / 2) * height;
  }
}

Observe that the estimateTrunkVolume method of a Tree has access to the width and height fields of the Tree class. These variables are accessible in the body of the method without being passed in as input arguments.

Methods will be, in the context of this class, either public or private. Like with fields, private methods are only accessible inside the class itself whereas public methods are available from any instance of the class in another file.

If a method is public, it can be called from any instance of the class:

Tree narrow = generateTree();
double volume = narrow.estimateTrunkVolume();

Methods with private designation are generally helper methods. For better understanding of public vs. private in methods, refer to notes on Encapsulation.

Constructor

Constructors are the tools that we use to actually instantiate an object from a class. Constructors are essentially special methods that take the name of the class itself. When a constructor is called with the new keyword, Java will provide the space in memory to create a new instance of the desired class and it will use the constructor to set up the new instance in the specified manner.

Let’s return once more to our Tree example:

public class Tree {
  public int age;
  private String species;
  private double height;
  private double width;

  public Tree(int age, String species, double height, double width) {
    this.age = age;
    this.species = species;
    this.height = height;
    this.width = width;
  }
}

Our constuctor is written right beneath the class’ fields (this is conventional). Note that it looks like a method named Tree without a specified return type. This is what indicates to Java that this thing is a constructor: no return type and sharing its name with the class itself. Notice how the input arguments to the constructor match the fields of the class: this is a typical pattern, where we have a constructor that assigns each of its inputs to the corresponding fields of the new object. A constructor can take fewer input arguments than fields in the class or vice versa. A constructor does not need a return statement.

Principles of Object Oriented Programming

Encapsulation

Encapsulation is the principle that an object should hide the details of how they work by providing a barrier between the interface of the object (what it can do) and its implementation (how it does what it does). The concept of separating implementation from interface is central to the notion of Abstraction as well, but Encapsulation focuses primarily on the tools that we use to hide the machinery of an object from the client that uses it.

Encapsulation is achieved in Java by use of Access Control, which itself is defined with visibility modifiers public and private. When a field or method is tagged with private, that field or method will only be accessible from within the file where it is defined. public fields and methods are, by contrast, available in any other file.

The public interface of a class is therefore the set of fields and methods available in the class that are defined as public. These are the methods that a user can call at will. If any fields are part of the public interface, then a user can access & modify these directly.

public class Person {
  public int age;

  public Person(int age) {
    this.age = age;
  }
}

In this class Person, the field age would be considered part of the public interface. This is concerning, though, because a client using Person objects could do the following:

Person harry = new Person(24);
harry.age = -20;

It doesn’t make sense for a Person to have a negative age, so a Person object shouldn’t be able to have their age be 0 or less. Leaving age as public allows anyone with access to a Person object to put that object into a nonsensical state. Thus, it makes better sense to do the following:

public class Person {
  private int age;

  public Person(int age) {
    this.age = Math.max(0, age);
  }

  public int getAge() {
    return this.age;
  }

  public void ageOneYear() {
    this.age += 1;
  }
}

By keeping age private, modifying the constructor, and adding methods getAge and ageOneYear to the public interface, we keep age as a field that’s just part of the implementation. If a user wants to see a Person’s age, they can use the getAge method (a getter) to see it. If they want to change it, we are specifying with ageOneYear that a Person can only age one year at a time, and our constructor makes it so that making a Person with a negative age is impossible.

Getters and Setters

Getters and setters are special types of methods that help implement Encapsulation in a class. A getter is a method that returns the value of some data stored by an instance of the class. Note that this could be returning a field directly, or it could return some piece of data stored implicitly by an instance.

public class Circle {
  private double radius;

  // Constructor omitted for space...

  public double getRadius() {
    return this.radius;
  }

  public double getDiameter() {
    return this.radius * 2;
  }

  public double getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

In this Circle example, we have defined three getters: getRadius, getDiameter, and getArea. Notice that the only field of the class, radius, is private. The only way to access this data is therefore using getRadius. There are no fields for the diameter or the area of the shape, but we might also call getDiameter and getArea getters for their corresponding data anyway.

Setters work similarly, but they provide controlled means of updating the values of data stored in an object.

public class Circle {
  private double radius;

  // Constructor & getters omitted for space...

  public void setRadius(double radius) {
    radius = Math.abs(radius);
    this.radius = radius;
  }
}

In this example, setRadius is a setter for the radius field that allows us to update that piece of data without risking the client filling the field with a negative (nonsensical) value.

Careful choices for public and private on the fields and methods of a class combined with getters and setters for private fields allow you to define an interface that hides and protects the implementation that you’ve chosen.

Abstraction

Abstraction is the process of representing real-world entities with properties relevant to the client that will be using it. The properties that we choose to represent an entity become the fields of the object. These chosen properties, along with our view of the stakeholder’s desired use of the object, then help to define the interface that will be available to users of the object.

Abstraction is fundamental to solving interesting problems with Computer Science: it is not possible to directly manipulate a Movie, a Movie Ticket, or a Movie Theater with Java code, but we can keep track of abstract instances of these things to solve problems about them (selling and tracking tickets for film screenings, for example).

When designing a class, we use abstraction to make conscientious choices about how a class will represent an entity.