introsoftwaresoftware-terminalsoftware-text_editorsoftware-javaquickstartquickstart-jshellquickstart-outputquickstart-variablesquickstart-inputquickstart-data_typesquickstart-functionsquickstart-commentsvariablesvariables-creating_variablesvariables-assignmentvariables-namesvariables-papervariables-scopeioio-printftypestypes-numerictypes-numeric-integerstypes-numeric-floatstypes-numeric-charstypes-numeric-modulotypes-numeric-conversionstypes-stringstypes-booleanstypes-primitivesflowchartsflowcharts-sequentialflowcharts-decisionsifif-ifif-comparisonsif-elseif-nestedif-else_ifif-mistakesbooleanboolean-operatorsboolean-short-circuitearly_apiearly_api-mathearly_api-stringloopsloops-whileloops-doloops-countingloops-flagloops-forjshell-functionsjshell-functions-definejshell-functions-calljshell-arraysjshell-arrays-createjshell-arrays-accessjshell-arrays-iterationjshell-arrays-ref_typesjava-compiled_introjava-compiled_intro-program_structurejava-compiled_intro-compilationjava-compiled_intro-syntaxtemplates

Introduction

This is a collection of notes for students taking CSC110: Programming and Problem Solving. Along with the videos, these notes introduce most of the topics you'll be learning about in CSC110 and can serve as a reference when you want to revisit an older topic. This is not a textbook and will not cover the material as thoroughly as a textbook would. The syllabus recommends a free textbook if you would like to read a more detailed explanation of a topic we present here.

Software

This section explains some of the software you'll be using during the semester.

Terminal

A terminal is a program that allows you to type commands for your computer to execute and view the results as text. You can run other programs from a terminal, and it can serve the same role as many other programs you're used to using.

Terminal Programs

On Unix-like operating systems (such as Mac and Linux), you should be able to find the default terminal program by searching your applications for "terminal". This terminal is similar to what you'll see used in the videos. Many of these terminals run a program called bash or zsh underneath, and they should all accept the commands you see in the videos and other examples, so Mac and Linux users don't need to set up any additional terminal software.

On Windows, there are two built-in terminal programs, neither of which I recommend using:

  • cmd is the classic Windows Command Prompt. Most commands that work with bash/zsh will not work in the command prompt.
  • Powershell is a more advanced terminal for Windows that supports some of the commands you'll see used in bash terminals. However, it will likely not be able to run the test scripts you receive with your assignments and may not work with all of the commands you see us use.

There are several popular terminal emulators for Windows that emulate bash and allow you to run the same commands as a bash terminal. A good example is git bash, which is part of the Git for Windows project. If you are using Windows, you should install Git for Windows or another terminal emulator. This will allow you to run the test scripts for assignments and use the terminal commands you see demonstrated in class. It also includes the program git, which is used in some later CSC/SER classes.

Terminal Videos

Mark Lewis, the author of a textbook previously used for this class, has a playlist of videos covering how to use a terminal on YouTube. Watch these videos for a brief demonstration of the different terminal commands, and refer to the cheatsheet below if you would like a summary of useful commands.

Terminal Cheatsheet

Important terms/concepts:

  • A directory is a folder on your computer.
  • A path is the location of a file or directory. It is a series of directory names separated by forward slashes on Unix-like systems or backslashes on Windows. Examples:
    • /home/zach/Documents/Syllabus.pdf is the location of a file named "Syllabus.pdf". It is in a folder named "Documents", which is in a folder named "zach", which is in a folder named "home", which is at the root of the filesystem (indicated by the leading "/").
    • C:\Users\Zach\Documents\Syllabus.pdf is the equivalent path on Windows.
  • A relative path only tells you how to get to a file or directory from your current location. For example, if we were in the folder /home/zach, then the relative path to "Syllabus.pdf" in the previous example would be Documents/Syllabus.pdf or ./Documents/Syllabus.pdf. A ./ at the start of a path refers to your current location, so a path beginning with ./ is always relative.
  • Many commands will include placeholder text written in all caps. That part is not meant to be copied literally when you run the command. For example, you would type cd Documents to move to the "Documents" folder rather than literally typing cd PATH.
  • Some commands have flags or optional arguments, which are normally written as one or two hyphens followed by a word or letter. These allow you to enable certain settings when running a command or include additional optional information. Some examples are the -a in ls -a or the --version in java --version.

Navigation:

  • pwd: "print working directory", tells you which folder you're in
  • cd PATH: "change directory", moves you from your current location to the directory at PATH
    • cd on its own or cd ~ will move you to your home directory
  • ls: "list", lists everything in the current directory
    • ls PATH: lists the contents of the directory at PATH
    • ls -a: includes hidden items in the output

Viewing Files:

  • cat PATH: show the contents of the file at PATH
  • less PATH: view the contents of the file at PATH, but with more features. Press q to exit less and return to the terminal's command prompt.

Manipulating Directories:

  • mkdir PATH: creates an empty directory at PATH
  • rmdir PATH: deletes the directory at PATH, but only if it is empty

Manipulating Files (these are dangerous and can permanently delete files, so use with care!):

  • rm PATH: delete the file at PATH. It's permanently gone, not moved to the trash or recycle bin.
  • mv PATH DESTINATION: move the file or directory at PATH to DESTINATION. This can also be used to rename a file or directory. If you move a file to another file with the same name, you will replace the file that was already there, permanently deleting the old file.

Text Editor

What is a Text Editor?

Code is written in text files. The file will usually have an extension to indicate what language it's written in (such as .jsh for jshell scripts and .java for Java source code), but it's fundamentally the same as a .txt file. A text file contains text with no formatting. You cannot make some text bold, change its color, align it to the right margin, etc. A text editor can display formatted and colored text, but none of this formatting is part of the text file.

Text files are different from .rtf (rich text files), .doc/.docx (Word Documents), PDF files, and Google Doc files. Those filetypes all contain formatting in addition to the text, and programs like Microsoft Word and Google Docs should never be used to write code.

To write code, you need a text editor. Most operating systems include a basic editor with very few features (such as notepad on Windows), but there are much more powerful editors developed specifically for writing code. These editors include many features that make writing code faster and easier than it would be in a program like notepad.

VS Code is a relatively new and very popular editor developed by Microsoft. It is widely used by software developers, includes many useful modern features, and can be customized with numerous plugins. I recommend using it unless you have a good reason to go with another editor.

Zach's Editor: Neovim

Neovim is a highly customizable text editor with a difficult learning curve, and I do not recommend using it for this class. I mention it only because it's the editor I use, so you'll be seeing it a lot if you're in my class or watching any of my videos. If you like the idea of an editor that requires a lot of effort to learn, but will pay off that effort by letting you edit text very quickly and without using a mouse, then it might be worth trying neovim (or regular vim, or Emacs). However, I don't expect most students to find learning one of these editors to be worth the effort right now, and that's why I recommend VS Code.

Learning Resources

A final piece of advice: find an editor that you like and learn to use it well. You'll have a much better time coding if you can take advantage of many of the features your preferred editor has to offer than if you switch constantly and never learn much about any one editor.

Java

The lab will cover how to install Java. Check the directions provided by your lab instructor.

Quickstart

This section introduces several fundamental conceps that you'll need in order to write any amount of code beyond a simple "Hello World" program. No topic is covered fully, and we will revisit these in more detail later.

Jshell

Introduction

Jshell is a REPL (Read, Execute, Print, Loop). When you run a REPL program, it does the following:

  1. Reads a command
  2. Executes the command
  3. Prints the result
  4. Loops back to step 1 until told to exit

For the first half of the semester, we'll be writing our programs as jshell scripts.

Note: terminal emulators will show a prompt when waiting for a command. This prompt often includes your current location and ends with the $ character. For clarity, the terminal's command prompt will be marked with $, and jshell's prompt will be marked with jshell>.

While jshell or a similar interactive program is running on your terminal, commands will go to that program instead of the terminal. If no such program is running, then your commands will be run by the terminal itself. This matters because jshell commands (like println and /exit) will only work in jshell, and terminal commands (like cd and ls) will not work in jshell.

Interactive Mode

To open jshell on the terminal, run the jshell command. You should see a prompt similar to the one below:

$ jshell
|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell>

You can now enter jshell commands and snippets of Java code. These will appear after the jshell> prompt. Once you've typed something, press enter to have jshell execute it. Most jshell commands (that aren't Java code) are prefixed with a forward slash (/). To exit jshell, type the command /exit and press enter:

$ jshell
|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell> /exit
|  Goodbye
$ 

Hello World: Interactive Mode

Now we'll write some Java code. Open jshell again and enter System.out.println("Hello, world!"); (note: most Java code will end with a semicolon (;) character). Jshell should respond by repeating Hello, world! on the next line:

$ jshell
|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell> System.out.println("Hello, world!");
Hello, world!

jshell>

Try running another println instruction with a different message between the quotes.

Hello World: Script

Exit jshell and open your text editor. Open a new file and type the following text into the file:

System.out.println("Hello, world!");
/exit

Save this file as helloWorld.jsh and return to the terminal. All of your jshell scripts should end with the .jsh file extension. Make sure your terminal is in the directory where you saved the file (ls should show your helloWorld.jsh file) and run the command jshell --execution local helloWorld.jsh. You should see the Hello, World! message printed on the terminal:

$ jshell --execution local helloWorld.jsh
Hello, world!
$ 

Interactive Mode vs Script

Running jshell on its own will launch jshell as an interactive REPL. If you want to experiment with a small amount of code and get immediate feedback, you should run jshell in interactive mode.

Running jshell --execution local followed by the name of a jshell script (a .jsh file) tells jshell to execute each line of the script in order. This is useful if you are writing a program and don't want to retype each line of code every time you test the program. Remember to always end your jshell scripts with the /exit command, or jshell will continue running once it reaches the end of the script.

When jshell runs in interactive mode, it will print additional information even if you don't include a println. For example, try typing 2 + 2; in jshell:

|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell> 2 + 2;
$1 ==> 4

jshell>

When running a script, you will only see error messages and output that you explicitly print. For example, write a script with the line from before (2 + 2;), save it as add.jsh, and run it with jshell add.jsh.

The script:

2 + 2;
/exit

The output from running it with jshell:

$ jshell --execution local add.jsh
$

We didn't include a print command, so no output was produced. This lets us control exactly what we want the program to show the user as it's running.

Finally, whenever we run a script with jshell, we need to include the argument --execution local with the jshell command. This is necessary to allow input commands (covered later) to work correctly. We only include --execution local when running a script, not when using interactive mode.

Running a script called myScriptName.jsh:

$ jshell --execution local myScriptName.jsh

Running in interactive mode:

$ jshell

Startup Script

To make input and output more convenient, we're going to tell jshell to run a few scripts on startup before it runs any of our code. Download the INPUT.jsh file from Blackboard, then make sure your terminal is in the same directory as INPUT.jsh. Run jshell, then run the command /set start -retain DEFAULT PRINTING INPUT.jsh in jshell. Exit jshell, start it again, and run the command /list -start. The result should look similar to this:

$ jshell
|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell> /set start -retain DEFAULT PRINTING INPUT.jsh

jshell> /exit
|  Goodbye
$ jshell
|  Welcome to JShell -- Version 17.0.7
|  For an introduction type: /help intro

jshell> /list -start

  s1 : import java.io.*;
  s2 : import java.math.*;
  s3 : import java.net.*;
  s4 : import java.nio.file.*;
  s5 : import java.util.*;
  s6 : import java.util.concurrent.*;
  s7 : import java.util.function.*;
  s8 : import java.util.prefs.*;
  s9 : import java.util.regex.*;
 s10 : import java.util.stream.*;
 s11 : void print(boolean b) { System.out.print(b); }
 s12 : void print(char c) { System.out.print(c); }
 s13 : void print(int i) { System.out.print(i); }
 s14 : void print(long l) { System.out.print(l); }
 s15 : void print(float f) { System.out.print(f); }
 s16 : void print(double d) { System.out.print(d); }
 s17 : void print(char s[]) { System.out.print(s); }
 s18 : void print(String s) { System.out.print(s); }
 s19 : void print(Object obj) { System.out.print(obj); }
 s20 : void println() { System.out.println(); }
 s21 : void println(boolean b) { System.out.println(b); }
 s22 : void println(char c) { System.out.println(c); }
 s23 : void println(int i) { System.out.println(i); }
 s24 : void println(long l) { System.out.println(l); }
 s25 : void println(float f) { System.out.println(f); }
 s26 : void println(double d) { System.out.println(d); }
 s27 : void println(char s[]) { System.out.println(s); }
 s28 : void println(String s) { System.out.println(s); }
 s29 : void println(Object obj) { System.out.println(obj); }
 s30 : void printf(java.util.Locale l, String format, Object... args) { System.out.printf(l, format, args); }
 s31 : void printf(String format, Object... args) { System.out.printf(format, args); }
 s32 : Scanner __in = new java.util.Scanner(System.in);
 s33 : String nextLine() { return __in.nextLine(); }
 s34 : String next() { return __in.next(); }
 s35 : int nextInt() { return __in.nextInt(); }
 s36 : int nextIntWithBase(int radix) { return __in.nextInt(radix); }
 s37 : long nextLong() { return __in.nextLong(); }
 s38 : long nextLongWithBase(int radix) { return __in.nextLong(radix); }
 s39 : double nextDouble() { return __in.nextDouble(); }
 s40 : float nextFloat() { return __in.nextFloat(); }
 s41 : boolean nextBoolean() { return __in.nextBoolean(); }

jshell>

If you see the same list of 41 lines after running the /list command, then your startup scripts should be set correctly. The DEFAULT script contains the imports on the first 10 lines, and it was already running every time you started jshell. Those imports make it easier to access some useful code included with Java, which is part of the Java Standard Library. The PRINTING script is lines 11 to 31, and it lets us run any of the output functions (such as println) without typing System.out. The INPUT.jsh script includes the last 10 lines, and it sets up some input functions to make it easier for us to read user input. We'll make use of these functions soon.

You don't need to understand any of the code in the startup scripts right now. We're using them specifically so you don't have to think about that stuff until you learn what it all means. Try using println to print a message, as shown below, to further verify that the startup scripts were loaded correctly:

jshell> println("Hello, world!");
Hello, world!

If you run into issues with the startup scripts, you can always reset them with the command /set start -retain DEFAULT, and then repeat the previous directions to add the PRINTING and INPUT.jsh scripts.

Semicolons

Jshell considers semicolons optional some of the time. I've chosen to include them in all of my code examples, and I recommend you use them in your code as well for the following reasons:

  • When we introduce code blocks, jshell will require semicolons
  • When we transition to compiled Java, semicolons will be required for your code to compile and run

I think you'll have an easier time dealing with semicolons if you're in the habit of using them from the start. If you leave them off your code now, you'll have more trouble in a few weeks when you need to start using them with if statement code blocks.

Summary

  • Use the jshell command to run in interactive mode
  • You can write jshell scripts in your text editor and save them as .jsh files
  • Use the jshell --execution local command followed by a script name to run a jshell script
  • Make sure you set the startup scripts as explained above and confirm that they're working

Output

A program's output is the information it sends to some external location, such as the terminal, the screen, a speaker, a file, or a web server. For now, our only method of producing output will be printing text to the terminal.

We will primarily use the println function, which you saw in the previous section. There is a similar function called print, which is slightly different. To illustrate the difference, here are two jshell scripts, abcPrintln.jsh and abcPrint.jsh, along with their output:

println("A");
println("B");
println("C");
/exit
print("A");
print("B");
print("C");
/exit
$ jshell --execution local abcPrintln.jsh
A
B
C
$ jshell --execution local abcPrint.jsh
ABC$

As you can see, the println function moves to the next line after printing. This is because it adds a line break to the end of whatever text it prints. The print function does not, which even leads to the terminal prompt ($) being on the same line as the second program's output!

Usually you'll want to use println, but print can be helpful if you want to create a line of output into multiple print statements.

Escape Sequences

If you've experimented with printing, then you may have noticed that you cannot print a double-quote ("). This is because double-quotes are used to mark the beginning and end of a string of text. You can print " and a number of other special characters using escape sequences. These are typically a backslash (\) followed by the character or a letter standing in for the special symbol you want to include:

  • \" is printed as "
  • \\ is printed as \
  • \t is printed as a tab
  • \n is printed as a line break

Try printing messages containing these escape sequences and see what happens!

Variables

Sometimes we want our program to remember information and reuse the information later. We tell our program to remember something by creating a variable and assigning the information to that variable.

