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.

Note: We leave the table of contents in full, but will not fill in the relevant information until it has been taught in lecture.

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

Recursion is a strategy of programming that uses a solution that’s determined in terms of itself. The recursive answer to the question of how long a given String is might be “one character longer than the length of this String without its first character.” This might seem like an unhelpful answer, but if we combine it with the knowledge that an empty String has a length of 0, then we’re actually approaching a solution. How long is "Java"? Well, it’s one character longer than "ava", which itself is one character longer than "va", which itself is one character longer than "a", which itself is one character longer than "", which has a length of 0 characters. All together, that means that "Java" is 1 + 1 + 1 + 1 + 0, or 4 characters long.

Here’s a look at the above algorithm written in Java.

public static int myLength(String s) {
  if (s.equals("")) { //base case
    return 0;
  }
  return 1 + myLength(s.substring(1)); //recursive case
}

Essential Components of a Recursive Algorithm

Tracing Recursion

Any time a recursive call is made, that call must be fully evaluated before the rest of the function can evaluate. This holds true for the successive recursive calls too, leading to multiple calls awaiting execution on a “call stack” that Java manages for us.

You can explore the execution of a recursive fibonacci function here.

Unit Testing

Unit testing is an essential practice for efficient and correct programming. It is useful for helping to define expected behaviors and it allows a programmer to check for correctness in the work that they’ve done.

In order to write unit tests in Java, we make use of JUnit.

Essentials of a Test Case

A test case is a way of testing the behavior of a small unit of code, typically a function, when run with a particular input (or set of inputs). Thus, the essential components for defining any test at a high level are:

  1. Inputs. These are the pieces of data that you’d like to see how your code handles. If you’re unit testing a search function, your input would be some particular array of Strings and a target String.
  2. Expected Output. This defines the value that your unit of code should evaluate to or return after being run on your chosen inputs. If your inputs to search were String[] arr = {"Harry", "Jules", "Michael"} and String target = "Jules", then the expected output of search(arr, target) would be 1, which is the index at which "Jules" can be found in the input array. You determine the expected output for a test by manually examining the inputs and the desired behavior of the function; you do not actually run the unit of code to generate this value!
  3. Actual Output. This is the actual value that your unit of code returned after being run on your chosen inputs. The actual output is evaluated by running the unit of code you’re testing on the inputs that you chose. That is, int actual = search(array, target) in this case.
  4. Assertion Statement. The assertion statement is the part of the JUnit test case that checks to see if the expected output matches the actual output. There are actually very many assertion statements one can use, but we mostly focus on assertEquals. That is, the assertion statement for the searching example might then look like assertEquals(expected, actual). In fact, you should usually provide a String as well that explains the purpose of the test case. That is: assertEquals("searching for Jules in array {Harry, Jules, Michael}", expected, actual).

Anatomy of a JUnit Test File

import static org.junit.Assert.*;
import org.junit.*;

public class SearchingTest {
    @Test
    public void TestSearchJulesInArrayWithJulesOutputs1() {
        String target = "Jules";
        String[] array = {"Harry", "Jules", "Michael"};
        int expected = 1;
        int actual = Searching.search(array, target);
        assertEquals("searching for Jules in array {Harry, Jules, Michael}", expected, actual);
    }
}

Of note:

Passing and Failing Tests

If a test is shown to pass, that means the expected output matched the actual output. Note that this doesn’t necessarily mean that your code is absolutely correct! You could have written the test incorrectly, or you could have chosen an input that works while most other inputs would cause the test to fail.

If a test is shown to fail, that means the expected output did not match the actual output. This might mean that there’s a bug in the unit of code you’re testing, or it might mean that you made a mistake when writing your test.

Testing helps you sniff out when your code isn’t working properly, but without a careful eye and a broad set of tests, it’s possible to miss what you’re looking for. Test carefully, and test widely.