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 beDocuments/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 typingcd 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
inls -a
or the--version
injava --version
.
Navigation:
pwd
: "print working directory", tells you which folder you're incd PATH
: "change directory", moves you from your current location to the directory atPATH
cd
on its own orcd ~
will move you to your home directory
ls
: "list", lists everything in the current directoryls PATH
: lists the contents of the directory atPATH
ls -a
: includes hidden items in the output
Viewing Files:
cat PATH
: show the contents of the file atPATH
less PATH
: view the contents of the file atPATH
, but with more features. Pressq
to exitless
and return to the terminal's command prompt.
Manipulating Directories:
mkdir PATH
: creates an empty directory atPATH
rmdir PATH
: deletes the directory atPATH
, 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 atPATH
. It's permanently gone, not moved to the trash or recycle bin.mv PATH DESTINATION
: move the file or directory atPATH
toDESTINATION
. 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.
Recommended Text Editor: VS Code
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
- Official VS Code tutorial: https://www.youtube.com/watch?v=B-s71n0dHUk
- Guide to vim: https://github.com/iggredible/Learn-Vim/blob/master/ch00_read_this_first.md
- Interactive vim tutorial: https://www.openvim.com/
- Guided tour of Emacs: https://www.gnu.org/software/emacs/tour/
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:
- Reads a command
- Executes the command
- Prints the result
- 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.
Print Functions
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 ofx
, and the result of this calculation becomes the new value ofx
. - 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 linenextInt()
: reads an integernextDouble()
: 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 thenextLine()
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 127short
: 2-byte (16-bit) integer value; stores any integer from -32768 to 32767int
: 4-byte (32-bit) integer value and the default type for integers in Java; stores any integer from about -2 billion to about 2 billionlong
: 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 3E38double
: 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 integerString
: a sequence of characters; the size of aString
depends on the number of characters in theString
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
.
boolean
s 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
andthisName
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 calledname
. 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 ->
5113y ->
10-4
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 ->
5113y ->
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 ->
51131015y ->
10-4526
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) ors
(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 than2
characters then it should be padded with0
s so that it takes up2
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 with0
to ensure our number is at least2
characters long, we change the5
to05
:"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 char
s, 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 achar
literal, so it must be escaped inside achar
literal:'\''
"
is used to mark the beginning and end of aString
literal, so it must be escaped inside aString
literal:"\""
Whitespace characters that are hard to print:
- A linebreak is represented as
\n
inside achar
orString
literal - A tab is represented as
\t
inside achar
orString
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 name | category | size (bits) | size (bytes) | min value | max value |
---|---|---|---|---|---|
byte | signed integer | 8 | 1 | -256 | 255 |
short | signed integer | 16 | 2 | -32,768 | 32,767 |
int | signed integer | 32 | 4 | -2,147,483,648 | 2,147,483,647 |
long | signed integer | 64 | 8 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
float | floating-point | 32 | 4 | -3.4028235E38 | 3.4028235E38 |
double | floating-point | 64 | 8 | -1.7976931348623157E308 | 1.7976931348623157E308 |
char | unsigned integer | 16 | 2 | 0 | 65,535 |
boolean | boolean | 8 | 1 | N/A | N/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 itchar
s are stored as integers and will readily convert to anint
- 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
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
println("Hello, world!")
Repeat It
println("Please enter some text.")
String text = nextLine()
println("Now I'll repeat what you just typed.")
println(text)
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).
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.
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.")
}
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.
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.")
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:
<
: less than<=
: less than or equal to>
: greater than>=
: greater than or equal to==
: equal to (a single=
is an assignment, not a comparison)!=
: not equal to
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 tob
year != 2022
:year
is not equal to2022
2 + 2 == 5
:2 + 2
is equal to5
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 String
s 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
, and10
) and two operators (the two<=
operators). The operators are identical, so we have to start with the one on the left:1 <= 7
istrue
, because1
is less than7
. Therefore, we'll replace1 <= 7
withtrue
: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:
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:
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:
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:
condition1 | condition2 | Version A prints | Version B prints |
---|---|---|---|
true | true | statement1 | statement1 |
true | false | nothing | statement2 |
false | true | statement2 | nothing |
false | false | statement2 | nothing |
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:
- Tomorrow it's at least going to rain or snow. (inclusive)
- 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.
a | b | a && b | a || b | a ^^ b | !a | !b |
---|---|---|---|---|---|---|
true | true | true | true | false | false | false |
true | false | false | true | true | false | true |
false | true | false | true | true | true | false |
false | false | false | false | false | true | true |
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 isfalse
, we don't need to check the second value to know that the result isfalse
. This is because an AND results infalse
unless both operands aretrue
. - When we are ORing two values (
||
) and we see that the first value istrue
, we don't need to check the second value to know that the result istrue
. This is because an OR results intrue
unless both operands arefalse
.
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 piMath.TAU
is an approximation of pi doubledMath.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 ofx
Math.ceil(x)
,Math.floor(x)
, andMath.round(x)
all roundx
(ceil always rounds up, floor always rounds down, and round follows standard rounding rules)Math.max(x, y)
andMath.min(x, y)
returns the largest (max) or smallest (min) ofx
andy
Math.pow(x, y)
raisesx
to the power ofy
Math.sqrt(x)
returns the square root ofx
Math.random()
generates a random number from 0.0 to 1.0 (excluding 1.0)Math.toDegrees(x)
andMath.toRadians(x)
convert the anglex
from radians to degrees or from degrees to radians- Many trigonometric functions,
such as
Math.sin(x)
,Math.cos(x)
, andMath.tan(x)
, are included in theMath
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)
returnstrue
ifs1
ands2
have the exact same sequence of characters and false if they do nots1.equalsIgnoreCase(s2)
is like.equals
, but it ignores differences in capitalizations.length()
returns the number of characters ins
s.toUpperCase()
ands.toLowerCase()
convert all of the letters ins
to uppercase or lowercase letterss.charAt(x)
returns the character at indexx
ins
s.indexOf(c)
returns the index where the characterc
first appears ins
s.lastIndexOf(c)
returns the index where the characterc
last appears ins
s1.startsWith(s2)
ands1.endsWith(s2)
return true ifs1
starts/ends withs2
and false if it does nots.substring(x)
returns a string containing every character ins
from indexx
until the end ofs
s.substring(x, y)
returns a string containing every character ins
from indexx
up to (but not including) indexy
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:
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);
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 writingnextInt()
ten times, but the point is to use a loop.
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.
// 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.
// 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 thiselse
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:
- A function named
sayHello
that prints the message "Hello, World!". - A function named
greetPerson
that takes a name and prints a greeting in the form of "Hello <name>, how are you doing today?". - 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:
- 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 isvoid
. - 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 aname
parameter. As with thesayHello
function, we're not producing information to use in other parts of the program, we're sending information outside of the program withprintln
. This means we don't need to return anything, and our return type isvoid
. - The
square
function needs to know what number it's squaring, and the number is supposed to be anint
according to the description, so we need a single parameter with the typeint
. This parameter doesn't mean anything beyond the fact that it's a number, so an arbitrary name likex
ora
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 anint
, so our return type should beint
.
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, andnull
for strings or other objects/reference types. A value ofnull
means there is no string at that position in the array. We'll learn more aboutnull
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
andy
are value types, then the assignment statementx = y
will copy the data fromx
intoy
. - 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
andy
are reference types, then the assignment statementx = y
will copy the memory address fromx
intoy
, which means bothx
andy
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 ofclass 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:
- Compile the program if you made any changes to the code since the last time
you compiled it. This uses the command
javac Filename
. - 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 yourmain
method'sargs
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, orfor
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!
- One small exception is the do-while loop: you must put a semicolon at the
end of the
- 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 (...
).
Print to standard out (jshell simplified I/O)
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;
}