The var Keyword

We can use the var keyword followed by a name and initial value to create a new variable. This follows the format: var VARIABLE_NAME = INITIAL_VALUE;. For example, var x = 5; will create a variable named x with an initial value of the number 5. var message = "Hello, World!"; will create a variable named message with an initial value of the text Hello, World!. You can create many types of variables, but for now we'll stick with numbers and text.

The Assignment Operator

The equals sign (=) can be confusing to new programmers. You've likely seen it used in math to establish a relationship between two expressions. For example, we might say "x = y + z" to establish that the variable x is the sum of the variables y and z. We might also make simpler statements, such as "x = 5". However, if we follow "x = 5" with the statement "x = 6", then we've created a contradiction because the variable x cannot equal 5 and 6 at the same time.

In programming, the equals sign means something different. If our code says x = 5;, we are not stating a fact about x as we would be in mathematics. What we're really doing is telling the computer to change the value associated with the variable x to the number 5. This is called an assignment, and the equals sign is the assignment operator. An assignment follows the pattern VARIABLE_NAME = EXPRESSION;. In our x = 5; example, x is the variable name and 5 is the expression. An expression can be a lone number or a single variable name, but it could also be a more complex calculation involving multiple numbers and variables. Expressions are also not restricted to numbers. "Hello, World!" is also an expression.

What does this mean? While the statement x = 5; followed by x = 6; would normally be a contradiction in mathematics, in programming these statements mean "set the value of x to 5" and "set the value of x to 6". They are instructions for the computer to change the value of x rather than declarations about the value of x. If our program follows both of these instructions, the result will be that our variable named x stores the number 6.

Outputting Variables

You can a variable with println to output the information stored in the variable. Copy the following code into a jshell script named variableOutput.jsh and run it with jshell:

var x = 5;
print("x is equal to ");
println(x);
x = 3 * x;
print("x is now equal to ");
println(x);
/exit

A couple of notes about the above program:

  • The variable x can be used in an assignment statement's expression to calculate a new value for itself. The calculation uses the old value of x, and the result of this calculation becomes the new value of x.
  • We only use the var keyword when we first create the variable. After it exists we only refer to it by its name, even if we assign a new value to it.

Names

Variable names must consist of letters, numbers, and the underscore (_) character, and they cannot start with a number. Among other things, this means they cannot include spaces. Java also has certain [reserved words] (https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html) that cannot be used as variable names.

Types

We'll learn more about types later. For now, keep in mind that a variable's type is set with its initial value and cannot be changed. If you make a variable that stores text, you cannot reassign it to hold a number. Try running the following code in jshell and you'll see an error message:

var x = "hello";
x = 5;

Good Variable Names

Variable names in Java should follow the "camelCase" naming convention. The name should start with a lowercase letter, and the first letter of each word after the first should be capitalized. All other letters are lowercase, and we usually don't use underscores.

Variable names should also be descriptive. If you make a variable to store somebody's age, then the name age describes what is in the variable and makes our code easier to understand. Don't name your variables arbitrary letters like a or x unless those names make sense in context. For example, x would be an appropriate name for an x-coordinate on a graph, and a single arbitrary letter is fine if the information in the variable is only a number with no further meaning in the context of the program.

Your program can still work if you do not follow these rules, but your code will be harder for a human to read. A human has to understand the code in order to use and maintain it, so writing readable code is very important.

Input

Now we'll look at how to read user input in your program. As you'll see shortly, it's important to know how to use variables before we read user input. The code in this section will not work if you haven't set up INPUT.jsh correctly, so make sure you do that before continuing. The scripts in this section will also fail if you try to run them without setting --execution local as part of the jshell command (but remember this only applies when running scripts, not when running jshell interactively).

Reading Text

You can read a line of text with the nextLine() function. If you try this in jshell, you should see the following:

jshell> nextLine();

Jshell will continue waiting until you type something and press enter, so go ahead and type some text:

jshell> nextLine();
Hello, Jshell!
$1 ==> "Hello, Jshell!"

jshell>

We can see that jshell saw what we typed, but if we want to use it in our code we need to assign the result to a variable. All of the input functions can be used in expressions, so you can assign input to a variable like this:

jshell> var input = nextLine();
Hello again, Jshell!

jshell> println(input);
Hello again, Jshell!

jshell>

Other Input Functions

There are many other input functions, but those most likely to be useful to you right now are:

  • next(): reads a single word instead of an entire line
  • nextInt(): reads an integer
  • nextDouble(): reads a real number

nextLine() Issue

The nextLine() function may not behave as you expect if you use it after one of the other input functions. Here's an example program you can try running:

var number1 = nextInt();
var number2 = nextInt();
var text = nextLine();
println(number1);
println(number2);
println(text);

This program should read two integers and a line of text, then print everything it read. However, when running the program you'll notice that it immediately prints the numbers and a blank line after we enter the second number. It doesn't give us a chance to type the line of text.

This is the result of how the input functions process input, not a bug. To understand why it happens, let's pretend all the user's input is in a single text file instead of being typed as the program runs. Here's an example of what we might type as input:

100
200
line of text

We'll also include a cursor to mark our position in this text as we process it with input functions. The cursor will be marked with the pipe (|) character:

|100
200
line of text

When we use the nextInt() function (or any input function that doesn't read an entire line of text), it will move the cursor to the end of the input that it read. If it doesn't find anything on the current line, then it will move to the next line and keep looking. Here is the cursor after the first nextInt():

100|
200
line of text

And here is the cursor after the second nextInt():

100
200|
line of text

The nextLine() function is a bit different. It moves to the end of the current line and reads everything it moved past, then skips the cursor to the start of the next line. After the nextLine(), we end up with this:

100
200
|line of text

Did you notice that the cursor hasn't moved through any new text? It was already at the end of the "200" line, so it jumped to the beginning of the third line and read nothing.

So how do we deal with this?

  • This will only happen if you use nextLine() after a different input function, so it won't come up very often.
  • If you need to use nextLine() after a different input function, run the nextLine() function once to move the cursor to the start of a fresh line of input, then run it again to get your input. A modified version of our program would look like this:
var number1 = nextInt();
var number2 = nextInt();
nextLine();
var text = nextLine();
println(number1);
println(number2);
println(text);

This program will work as intended and read/print all three inputs from the user.

Data Types

So far we've simplified the different types of information you can put in a variable. Now we'll go over some of Java's basic types in more detail.

We won't go into detail about binary right now, but it's worth mentioning that every piece of information in your computer is represented as a series of ones and zeroes, called bits (binary digits). These bits form a binary number. Even though everything is a binary number when you look at the computer's memory, different types specify different rules for interpreting these binary numbers. These rules let us decode binary into text, integers, real numbers, and more complex forms of data.

Most of the types we cover in this section have a limited size, and they always use the exact same number of bits no matter what value they represent. Rather than bits, we measure the space taken up by data in bytes, which are chunks of 8 bits.

Numbers

There are two categories of numbers: integers and floating-point numbers. Integers can only represent zero and positive or negative whole numbers. Floating-point numbers can represent real numbers (fractions, irrational numbers like pi, and other numbers that require a decimal), as well as zero and whole numbers.

The integer types are:

  • byte: 1-byte (8-bit) integer value; stores any integer from -128 to 127
  • short: 2-byte (16-bit) integer value; stores any integer from -32768 to 32767
  • int: 4-byte (32-bit) integer value and the default type for integers in Java; stores any integer from about -2 billion to about 2 billion
  • long: 8-byte (64-bit) integer value; stores any integer from about -9 quintilion to about 9 quintillion

The floating-point types are:

  • float: 4-byte (32-bit) floating-point value; stores numbers from about -3E38 to about 3E38
  • double: 8-byte (64-bit) floating-point value and the default type for floating-point numbers in Java; stores numbers from about -1.7E308 to about 1.7E308

The differences in ranges may be surprising, and there is a significant drawback to floating-point numbers, but we'll cover that later when we take a closer look at binary and how numbers are represented in memory.

You can perform basic arithmetic with integers and floating-point numbers. If you use numbers of different types, then they will be converted to the type that can represent a greater range of numbers. The symbols to use for arithmetic are +, -, * (multiplication for), and / (for division).

Text

There are two types commonly used for representing text:

  • char: a single character encoded as a 2-byte integer
  • String: a sequence of characters; the size of a String depends on the number of characters in the String

The text we've been putting in quotations are String values. If we want a char, we need to use single quotes (') instead. For example, "A" is a String and 'A' is a char.

We can combine strings with other values using concatenation. This uses the + symbol, and at least one of the values must be a String. If one of the values is not a String, it will be converted to one, and then the second value will be appended to the first value. For example, "10" + 5 is equal to "105" because the 5 is converted to a String and appended to the "10". 10 + 5 without any quotes will perform regular addition and give us 15.

If you try to add a char and a number, the char will be converted to its numeric representation and treated as an int value, then regular addition will be performed. For example, 'a' + 5 is 102 because the character 'a' is internally represented by the number 97 (look up an ASCII table if you're curious about why). This may seem counter-intuitive, but as we'll see later it can actually be quite useful. If we use a String instead of a char, we'll go back to concatenation: "a" + 5 is "a5".

Booleans

We likely won't use booleans for a while, but they're briefly explained here for the sake of completeness. A boolean value is either true or false. booleans are used in Boolean Logic, which allows our programs to make decisions based on input instead of always doing the same thing. We'll talk more about the boolean type when we introduce if statements.

Explicit and Inferred Types

The var keyword has Java infer the type of a variable based on the initial value. If we assign an initial value of 5, for example, then it will infer that the type is int. However, it is standard practice in Java to specify the type of a variable rather than using the var keyword in most situations. Instead of var x = 5;, we would write int x = 5;, putting the type before the variable name instead of var. This makes it easier to specify types other than the default int and double for numbers, and it makes it easier to tell what type a variable is when looking at the code. From now on, most examples will use explicit types rather than inferred types.

Functions

A function is a series of instructions (lines of code) that can be called from another part of the program. We'll learn more about functions and how to write our own functions later, but for now we'll only look at how to call them. We've already used several functions in our code, such as println and nextLine.

Function Call Syntax

A function call in Java follows the format FUNCTION_NAME(ARGUMENTS). Function names follow the same rules as variable names, and like variables they should use camelCase and be descriptive.

Some functions require arguments, much like programs on the terminal. An argument is addtional information that the function must have in order to work, and the arguments for a function call always appear in a comma-separated list between parenthesis (( and )) after the function's name. Sometimes these arguments are optional (technically there are two functions with the same name, one that requires arguments and one that doesn't), but the parenthesis are always required, even if the function never has arguments. An example would be the println function, which can be called with no arguments (println() only prints a blank line), but it otherwise needs an argument to tell it what text to print (println("Hello, World!"); prints the message Hello, World!).

Return Value

Some function calls return information after they finish, and this information can be used an part of an expression. All of the input functions return the information they read from the terminal. If you call a function that returns something, you should almost always use the result for something or store it in a variable for later. Calling readInt on its own isn't very useful because the integer isn't saved anywhere. Calling readInt and assigning the result to a variable (int x = readInt();) lets you use the number you read in other parts of your code.

Methods

You may see the term "method" used to refer to functions. A method is a concept from object-oriented programming, and it refers to a function attached to an object. Technically, most functions in Java are methods, including all of the functions we've used so far. For now, I'll use the term function for anything that is presented to you as a standalone function.

Comments

A comment is text that is ignored when compiling or running a program. Comments can be used for many purposes, and the way you use comments will evolve as you learn more about programming.

Types of Comments

A single-line comment is created with two forward-slashes:

// this text is a comment and will be ignored when the program runs

Everything after the slashes is ignored. You can put a comment on the same line as code, but I find it's easier to read code when comments are on separate lines:

// a comment on the same line as the code:

int x = 5; // this makes a variable named x with an initial value of 5

// a comment on its own line (usually my preference):

// this makes a variable named y with an initial value of 7
int y = 7;

You can create a comment that spans multiple lines (a block comment) using /* to mark the beginning of the block and */ to mark the end of the block:

/*
All of the text in this example is inside of a block comment.

Nothing I write here will be treated as code if you were to run this in jshell.

If this were a real program, it would be pointless :(
*/

How to Use Comments

