In this class, we will write programs.
But first, what is a program?
A program is a sequence of instructions that specifies how to
perform a computation.
Every program you've
ever used, no matter how complicated, is made up of small instructions that
look much like the following:
-
input: get data from the keyboard, a file, a sensor, or some other device
-
output: display data on the screen, or send data to a file or other device.
-
math: perform basic mathematical operations like addition and division.
-
decisions: check for certain conditions and execute the appropriate code
-
repetition: perform some action repeatedly, usually with some variation.
We can think of programming as the process of:
breaking down a large, complex task into smaller and smaller subtasks.
The process continues until the subtasks are simple enough to be performed with
the basic instructions provided by the computer.
What is Computer Science?
One of the most interesting aspects of writing programs is deciding how to
solve a particular problem, especially when there are multiple solutions.
In order to determine which way is best for a given situation,
we need techniques for describing and analyzing solutions formally.
Computer science is the science of algorithms, including their discovery and
analysis.
An algorithm is a sequence of steps that specifies how to solve a
problem.
- Some algorithms are faster than others, and some use less space
in computer memory.
Designing algorithms and writing code is difficult and error-prone.
For historical reasons, programming errors are called bugs, and the process of tracking them down and
correcting them is called debugging.
The programming language you will learn in this class is Java, which is a high-level language.
A high-level language is any programming language that enables development of a program
in a much more user-friendly programming context and is generally independent of the computer's hardware
architecture.
Other high-level languages you may have heard of include:
- Python
- C and C++
- Ruby
- JavaScript
Before they can run, programs in high-level languages have to be translated
into a low-level language, also called “machine language”.
This translation takes some time, which is a small disadvantage of high-level languages.
High-level languages have two advantages:
- It is much easier to program in a high-level language.
- Programs take
less time to write, they are shorter and easier to read, and they are more
likely to be correct.
- High-level languages are portable, meaning they can run on different
kinds of computers with few or no modifications.
- Low-level programs
can only run on one kind of computer, and have to be rewritten to run
on another.
Two kinds of programs translate high-level languages into low-level languages:
interpreters and compilers
- An interpreter reads a high-level program and
executes it, meaning that it does what the program says.
It processes the program a little at a time, alternately reading lines and performing computations
- A compiler reads the entire program and translates it completely
before the program starts running. In this context, the high-level program is called the source
code, and the translated program is called the object
code or the executable. Once a program is compiled, you can execute it
repeatedly without further translation. As a result, compiled programs often
run faster than interpreted programs.
As aforementioned, Java is a high-level, class-based, object-oriented programming
language and software platform that runs on billions of devices,
including notebook computers, mobile devices, gaming consoles,
medical devices and many others. The rules and syntax of Java are
based on the C and C++ languages.
Here are some specific examples of real world applications that use Java:
- LinkedIn
- Minecraft
- Amazon Web Services (AWS)
To create an application using Java, you will need:
-
to download and install the Java Development Kit (JDK),
which is available for Windows, macOS, and
Linux.
-
A command-line interface (CLI) on your computer that allows you to
create and delete files, run programs, and navigate through
folders and files (On a Mac, it's called Terminal, and on Windows, it's Command
Prompt) OR an integrated development environment (IDE) - a software
application that helps programmers develop software code
efficiently by combining capabilities such as software editing,
building, testing, and packaging in an easy-to-use application,
such as
Eclipse,
NetBeans,
IntelliJ, DrJava, etc.
You write the program in the Java programming language, then a
compiler turns the program into Java bytecode—the instruction set
for the Java Virtual Machine (JVM) that is a part of the Java
runtime environment (JRE). Java bytecode runs without modification
on any system that supports JVMs, allowing your Java code to be run
anywhere. The Java software platform consists of the JVM, the Java
API, and a complete development environment. The JVM parses and runs
(interprets) the Java bytecode.
The Java API consists of an
extensive set of libraries including basic objects, networking and
security functions; Extensible Markup Language (XML) generation; and
web services. Taken together, the Java language and the Java
software platform create a powerful, proven technology for
enterprise software development.
To start programming in Java, we can write a simple program that
outputs "Hello, World!" on the screen. Since it's a very simple
program, it's often used to introduce a new programming language to
a newbie! 😃
Let's explore how Java "Hello, World!" program works:
// Our First Program
public class HelloWorld {
public static void main(String [] args) {
System.out.println("Hello, World!");
}
}
How does the "Hello, World!" program work?
-
In Java, any line starting with // is a single-line comment
(We will learn more about multi-line comments in a bit). Comments
are intended for users reading the code to understand the intent
and functionality of the program. It is completely ignored by the
Java compiler (an application that translates Java program to Java
bytecode that computer can execute).
-
Every application begins with a class definition. In this
particular program, HelloWorld is the name of the class and the
name of the class should match the filename in Java.
-
Next is the main method. Every application in Java must contain
the main method. The Java compiler starts executing the code from
the main method, and it's mandatory in each of our executable Java
programs. The signature of the main method in Java is:
public static void main(String [] args) { ... }
-
Lastly, we have a print statement. It prints the text Hello,
World! to standard output (your screen). The text inside the
quotation marks is called a String literal in Java.
Notice the print statement is inside the main function, which
is inside the class definition.
You may have seen variables before (Think back to high school algebra!
Solving for X, etc.)
In programming, a variable is a location in memory (storage area) that
holds data.
(For now just one value at a time ... we'll come back to this later)
To indicate the storage area, each variable is given a unique name.
The names of variables, called identifiers, conform to the
following rules:
-
Identifiers CANNOT be a keyword (ex. keywords like int, for, class,
etc CANNOT be used as a variable name (or identifier) as they are
part of the Java programming language syntax
-
Identifiers are case-sensitive (ex. age, AGE, Age, etc. are all
valid identifiers and can all exist within the same program,
although NOT RECOMMENDED 😡)
-
Identifiers can be made up of a sequence of letters and digits.
However,
it must begin with a letter, '$' (dollar sign) or '_'
(underscore).
(It is convention to start an identifier with a letter) The first
letter of an identifier CANNOT be a digit.
-
Identifiers CANNOT contain whitespaces, nor symbols such as @, #,
and so on.
- Examples of VALID ✔️ identifiers:
- player1
- score
- level
- highScore
- Examples of INVALID ❌ identifiers:
- pl@yer
- 1score
- class
- highest Score
It is easy to declare a variable!
First we identify the necessary keyword for the variable datatype.
For example, a whole number should be declared as an integer type
known as int while a decimal number should be declared as a float or
double.
int score = 42;
This syntax can be used to declare both local and global variables
(we'll talk about this more later 😉).
In the previous example, we have assigned value to the variable
during declaration -- this is known as initialization, because we
assign an initial value to a variable.
However, we can also declare variables and assign variables
separately.
For example:
int score;
score = 42;
When you declare a variable, you create a named storage location.
When you make an assignment to a variable, you update its value.
The value of a variable can be modified throughout the program
(that's what makes it a variable, opposed to a
literal)
For example:
int score = 0;
//more code to be added here
score = 42;
Here, initially, we set the score to 0.
Later (after some more code -- not yet specified) we changed it to
42.
It is important to remember, however, that we CANNOT change the data
type of a variable in Java within the same scope (we'll discuss
scope a bit more later, for now just think of our scope limited to
the main method)
We can display the value of a variable using print or println.
int score = 42;
//What should be printed below?
System.out.println(score);
When we talk about displaying a variable, we generally mean the value of the variable.
To display the name of a variable, you have to put it in quotes.
The scope of a variable is the part of the program where the variable is accessible.
In general, a set of curly brackets { } defines a scope.
A variable declared inside pair of brackets “{” and “}” in a method is only accessible within the scope of
the brackets only.
(This will make more sense when we explore conditional statements, loops, methods, etc.)
We can also declare variables directly inside the class (outside any specific method -- we will revisit this
once we begin writing more methods).
These variables can be directly accessed anywhere in class.
Operators are symbols that perform operations on variables and
values. For example, we know that + is an operator used for
addition, while * is an operator used for multiplication.
Operators in Java can be classified into the following types:
- Arithmetic Operators
-
Arithmetic operators are used to perform arithmetic operations
on variables and data.
- The various arithmetic operators we use include:
- + (Addition, e.g. a + b)
- - (Subtraction, e.g. a - b)
- * (Multiplication, e.g. a * b)
- / (Division, e.g. a / b)
- % (Modulus -Remainder after division, e.g. a % b)
- ++ (Increment, e.g. a++ or ++a)
- -- (Decrement, e.g. a-- or --a)
- Assignment Operators
-
Assignment operators are used in Java to assign values to
variables.
- A few assignment operators available in Java include:
- = (Assignment, e.g. a = b)
-
+= (Additional assignment, e.g. a += b is equivalent to a = a
+ b)
-
we can also write assignment operators for other arithmetic
operations such as subtraction, multiplication, division,
modulus, etc.
- Relational (or Comparison) operators
-
Relational/Comparison operators are used to compare two values
(or variables). The values returned from comparison are known as
boolean values and will either be true or false.
-
A few relational/comparison operators available in Java include:
- == (Equal to, e.g. a == b)
- != (Not equal to, e.g. a != b)
- > (Greater than, e.g. a > b)
-
< (Less than, e.g. a < b)
- >= (Greater than or equal to, e.g. a >= b)
-
<= (Less than or equal to, e.g. a <=b)
- Logical Operators
-
Logical operators are used to determine the logic (whether a
statement is true or false)
between
variables or values.
- A few logical operators available in Java include:
-
&& (Logical and - returns true if both statements are true,
e.g. a < 5 && b < 5)
-
|| (Logical or - returns true if one of the statements is
true, e.g. a < 5 || b < 5)
-
! (Logical not - reverses the result, returns false if the
result is true, e.g. !(a < 5 && b < 5)))
- Unary Operators
- Post-increment/decrement (also known as postfix)
-
Here our code performs the specified operation first,
and then increments/decrements the value.
// Postfix increment example
public class PostfixExample {
public static void main(String [] args) {
int a = 5;
System.out.println(a++);//prints the current value of a (5) BEFORE incrementing
System.out.println(a);//value has been printed out, incremented, now prints 6
}
}
Bitwise Operators
TO REMEMBER:
Floating-point numbers in Java are used to represent fractional values or numbers with a large range of
possible values.
They are primarily used when precise decimal calculations are not required.
Java uses two primitive data types to represent floating-point numbers: float and double.
- The float data type is a 32-bit single-precision floating-point number
- The double data type (which we will use in this class rather than float) is a 64-bit
double-precision floating-point number.
This makes the double data type more accurate than the float data type.
We can create double variables and assign values to them using
the same syntax we used for the other types:
double payRate = 42.5;
The simplest way to convert a floating-point value to an integer
is to use a type cast, so called because it molds or “casts”
a value from one type to another.
The syntax for type casting is to put the name of the type
in parentheses and use it as an operator.
double payRate = 42.5;
int pay = (int) payRate;
What is the value of the variable pay?
Integer vs Floating Point Division
In math, division often allows for remainders.
It is why whenever a number is NOT evenly divisible by another number
it can result in a decimal number opposed to a whole number
e.g. 5/2 is equal to 2.5
However, integer division and division as we know from math class
(also known as floating-point division) are two different ways of performing division
operations.
Integer division is the division operation performed on two integers, resulting in an integer
quotient.
The result is obtained by discarding the fractional part of the division, if any.
In other words, it performs a "round towards zero" operation.
- For example, if you perform an integer division of 10 divided by 3, the result is 3 because it discards
the fractional part.
- Similarly, if you divide -10 by 3 using integer division, the result is -3, as it still rounds towards
zero.
On the other hand, floating-point division is the division operation performed on floating-point numbers
(those with decimal places).
It produces a result with decimal places, including the fractional part.
This type of division is typically more accurate and provides more precise results.
In Java, when dividing two integers using the '/' operator,
integer division is performed if both operands are integers.
To perform floating-point division, at least one of the operands should be a floating-point number.
For example:
int result = 10 / 3;//Integer division, result is 3
double result = 10.0 / 3;//Regular division, result is approximately 3.3333
It is important to be aware of the distinction between integer division and floating-point division,
especially when working with different data types and when precision is required in calculations.
Java has a lot of built-in Math methods that allow us to perform more complex mathematical computations.
The Math class is found in the java.lang package, so you don't have to import it.
For example, if you need to get the square root of a number, we use the Math.sqrt() method.
Some frequently used Math class methods include:
- Math.abs(n) - for computing the absolute value of a particular number
- Math.sqrt(n) - for computing the square root of a particular number
- Math.pow(base, exp) - for computing the result of a particular base raised to the power of a
particular exponent
Although not a method, Math.PI - is a static final double constant in Java, equivalent to in π Mathematics
You can learn more about the Java Math class methods from the Java API here
Generating Random Numbers
Most computer programs do the same thing every time they run,
meaning they are deterministic. Usually we want our
programs to be deterministic since we expect the same
calculation to yield the same result. But for certain applications (e.g. games)
we want to write programs that are nondeterministic.
It is hard for a computer to generate truly random numbers. But there are algorithms
that generate unpredictable sequences called pseudorandom numbers. For
most applications, they are as good as random.
The Math.random() method returns a pseudorandom
double type number between 0.0 and 1.0,
where 0.0 is inclusive and 1.0 is exclusive.
When this method is first called,
it creates a single new pseudorandom-number generator
public class RandomExample {
public static void main(String [] args) {
double rand = Math.random();//Generate random number
System.out.println("Random Number:" + rand);//different each run
}
}
to generate a random number in range:
Math.random() * (max - min) + min;
There are three types of errors can occur in a program:
- Compiler errors
- Run-time errors
- Logic errors
Compiler errors occur when you violate the syntax rules of the Java language.
For example, forgetting a semicolon at the end of a statement or
that parentheses and braces have to come in matching pairs.
So (1 + 2) is legal, but 8) is not.
In the latter case, the program cannot be compiled, and the compiler displays an error.
- Error messages from the compiler usually indicate where in the program the error occurred, and sometimes
they can tell you exactly what the error is.
Run-time errors occur while the interpreter is executing byte code
and something goes wrong.
These errors are also called “exceptions” because they usually
indicate that something exceptional (and bad) has happened.
(Example: attempting divide by zero)
- When a run-time error occurs, the interpreter displays an
error message that explains what happened and where.
The third type of error is the logic error.
If your program has a logic error, it will compile and run
without generating error messages, but it will not do the right thing.
Instead, it will do exactly what you told it to do (even if it isn't the right solution).
- Usually the compiler and the interpreter CANNOT help you,
since they don't know what the right thing is.
So far we have used System.out.println() to display text to the
screen. But what does it actually mean?
System is a class (that belongs to the java.lang package,
which is imported automatically) that provides methods related to the “system” or
environment where programs run.
System.out represents the Standard Output Stream. That means that if we want to print any
statement on the console, we
should use the following statement:
System.out.print();
There are three methods we use to print statements:
- print(String s)
- Used to print on the console.
- Accepts a String as a parameter (we can concatenate parts to
the String if necessary, i.e. “Hello” + “ World”, etc.
however, it is typical convention to include all string literals
in the same pair of quotations if they are consecutive)
- After printing the statement, the cursor remains on the same line.
- println(String s)
- An upgraded version of the print() method,
also used to display text on the console.
- After printing the statement,
it throws the cursor at the start of the next line.
- printf(String format, datatype args)
- Accepts two parameters:
- format: It is a formatted String, uses placeholders
with specific conversion characters to refer to the
arguments that will be replaced when printing
-
args: It is an argument referenced by the format specifiers.
If the number of arguments is more than the format specifiers, the other arguments are ignored.
The number of arguments may be zero.
A formatted placeholder is declared as follows:
%[flags][width][.precision]conversion-character
With any flags, width, and precision as optional modifiers
Feel free to consult the following:
printf() throws:
- a NullPointerException if the format is null
- an IllegalFormatException if a format string contains illegal syntax
- an IllegalFormatConversionException if the placeholder and corresponding argument are not of the
same type
The \n is an escape sequence, which is a sequence of characters that represents a special
character. The backslash allows you to “escape” the string's
literal interpretation.
As we previously discussed, a variable in Java must be declared as a specific
data type. These data types are divided into two groups:
- Primitive Data Types, which specify the size and type of variable values and has no
additional methods
AND
- Non-primitive data types, also called reference types because they refer to objects
Primitive Data Types
Data Type |
Size |
Description |
byte |
1 byte |
Stores whole numbers from -128 to 127 |
short |
2 bytes |
Stores whole numbers from -32,768 to 32,767 |
int |
4 bytes |
Stores whole numbers from -2,147,483,648 to 2,147,483,647 |
long |
8 bytes |
Stores whole numbers from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
char |
2 bytes |
Stores a single character/letter or ASCII values |
float |
4 bytes |
Stores fractional numbers. Sufficient for storing 6 to 7 decimal digits |
double |
8 bytes |
Stores fractional numbers. Sufficient for storing 15 decimal digits |
boolean |
1 bit |
Stores true or false
values |
The main difference between primitive and non-primitive data types are:
- Primitive types are predefined (already defined) in Java.
Non-primitive types are created by the programmer and is not
defined by Java (except for String)
- Non-primitive types can be used to call methods to
perform certain operations, while primitive types cannot.
- A primitive type has always a value,
while non-primitive types can be null.
- A primitive type starts with a lowercase letter,
while non-primitive types starts with an uppercase letter. (e.g. Strings, etc.)
We will revisit the concept of Strings in depth a bit later but for now just know that
Strings contain a sequence of "characters" strung together.
Characters can be letters, numbers, punctuation marks,
symbols, spaces, tabs, etc.
We can initialize a String simply by stating:
String s = "Hello";
In most cases, we can use a String as a variable data type just like we have for int, double, etc.
(Although all Strings we create are technically objects and have more behaviors than the primitive data
types --
but we'll talk more about that later)
In general, you cannot perform mathematical operations on strings, even if
the strings look like numbers. The following expressions are illegal:
- "Hello" - 1
- "World" / 123
- "Hello" * "World"
However, the + operator DOES work with strings.
For
strings, the + operator performs concatenation, which means joining end-to-end.
So "Hello, " + "World!" yields the string "Hello, World!".
Or if you have a variable called name that has type String, the expression
"Hello, " + name appends the value of name to the hello string, which creates
a personalized greeting
Since addition is defined for both numbers and strings,
Java performs automatic conversions you may not expect:
What do we expect the following code to print?
String s = "Hello";
System.out.println(1 + 2 + s);
How about this one?
System.out.println(s + 1 + 2);
Java executes these operations from left to right.
- In the first example, 1 + 2 is
3, and 3 + the value of variable s ("Hello") is "3Hello"
- In the second example, "Hello" + 1 is
"Hello1", and "Hello1" + 2 is "Hello12"
Reading Input: The Scanner Class
The System class also provides the special value System.in, which
is an InputStream that provides methods for reading input from the
keyboard. These methods are not easy to use; fortunately, Java provides
other classes that make it easier to handle common input tasks.
All reading done in Java uses the Scanner class. Using this class,
we can create an object to read input from the standard input channel
System.in (in our case, the keyboard)
To use the Scanner class, it is necessary to import the class Scanner from
the library java.util by including the following line at the top (before the class declaration) of
program:
import java.util.Scanner;
We can declare a Scanner object by saying:
Scanner sc = new Scanner (System.in);
-
sc is the name of the Scanner object (this can be changed and is
entirely up to the person who writes the code)
-
and System.in connects our object to standard input (the keyboard)
The Scanner class has multiple built-in methods that we can access to help
us read data, for example: we can use sc.nextLine() to read in a line of
String(s) (spaces included)
Other methods include:
- nextBoolean(): reads a boolean value from the user
- nextByte(): reads a byte value from the user
- nextDouble(): reads a double value from the user
- nextFloat(): reads a float value from the user
- nextInt(): reads a int value from the user
-
next(): reads a complete token (String) from the user. A complete token
is preceded and followed by input that matches the delimiter pattern, in
most instances this is a space
When we are done reading in data, it is customary to CLOSE the Scanner to avoid
a possible memory leak. This is simple, we would just state thr following:
sc.close();
Once we no longer need the Scanner (this is typically found at the last could lines of the method)
Now that we have had some experience with Scanner, we need to talk about some unexpected
behavior that may occur
When you read a String followed by an int, everything works just fine.But
when you read an int followed by a String, something strange happens.
System.out.print("What is your age? ");
age = in.nextInt();
System.out.print("What is your name? ");
name = in.nextLine();
System.out.printf("Hello %s, age %d\n", name, age);
Try running this example code. It doesn't let you input your name, and it
immediately displays the output:
What is your name? Hello , age 45
When you call nextInt(), it reads characters until it gets to a non-digit.
At this point, nextInt() returns 45. The program then displays the prompt
"What is your name? " and calls nextLine(), which reads characters until it
gets to a newline. But since the next character is already a newline, nextLine
returns the empty string "".
To solve this problem, you need an extra nextLine() after nextInt()
System.out.print("What is your age? ");
age = in.nextInt();
in.nextLine();// read the newline
System.out.print("What is your name? ");
name = in.nextLine();
System.out.printf("Hello %s, age %d\n", name, age);
This technique is common when reading int or double values that appear on
their own line.
First you read the number, and then you read the rest of the
line, which is just a newline character.
Example: Inches to Centimeters
Now let's see an example, We will use a Scanner to input a measurement
in inches, convert to centimeters, and then display the results.
First let's declare the variables and create the Scanner object that we will call input
int inches;
double cm;
Scanner input = new Scanner(System.in);
Next, let's prompt the user for the input (prompts are important because they let the user know HOW
they should interact with the program)
We'll use print() instead of println() so they can enter the input on the same line as the prompt
and use the Scanner method nextInt(), which reads input from the keyboard
and converts it to an integer
System.out.print("Enter # of inches: ");
inches = input.nextInt();
Now we can multiply the number of inches entered by 2.54 (the number of centimeters there are per
inch)
and display the results
cm = inches * 2.54;
System.out.print(inches + " inches = ");
System.out.printf("%.2f cm%n", cm);
This code works but it does have a minor flaw. If another programmer
reads this code, they might wonder where 2.54 comes from.
How can we modify this for clarity?
A value that appears in a program, like 2.54 (or " inches = "), is called a literal.
In general, there is nothing wrong with literals.
But when numbers like 2.54 appear in an expression with no explanation, they make code hard to read.
And if the same value appears many times, and might have to change in the
future, it makes code hard to maintain.
For the benefit of others (and yourself in the future), it would be better to assign this value to
a variable with a meaningful name:
double cmPerInch = 2.54;
cm = inches * cmPerInch;
This version is easier to read and less error-prone, but it still has a problem.
Variables can vary, but the number of centimeters in an inch does not.
Once we assign a value to cmPerInch, it should never change.
Luckily, Java provides a language feature that enforces that rule, the keyword final.
final double CM_PER_INCH = 2.54;
Declaring a variable as final means that it cannot be reassigned once
it has been initialized. If you try, the compiler reports an error.
Variables declared as final are called constants.
- By convention, names for constants are all uppercase, with the underscore character (_) between
words.
At this point, we have now seen enough Java to write useful programs that solve
everyday problems.
We can:
- import Java library classes
- create a Scanner
- get input from the keyboard
- complete any necessary calculations
- print and format output to the screen
At this point, we have seen all of the elements that make up Java programs.
Figure 3.2 shows these organizational units.
Element |
Definition |
Package |
A collection of classes, which define methods. |
Class |
In object-oriented programming, a class is a basic building block.
It can be defined as template that describes the data and behaviour
associated with the class instantiation. |
Method |
A method in Java is a block of code that, when called,
performs specific actions mentioned in it. When you execute a class with the
Java interpreter, the runtime system starts by calling the class's main() method.
If there are other methods involved, the main() method then calls all
the other methods required to run your application.
The main() method is the key to making a Java program executable.
|
Statement |
A statement is roughly equivalent to sentences in natural languages.
A statement forms a complete unit of execution and almost always ends in a semicolon (;) |
Expression |
An expression is a construct made up of variables, operators, and method invocations,
which are constructed according to the syntax of the language, that evaluates to a single value
|
Token |
Tokens are the basic building blocks of a program.
They are the smallest individual units of a program
that have meaning to the compiler and are used to represent the various elements
of a program, such as keywords, identifiers, operators, and literals |
Using Files: Reading from a File
As previously mentioned, we use a Scanner to read data whether or not the data is from the keyboard or a file.
To read from a file we only need to make one or two modifications from what we learned from reading from
the keyboard.
First, since we are using files we need to allow our program(s) to use them by including:
import java.io.File;//import the File class
Since we are dealing with files, we HAVE to add a throws Exception declaration to main:
public static void main (String [] args) throws Exception {
and create the File object that we will be reading from:
File file = new File("[filename].txt");
And create a Scanner like we have previously done, only changing our reference from the keyboard to the
corresponding file object:
Scanner sc = new Scanner(file);
-
It is important that you remember to create the .txt file that you are trying to
reading from and make sure that it is in the correct directory or to specify the path
where the file is located when creating the File object else you will receive a FileNotFoundException
when you attempt to run your code.
Using Files: Writing to a File
We already know about writing data to the screen by using System.out,
but how about writing data to an outside source such as a file?
Writing to a file is slightly less work since we do not have
to manually create the .txt file ourselves prior to running the program,
the program will do the creation for us. There are many different ways to
write to file in Java but in this class we will keep it relatively
simple and use the PrintWriter class.
Like before, we need to import the PrintWriter class in order to use it:
import java.io.PrintWriter;
Whether we are writing or reading, we are still dealing with files so we still HAVE to add a throws
Exception declaration to main:
public static void main (String [] args) throws Exception {
and create the File object that we will be reading from:
We will use the PrintWriter class to create our file:
PrintWriter pw = new PrintWriter("[filename].txt");
-
It is important to make sure that the filename that you are trying to create
does not already exist in the directory that you are trying to create it in
otherwise, you will overwrite the file and lose your original file data
Now we can use the PrintWriter object to write, the same way that we wrote
to the screen only replacing System.out with our PrintWriter object,
i.e. pw.print(), pw.println(), pw.printf(), etc.
Just like with the Scanner, we need to CLOSE the PrintWriter object:
pw.close();
The intepreter only knows that the program is finished writing
to the file after we close the PrintWriter, so if we forget to close
the PrintWriter the file may appear empty.
Importantly, .close() automatically flushes the stream before closing it,
meaning any data remaining in the buffer will be written to the output.
So far we have seen programs that do pretty much the same thing
every time, regardless of the input. For more complex computations,
programs usually react to the inputs, check for certain conditions,
and generate appropriate results.
We mentioned relational operators briefly before but let's discuss how
they work, more in depth:
Relational operators are used to check conditions like whether two values
are equal, or whether one is greater than the other.
x == y |
// x is equal to y |
x != y |
// x is not equal to y |
x > y |
// x is greater than y |
x < y
|
// x is less than y |
x >= y |
// x is greater than or equal to y |
x <= y
|
// x is less than or equal to y |
You have probably seen these operations before, but it is important to note that the Java
operators are different from the mathematical symbols like =, ≠, and ≤.
A common error is to use a single = instead of a double ==
Remember that:
- = is the assignment operator
- and == is a comparison operator
The two sides of a relational operator must be compatible
For example, the expression: 5 < "6" is invalid because 5 is an int and "6" is a String
However, when evaluating the expression 5 (an int) < 6.0 (a double),
Java automatically converts the 5 to 5.0 because when comparing values of different
numeric types, Java applies the same conversion rules we saw previously with the assignment operator.
The result of a relational operator is one of two special values, either true or false.
These values belong to the data type boolean. In fact, they are the only
boolean values.
Java has three logical operators:
Which respectively stand for:
The results of these operators are similar to their meanings
in English.
For example:
- (x > 0 && x < 10) is true when x is both greater than zero and
less than 10
- The expression (evenFlag || n % 3 == 0) is true if either condition
is true, that is, if evenFlag is true or the number n is divisible by 3
- And the ! operator inverts a boolean expression, so !evenFlag is true
if evenFlag is not true (false)
Logical operators evaluate the second expression only when necessary. For
example:
- (true || anything) is always true,
so Java does not need to evaluate the expression anything
- and (false && anything) is always false
Ignoring the second operand, when possible, is called short circuit evaluation, by analogy with an
electrical circuit.
Short circuit evaluation can save time, especially if anything takes a long time to evaluate.
It can also avoid unnecessary errors, if anything might fail.
If I told you I would show you a picture of a blue square and then you saw the picture and it
wasn't true, which part of my claim “a blue square” wasn't true?
Maybe it was a square and it was a color other than blue? Or maybe it wasn't even a square at all? Maybe it
was neither blue NOR a square entirely?
You could write this as a logic statement like below using negation (!) and the AND (&&) operator since
both parts have to be true for the whole statement to be true.
!(a && b)
//a = "blue"
//b = "square"
If you ever have to negate an expression that contains logical operators, and you probably
will, De Morgan's laws can help.
De Morgan's Laws were developed by Augustus De Morgan in the 1800s.
They show how to simplify the negation of a complex boolean expression,
which is when there are multiple expressions joined by an AND (&&) or OR (||), such as
(x < 3) && (y>
2).
Negating a logical expression is the same as negating each term and changing the operator.
The ! operator takes precedence over && and ||, so you don't have to put parentheses around the individual
terms !A and !B.
Here's an easy way to remember De Morgan's Laws:
- move the NOT inside, AND becomes OR
- and move the NOT inside, OR becomes AND
In Java, De Morgan's Laws are written with the following operators:
- !(a && b) is equivalent to !a || !b
- !(a || b) is equivalent to !a && !b
Going back to our example above, using De Morgan's Laws:
- !("blue" && "square") is equivalent to !("blue") or !("square")
You can also simplify negated boolean expressions that have relational operators like <,>, ==. You can remove
negation by moving it inside and flipping the relational operator to its opposite sign.
For example, (!) not (c equals d) is the same as saying c does not equal d.
An easy way to remember this is to:
- Move the NOT inside, flip the sign
- i.e. == becomes !=, < becomes>=, and > becomes <=
In this case, negating each term means using the “opposite” relational operator. For example:
- !(x < 5 && y==3) is the same as x >= 5 || y != 3
- !(x >= 1 || y != 7) is the same as x < 1 && y==7
It may help to read these examples out loud in English. For instance, "If I
don't want the case where x is less than 5 and y is 3, then I
need x to be greater than or equal to 5, or I need y to be anything but
3."
You should be able to show that two boolean expressions are equivalent.
One way to do this is by using truth tables.
For example, we can show that !(a && b) == !a || !b by constructing the truth table below
and seeing that they give identical results for the 2 expressions (the last 2 columns in the table below are
identical!):
a |
b |
!(a && b) |
!a || !b |
true |
true |
false |
false |
false |
true |
true |
true |
true |
false |
true |
true |
false |
false |
true |
true |
Often, you can simplify boolean expressions to create equivalent expressions.
For example, applying De Morgan's Laws to !(x < 3 && y> 2) yields !(x < 3) || !(y> 2) as seen in the
figure below.
This can then be simplified further by moving the not operator inside and flipping the relation
operators.
So, !(x < 3) || !(y> 2) is simplified to (x >= 3 || y <= 2) where the relational operators are flipped
and the negation is removed.
These two simplification steps are seen below.
Our programs have so far been linear. In other words, the programs have executed from top to bottom without
major surprises or conditional
behavior.
However, we usually want to add conditional logic to our programs. By this we mean
functionality that's in one way or another
dependent on the state of the program's variables.
To branch the execution of a program based on user input, for example, we need to use something known as a
conditional statement.
The simplest conditional statement in Java is the if statement:
if (x > 0) {
System.out.println("x is positive");
}
An if statement specifies a statement(s) to be executed only if a particular boolean expression is
true
The expression in parentheses is called the condition.
- If it is true, the statements in braces get executed.
- If the condition is false, execution skips over that block of
code.
The condition in parentheses can be any boolean expression.
A second form of conditional statement has two possibilities, indicated by if and else.
The possibilities are called branches, and the condition determines which one gets executed:
if (x > 0) {
System.out.println("x is positive");
} else {
System.out.println("x is NOT positive");
}
If the value of x is greated than zero, we know that x is positive, and this code fragment displays a message
to that effect.
If the condition is false, the second print statement is executed instead. Since the condition must be true or false,
exactly one of the branches will run.
The braces are optional for branches that have only one statement. So we could have written the
previous example this way:
if (x > 0)
System.out.println("x is positive");
else
System.out.println("x is NOT positive");
However, it's better to use braces – even when they are optional – to avoid
making the mistake of adding statements to an if or else block and forgetting
to add the braces.
if (x > 0)
System.out.println("x is positive");
System.out.println("x is NOT positive");
This code is misleading because it's not indented correctly. Since there are no braces,
only the first println is part of the if statement. Here is what the compiler actually sees:
if (x > 0) {
System.out.println("x is positive");
}
System.out.println("x is NOT positive");
As a result, the second println runs no matter what.
Even experienced
programmers make this mistake; check out
Apple's
“goto fail” bug.
There is also a short-hand if else, which is known as the ternary operator because it consists of
three operands.
It can be used to replace multiple lines of code with a single line, and is most often used to replace simple
if/else statements:
The syntax is as follows:
So we can modify our previous if/else block using the ternary operator as follows:
String result = (x > 0) ? "x is positive" : "x is NOT positive";
System.out.println(result);
Sometimes you want to check related conditions and choose one of several
actions. One way to do this is by chaining a series of if and else statements.
We can use the else if statement to specify a new condition if the first condition is false.
These chains can be as long as you want, although they can be difficult to
read if they get out of hand.
One way to make them easier to read is to use
standard indentation, as demonstrated in these examples. If you keep all the
statements and braces lined up, you are less likely to make syntax errors.
We can modify out previous if/else block (either printing out "x is positive" or "x is NOT positive") to
include more specific options:
if (x > 0) {
System.out.println("x is positive");
} else if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}
Now we can further specify for values of x that are not positive, whether they be 0 or a negative number.
In addition to chaining, you can also make complex decisions by nesting one
conditional statement inside another. We could have written the previous
example as:
if (x > 0) {
System.out.println("x is positive");
} else {
if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}
}
The outer conditional has two branches.
The first branch contains a print
statement, and the second branch contains another conditional statement,
which has two branches of its own.
These two branches are also print statements, but they could have been conditional statements as well.
These kinds of nested structures are common, but they get difficult to read
very quickly. Good indentation is essential to make the structure (or intended
structure) apparent to the reader.
To store a true or false value,
you need a boolean variable.
You can create one like this:
boolean flag;//variable declaration
flag = true;//assignment
boolean testResult = false;//both declaration and assignment
Since relational operators evaluate to a boolean value, you can store the result of a comparison in a
variable:
boolean positiveFlag = (x > 0);//true if x is positive
boolean negativeFlag = (x < 0);//true if x is negative
boolean zeroFlag = (x == 0);//true if x is zero
The parentheses are unnecessary, but they make the code easier to read.
A variable defined in this way is called a flag, because it signals or “flags” the presence or absence
of a condition.
You can use flag variables as part of a conditional statement later:
if (positiveFlag){
System.out.println("x is positive");
}
Notice that you don't have to write if (evenFlag == true). Since
evenFlag
is a boolean, it's already a condition. Likewise, to check if a flag is
false:
if (!positiveFlag){
System.out.println("x is NOT positive");
}
Instead of writing many if/else statements, we can use the switch statement.
Unlike if-then and if-then-else statements, the switch statement can have a number of possible execution
paths.
A switch works with the byte, short, char, and int primitive data types. It also works with
enumerated types (special data type that enables for a variable to be a set of predefined constants),
the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and
Integer
The switch statement selects one of many code blocks to be executed:
This is how it works:
- The switch expression is evaluated once.
- The value of the expression is compared with the values of each case.
- If there is a match, the associated block of code is executed.
- The break and default keywords are optional, but extremely useful. We'll talk about this in
a bit.
Let's try an example that reads in a number and uses it to calculate the day of the week:
Scanner sc = new Scanner (System.in);
System.out.print("Enter a number (1-7): ");
int day = sc.nextInt();
Now, let's write some code that will represent the following:
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
- Sunday
When Java reaches a break keyword, it breaks out of the switch block.
This will stop the execution of more code and case testing inside the block.
When a match is found, and the job is done, it's time for a break. There is no need for more testing.
A break can save a lot of execution time because it "ignores" the execution of all the rest of the code in
the switch block.
The default keyword specifies some code to run if there is no case match.
Note that if the default statement is used as the last statement in a switch block, it does not need a
break.
If the same outcome applies to more than one case we can also combine cases.
In modern Java (14+), the switch statement got a significant upgrade with the introduction of arrow notation (->)
switch (day) {
case 1 -> System.out.println("Monday");
case 2 -> System.out.println("Tuesday");
case 3 -> System.out.println("Wednesday");
case 4 -> System.out.println("Thursday");
case 5 -> System.out.println("Friday");
case 6 -> System.out.println("Saturday");
case 7 -> System.out.println("Sunday");
default -> System.out.println("Invalid input! Please enter a number between 1 and 7.");
};
The following rules apply:
- Before the arrow, you may list several cases separated by commas
- The arrow may be followed by either a single code statement or a code block in curly braces
- break statements are omitted in this notation
For example, if we need to determine if the day happened to be a weekday or a weekend:
switch (day) {
case 1, 2, 3, 4, 5 -> System.out.println("Weekday");
case 6, 7 -> System.out.println("Weekend");
default -> System.out.println("Invalid input! Please enter a number between 1 and 7.");
};
Let's say we needed to save the String literal in a variable (maybe we need it for something later), we can use switch to assign a case-specific value to a variable.
In the following example, instead of printing the actual day represented by the integer, we store it in the dayOfTheWeek variable:
String dayOfTheWeek;
switch (day) {
case 1 -> dayOfTheWeek = "Monday";
case 2 -> dayOfTheWeek = "Tuesday";
case 3 -> dayOfTheWeek = "Wednesday";
case 4 -> dayOfTheWeek = "Thursday";
case 5 -> dayOfTheWeek = "Friday";
case 6 -> dayOfTheWeek = "Saturday";
case 7 -> dayOfTheWeek = "Sunday";
default -> System.out.println("Invalid input! Please enter a number between 1 and 7.");
};
To use the variable, dayOfTheWeek, afterward, this conventional notation – even though we've covered every weekday – requires us to initialize the variable beforehand.
Otherwise, the compiler would abort with the error message "Variable 'dayOfTheWeek' might not have been initialized".
String dayOfTheWeek = "N/A"; //initialize to default
then we should be able to print out the day (however we have not done any error prevention this particular example ... yet):
String dayOfTheWeek = "N/A"; //initialize to default
switch (day) {
case 1 -> dayOfTheWeek = "Monday";
case 2 -> dayOfTheWeek = "Tuesday";
case 3 -> dayOfTheWeek = "Wednesday";
case 4 -> dayOfTheWeek = "Thursday";
case 5 -> dayOfTheWeek = "Friday";
case 6 -> dayOfTheWeek = "Saturday";
case 7 -> dayOfTheWeek = "Sunday";
default -> System.out.println("Invalid input! Please enter a number between 1 and 7.");
};
System.out.printf("The day of the week is %s%n", dayOfTheWeek);
Now that we have learned a bit more about user input and choices, what about if the choices AREN'T numbers?
What if they were letters? Or symbols?
The char data type is used to store a single character. The character must be surrounded by
single quotes, like 'A' or 'c'
char choice = 'B';
Alternatively, we can use ASCII values, you can use those to display certain characters:
char first = 65;
char second = 66;
char third = 67;
System.out.println(first);
System.out.println(second);
System.out.println(third);
Unfortunately the Scanner class does not have a specific method used to read in char values the same we we
can ints (.nextInt()), doubles (.nextDouble()), etc.
However, since we CAN read in Strings and Strings are made up of character values, there is a way around
this:
char choice = sc.next().charAt(0);
The .charAt() method returns the character at the specified index in a String and the
index of the first character is 0 (we will talk more about the other characters when we talk more about
Strings and their methods)
Doing this is a loophole to read in char values from the keyboard.
Documentation on this page is taken from the following:
-
Allen Downey and Chris Mayfield,
Think Java: How to Think Like a Computer Scientist, 2nd Edition, Version 6.1.3, Green Tea Press, 2016, Creative Commons License.
-
GeeksforGeeks
-
Programiz
-
IOFLOOD