Comments can serve many purposes, and how you use them depends on many things, such as who you're working with, the project you're working on, and the language you're coding in. For a beginner programmer, I would recommend using comments for the following purposes:

  • Taking notes inside your code to explain what the code is doing
  • Temporarily disabling ("commenting out") code without deleting it (put // at the start of a line of code and it will be ignored when you run the program). This is helpful if you are trying to fix a problem and want to disable a line of code that causes an error or see what happens when that line is skipped.
  • Documenting important information about your program, such as the author(s), the program's purpose, citations if appropriate
  • Outlining part of a program before you write it
  • Leaving "todo" comments, or reminders about tasks you plan to finish later

Some of these will not always be relevant to you. For example, it's usually not helpful to put comments throughout your program explaining every line of code once you get the hang of programming. You and the other experienced programmers will generally assume that the people working on a project understand code and don't need every line explained. However, these types of comments can be useful if you're learning a new language, and they're good to include in code samples that are meant to teach a new concept to someone.

Variables

This chapter covers variables in a little more depth than the Quickstart chapter. Some information will be repeated here so that this chapter can serve as a better reference.

A variable acts like a container for data. It has a type, and a value is assigned to (or stored in) the variable. You can change the value assigned to a variable (called reassignment), but the variable's type is set when it is created and can never be changed. Only values of the appropriate type can be assigned to a variable.

Creating Variables

There are a few terms that are helpful to cover. The templates chapter has some examples to illustrate declaring, initializing, and reassigning variables.

  • Type: a variable's type tells us what kind of information it contains. A computer stores everything as a series of 0's and 1's (bits), and the type tells it how to interpret those bits. Two identical sequences of bits can each mean something completely different depending on their types.
  • Value: a variable's value is the information stored in it.
  • Declare: creating a new variable is called declaring a variable, and we might refer to a line of code that declares a variable as a "variable declaration". This is also when the variable's type is set.
  • Initialize: when we set a variable's initial value, this is called "initialization," or initializing the variable. This usually happens in the same line of code that declares the variable, but we are allowed to declare a variable without initializing it (this is usually a bad idea, so don't do it unless you have a good reason).
  • Identifier: the term "identifier" refers to the name by which we identify a variable (as well as other constructs, such as functions and classes, which we'll cover later). There are rules for what is and isn't a valid identifier, and with a few exceptions we cannot use the same identifier for multiple variables (when this is allowed, it's called "shadowing", but you should avoid doing this most of the time).

Variable Declaration

There are two ways to declare a new variable: using the var keyword and allowing Java to infer the type, or explicitly stating the type before the variable's name. Here are two examples:

var x = 5; // type is inferred to be int
int y = 5; // type is explicitly stated as int

In Java, we usually state the type explicitly. The var keyword is sometimes used, but I recommend you stick to explicitly typed variables and avoid using var. When the type appears before the name, this can make it easier for you to determine a variable's type when reading the code.

Variable Initialization

In the previous example, both variables were initialized at the same time they were declared (the = 5 sets their initial value to 5). However, if we state the type of a variable, we are allowed to initialize it later:

int x; // declared, but not initialized
println("We've not yet set a value for x.");
x = 5; // this is the first time we set x's value, so it's now initialized
println("Now x is initialized to " + x + ".");

If you have an uninitialized variable, it is an error to try to access the data stored inside that variable. If we try to print the value of x before it's been initialized, then we'll get an error message:

int x;
println("I'd tell you that x is equal to " + x + ", but that's not allowed yet!");
x = 10;
println("Now we can safely print that x is equal to " + x + ".");

Usually you won't have a reason to declare a variable without initilizing it. We'll see some examples later on where it makes sense to do this, but for now you should always initialize your variables when you declare them.

Assignment Operator

The assignment operator (=) can cause some confusion. When you start writing comparisons and using the equality operator (==), it's easy to mix up what each of these mean. One of the reasons this can be confusing is the difference between imperative programming (the style of programming that Java primarily supports and that we will be using) treats variables differently from mathematics.

Variables in Math

If this were an algebra class and I gave you the following statements, you would probably be confused:

x = 5
x = 6
x = x + 3

The first two statements, x = 5 and x = 6 contradict one another and imply that 5 = 6, which we know is not true. The third statement contradicts itself, because a number cannot equal itself plus 3 (this would imply that 0 = 3 if we subtract an x from both sides). In math, we would view the above statements as factual statements about the variable x, and because of the contradictions they cannot be true statements (the first or second would be fine on their own).

Variables in Imperative Programming

In Java, the previous statements are perfectly fine and do not contradict one another. This is because the = sign does not have the same meaning in Java that it has in algebra. In Java, the = sign is the assignment operator, and it is instructing the computer to change the value associated with x. The value of x is allowed to change over time, and it will be different after each instruction that assigns a value to x. After the first statement, x will be equal to 5. After the second, it will be equal to 6. After the third, it will be equal to 9, because it uses the previous value (6) in the calculation before it assigns the new value. We can see this if we add a declaration and several print statements to the program and run it in Jshell:

int x;
x = 5;
println("x is " + x);
x = 6;
println("x is " + x);
x = x + 3;
println("x is " + x);

The equality operator (==) has a meaning closer to how we would interpret an equals sign in mathematics. It is stating that two values are the same, but it is still valid to make false or contradictory statements with == in Java. If we do, then the result will be false instead of true. This is because the equality operator is asking a question rather than asserting that something must be true, and both yes (true) and no (false) are valid answers to that question. We can demonstrate this with the following program:

int x = 10;
int y = 13;
boolean xEqualsItself = (x == x);
boolean xEqualsY = (x == y);
boolean xEqualsFive = (x == 5);
boolean xEqualsTen = (x == 10);
boolean xEqualsItselfPlus1 = (x == x + 1);
println("The value of x is " + x);
println("The value of y is " + y);
println("Does x equal itself? " + xEqualsItself);
println("Does x equal y? " + xEqualsY);
println("Does x equal five? " + xEqualsFive);
println("Does x equal ten? " + xEqualsTen);
println("Does x equal itself plus one? " + xEqualsItselfPlus1);

The parenthesis around the equality checks on lines 2-5 are not necessary, but I've included them to make it clear what the order of operations will be. First we evaluate whether x is equal to the value to the right of the ==, which results in true or false, and then we assign the result to the variable on the left of the =. Try running this, and note that despite some of the answers being false, we don't actually get an error from those false statements. Again, this is because they are questions rather than statements of fact.

Variable Names

Variable names, and identifiers in general, have to follow a few rules in Java. You can find these rules at the end of this page, but we'll summarize them here:

  • You cannot use a [Java keyword]( https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html as an identifier.
  • An identifier can only contain letters (uppercase or lowercase), numbers, the underscore character (_), or the dollar sign ($) character.
  • An identifier cannot begin with a number.
  • Identifiers are case-sensitive, which means thisname and thisName are different identifiers even though they look nearly the same.

There are also some optional rules that you should follow with identifiers. These rules are only optional in the sense that your code can technically run if you break these rules, but your code will be harder to read and much more likely to contain errors if you don't follow them.

  • Java variable names should follow the lower camel case naming convention. This means all variable names should start with a lowercase letter, contain no underscores or dollar signs, and each word after the first should start with an uppercase letter. For example thisVariableNameIsUsingLowerCamelCase, although in practice you don't want your variable names to be that long. This is important because most other Java code (including the standard library code you will be using in your own programs) is written this way, and not following this convention will make the names in your code inconsistent and easy to mix up.
  • Don't create two identifiers that are the same except for capitalization. This is confusing and makes it easy to accidentally use the wrong identifier.
  • Whenever possible, give your variables descriptive names. If a variable has some inherent meaning (such as a variable that stores a person's name), then use a name that will tell the reader what that meaning is. For example, a variable that stores someone's age could be called age, and a variable that stores a person's name could be called name. This makes it much easier to understand your code.

Pen & Paper Analogy

Many new programmers have difficulty learning to think it terms of variables, but this is a fundamental skill for writing code. It may be helpful to think about variables in terms of writing information down by hand on a piece of paper or a whiteboard. Everything we can do with a variable can be represented this way, which lets you simulate small amounts of code by hand or think through the process in your head to better understand what's going on.

Declare and Initialize

If we declare a variable in a program (int x), this is equivalent to writing the label x on our paper. When we initialize that variable in code (x = 5), we would write the initial value on the paper beneath or next to x.

Example:

int x;
x = 5;
int y = 10;

x -> 5

y -> 10

Reassign

Reassigning the variable in code (x = 10) is equivalent to crossing out the old value of x and replacing it with the new value.

x = 11;
y = -4;
x = 3;

x -> 5 11 3

y -> 10 -4

Print

Printing the variable (println(x)) means we're communicating it to someone else. We could write it down on a separate piece of paper, or we could read the text out loud to the other person.

println(x);
println(y);

Variable record

x -> 5 11 3

y -> 10 -4

Output paper

3

-4

Use in a Calculation

If we use our variable in a calculation, we would write the expression with our variable, then substitute the variable's value as we simplify the expression one step at a time.

x = 10;
y = 5;
x = x + y;
y = x * 2 - 4;
println("x = " + x + " and y = " + y);

Variable record

x -> 5 11 3 10 15

y -> 10 -4 5 26

Scratch paper for calculations

Calculate x + y

x + y

10 + y

10 + 5

15

set x equal to 15

Calculate x * 2 - 4

x * 2 - 4

15 * 2 - 4

30 - 4

26

set y equal to 26

Calculate "x = " + x + " and y = " + y

"x = " + x + " and y = " + y

"x = " + 15 + " and y = " + y

"x = 15" + " and y = " + y

"x = 15 and y = " + y

"x = 15 and y = " + 26

"x = 15 and y = 26"

write "x = 15 and y = 26" to the output paper

Output paper

3

-4

x = 15 and y = 26

Variable Scope and Shadowing

Scope

Once we start using code blocks, we can have variables with different scopes in our programs. A variable's scope is the portion of the program where it exists. If we try to refer to a variable outside of its scope, we'll get an error because it doesn't exist.

When you declare a variable inside of a code block, that variable's scope is limited to the inside of that code block. Once the code block ends, the variable no longer exists. Try running the following example:

if (true) {
    int text = "Hello";
    println(text);
}
println(text);

We'll get an error when we try to print text a second time because it only exists inside the code block.

Shadowing

Shadowing occurs when we have two variables with the same name. The newer variable "shadows" the older variable, taking its place until the newer variable goes out of scope. Jshell will allow shadowing within the same scope, which permanently replaces the old variable, but in compiled Java we can only shadow when the new variable has a different scope from the original.

// this will work fine in jshell, but it won't compile as part of a Java program
int x = 5;
int x = 10; // stating the type declares a new variable named x

// the correct way to change the value of a variable from 5 to 10 is to reassign
// it, not declare the variable a second time
int y = 5;
y = 10; // reassigns the variable instead of redeclaring it

Usually it's best to avoid this, but in my experience it's common for new programmers to unnecessarily declare the same variable multiple times until they learn the difference between declaring and initializing a variable and reassigning a variable. Jshell makes this bad habit easier to form because it allows shadowing within the same scope, as shown above. Compiled Java will still allow shadowing when the scopes differ:

// shadowing
int x = 5;
if (true) {
    // this is a new variable named x, and it will cease to exist at the end of
    // this block of code
    int x = 10;
}
// this prints the original x value of 5 because we made a new temporary x
// instead of reassigning x
println(x);

// no shadowing
int y = 5;
if (true) {
    // we reassign y instead of declaring a new y with `int y = ...`
    y = 10;
}
// the original y was reassigned to 10, so this will print 10
println(y);

In general, I recommend that you avoid shadowing because of the errors that it enables you to make.

I/O

This chapter covers input and output (I/O) in more detail than the quickstart chapter. It will skip the simplified I/O scripts we use in jshell to get started, but other information will be repeated as appropriate to make this a more useful reference.

Terms

Input refers to information fed into a system. This includes text you type into a messaging app, button presses on a game controller, and tapping on the screen of a smartphone.

Output refers to information produced by a system. This includes text and images displayed on your monitor, a file saved to your hard drive, and sounds played through earbuds or speakers.

I/O Devices

There are many physical I/O devices that can be used with a computer. Below are a few examples.

Input Devices:

  • Keyboard
  • Mouse
  • Touchscreen
  • Gamepad
  • Camera
  • Microphone
  • Scanner

Output Devices:

  • Monitor
  • Speaker
  • Headphones/earbuds
  • Printer
  • Gamepad (some have speakers, vibration, and lights)

Program I/O

This chapter focuses primarily on program I/O: information provided to and produced by the programs we write. There are many forms of I/O used by programs; here are a few examples:

  • Standard I/O: standard I/O refers to input received through the user typing on a terminal and output produced by printing to the terminal.
  • Input Devices: programs can respond to input from devices such as keyboards, mice, and gamepads.
  • Graphics: programs can draw shapes, text, and images in windows and display these on the monitor or other display devices.
  • Files: programs can get input by reading files on storage devices such as hard drives, solid-state drives, usb flash drives, and DVDs, and they can produce output by writing to files on these devices.
  • The Internet: programs can use the internet to send information to and receive information from other computers.

The quickstart section covers some simplified functions for standard I/O. This chapter will cover functions and classes typically used to interact with standard I/O. Later we'll return to this chapter to learn about file I/O.

Formatting with printf

String concatenation works well for combining sever string literals and variables when no additional formatting is required, but it can be cumbersome when there are many variables and doesn't offer any tools for controlling how values are formatted. You can write your own code to control formatting, but this can get repetitive and tedious. The printf function offers a better solution in these situations.

Look at the following code that prints the time for a 12-hour clock using concatenation:

String amOrPm = nextLine();
int hour = nextInt();
int minute = nextInt();
int second = nextInt();

String minuteFormatted;
if (minute < 10) {
    minuteFormatted = "0" + minute;
} else {
    minuteFormatted = "" + minute;
}
String secondFormatted;
if (second < 10) {
    secondFormatted = "0" + second;
} else {
    secondFormatted = "" + second;
}

String time = hour + ":" + minuteFormatted + ":" + secondFormatted + " " + amOrPm;
println(time);

This code works, but it has the drawbacks we just mentioned: the concatenated string expression is long and hard to read due to all of the breaks in the string literals and + symbols joining the different parts, and we have to write additional code to control how some of the numbers are formatted.

Compare this to using the printf function:

String amOrPm = nextLine();
int hour = nextInt();
int minute = nextInt();
int second = nextInt();

printf("%d:%02d:%02d %s\n", hour, minute, second, amOrPm);

This produces the same output, but with far less code. The downside is that we may not know how to read the format string at the beginning. Let's fix that!

Format Strings and Args

The printf function takes a format string that includes placeholder values for any variables that we want to insert into the string. After this format string, we include a list of arguments (the values we want to insert) in the same order that their placeholders appear in the string.

It's also important to note that printf, unlike println, does not automatically add a line break to the end of its output. If we want a line break, and we usually do, then we need to include the \n escape sequence in the format string.

Formatting Specifiers

The placeholders are "formatting specifiers," and their job is to convey the type of value to insert into the string as well as how we want to format that value. A formatting specifier has two or three parts:

  • They begin with a percent sign %
  • They can (but don't have to) include some formatting information in the middle, such as the 02 in our time example
  • They end in a character that represents the type of value we're going to insert at that point in the string, such as d (decimal, as in "base-10", integer) or s (string)

Interpreting Our Example

With all this in mind, how do we interpret the "%d:%02d:%02d %s" from our time example?

  • Each %d represents an integer
  • A %02d is an integer with additional formatting: if it's shorter than 2 characters then it should be padded with 0s so that it takes up 2 characters of space
  • A %s is a string (the "AM"/"PM" value)
  • The colons (:) and space are not part of any placeholder values, so they'll be printed as they appear in the string

Let's apply these rules and see what our string looks like if hour is 9, minute is 5, second is 30, and amOrPm is "AM":

  • hour is the first argument, so it takes the place of the %d: "9:%02d:%02d %s"
  • minute is the second argument, so it takes the place of the first %02d, and because %02d specifies that we pad with 0 to ensure our number is at least 2 characters long, we change the 5 to 05: "9:05:%02d %s"
  • second is the third argument, so it takes the place of the second %02d: "9:05:30 %s"
  • amOrPm is the last argument, so it takes the place of the %s at the end of the format string: "9:05:30 AM"

Documentation

This example showed a few of the many types and methods for formatting those types that printf supports. Java has detailed documentation for formatting strings, and there are also some tutorials that you may find helpful. Not all of this documentation is going to make sense early on, but the more you use printf and check the documentation, the better you'll be able to use Java's documentation to find the information you need.

Types

This chapter covers primitive numeric types and Strings. Several new terms are used in this chapter, so here are some definitions:

  • Primitive Type: if we store a value of a primitive type in a variable, the value is stored directly in the variable. Most of the types we're working with for now are primitive types (except for String).
  • Reference Type: reference types are stored in a different memory location called the "heap", and variables store the value's location rather than the entire value (this is because reference types are usually large, and storing a single copy uses much less memory than giving each variable its own copy). We don't have to worry about this right now, but I'm mentioning it so you know the term for types that aren't primitive.
  • Literal: if we "hard-code" a value, writing the actual number, string, or char in the code rather than using a variable, this is called a literal value. For example: numbers (0, 3.14, or -20), chars ('a', or '!'), and strings ("hello" or "this is a string").

Numeric Types

Most of the types we cover in this chapter represent numbers. We'll usually use only a few of these types, but they're all covered here so you're familiar with the terms.

Integers

Integer types can represent positive and negative whole numbers, as well as the number zero. Java's integer types are long, int, short, and byte.

int is Java's default integer type. Most of our integers will use the int type, but long will be necessary if we're dealing with numbers too large to represent with int.

Integer Division

We can add, subtract, multiply, and divide integers, but division may not work the way you expect. Dividing one integer by another always results in an integer. If there is a remainder, the result is truncated, which means the remainder is dropped. This causes integer division to always round towards zero (negative numbers round up and positive numbers round down).

The modulo operator can be used if you would like the remainder from dividing two integers instead of the quotient.

Overflow

Each integer type can only represent values within a set range, and if an integer exceeds this value it will overflow. When an integer overflows, it wraps around to the opposite end of its range of possible values. To see this in action, try running the jshell script below. Before you run it, write down what number you think each print statement will output.

byte a = 255;
byte b = -256;
byte c = 200;
int d = 2147483647;

println(a + 1);
println(a + 5);
println(b - 1);
println(b + 1);
println(c + 55);
println(c + 56);
println(c + 100);
println(d + 1);

We'll see exactly why this happens when we learn how binary numbers work.

Floating-Point Numbers

Floating-point types can represent real numbers, which includes integers, fractions, and numbers like \( \pi \) that cannot be represented as a fraction. Java's floating-point types are float (single precision floating-point number) and double (double-precision floating-point number).

double is Java's default floating-point type, and we will not normally have a reason to use the less precise float type.

Precision

Floating-point numbers do not represent exact values. You may have noticed that they can store a far larger range of values than integer types that use the same amount of memory, and this is only possible because they have a limited precision. For now, it's best to think of a floating-point number as a close approximation or a small range of possible values rather than a precise value.

Due to this limited precision, you will sometimes see floating-point calculations that result in values that are slightly off from what you would expect if you were working with exact numbers. For example, 0.1 + 0.2 will result in 0.30000000000000004 instead of the expected 0.3. As you'll be reminded in the chapters on if statements and boolean logic, this lack of precision makes it a bad idea to try checking whether two floating-point values are exactly the same.

Overflow

Floating-point numbers do not overflow like integers. If you exceed the minimum or maximum value that a floating-point format can represent, you'll end up with Infinity or -Infinity.

Underflow

Floating-point numbers can experience underflow. This can happen if you add or subtract a small number from a much larger number, such as subtracting 1 from 1000000000000000000000.0. The limited precision of floating-point numbers means it can only represent the first 17 or so digits of the number (the significant digits, if you're used to scientific notation), and changes to the later digits are too small to affect these significant digits. This is why, in Java, 1000000000000000000000.0 + 1.0 will equal 1000000000000000000000.0.

Why

Like some of the quirks of integers, for now it's most important to understand that they exist and that they are supposed to function this way. We'll learn more about how they work when we learn about binary numbers.

Chars

The char type represents a text character, but internally it is an unsigned integer. Strings are made up of chars, and the fact that a char is a number allows us to manipulate it in some interesting ways.

ASCII and Numeric Representation

If you look up an ASCII table, you'll see the numeric values of some characters. If you were to convert the character 'a' into an int, for example, it would be 97. ASCII is an older format, but the characters on that table are generally encoded as the same numbers in more modern formats.

Note that the encodings for the digits 0 to 9, the lowercase letters a to z, and the uppercase letters A to Z are sequential. 'a' is 97, 'b' is 98, etc. We can use this fact to determine a letter's place in the alphabet, whether a char is a letter, digit, uppercase, or lowercase, and what letters come before or after another letter.

Escape Sequences

Some characters have a special meaning inside String and char literals, and some characters cannot be displayed or easily typed into a program. To include these characters in a String or char literal, we need to escape them with a backslash (\, which is usually located above the return key on a keyboard).

Characters with special meaning:

  • \ is used to escape other characters, so we need to escape it: '\\'
  • ' is used to mark the beginning and end of a char literal, so it must be escaped inside a char literal: '\''
  • " is used to mark the beginning and end of a String literal, so it must be escaped inside a String literal: "\""

Whitespace characters that are hard to print:

  • A linebreak is represented as \n inside a char or String literal
  • A tab is represented as \t inside a char or String literal

Modulo

The modulo operator is like a fifth arithmetic operation. You're probably used to addition (+), subtraction (-), multiplication (*), and division (/). Modulo is written as %, and it will divide its operands but give us the remainder rather than the quotient. This is the information we normally lose when performing integer division.

For example, if we calculated 100 / 40 by hand, we would end up with either 2.5, 2 and 1/2, or 2 with a remainder of 20, depending on how we wanted to write the result. The last version, 2 with remainder 20, shows us the information we get with integer division (2, the quotient) and modulo (20, the remainder). 100 / 40 would result in 2, and 100 % 40 would result in 20.

Modulo has many uses. A couple that you'll encounter in this class are:

  • Checking if one number is divisible by another number, particularly whether numbers are divisible by 2 (even/odd)
  • Breaking a quantity in a small unit (such as cents) into one or more larger units along with the leftover quantity in the original unit (such as converting from cents into dollars while remembering the leftover cents: 327 cents is 3 dollars and 27 cents).

Numeric Conversions

You can convert between different numeric types. Some of these conversions must be explicitly stated in your code, and others can be performed implicitly. In general, conversions from a smaller type to a larger type (widening conversions) can happen automatically because they shouldn't cause a loss of information. Conversions from a larger type to a smaller type will result in an error unless you explicitly tell the program to perform the conversion because these risk losing information and causing other errors that are much more difficult to fix.

Implicit Conversions

Java can implicitly convert from any type earlier in this list to a type later in the list:

  • byte
  • short
  • int
  • long
  • float
  • double

Java will also implicitly convert from a char to int or any type below int on the list, but it will not implicitly convert any type to a char.

The following code demonstrates a few implicit conversions.

byte a = 10;
short b = a;
int c = b;
long d = c;
float e = d;
double f = e;

println(a + ", " + b + ", " + c + ", " + d + ", " + e + ", " + f);

Explicit Conversions

Any other conversions between numeric types must be explicitly stated in your code. If we don't do this, then we'll get a syntax error. If we reverse the order of the types in the last example, we'll see this happen with every line but the first. Try running the following script in jshell:

double a = 10;
float b = a;
long c = a;
int d = a;
short e = a;
byte f = a;

To make this work, we need to state the type we want to convert to in parenthesis before the expression we wish to convert:

double a = 10;
float b = (float)a;
long c = (long)a;
int d = (int)a;
short e = (short)a;
byte f = (byte)a;

println(a + ", " + b + ", " + c + ", " + d + ", " + e + ", " + f);

Strings

Strings are sequences of characters. They have many applications and come with useful built-in methods, but to begin we'll mostly use them for producing output. We'll revisit this chapter later to learn about more advanced string manipulation.

String Literals

A string literal is text enclosed within double-quotes ("). You can use escape sequences in string literals just like char literals.

Concatenation

Anything can be concatenated with a string. The plus sign + performs string concatenation instead of addition whenever one of the operands is a string.

If one of the operands is a string and the other is a different type, Java will convert the other operand to a string before concatenating them. The second operand is then appended to the first operand, forming a new string. For example, if we concatenated "Hello" with "World" ("Hello" + "World"), we would get "HelloWorld" (if you want a space, make sure to include it in one of the operands!).

You can concatenate multiple values together, but remember that the concatenation will be evaluated left to right. If you want to perform any arithmetic with numeric values before they are concatenated, you may need to surround them with parenthesis to ensure the arithmetic happens first. For example, "sum: " + 2 + 3 will result in "sum: 23". If we wanted to add 2 and 3 before concatenating, we can add parenthesis: "sum: " + (2 + 3) will result in "sum: 5".

Booleans

The boolean type can only represent two values: true and false. It will be more relevant later when we learn about if statements and boolean expressions.

Primitive Type Summary

The table below summarizes Java's primitive types.

type namecategorysize (bits)size (bytes)min valuemax value
bytesigned integer81-256255
shortsigned integer162-32,76832,767
intsigned integer324-2,147,483,6482,147,483,647
longsigned integer648-9,223,372,036,854,775,8089,223,372,036,854,775,807
floatfloating-point324-3.4028235E383.4028235E38
doublefloating-point648-1.7976931348623157E3081.7976931348623157E308
charunsigned integer162065,535
booleanboolean81N/AN/A

A few notes about the above table:

  • The size is how much of the computer's memory a single value of this type requires
  • The char type represents a single text character, but it chars are stored as integers and will readily convert to an int
  • The main integer types are listed as signed, which means they can represent positive and negative numbers (and zero). An unsigned integer type (such as char) can only represent positive numbers (and zero).
  • The notation used for the min/max values of floating-point numbers is a form of scientific notation called "E notation". The E38 means "multiplied by 10^38," and the E308 means "multiplied by 10^308". These are extremely large numbers, which is why we write them with scientific notation.
  • The String type is not a primitive type and does not appear on the table

Flowcharts

The flowcharts in this book will follow a similar style to those shown in the flowcharts section of the Programming Fundamentals textbook (by Busbee and Braunschweig) , which is available for free from libretexts.org. I'll include examples as each symbol is introduced in this chapter, but I still recommend reading the flowcharts section of the book linked above.

Software

If you want to edit flowcharts collaboratively or want a convenient web app to work with, then I recommend app.diagrams.net. There are also free programs that specialize in editing flowcharts and other diagrams. The examples you see in my notes were made using Dia.

Sequential Instructions

At its core, a flowchart is a sequence of instructions that must be followed in order.

Symbols

You will see most of the symbols below in any flowchart in these notes. This is mostly redundant with the summary of symbols shown in the textbook.

  • Terminators: the capsule shapes mark the start/end of a flowchart
  • I/O Instructions: paralellograms indicate an instruction that performs input or output
  • Process Instructions: rectangtles represent most other instructions that do not involve input or output
  • Arrows: each part of a flowchart is connected to one or more others by arrows
  • Lines: if the flowchart reads left to right, top to bottom, then the arrows can be left off the connections

Sequential Symbols

Reading a Flowchart

Beginning with the start terminator, each instruction in the flowchart is performed in sequence. Below are several examples followed by equivalent jshell scripts.

Hello World

Hello World

println("Hello, world!")

Repeat It

Repeat It

println("Please enter some text.")
String text = nextLine()
println("Now I'll repeat what you just typed.")
println(text)

Simple Age

Simple Age

println("Please enter the year you were born.")
int birthYear = nextInt()
println("Please enter the current year.")
int currentYear = nextInt()
int maxAge = currentYear - birthYear
int minAge = maxAge - 1
println("You are either " + minAge + " or " + maxAge + " years old.")

Note that this last flowchart left out the details of what text the program should print. Instead, the output instructions focused on the purpose of the output. This is usually fine; the purpose of our flowchart is to show the structure of our code, not specify the exact details of its input and output.

Decision Points

Decision points allow our flowchart to branch in multiple directions, and the condition in the decision point determines which branch we follow. All branches must eventually converge and lead to the flowchart's end terminator.

Symbols

A diamond shape represents a decision or branching point. The text in this symbol states the condition, which must usually be true or false. Each branch coming from this symbol is labelled with the result that would cause the program to follow that branch (again, usually true and false).

Decision Symbols

Selection vs Iteration

There are two ways we can use decision points: selection and iteration.

  • Selection: the decision point chooses which instruction(s) to perform next, but these do not lead back to earlier parts of the flowchart and converge before the end terminator.
  • Iteration: the decision point chooses whether to follow a branch that leads back to an earlier part of the flowchart or to continue towards the end of the flowchart. Iteration allows us to repeat instructions multiple times until a condition is met.

In Java code, selection is handled with if statements and switch statements. Iteration is handled with loops (while, do-while, for).

Selection Examples

The flowcharts below demonstrate selection. The equivalent jshell scripts are also shown after each flowchart.

Type Five

println("Please type the number 5.")
int five = nextInt()
if (five == 5) {
    println("Thank you for typing 5.")
} else {
    println("Error: you didn't type 5.")
}

Sign

println("Please enter a number.")
int num = nextDouble()
if (num > 0) {
    println(num + " is a positive number.")
} else {
    if (num < 0) {
        println(num + " is a negative number.")
    } else {
        println(num + " is zero, which is neither positive nor negative.")
    }
}

Iteration Examples

The flowcharts below demonstrate iteration. The equivalent jshell scripts are also shown after each flowchart.

Type Five Loop

This flowchart duplicates an instruction to more closely match the structure of the jshell code below.

println("Please type the number 5.")
int five = nextInt()
while (five != 5) {
    println("Error: you didn't type 5. Try again.")
    five = nextInt()
}
println("Thank you for typing 5.")

Countdown

println("Please type a positive integer.")
int countdown = nextInt()
while (countdown > 0) {
    println(countdown + "!")
    --countdown
}

If Statements

If statements allow a program to make decisions based on user input. With an if statement, you can ask a yes/no question about some data and decide what code to execute next based on the answer. This chapter will cover different parts of an if statement as well as how to use an if statement.

If

On its own, an if statement lets the program decide whether or not to run a block of code. This means the code inside the if block can be skipped. For example:

println("Please enter a number less than 100.");
int number = nextInt();

if (number < 100) {
    println("Thanks for following directions!");
}

This program only prints its second message if the user types a number less than 100. If the user types a number greater than or equal to 100, then the program will skip the second print statement.

An if statement has several parts:

  • The if keyword
  • A condition in parenthesis after the if, which is always a boolean expression
  • An opening curly brace ({) to begin a code block; this code block and its contents are the body of the if statement
  • Some code indented within the code block
  • A closing curly brace (}) to end the code block

When an if statement is executed, the condition is first evaluated. This will result in either true or false. If the condition is true, then the body of the if statement will execute. If the condition is false, then the program skips the body and continues executing the rest of the program.

Semicolons

Note that we do not include a semicolon on the lines with curly braces. Java expects that a statement, as in a line of code that represents a complete instruction, will always end in a semicolon. The if (condition) { line is not an instruction on its own, so we should not place a semicolon on that line. In general, any time you write a line of code that would normally be followed by an opening curly brace (such as an if), you should not use a semicolon on that line.

Comparisons

In order to write a condition, you need to be able to write a boolean expression. One common type of boolean expression is a comparison between two values. Here are the comparison operators that Java supports:

Here are a few examples of boolean expressions using these comparison operators along with the equivalent English statement:

  • a >= b: a is greater than or equal to b
  • year != 2022: year is not equal to 2022
  • 2 + 2 == 5: 2 + 2 is equal to 5

These will result in true if the comparison is making a true statement, and false if it is not. For example, a >= b will be true whenever a is greater than or equal to b, and false when a is less than b. This comparison contains variables, so its value will depend on those variables. A comparison between two constant values, such as 2 + 2 == 5, will always result in the same value (in this case, false, because 2 + 2 is equal to 4 and not 5).

String Comparisons

The comparison operators above are only meant to be used with primitive types. If you use them to compare Strings or other reference types, then your code will usually not work correctly.

If you want to compare two strings, you'll need to use the .equals() method:

println("Please enter your name.");
String name = nextLine();

if (name.equals("Zach")) {
    println("Hello, Professor Kohlberg!");
} else {
    println("Hello, " + name + "!");
}

Common Mistakes

A few mistakes to avoid with comparisons:

  • The = operator does not check for equality; do not use it in a boolean expression
  • Do not check floating-point values for equality with == or !=; the lack of precision inherent to floating-point numbers will cause false positives and false negatives

Ranges

You cannot combine comparisons like this: 1 <= x <= 10. This would be a perfectly natural way to state that x is a number from 1 to 10, and this is how you'd write such a statement in algebra, but your program will not interpret it the way we might hope. Let's look at how a program will evaluate the boolean expression 1 <= x <= 10 while x stores the value 7:

  • First, we'll substitute the value of x: 1 <= 7 <= 10.
  • Now, 1 <= 7 <= 10 contains three known values (1, 7, and 10) and two operators (the two <= operators). The operators are identical, so we have to start with the one on the left: 1 <= 7 is true, because 1 is less than 7. Therefore, we'll replace 1 <= 7 with true: true <= 10.
  • How do we evaluate true <= 10? We can't! This is why you'll see an error if you try to write a comparison this way.

If we want to ask whether x is between 1 and 10, we'll need to write two separate comparisons and join them with the && operator to require both to be true for the whole expression to result in true. && and other boolean operations are covered in the next chapter.

Else

Optionally, an if statement may include an else immediately after its body. The else is followed by its own body, which the program will execute if the if's condition is false or skip if the if's condition is true. The pattern looks like this. Note that, like if (condition) {, else { should never include a semicolon.

An if with no else can either execute or skip a block of code. An if with an else will always execute either the if block or the else block. If you want your program to execute additional code some of the time, then you should probably use an if on its own. If you have two different blocks of code and always want one or the other to execute, then an if followed by an else is appropriate.

Finally, note that an else never has a condition. It will only execute when the if's condition is false, so a condition is not necessary.

Nested Ifs

We can write an if or if-else inside the body of an if or an else. A common term for this is "nesting". Code nested within multiple code blocks should be indented once for each code block:

if (condition1) {
    if (condition2) {
        statement1; // nested twice
    } else {
        statement2; // nested twice
    }
} else {
    statement3; // nested once
    if (condition3) {
        if (condition4) {
            statement4; // nested thrice
        }
    }
}

A flowchart equivalent to the above code would look like this:

Flowchart: Nested Ifs

Some people dislike excessive nesting, and I personally find code easier to read when nesting is kept to a minimum. However, it's a useful tool for combining multiple if statements or other structures.

Else-If

We'll sometimes want a conditional statement with more than two cases. For example, what if we wanted to convert a number from 1 to 7 into a day of the week? We could handle this with nested if-else statements, but it's not great:

int day = nextInt();

if (day == 1) {
    println("Sunday");
} else {
    if (day == 2) {
        println("Monday");
    } else {
        if (day == 3) {
            println("Tuesday");
        } else {
            if (day == 4) {
                println("Wednesday");
            } else {
                if (day == 5) {
                    println("Thursday");
                } else {
                    if (day == 6) {
                        println("Friday");
                    } else {
                        if (day == 7) {
                            println("Saturday");
                        } else {
                            println("Error");
                        }
                    }
                }
            }
        }
    }
}

We technically don't need to use a code block for an if or an else if there's only a single statement inside of it. We can even put it on the same line as the if or the else:

// The way we've been writing if statements, and the way you should continue to
// write them. The curly brackets usually improve clarity even when they aren't
// technically required.
if (condition) {
    statement;
}

// Note: this won't work in a jshell script
if (condition)
    statement;

if (condition) statement;

Now, consider that the if (day == 2), its else, and all of the code inside their bodies technically qualifies as a single compound statement (albeit, a very large one). This allows us to remove the nested code blocks and flatten our previous structure:

int day = nextInt();

if (day == 1) {
    println("Sunday");
} else if (day == 2) {
    println("Monday");
} else if (day == 3) {
    println("Tuesday");
} else if (day == 4) {
    println("Wednesday");
} else if (day == 5) {
    println("Thursday");
} else if (day == 6) {
    println("Friday");
} else if (day == 7) {
    println("Saturday");
} else {
    println("Error");
}

The else-if is not a special language feature, just a consequence of some lenient formatting rules that allow us to create a "chain" of multiple if-elses. This second version is much easier to read than the first version, so we see a significant benefit to leaving out the optional braces after each else (except the last one). This is one of the few exceptions to our rule about always including the curly braces after an if or an else.

Here is a flowchart for the else-if chain show above:

Flowchart: Else-If Chain

Mistakes to Avoid

Semicolon Misuse

With if statements, we've introduced lines of code that don't end in semicolons. As a general rule, you should never add a semicolon to a line of code that normally ends in an opening curly brace. If we review the structure of an if statement, we'll see that this means you do not add semicolons after an if or an else, or the condition of an if.

if (condition) {
    statement;
} else {
    statement;
}

We also do not normally put semicolons after curly braces, whether they are opening or closing braces.

Programmers who aren't used to the rules for semicolons sometimes make the mistake of adding them where they aren't needed. Depending on where you add the extra semicolon, it may significantly alter the meaning of your code in a way you did not expect. For example, what do you think will happen when we run the following jshell script?

int x = nextInt();
if (x < 0); {
    println("You entered a negative number.");
}

Try running the program and entering different numbers: try a negative number, zero, and a positive number. Did the program work as you expected?

There's an extra semicolon after the if statement's condition on the second line. This is valid code, but it probably doesn't mean what the programmer intended. Java interprets if (condition); as an empty if statement with no body. Java also allows us to create code blocks that aren't tied to if statements or other structures. All of this means that the previous program is equivalent to this program:

int x = nextInt();

if (x < 0) {
    ; // Empty statement does nothing
}

// A lone block of code that will always execute, even when x isn't negative!
{
    println("You entered a negative number.");
}

We'd get a similar result if we placed a semicolon after an else:

int x = nextInt();
if (x < 0) {
    println("You entered a negative number.");
} else; {
    println("You entered zero or a positive number.");
}

Notice the semicolon after the else on the fourth line. Try running this program and noting when its output is incorrect, then run it without the extra semicolon. We have the same issue as before: the else's body is actually the empty statement denoted by the semicolon, and the code block that appears to be its body is actually completely separate from the if-else.

Poor Indentation

You should always indent code nested within a code block, and you should indent consistently. There's no set amount that you have to indent in Java, but four spaces is pretty common. Whatever the space you use for one level of indentation, this should be the same throughout your entire program. Consistent formatting is important for keeping your code readable by a human.

Some examples of poor indentation:

// No indentation
if (condition1) {
if (condition2) {
statement1;
} else {
statement2;
}
statement3;
}

// Inconsistent: some lines are indented and others are not
if (condition1) {
    if (condition2) {
        statement1;
} else {
    statement2;
}
statement3;
}

// Inconsistent: different numbers of spaces used for indentation
if (condition1) {
  if (condition2) {
      statement1;
   } else {
 statement2;
     }
     statement3;
}

// Inconsistent: first level indents 4 spaces, second level indents 2 spaces
if (condition1) {
    if (condition2) {
      statement1;
    } else {
      statement2;
    }
    statement3;
}

Below are examples of good indentation:

if (condition1) {
    if (condition2) {
        statement1;
    } else {
        statement2;
    }
    statement3;
}

if (condition1) {
  if (condition2) {
    statement1;
  } else {
    statement2;
  }
  statement3;
}

if (condition1) {
        if (condition2) {
                statement1;
        } else {
                statement2;
        }
        statement3;
}

Dangling Else

Aside from readability and their necessity in jshell scripts, another reason to always include curly braces is to avoid creating a "dangling else":

// Despite the indentation, both versions of this code are identical. How will
// it be interpreted by the computer?

// Version A
if (condition1)
    if (condition2)
        statement1;
else
    statement2;

// Version B
if (condition1)
    if (condition2)
        statement1;
    else
        statement2;

// If you would like to check this in a jshell script, you'll need to put
// everything on one line and replace the placeholders with actual code. Try it
// with each combination of true/false values for the conditions and try to
// determine whether version A or B is correct.
bool condition1 = true;
bool condition2 = true;
if (condition1) if (condition2) println("statement1"); else println("statement2");

The following two flowcharts show the difference in logic implied by the indentation:

Version A:

Flowchart: Dangling Else Version A

Version B:

Flowchart: Dangling Else Version B

If you test this code, you'll see that version B describes what actually happens. The else is attached to the closest if statement it can be attached to. The fact that we have a rule for how to consistently interpret this makes it unambiguous to the computer, but to a human it can be unclear how to interpret this code. The use of curly braces and proper indentation removes all ambiguity, makes it easier to read, and allows us to tell the computer to use either interpretation:

// If we meant for it to be interpreted as version A, curly braces can make this
// happen
if (condition1) {
    if (condition2) {
        statement1;
    }
} else {
    statement2;
}

// If we meant for it be interpreted as version B, curly braces clarify our
// intent
if (condition1) {
    if (condition2) {
        statement1;
    } else {
        statement2;
    }
}

Version A vs Version B

If you had trouble determining whether version A or version B was correct earlier, the following truth table may help. It shows what we should expect to happen in version A compared to version B:

condition1condition2Version A printsVersion B prints
truetruestatement1statement1
truefalsenothingstatement2
falsetruestatement2nothing
falsefalsestatement2nothing

If you try each combination of truth values in jshell with no braces, you'll observe the results predicted for version B.

Variable Scope and Shadowing

Accidental shadowing is a common cause of errors when writing if statements. Make sure you understand the difference between declaring/initializing and reassigning variables so that you can avoid this type of error.

A similar problem can occur if you declare a variable in the wrong scope. If you want to initialize a variable inside of an if statement, it may be tempting to declare it inside of the if statement. However, if you intend to use it outside of the if statement, then this will not work. See the example in the section covering variable scope.

If you want your if statement to initialize a variable, I recommend declaring it before the if statement, then initializing it within the if statement. You will also need to ensure your if statement or chain of else-ifs ends with an else unless you assign a default value when first declaring the variable.

// Initialize within if statement
int x;
if (condition1) {
    x = 1;
} else if (condition2) {
    x = 2;
} else { // must end in an else to ensure x is always initialized
    x = 3;
}

// Initialize with default value, then reassign within if statement
int x = 3;
if (condition1) {
    x = 1;
} else if (condition2) {
    x = 2;
} // no else required because we already initialized with default value of 3

Boolean Logic

The boolean type has only two possible values: true and false. We can use several operators with these values to form larger boolean expressions to use with if statements and other constructs to ask complex questions. This chapter will focus on understanding the boolean logic operations and how to use them.

Boolean Operators

There are four boolean logic operators we need to cover: AND, OR, XOR, and NOT. These operators take one or two boolean operands and result in a new boolean value. In Java and many other programming languages, these operators are written as follows:

  • AND: &&
  • OR (inclusive OR): ||
  • XOR (eXclusive OR): ^^
  • NOT: !

AND

If I were to say "it's going to rain and snow tomorrow," then my statement will only be true if it both rains and snows tomorrow. If it only rains, only snows, or doesn't rain or snow, then my statement is false. This is how the boolean AND works.

a && b only results in true if both a and b are both true. If at least one operand is false, then a && b results in false.

OR and XOR

In English, the word "or" can be used inclusively or exclusively: an inclusive 'or' allows both options to be true, and an exclusive 'or' requires exactly one to be true. For example, consider the following two statements:

  1. Tomorrow it's at least going to rain or snow. (inclusive)
  2. Due to the weather, school will either start late or be cancelled entirely. (exclusive)

The first sentence would be true if it both rained and snowed. In the second sentence, the options presented are mutually exclusive; only one can be true.

a || b (inclusive or) always results in true unless both a and b are false. If both operands are false, then a || b results in false. a ^^ b (exclusive or) results in true if exactly one of its operands is true and the other is false. a ^^ b results in false if both of its operands are true or both are false. You can also say a ^^ b is true if a isn't equal to b, or false if a and b are the same.

NOT

The NOT operator only applies to one operand. !a results in the opposite of whatever a is equal to: true if a is false, or false if a is true. This is how the word "not" is used in English. If I say "I'm not going out tomorrow," that statement is true if I don't go out and false if I do go out.

Truth Table

Below is a table showing the result of applying each boolean operator to every permutation of true/false values.

aba && ba || ba ^^ b!a!b
truetruetruetruefalsefalsefalse
truefalsefalsetruetruefalsetrue
falsetruefalsetruetruetruefalse
falsefalsefalsefalsefalsetruetrue

Short-Circuiting

Sometimes, a boolean expression involving the && or || operator can be fully evaluated without checking the second operand. In these situations, the computer will skip evaluating the second operand, and this is called "short-circuiting".

When Do We Short-Circuit?

There are two situations where we can short-circuit a boolean expression:

  • When we are ANDing two values (&&) and we see that the first value is false, we don't need to check the second value to know that the result is false. This is because an AND results in false unless both operands are true.
  • When we are ORing two values (||) and we see that the first value is true, we don't need to check the second value to know that the result is true. This is because an OR results in true unless both operands are false.

You can see this if you check the truth table shown in the operators section. Both rows where a is false result in false for a && b, and both rows where a is true result in true for a || b. You don't need to know what's in column b in either of those situations to tell me what's going to be in the column for && or ||. This is why we can "short-circuit" and skip looking in column b.

On the other hand, if a is true, then you still need to check b to determine what's under a && b, and if a is false, then you still need to check b to determine what's under a || b. In these situations, we need to evaluate b before we know the result of the expression. This means we cannot short-circuit.

Uses

Short-circuiting can be useful when we want to evaluate two boolean expressions, but one of them will cause an error in certain situations. For example, we're not allowed to divide integers by zero. If we want to divide two unknown integers, we need to make sure they aren't zero first:

int x = nextInt();
int y = nextInt();

if (y != 0) {
    println(x / y);
} else {
    println("We can't divide by zero!");
}

Let's say we want to know whether x / y is an even number. We could write our code like this:

int x = nextInt();
int y = nextInt();

if (y != 0) {
    if ((x / y) % 2 == 0) {
        println("x / y is even");
    } else {
        println("x / y is not even");
    }
} else {
    println("x / y is not even"); // if we can't divide x / y, then it's not even
}

However, that's long and repetitive. We can simplify this code by taking advantage of short-circuiting:

int x = nextInt();
int y = nextInt();

if (y != 0 && (x / y) % 2 == 0) {
    println("x / y is even");
} else {
    println("x / y is not even");
}

If the first part, y != 0, is false, then y is zero and dividing will cause an error. However, if we have false followed by &&, then we don't need to evaluate the part after the && to know that the && results in false. Java will use this fact to skip evaluating the (x / y) % 2 == 0, and we won't divide by zero and cause an error.

We can use || to ask the same question with short-circuiting if we change the comparisons:

int x = nextInt();
int y = nextInt();

if (y == 0 || (x / y) % 2 != 0) {
    println("x / y is not even");
} else {
    println("x / y is even");
}

Early Java API

The acronym "API" stands for Application Programming Interface. It refers to the public-facing part of a program or code library that other code can interact with. For example, the Java language has a large standard library with a well-documented API that you'll use more as you learn the language. This chapter will introduce a few parts of Java's API that will be useful in some of the programs you are currently writing or will write in the near future.

Math

Java's Math class is a collection of functions and constants that are used in many programs. We'll cover a few that may be relevant to you in the near future, but you can always view the online documentation if you want to see what else is available.

We've not yet explained the term class, and it won't be that relevant until we switch to compiled Java. For now, think of a class as a container for other code, such as the constants and functions described below.

Using the Math API

You can access any of the functions or constants in the Math class by typing Math. followed by the name of that constant or function:

// prints the value of PI
println(Math.PI);

int a = nextInt();
int b = nextInt();
// prints the larger of the two numbers entered by the user
println(Math.max(a, b));

Constants

There are only a few constants in the Math class, but they are frequently used in mathematics and may be relevant to some of your programs:

  • Math.PI is an approximation of pi
  • Math.TAU is an approximation of pi doubled
  • Math.E is an approximation of e

Functions

The functions shown below will use placeholder variable names, which you should replace with the appropriate variables or literals from your program when you use these functions.

Some of the functions in Math:

  • Math.abs(x) takes the absolute value of x
  • Math.ceil(x), Math.floor(x), and Math.round(x) all round x (ceil always rounds up, floor always rounds down, and round follows standard rounding rules)
  • Math.max(x, y) and Math.min(x, y) returns the largest (max) or smallest (min) of x and y
  • Math.pow(x, y) raises x to the power of y
  • Math.sqrt(x) returns the square root of x
  • Math.random() generates a random number from 0.0 to 1.0 (excluding 1.0)
  • Math.toDegrees(x) and Math.toRadians(x) convert the angle x from radians to degrees or from degrees to radians
  • Many trigonometric functions, such as Math.sin(x), Math.cos(x), and Math.tan(x), are included in the Math class

String

Java's strings come with a bunch of useful methods, which are functions attached to objects. Unlike the various primitive types we've covered, strings are objects that contain both information (the characters in the strings) and methods (functions that operate on those characters). We can call these methods on any string literal or variable:

String name = "zach";
println(name);
println(name.toUpperCase());
println(name.charAt(3));
println(name);
println("hello".substring(1));
println("HElLo".toLowerCase());

A note about all of these methods: Java's strings are immutable, which means they cannot be modified after they are created. Converting a string to all uppercase letters does not change the original string, it creates a new string with the same text, but all of the letters are capitalized. This can be observed when we print name for a second time in the previous example. Any method you call on a string will leave the original intact.

Indexes

The term index refers to the position of something in a sequence. In Java and most other programming languages, the index 0 corresponds to the first position within a sequence, the index 1 corresponds to the second position, and so on. This is called "zero-indexing", and it applies to any index of a character within a string. For example, the 'H' in "Hello, world!" is at index 0, and the ',' is at index 5.

String Methods

As with Math, you can read the full API page for string if you'd like to see more detail than I've provided in this section. These methods will also be shown with placeholder variables (s, s1, s2, etc. for strings, c, c1, c2, etc. for characters, and x, y, etc. for numbers). Replace these with the appropriate variables or literals from your own code when using these methods.

  • s1.equals(s2) returns true if s1 and s2 have the exact same sequence of characters and false if they do not
  • s1.equalsIgnoreCase(s2) is like .equals, but it ignores differences in capitalization
  • s.length() returns the number of characters in s
  • s.toUpperCase() and s.toLowerCase() convert all of the letters in s to uppercase or lowercase letters
  • s.charAt(x) returns the character at index x in s
  • s.indexOf(c) returns the index where the character c first appears in s
  • s.lastIndexOf(c) returns the index where the character c last appears in s
  • s1.startsWith(s2) and s1.endsWith(s2) return true if s1 starts/ends with s2 and false if it does not
  • s.substring(x) returns a string containing every character in s from index x until the end of s
  • s.substring(x, y) returns a string containing every character in s from index x up to (but not including) index y

Loops

Loops allow a program to repeat a block of code an arbitrary number of times. Learning to use loops will vastly increase what you are able to accomplish with a few lines of code.

While Loop

The first type of loop we'll loop at is called a while loop. This loop will repeat its body for as long as some condition remains true. For example, if we want to print the numbers 1 to 100, we can write a loop that adds 1 to a variable and prints it for as long as that variable is less than 100.

int x = 0;
while (x < 100) {
    x = x + 1;
    println(x);
}

This code is equivalent to the following flowchart:

Flowchart: 1 to 100 While Loop

Structure of a While Loop

A while loop has the same structure as an if statement:

  • The while keyword
  • A condition (boolean expression) in parenthesis after the while
  • A body (a code block enclosed within curly braces)

The templates section shows this structure along with another example loop.

The two differences between an if statement and a while loop are:

  • The while loop repeats for as long as its condition is true, and the if statement does not repeat (this is why we don't call an if statement an "if loop").
  • The while loop does not allow for an optional else when its condition is false.

Infinite Loops

Several common mistakes can create infinite loops, which are loops that will never terminate on their own. When a program encounters an infinite loop, it will remain stuck in the loop until the user forces the program to stop.

Semicolon

One way to accidentally create an infinite loop is to place a semicolon after the condition:

while (condition); {
    statements;
}

This is similar to misusing semicolons after if statements, but the effect is different. This creates an empty loop, which will never change its condition and therefore never terminate (technically you could have such a loop terminate if the condition were able to change on its own, but this isn't typical). Like the if statement, the code shown above is equivalent to this:

while (condition) {
    ; // empty statement
}

// unconditional code block, which would execute if we could ever reach it
{
    statements;
}

Update Doesn't Match Condition

In most cases, this type of loop will technically terminate due to integer overflow, but it'll take far longer than you intended. If you're counting up with a condition that expects you to get below a certain value, or are counting down with a condition that expects you to get above a certain value, then your loop will not end until your counter variable overflows:

int x = 100;
while (x > 0) {
    // This should be x = x - 1
    x = x + 1;
    println(x);
}
int x = 1;
while (x <= 100) {
    // This should be x = x + 1
    x = x - 1;
    println(x);
}

No Update Step

Many while loops have a statement that serves to update its condition. A good example would be a counting loop, which always has a statement that increases or decreases its counting variable. If we forget this update step in a loop that requires it, then the condition will always be true and the loop will never terminate.

int x = 0;
while (x < 10) {
    println(x);
    // we forgot to include x = x + 1;
}

Accidental Shadowing

Like the semicolon problem, accidental shadowing can occur in while loops the same way it occurs in if statement. The result will usually be an infinite loop if we shadow a variable used in the condition. Fortunately, some forms of accidental shadowing are not valid syntax and will produce a syntax error.

int number = 0;
while (number < 1 || number > 10) {
    println("Please enter a number from 1 to 10.");
    // This will compile and cause an infinite loop because we're not
    // reassigning the original number variable
    int number = nextInt();
}
int x = 0;
while (x < 10) {
    println(x);
    // This will not compile because we're declaring a new x variable while
    // using the old x to initialize it. Syntax errors are usually easier to fix
    // than other types of errors.
    int x = x + 1;
}

Do-While Loop

A do-while loop is a slight variant of a while loop. It checks its condition at the end of the loop rather than at the beginning, so it will always execute the body at least once, even if its condition is initally false. Compare the following code and flowchart to the while loop flowchart to see the difference:

int x = 0;
do {
    x = x + 1;
    println(x);
} while (x < 100);

Flowchart: Do-While Loop

The structure of a do-while loop is similar to a while loop, with two small differences:

  • The loop begins with the do keyword and no condition
  • We still use the while keyword, but after the loop's body, and there is a semicolon after the condition

The do-while loop exists purely for convenience, and chance are you'll rarely use it. It's worth using when it makes your code clearer or more concise, but you can always use a standard while loop to accomplish the same thing.

Counting Loop

A counting loop repeats a set number of times, using a variable to count the current number of repetitions. Counting loops are useful if you know how many times you want your loop to execute its body, or if your program can calculate the necessary number of repetitions before the loop begins.

It's usually preferable to use a for loop when you want a counting loop, but we'll introduce it using while loop syntax.

A counting loop will usually look something like this:

// variable to track total repetitions
int count = 0;

// loop ends when we reach the total
while (count < totalRepetitions) {
    // whatever work the loop is supposed to do goes here
    ...

    // we usually increment our count variable at the end of the loop
    count = count + 1;
}

Example: Count Down/Count Up

Printing a sequence of numbers is a suitable task for a counting loop. We can count from 1 to 10 with the following loop:

int count = 0;

while (count < 10) {
    println(count + 1);

    count = count + 1;
}

There are many ways we could structure this loop. This first example follows the exact same pattern as the example, but there are different ways we could simplify it:

int count = 1;

while (count <= 10) {
    println(count);

    count = count + 1;
}

Since we want to print numbers from 1 to 10, not 0 to 9, we can change our count value's range to be the exact numbers we're printing. It doesn't matter if the counting loop starts counting at 0, or whether it counts up or down or by increments other than 1. Go with whatever makes sense for the task at hand.

int count = 0;

while (count < 10) {
    count = count + 1;

    println(count);
}

This loop increments the count variable before the print statement so that we don't have to add one inside the print statement. I prefer to avoid moving the increment step away from the end of the loop for a couple of reasons:

  • If it's always at the end of the loop, it stands out and is easy to find.
  • The value changes during that step, so any code referring to the count will get a different value depending on whether it's before or after the increment step.

This comes down to personal preference and sticking to familiar patterns to lower the chances that I'll make a careless mistake.

I mentioned that a counting loop could count up or down, so let's take a look at a loop that prints a countdown. I'll start off by forcing it to follow the original template and make its variable count up from zero, then show a cleaner version that adjusts the range to suit the task:

int count = 0;

while (count < 10) {
    // We can do a bit of math to ensure we print a countdown even though our
    // count variable is increasing
    println(10 - count);

    count = count + 1;
}
int count = 10;

while (count > 0) {
    // Now that the range is equal to the numbers we want to print and in the
    // order we want to print them, we can print the count directly
    println(count);

    count = count - 1;
}

There are two reasons that I prefer the second version:

  • The code is slightly clearer because the range of numbers used for the count is the same as the numbers we want to print.
  • The top end of the range, 10 only appears once. Repeating code or information is usually bad, because if we need to go back and change it we always have a chance to introduce a bug by forgetting one of the copies we were supposed to update.

The count up/count down loop isn't too complex, and there are many ways you can adjust it. Try making it count by 2 or 3 instead of 1, or count through a range of numbers entered by the user (for example, if the user typed 2 and 14, then the loop would print the numbers 2 to 14).

Example: Read Set Amount of Input

In the previous example, we needed to use the count variable in the task that the loop is repeating. Sometimes we don't care about the value of count and just want to repeat something a set number of times.

Try coding this program on your own before you look at my solution:

  • Ask the user to enter ten integers, then print the sum after they enter the last number.
  • Your code should only include a single call to nextInt(). You could write this program by writing nextInt() ten times, but the point is to use a loop.

Flowchart: Sum Ten Ints

Here's my solution:

println("Please enter ten integers.");

int sum = 0;

// It's common practice to name count variables i
int i = 0;

while (i < 10) {
    int input = nextInt();
    sum = sum + input;

    i = i + 1;
}

println("The sum of your numbers is " + sum + ".");

Flag-Controlled Loops

A flag-controlled loop is a while loop that uses a boolean flag variable to control when the loop ends rather than putting the logic in its condition. A flag-controlled loop can simplify your loop's condition, remove duplicate code, and make your code overall easier to read when used appropriately.

A flag-controlled loop will usually look something like this:

// a boolean variable tells us whether to continue the loop
boolean flag = true;

// instead of using boolean logic in our condition, we check our flag value
while (flag) {
    // whatever work we want the loop to do goes here
    ...

    // at some point we need to check whether to change our flag value and end
    // the loop
    if (loopShouldEnd) {
        flag = false;
    }

    // we might do additional work after the flag check, but this would still
    // happen even if the flag is set to false

    // we can have multiple checks that potentially end the loop
    if (loopShouldEndForAnotherReason) {
        flag = false;
    }
}

Example: Require Valid Input

Write a program that requires a user to enter a positive integer followed by a factor of that integer. I recommend trying to write this program yourself before viewing the solution. You can use the flowchart below as a guide, then read a walkthrough of my solution after the flowchart.

Flowchart: Flag Loop for Valid Input

// Variables to store user input
int number = 0;
int factor = 0;

// Flag variable
boolean validInput = false;

// Loop repeats until flag is true
while (!validInput) {
    // Prompt for first input
    println("Please enter a positive integer.");
    number = nextInt();

    // Prompt for second input
    println("Please enter a factor of " + number + ".");
    factor = nextInt();

    // Check whether all input is valid
    if (number <= 0) {
        // Error message
        println(number + " is not positive!");
    } else if (factor <= 0 || number % factor != 0) {
        // Error message
        println(factor + " is not a factor of " + number + "!");
    } else {
        // Change flag to a value that will end the loop
        validInput = true;
    }
}

// The loop will only end when we have valid input, so we can proceed assuming
// factor is a factor of number.
int otherFactor = number / factor;
println(number + " can be factored into " + factor + " and " + otherFactor);

The code below functions identically to the flag-controlled loop, but it does not use a flag variable.

// Variables to store user input
int number = 0;
int factor = 0;

// Loop repeats until input is valid
while (number < 0 || factor < 0 || number % factor != 0) {
    // Prompt for first input
    println("Please enter a positive integer.");
    number = nextInt();

    // Prompt for second input
    println("Please enter a factor of " + number + ".");
    factor = nextInt();

    // Check whether all input is valid
    if (number < 0) {
        // Error message
        println(number + " is not positive!");
    } else if (factor < 0 || number % factor != 0) {
        // Error message
        println(factor + " is not a factor of " + number + "!");
    }
}

// The loop will only end when we have valid input, so we can proceed assuming
// factor is a factor of number.
int otherFactor = number / factor;
println(number + " can be factored into " + factor + " and " + otherFactor);

There are pros and cons to each version of the code:

  • The flag condition (!validInput) is easier to read than the non-flag condition (number < 0 || factor < 0 || number % factor != 0).
  • The flag loop avoids duplicating the conditions between the loop and the if statements. Duplicate code makes it easier to introduce errors: you have to remember to change each copy if you modify the program, which is easy to forget.
  • The version without the flag is more concise, which means there is less code to read and fewer places for us to make mistakes.

When you're choosing how to structure your code, you should consider the benefits and drawbacks of each approach. I would probably use a flag in this case, but if we weren't including error messages we don't benefit much from a flag variable:

  • The duplicate code was in the if statements that print error messages, and removing the error messages would mean neither version duplicates code.
  • The flagless code would remove the if statements entirely, whereas the flag code must include an if statement at the end just for breaking out of the loop.

Example: Reading a List

Write a program that reads a list of strings from the user, concatenating each string into one long list string. When the user types "quit", print the list and end the program. You should also try writing this one yourself using the flowchart before you view the solution.

Flowchart: Flag Loop for Reading a List

// User input variable
String list = "List: ";

// Flag variable
boolean readingList = true;

// Loop repeats until flag is false
while (readingList) {
    // User input
    println("Enter an item for the list or \"quit\" to quit.");
    String input = nextLine();

    // Check for quit
    if (input.equalsIgnoreCase("quit")) {
        // Set flag to end loop
        readingList = false;
    } else {
        // Append input to list if it's not the quit command
        list = list + input + ",";
    }
}

// Remove the last comma from the list
// This isn't required by the flowchart, but it makes the output nicer
list = list.substring(0, list.length() - 1);

// Print the whole list
println(list);

Without the flag, our code would look like this:

// User input variables
String input = "";
String list = "List: ";

// Loop repeats until user types quit
while (input.equalsIgnoreCase("quit")) {
    // User input
    println("Enter an item for the list or \"quit\" to quit.");
    input = nextLine();

    // Check for quit
    if (!input.equalsIgnoreCase("quit")) {
        // Append input to list if it's not the quit command
        list = list + input + ",";
    }
}

// Remove the last comma from the list
list = list.substring(0, list.length() - 1);

// Print the whole list
println(list);

Consider the pros and cons of each version of this program. Which do you prefer?

For Loop

The for loop exists to make it easier to code certain types of loops, such as counting loops. With a while loop, we need to declare and initialize the counting variable before the loop, check the value in the loop's condition, and update the counting variable's value in the loop's body:

int i = 0; // init

while (i < 100) { // condition
    println(i);

    i = i + 1; // update
}

A for loop lets us combine all three of these steps in the parenthesis after the for keyword:

for (int i = 0 /*init*/; i < 100 /*condition*/; i = i + 1 /*update*/) {
    println(i);
}

The for loop is an example of syntactic sugar, or something that isn't strictly necessary, but that makes it easier or clearer to write some kind of code. The for loop is generally going to be more concise than a counting while loop, and putting all of the counting logic in the same place can make it easier to read. Any loop that can be written as a while loop can be written as a for loop, and any loop that can be written as a for loop can be written as a while loop. You should use whichever one seems best suited to the problem you are solving.

Abbreviations for Incrementing Variables

You may or may not have seen the +=, ++, -=, and -- operators before, but they're commonly used with for loops. These operators allow you to more concisely increment (increase the value of) or decrement (decrease the value of) a variable:

int i = 0;

// the following lines of code all increase the value of i by 1
i = i + 1;
i += 1;
i++;
++i;

// the following lines of code all decrease the value of i by 1
i = i - 1;
i -= 1;
i--;
--i;

There is technically a small difference between putting the ++ and -- operator before or after a variable, but this only matters if you are using it in an expression: int x = i++ and int x = ++i will result in different values for x. Otherwise, it isn't going to matter which way you write these operators, so go with the style you prefer. I also tend to avoid using them in situations where they behave differently because it can make the code harder to read.

Functions

Functions were briefly mentioned in the quickstart chapter, and you've been using several functions for a while: println, nextLine, and the other I/O commands are all functions.

We could technically write our programs without using functions, but in reality this would be impractical. Functions offer many benefits, including:

  • Abstraction: it's often convenient to tell the computer to perform a task and not worry about the details. If we had to look at each step of every task the computer performed, down to the low-level instructions executed by the CPU, then we would be overwhelmed with information and find it difficult to write any code. Consider printing to the terminal: do you know how your program takes a string and makes it appear on the terminal? It's a much more complicated process than you might expect, but because Java includes a println function to handle this for you, you can tell your program to produce output without worrying about every little detail.
  • Organization: we can define functions for different parts of our program, breaking it into smaller parts that we can write one at a time. The larger a program is, the more important it is to keep code organized.
  • Testing: when we want to test our program, it's much easier to test individual functions than it is to test the entire program at once. This type of test is called a unit test, and it's heavily used in most software projects to ensure code works as intended.
  • Avoid Repetition: when we copy-paste or write the same code in multiple places, we make our code longer and add opportunities to make mistakes. Longer code takes more effort to read and modify, and if we change code that appears in several places then we can easily forget to update every occurrence of that code. It's usually better to write the code once as a function and call the function when we need to use that code.

We've already benefitted from the abstractions provided by Java's built-in functions, but to fully benefit from functions we need to know how to write our own. This chapter will cover how to do this in a Jshell script.

Defining Functions in Jshell

A function definition has the following parts:

  • If the function returns a value, then it should begin with a return type, which tells us what type of value it returns. If a function doesn't return a value, then it should begin with the keyword void.
  • The function has a name after the return type. Function names follow the standard rules for Java identifiers that we learned for variable names.
  • If the function has any parameters, then these are listed in parenthesis after the function name. If the function has no parameters, then we must still include a set of empty parenthesis after the name.
  • The function's body (the code it will execute) goes inside a code block after the parameter list.
<return type> <function name>(<parameter list>) {
    <function body>
}

A function's parameters tell the program what information the function needs to receive from the code that calls it. A parameter acts like a variable, but its initial value is supplied when we call it. The parameter list just looks like a comma-separated list of variable declarations. For example, if a function needed two parameters, a double and a boolean, then its parameter list might look like this: functionName(double x, boolean y). Like variable names, function and parameter names should be descriptive whenever possible. I've used generic names here because we have no further context for what these parameters mean. If they referred to a distance and whether that distance was in metric units, then we might instead write functionName(double distance, boolean isMetric).

Return Values

If a function is meant to create information for another part of the program to use, then it should return that information to the code that called it. You have already used functions with return values: all of the input functions ( nextLine, nextInt, etc.) return the value they read from the user, and all of the Math functions (abs, min, sqrt, etc.) return the number they calculated.

If we want our function to return a value, then we need to include the value's type before the name. We also need to give our function a return statement that includes the value it returns. A return statement is the return keyword followed by an expression whose type matches the function's return type. For example, if we had a function that adds two double values, then its return statement might look like this: return x + y;, where x and y are the function's parameters. The entire function would look like this:

double add(double x, double y) {
    return x + y;
}

This isn't a useful function because it's easier to add by writing 2 + 3 than by calling our function with add(2, 3). This is just a short function that demonstrates what a return statement looks like.

If our function has a return type, then it must always return a value of that type. It should not be possible for such a function to end without returning. For example, take a look at the following divide function:

int divide(int x, int y) {
    if (y != 0) {
        return x / y;
    }
}

This is not a valid function because when y is equal to 0, the function does not return a value. It must either always return a value or never return a value. There are exceptions (and they're literally called exceptions), but for now all of our functions must return something if they have a return type. We don't have a good way to handle errors yet, so the best we can do for now is let our divide function attempt the division and crash if it divides by zero:

int divide(int x, int y) {
    return x / y;
}

When new programmers are using if statements or loops in a function and have multiple return statements, it's common to accidentally write functions like the first version of divide that aren't guaranteed to return a value. A few tips for avoiding this:

  • If your return statement is inside an if statement, make sure that if statement has an else clause with its own return statement.
  • If you have an else-if chain with return statements inside the bodies of the ifs/else-ifs, then make sure the chain ends with an else and that this else contains a return statement.
  • If you have a return statement inside of a loop, then you should probably have a default return statement at the end of the function body in case the loop is never executed or the return statement inside the loop is never triggered.

Finally, note that as soon as a function reaches a return statement, the function will stop executing. If you put code after a return statement, it will never execute:

int divide(int x, int y) {
    return x / y;
    println("This will never be printed!");
}

Practice

Here are a few descriptions of small functions we could write. Try writing a few of them yourself before looking at the solutions.

Before you write a function definition, it may help to answer two questions:

  • Does this function require any parameters? If it always does the same thing, or it doesn't rely on input from other parts of the program, then it probably doesn't need parameters.
  • Does this function return anything? If another part of the program would want to use a value created by the function, then it should return that value. Printing a value is not the same as returning it, and if the function's only job is to print something then it probably doesn't return a value.

The function descriptions:

  1. A function named sayHello that prints the message "Hello, World!".
  2. A function named greetPerson that takes a name and prints a greeting in the form of "Hello <name>, how are you doing today?".
  3. A function named square that calculates (but does not print) the square of an integer.

Solution code:

void sayHello() {
    println("Hello, World!");
}

void greetPerson(String name) {
    println("Hello " + name + ", how are you doing today?");
}

int square(int x) {
    return x * x;
}

Solution explanations:

  1. The sayHello function does not require any parameter input because it always does the same thing. No part of the function's behavior needs to change, and it doesn't need additional information to print "Hello, World!". The function is printing rather than producing information for another part of the program, so it also doesn't need to return anything. This is why its return type is void.
  2. The greetPerson function is supposed to greet someone by name, but the description does not say what name we're supposed to use. Instead, it's supposed to take any name and insert it into a message, which means it needs us to tell it what name to print. This is why we have a name parameter. As with the sayHello function, we're not producing information to use in other parts of the program, we're sending information outside of the program with println. This means we don't need to return anything, and our return type is void.
  3. The square function needs to know what number it's squaring, and the number is supposed to be an int according to the description, so we need a single parameter with the type int. This parameter doesn't mean anything beyond the fact that it's a number, so an arbitrary name like x or a is fine here. The function is supposed to calculate something, not print it, so in order for this to be useful we'll have to return the resulting value so another part of the program can make use of it. The calculation produces an int, so our return type should be int.

Calling Functions

A function call is how we tell our program to execute the code in a function's body. We've already written plenty of function calls: the statements println("Hello, World!"); and String name = nextLine(); both contain function calls. A function call has two parts:

  • A function call begins with the function's name.
  • A function call includes a list of argument in parenthesis after the function name, or a set of empty parenthesis if the function requires no arguments.

The argument list in a function call is a list of values to use when initializing the function's parameters. Let's consider the add function defined in the previous section:

double add(double x, double y) {
    return x + y;
}

We can't run the code x * y without first initializing x and y. If we call add, then we need to provide an initial value for each parameter: add(2, 3). The first argument is used to initialize the first parameter (x), the second argument initializes the second parameter, and so on. You may hear the terms "argument" and "parameter" used interchangeably because they're both referring to a function's inputs. It's usually clear from context what we mean when we say "argument" or "parameter", so don't worry about getting them mixed up. You may also see the term "formal parameter" used to refer to the parameters in the function's definition and the term "actual parameter" used for the values supplied with a function call.

Examples

If we want to call the functions defined in the examples from the previous section, we would write:

// will print "Hello, World!"
sayHello();

// will print "Hello Zach, how are you doing today?"
greetPerson("Zach");

// will set x equal to 16
int x = square(4);

// will print 16
println(x);

// combines the previous two statements into one step; will also print 16
println(square(4));

Arrays

An array is a collection of values, all of which have the same type. They allow you to store multiple values in the same variable, which makes it convenient to process large amounts of information with a short loop.

Creating Arrays

Declaring an Array

We can have an array of ints, strings, doubles, or any of the other types we've seen so far. We can even make arrays of arrays, but that's a topic for later.

An array is stored in a single variable, and unless we're using var to infer the type, we need to state that it's an array and what it's an array of. We can specify an array of some other type by adding a pair of square brackets ([]) after the other type. For example, int[] is an array of integers, and String[] is an array of strings. The following code declares, but does not initialize, several array variables of different types.

int[] arrayOfInts;
String[] arrayOfStrings;
double[] arrayOfDoubles;

Creating a new Array

We'll usually want to initialize an array variable by creating a new array. To create an array, we write the new keyword, the array's type, and the array's size inside the square brackets. For example, new int[10] would create a new array that holds ten integers. We'll modify the previous code to declare and initialize our array variables:

int[] arrayOfInts = new int[10];
String[] arrayOfStrings = new String[5];
double[] arrayOfDoubles = new double[1000];

This has created three arrays: an array of ten integers, an array of five strings, and an array of one-thousand doubles.

A couple of notes about these arrays before we continue:

  • Each array's size is fixed once it has been created. We cannot grow or shrink these arrays.
  • The arrays are not empty. An array always contains exactly the same number of values. When we create new arrays like this, they are initialized with the default value for their type: 0 for numeric types, false for booleans, and null for strings or other objects/reference types. A value of null means there is no string at that position in the array. We'll learn more about null later, but for now it's enough to understand that it represents the absence of an object.

If we want to create a small array that starts with a specific set of values instead of the default value for its type, then we have another option:

int[] countdown = new int[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
String[] names = new String[] { "Alice", "Bob", "Charlie" };

This could be used for larger arrays, but it's not very practical. If our array isn't, we'll probably want to use a loop to set the initial values after we create the array.

Accessing and Mutating Arrays

Now that you know how to create an array, what can you do with it? Let's start by printing the array:

int[] primes = new int[] { 2, 3, 5, 7, 11, 13 };
println(primes);

Was the output what you expected? Probably not. Java's default method for converting objects into strings doesn't produce a very useful string for an array. We can convert an array to a string with a loop, but first we need to learn how to access the individual values in an array.

Indexing

We mentioned indexing in the String API section, but it's worth going into more detail now that we're dealing with arrays. To access a single value stored in an array (we call these values the array's elements), we need to specify its index. The index of an element is that element's distance from the beginning of the array. This means the first element has an index of 0 because it is at the beginning of the array. The second element has an index of 1 because it's one space away from the start of the array.

If we want to refer to a specific element of an array, we put that element's index in square brackets after the name of the array:

int[] primes = new int[] { 2, 3, 5, 7, 11, 13 };
println(primes[0]); // prints 2
println(primes[1]); // prints 3
println(primes[2]); // prints 5
println(primes[3]); // prints 7
println(primes[4]); // prints 11
println(primes[5]); // prints 13

We can reassign one element of the array as long as we specify that element's index:

int[] primes = new int[] { 2, 3, 5, 7, 11, 13 };
primes[2] = 10;
println(primes[0]); // prints 2
println(primes[1]); // prints 3
println(primes[2]); // prints 10!

If you don't specify an index, you'll be attempting to reassign the variable that holds the array. This will usually result in a syntax error:

int[] primes = new int[] { 2, 3, 5, 7, 11, 13 };
primes = 10; // primes is an array, not an int, so we can't reassign it to 10

Getting Used to Zero-Indexing

Many people find zero-indexing counterintuitive. If you think of the position of an element, then you need to subtract one from the position to get the element's index. This isn't intuitive, and it forces you to do extra work each time you think about the index as a position.

The best analogy I've heard for zero-indexing is a ruler: the first mark on a ruler represents a distance of zero, not one. This is why I recommend thinking about indexes as distances rather than positions.

Array Bounds

You can get the length of an array by typing .length after the name:

int[] primes = new int[] { 2, 3, 5, 7, 11, 13 };
println(primes.length); // prints 6

The only valid indexes for an array are the integers from zero to its length minus 1. The length is never a valid index. For example, if we had an array of length 10 then the tenth and last element would be at index 9. An index of 10 would be the eleventh element, and the array only has ten elements.

If we try to index an array with a negative number, or with a number greater than or equal to its length, then this will cause an ArrayIndexOutOfBoundsException, which is a type of error that will cause the program to crash.

Iterating Over Arrays

So far, arrays haven't made our lives any easier. If we always have to provide a specific index when we interact with an array, it might as well be a bunch of separate variables. This:

String[] names = new String[] { "Alice", "Bob", "Charles" };
names[1] = "Robert";
println(names[0]);
println(names[1]);
println(names[2]);

is no different from this:

String name1 = "Alice", name2 = "Bob", name3 = "Charles";
name2 = "Robert";
println(name1);
println(name2);
println(name3);

In fact, the second version is probably easier to follow. So why would we use an array?

The short (and probably not very helpful) answer is because we can also index an array using a variable:

String[] names = new String[] { "Alice", "Bob", "Charles" };
int index = 1;
names[index] = "Robert";
index = 0;
println(names[index]);
index = 1;
println(names[index]);
index = 2;
println(names[index]);

Acutally, that's even worse, isn't it? What if we just focus on the printing, and we increment the index instead of assigning specific numbers?

String[] names = new String[] { "Alice", "Bob", "Charles" };
int index = 0;
println(names[index]);
index = index + 1;
println(names[index]);
index = index + 1;
println(names[index]);

That's not much better either. Obviously there's a point to all this, I'm not just wasting your time. Take another look at the last version. Did you notice two of the lines were copied and pasted several times with no changes? Isn't there a more convenient way to repeat two lines of code over and over again?

String[] names = new String[] { "Alice", "Bob", "Charles" };
int index = 0;
while (index < 3) {
    println(names[index]);
    index = index + 1;
}

It turns out there is! The fact that we can index with a variable lets us use a counting loop to print each element of the array. This is even nicer if we use a for loop:

String[] names = new String[] { "Alice", "Bob", "Charles" };
for (int i = 0; i < 3; ++i) {
    println(names[i]);
}

What happens if we add some more names?

// without an array
String name1 = "Alice", name2 = "Bob", name3 = "Charles", name4 = "David",
       name5 = "Eve";
println(name1);
println(name2);
println(name3);
println(name4);
println(name5);

// with an array
String[] names = new String[] { "Alice", "Bob", "Charles", "David", "Eve" };
for (int i = 0; i < names.length; ++i) {
    println(names[i]);
}

I changed the condition to i < names.length instead of i < 5 because names.length will always be the length of the array. Now if the array gets larger again we won't have to change any part of the loop. Hopefully this shows you why it's nice to have an array. Just in case you're not convinced, consider what this would look like if we had 1000 names.

// without an array
String name1 = "Alice", name2 = "Bob", /* 997 names omitted */ name1000 = "asdf";
println(name1);
println(name2);
println(name3);
println(name4);
println(name5);
println(name6);
/* 993 print statements omitted */
println(name1000);

// with an array
String[] names = new String[] { "Alice", "Bob", /* 997 names omitted */ "asdf" };
for (int i = 0; i < names.length; ++i) {
    println(names[i]);
}

The loop can still print the entire array with three lines of code. This is part of what makes arrays so powerful: when combined with a loop, you can process vast amounts of information with several lines of code.

Enhanced For Loop

Java has another type of for loop called an "enhanced for loop". You might also see this type of loop referred to as a "foreach" loop or a "for comprehension". An enhanced for loop is useful when you want to iterate over an array, but you don't need to modify the array and you don't need to know the index of an element.

for (String name: names) {
    println(name);
}

The for (String name: names) { is another example of syntactic sugar. The enhanced for loop shown above is equivalent to the following regular for loop:

for (int i = 0; i < names.length; ++i) {
    String name = names[i];
    println(name);
}

The for (String name: names) { is a shorter way of writing the first two lines, but you no longer have access to the i variable inside your loop.

Reference vs Value Types

Arrays and Strings are both types of objects. We can refer to them as reference types, which differ from primitive types (also called value types) in several ways. The primary difference is how the program stores their data:

  • Value-type variables directly store the data for the value they represent. If x and y are value types, then the assignment statement x = y will copy the data from x into y.
  • Reference-type variables store a memory address that points to their data, which is stored in a region of memory called the heap. If x and y are reference types, then the assignment statement x = y will copy the memory address from x into y, which means both x and y will be pointing to the same object.

Comparing Reference Types

When you compare two variables using == or !=, the program checks whether the data stored in the variables is identical. This works fine for value types, which directly store a representation of their value. This will not work for reference types, because the data in the variable is a memory address and not a representation of the data those variables represent.

If x and y are reference types, then this means x == y will ask whether x and y point to the same location in memory, not whether they point to identical data. If you have two different but identical objects stored in x and y, then x == y will evaluate to false. Try running the code below to see this in action:

int[] a = new int[] { 1, 2, 3 };
int[] b = new int[] { 1. 2, 3 };
int[] c = a;

if (a == b) {
    println("a equals b");
} else {
    println("a doesn't equal b");
}

if (b == c) {
    println("b equals c");
} else {
    println("b doesn't equal c");
}

if (a == c) {
    println("a equals c");
} else {
    println("a doesn't equal c");
}

String x = new String("Hello");
String y = new String("Hello");
String z = y;

if (x == y) {
    println("x equals y");
} else {
    println("x doesn't equal y");
}

if (y == z) {
    println("y equals z");
} else {
    println("y doesn't equal z");
}

if (x == z) {
    println("x equals z");
} else {
    println("x doesn't equal z");
}

Some reference types define an equals method to compare their data instead of their memory addresses. The String type does this, so we can use x.equals(y) to ask whether the contents of two strings x and y are equal. If x and y are strings, then x.equals(y) behaves the same as a == b would behave if a and b were value types.

Other reference types, such as arrays, do not define their own version of the equals method. These reference types still have the equals method, but it won't be any more helpful than ==. If we want to compare two arrays, then we need to write our own code to check each element of the arrays and determine whether they're equal:

boolean areEqual(int[] a, int[] b) {
    // If the lengths differ, then we know they're different. It also wouldn't
    // be safe to iterate over both with one loop, so it's important to return
    // before we get to the for loop.
    if (a.length != b.length) {
        return false;
    }

    // If the lengths are the same (and we'll return earlier if they aren't),
    // then we can use a loop to compare the elements at each index of the
    // arrays.
    for (int i = 0; i < a.length; ++i) {
        // If two elements at the same index don't match, then the arrays aren't
        // equal
        if (a[i] != b[i]) {
            return false;
        }
    }

    // If we don't find any pairs of unequal elements, then we know the arrays
    // have the same length and contain the same elements. Sounds like they're
    // equal!
    return true;
}

int[] a = new int[] { 1, 2, 3 };
int[] b = new int[] { 1, 2, 3 };
int[] c = new int[] { 1, 2, 3, 4 };

println("If we use the == comparison operator:");

if (a == b) {
    println("a equals b");
} else {
    println("a doesn't equal b");
}
if (b == c) {
    println("b equals c");
} else {
    println("b doesn't equal c");
}
if (a == c) {
    println("a equals c");
} else {
    println("a doesn't equal c");
}

println("If we use the equals method:");

if (a.equals(b)) {
    println("a equals b");
} else {
    println("a doesn't equal b");
}
if (b.equals(c)) {
    println("b equals c");
} else {
    println("b doesn't equal c");
}
if (a.equals(c)) {
    println("a equals c");
} else {
    println("a doesn't equal c");
}

println("If we use the areEqual function:");

if (areEqual(a, b)) {
    println("a equals b");
} else {
    println("a doesn't equal b");
}
if (areEqual(b, c)) {
    println("b equals c");
} else {
    println("b doesn't equal c");
}
if (areEqual(a, c)) {
    println("a equals c");
} else {
    println("a doesn't equal c");
}

Immutable Data

The String type is immutable, which means that we can't modify (or mutate) the data inside of a string after we create the string. However, we can create new strings based on an existing string, which is what happens when we concatenate strings or call one of the string methods (such as toLowerCase). The following code demonstrates that the original string a won't change when we concatenate it with other strings and call various methods on it.

String a = "Hello";

// Show the value of a
println("a: " + a);

// Create new strings using a
String b = a + " World";
String c = a.toUpperCase();
String d = a.replace("ello", "i");

// Show the value of a again (hasn't changed)
println("a: " + a);

// Show the value of the variables we created using a
println("b: " + b);
println("c: " + c);
println("d: " + d);

If we reassign a variable containing a reference to immutable data, then the original data still exists. Just because the string a points to is immutable, this doesn't mean the memory address stored in a can't be changed to point to different data.

// Create a new string
String a = "Hello";

// Create a second variable pointing to the same string
String b = a;

// Show the values in each string
println("a: " + a);
println("b: " + b);

// Create a new string and change a to point to the new string
a = a.toUpperCase();

// Show the values in each string again, which demonstrates that the original
// string (stored in b) has not changed
println("a: " + a);
println("b: " + b);

Mutable Data

Unlike the String type, an array is mutable (which means its data can change). We can reassign any element of an array to a different value, which changes part of the array. If multiple variables point to the same array, then they will both reflect this change.

// Need a way to print arrays
void printArray(String label, int[] array) {
    // Include a label for the array
    String s = label + ": ";
    // Add each int to the string
    for (int x: array) {
        s += x + " ";
    }
    println(s);
}

// Create a new array
int[] a = new int[] { 1, 2, 3, 4 };

// Show the value of a
printArray("a", a);

// Create variables that point to the same array as a
int[] b = a;
int[] c = a;
// Create a variable that points to a different array, but contains the same
// elements as a
int[] d = new int[] { a[0], a[1], a[2], a[3] };

// Change a different element of each variable's array
a[0] = 5;
b[1] = 6;
c[2] = 7;
d[3] = 8;

// Show the value of the array that a points to
printArray("a", a);

// b and c contain the same elements because they point to the same array as a
printArray("b", b);
printArray("c", c);

// d contains different elements because it points to a different array from a
printArray("d", d);

// Create a new array using the current elements of a
int[] e = new int[] { a[0], a[1], a[2], a[3] };

// e has the same elements as a
printArray("e", e);

// Change each element of e
e[0] = -1;
e[1] = -2;
e[2] = -3;
e[3] = -4;

// Changing e hasn't affected a, b, c, or d because e is a different array
printArray("a", a);
printArray("b", b);
printArray("c", c);
printArray("d", d);
printArray("e", e);

Jshell to Compiled Java

This chapter covers the major changes you'll encounter when switching from jshell scripts to compiled Java programs. We started off using jshell because it simplified a few things (shorter I/O commands, no need to worry about the mysterious Java program structure), but at this point jshell is limiting us more than helping us.

Java Program Structure

Java programs have to follow a specific structure. For now, we're just going to look at programs that fit into a single .java file, but later we'll see longer programs that span two or more files.

We'll be using the hello world program below as an example to explain the structure of a Java program:

// The body of the HelloWorld class contains our program
// - the class keyword is new
// - HelloWorld is an identifier (the class's name)
class HelloWorld
{
    // The main method (a function attached to the HelloWorld class) is the
    // entry point for our program
    //
    // There aren't many new concepts here:
    // - the public and static keywords
    //
    // We've seen everything else before
    // - the void keyword tells us that the main method returns nothing
    // - main is an identifier (the method's name)
    // - the parenthesis after main contain the method's parameter list
    // - the String[] type is an array of strings
    // - args is an identifier (the parameter's name)
    public static void main(String[] args)
    {
        // This print statement prints the string "Hello World" to standard out
        // - System.out is new
        // - we've been using the println method since the quickstart chapter
        System.out.println("Hello World");
    }
}

Classes

The first new concept we have to cover is a class. We're not going to fully explore classes right away, so we'll start with a simplified definition for a class: a class is a container for our program and its methods (functions). This is an incomplete and misleading definition, but it's good enough for now.

There are a few other things you should know about classes:

  • A class will usually start with the public keyword (public class ClassName instead of class ClassName). You're likely to see this in other examples, but for now it's unnecessary.
  • A class's name follows the usual rules for an identifier, but unlike variable and function names, class names should always start with a capital letter.
  • The class's name must match the filename. The hello world example must be in a file named HelloWorld.java, with the same exact capitalization.
  • The body of our class goes in a code block after the class name. This will include all of the code in our program for now.

The Entry Point

A Java program can consist of many classes, and each class can contain many methods. Most of the code in a class (for now, all of the code) has to be inside of the class's methods. However, if we compile and run our program, it needs to know which method to call when the program starts. This is the program's entry point, and it's always going to be a method with the name main and an array of strings as its only parameter.

With a few changes, most of your jshell scripts can be converted into Java programs by placing all of their code into a class's main method.

Program Arguments

When you enter a command on the terminal, you can include one or more arguments after the name of the command. For example, the cd command is usually followed by an argument to specify which directory you want to move to. This is very similar to how a function call in Java can accept arguments.

When you run a Java program, you can include arguments for the program on the terminal. These arguments will be parsed as strings and placed in your main method's args parameter.

Compile and Run

There are two steps to running a Java program on the terminal:

  1. Compile the program if you made any changes to the code since the last time you compiled it. This uses the command javac Filename.
  2. Run the compiled program with the command java ClassName.

For practice, try using the following commands to compile and run the hello world program from the previous section: javac HelloWorld.java, then java HelloWorld.

A few notes about compiling and running Java programs this way:

  • You need to be in the folder containing the source code (the .java file(s)).
  • The javac command uses the program's file name, which ends in .java.
  • The javac command will generate a .class file for each .java file it compiles. These .class files are your compiled program.
  • The java command uses the class name, which does not end in .java.
  • You can include additional arguments after the class name when using the java command. These arguments will end up in your main method's args parameter.

Example of Program Arguments

Try running the following program with different sets of command line arguments:

class ArgsProgram
{
    public static void main(String[] args)
    {
        System.out.println("Printing all program args:");
        for (int i = 0; i < args.length; ++i)
        {
            System.out.printf("\targs[%d] = %s\n", i, args[i]);
        }
    }
}

Try using each of the commands below (don't include the $, it just marks the start of a terminal prompt) and predicting what the output will be before you run the command. Are any of the results surprising? What can you learn about command line arguments from the results?

$ java ArgsProgram a b c
$ java ArgsProgram a,b,c
$ java ArgsProgram a, b, c
$ java ArgsProgram "a b c"
$ java ArgsProgram a\ b c
$ java ArgsProgram Hello, World
$ java ArgsProgram "Hello, World"

Compiler Errors and Runtime Errors

Our jshell scripts were interpreted by the jshell program. This means it looked at one line or code block at a time and tried to run that piece of code before looking at the next piece. If we typed some invalid code, then we would see an error when it tried to run that code and it would continue trying to run the rest of our script.

A Java program is compiled. This means the compiler checks the code to make sure it follows Java's syntax rules, then it converts the code into Java bytecode, which can be executed by the Java Virtual Machine. Certain types of errors will prevent our code from compiling, which means we can't even try to run the program. These are called compiler errors or syntax errors. If our code doesn't follow Java's syntax (grammar) rules, then it isn't valid Java code and will be rejected by the compiler.

Syntax errors can be annoying, especially as a beginner who makes this type of error frequently. However, these is the best kind of error to have in your program. A syntax error will be caught immediately when you compile your program, and some editors will warn you about syntax errors as you are editing your code, before you even try to compile. Your editor or compiler can tell you exactly where the syntax error is located, and the error message will often be helpful when you try to figure out the problem.

Runtime errors, on the other hand, cannot be caught by the compiler. A runtime error occurs while your program is running when the program tries to do something that it isn't allowed to do (such as index an array with a negative number or divide an integer by zero). A runtime error will immediately crash your program and show an error message. This type of error can be harder to diagnose, because it may only happen with certain inputs, and you won't get a warning from your editor or compiler about it. You'll usually have to spend a little bit of time investigating a runtime error before you'll know how to fix it.

Semantic Errors

It's also worth mentioning a third type of error, called a logic error or a semantic error. This type of error is usually the most difficult to fix, because it often won't crash your program and provide an error message to show you what went wrong. A logic error occurs when you make a mistake in your program's logic that causes it to run but produce incorrect output or otherwise behave incorrectly. Sometimes this can lead to an infinite loop, or maybe your program will print the wrong answer to a question five percent of the time. You can catch logic errors by manually testing your program or by writing automated tests, like the test scripts included with some of the projects.

Syntax Changes

Output

The PRINTING script we added to our jshell startup created shorter print functions for our jshell scripts to use. This is why we could type println instead of System.out.println. In a Java program, we need to specify an output stream when we want to print something. System.out is an output stream connected to standard out, and System.err is an output stream connected to standard err (which is used for printing error messages). All of the print functions we used in jshell still work in Java as long as you begin them with System.out..

Input

The INPUT.jsh script we added to our jshell startup created a Scanner and some shorter input functions (nextInt, nextLine, etc.) for our jshell scripts to use. To do the same thing in a Java program, we need to create a scanner connected to an input stream (System.in is what we'll normally use), then we can call that scanner's input methods. The same input functions will work as long as we call them as methods on our System.in scanner.

We also need to import the scanner class before we can use it in our program. An import statement normally goes at the beginning of our program, before the start of our class. See the code below for a complete example of creating and using a System.in scanner.

// Import the scanner at the beginning of the file
import java.util.Scanner;

// Remember to name this file InputExample.java so it can compile
class InputExample
{
    public static void main(String[] args)
    {
        // Create a new scanner to read from the System.in input stream
        // NOTE: We should only create the System.in scanner once in our entire
        // program! We should only have one scanner reading from a particular
        // input stream.
        Scanner input = new Scanner(System.in);

        // The usual prompt for input pattern, but with a scanner!
        System.out.println("Please enter your first name.");
        String firstName = input.nextLine();

        System.out.println("Please enter your last name.");
        String lastName = input.nextLine();

        // Don't forget we have printf as well as println
        System.out.printf("Your full name is %s %s.\n", firstName, lastName);
    }
}

Functions

Functions have to be formatted slightly differently in a Java program compared to a jshell script. They're also referred to as methods (although the term function is still accurate, just less specific). All of our functions will need to include the static keyword before their return type (just like main). This marks them as a standalone function that can be called from the class. When we learn how to instantiate our classes later on, we'll be able to write non-static methods.

We also cannot define methods inside of other methods. Jshell didn't allow this either, but if you copy a jshell script into your main method, then you'll end up with all of your functions defined inside of the main method.

Finally, we don't have to worry about the order of our method definitions in a Java program! You can define all of your other methods after the main method, and Java won't have a problem with this.

Semicolons

Jshell did not require semicolons at the end of statements outside of code blocks. In Java, statements typically need to end with a semicolon. There are some lines of code where we should never use semicolons, and we've covered many of these before. However, let's go over them all again right here:

  • Never put a semicolon at the end of an if statement, while loop, or for loop. The chapters about if statements and loops goes into more detail about why.
    • One small exception is the do-while loop: you must put a semicolon at the end of the while line in a do-while loop, but you should never do this to a regular while loop!
  • Never put a semicolon at the end of a function signature or class declaration.
  • We rarely need to put semicolons after curly braces, at least not when they're marking a code block.

All of the code examples in this book will show examples of where to use semicolons and where not to use them. You're probably somewhat used to this already from writing code in code blocks for jshell scripts. The only difference when switching to Java programs is that they're no longer optional in the main body of your program.

Code Blocks

One other small change is the structure of code blocks. Jshell's interpreter doesn't allow you to write the opening brace for the body of an if statement or a loop on the line after the if statement or loop. A Java program has no problem with this, as you can see in the example programs. It doesn't matter which style you use when writing code blocks, but you should always be consistent. Don't switch styles in the middle of a program.

Syntax Templates and Common Patterns

This section contains examples of patterns you'll see and use frequently while learning Java. Each entry will have a label to describe the pattern's purpose, one or more versions of the pattern with placeholders written between arrow brackets <like this>, and one or more examples of code that follows the pattern. If code is left out of the middle of a pattern or example (usually because that part isn't relevant to the pattern), it will be replaced with an ellipsis (...).

Pattern:

// Print a string literal
println(<string literal>);

// Print a variable
println(<variable name>);

// Print a concatenated string
println(<string concatenation expression>);

Examples:

// Print a string literal
println("Hello, world!");
println("I'm a string literal!");

// Print a variable
println(name);
println(iAmAVeryLongVariableName);

// Print a concatenated string
println("Your name is: " + name + ".");
println("The value of x is " + x + ", and the value of y is " + y + ".");

Declare and initialize a variable

Pattern:

// Infer the variable's type
var <variable name> = <initial value expression>;

// Explicitly state the variable's type
<type> <variable name> = <initial value expression>;

Examples:

// Infer the variable's type
var mailingAddress = "123 Fake Ln";
var accountBalance = 5 - 3;

// Explicitly state the variable's type
String mailingAddress = "123 Fake Ln";
int accountBalance = 5 - 3;

Declare a variable without initializing

Pattern:

<type> <variable name>;

Examples:

double area;
String userName;

Read user input to a variable (jshell simplified I/O)

Pattern:

// Declare & initialize a new variable with inferred type
var <variable name> = <input function call>;

// Declare & initialize a new variable with explicit type
<type> <variable name> = <input function call>;

// Reassign existing variable
<variable name> = <input function call>;

Examples:

// Declare & initialize a new variable with inferred type
var year = nextInt();
var initial = nextChar();

// Declare & initialize a new variable with explicit type
int year = nextInt();
char initial = nextChar();

// Reassign existing variable
year = nextInt();
initial = nextChar();

If statement

Pattern:

if (<condition>) {
    <statement(s)>;
}

Examples:

if (x % 2 != 0) {
    println("X is odd.");
}
if (numberOfDays < 365) {
    println("The first year isn't over yet.");
    println("There are " + (365 - numberOfDays) + " left in the year.");
}

If statement with else

Pattern:

if (<condition>) {
    <statement(s)>;
} else {
    <statement(s)>;
}

Examples:

if (x % 2 == 0) {
    println("X is even.");
} else {
    println("X is odd.");
}
if (numberOfDays < 365) {
    println("The first year isn't over yet.");
    println("There are " + (365 - numberOfDays) + " left in the year.");
} else {
    println("The first year has ended.");
    println("It's been " + (numberOfDays - 365) + " since the end of the first year.");
}

Check if value is in a range

Pattern:

// Boolean expression, exclusive range
<lower bound> < <value> && <value> < <upper bound>

// Boolean expression, inclusive range
<lower bound> <= <value> && <value> <= <upper bound>

// If statement, exclusive range
if (<lower bound> < <value> && <value> < <upper bound>) {
    ...
}

// If statement, inclusive range
if (<lower bound> <= <value> && <value> <= <upper bound>) {
    ...
}

Examples:

// Boolean expression, exclusive range
0 < x && x < 11

// Boolean expression, inclusive range
1 <= x && x <= 10
minimumHeight <= height && height <= maximumHeight

// If statement, exclusive range
if (0 < x && x < 11) {
    println("x is a number from 1 to 10");
}

// If statement, inclusive range
if (1 <= x && x <= 10) {
    println("x is a number from 1 to 10");
}
if (minimumHeight <= height && height <= maximumHeight) {
    ...
}

Else-If Chain

Pattern:

if (<condition>) {
    <statement(s)>;
} else if (<condition>) {
    <statement(s)>;
} else if ...
...
} else {
    <statement(s)>;
}

Example:

if (month == 1) {
    println("January");
} else if (month == 2) {
    println("February");
} else if ...
...
} else {
    println("December");
}

Initialize Variable With If

Pattern:

<type> <name>;
if (<condition>) {
    <name> = <expression>;
} else {
    <name> = <expression>;
}

<type> <name>;
if (<condition>) {
    <name> = <expression>;
} else if (<condition>) {
    <name> = <expression>;
} else if ...
...
} else {
    <name> = <expression>;
}

Example:

String parity;
if (number % 2 == 0) {
    parity = "even";
} else {
    parity = "odd";
}

String monthText;
if (monthNumber == 1) {
    monthText = "January";
} else if (monthNumber == 2) {
    monthText = "February";
} else if ...
...
else {
    monthText = "December";
}

While Loop

Pattern:

while (<condition>) {
    statement(s);
}

Example:

int x = 0;
while (x < 1 || x > 10) {
    println("Please enter a number from 1 to 10.");
    x = nextInt();
}

Counting Loop

Pattern:

// Count up
int <counting variable> = 0;
while (<counting variable> < <total repetitions>) {
    <statement(s)>;
    <counting variable> = <counting variable> + 1;
}

// Count down
int <counting variable> = <total repetitions>;
while (<counting variable> > 0) {
    <statement(s)>;
    <counting variable> = <counting variable> - 1;
}

Examples:

// Count up 0 to 14
int count = 0;
while (count < 15) {
    if (count % 2 == 0) {
        println(count + " is even.");
    } else {
        println(count + " is odd.");
    }
    count = count + 1;
}

// Count up 1 to 15
int count = 1;
while (count <= 15) {
    if (count % 2 == 0) {
        println(count + " is even.");
    } else {
        println(count + " is odd.");
    }
    count = count + 1;
}

// Count down 15 to 1
int count = 15;
while (count > 0) {
    if (count % 2 == 0) {
        println(count + " is even.");
    } else {
        println(count + " is odd.");
    }
    count = count - 1;
}

// Count down 14 to 0
int count = 14;
while (count >= 0) {
    if (count % 2 == 0) {
        println(count + " is even.");
    } else {
        println(count + " is odd.");
    }
    count = count - 1;
}