Intro to Programming

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.



Programming Languages

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:

  1. 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.

  2. 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.




What is Java?

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.

Java Process

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.




Hello World

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?

  1. 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).

  2. 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.

  3. 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) { ... }

  4. 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.


Comments

Comments have no effect on the execution of the program, but they make it easier for other programmers (and yourself!) to understand what you meant to do and how the program works.

There are three types of comments:

  • // single line comments

  • /*
    multiline comments
    */

  • /**
    multiline Documentation Comments to create formal documentation using Javadoc
    */



Variables

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:



Declaring Variables

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)



Printing Variables

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.



Variable Scope

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.)



Global Variables

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.




Java Operators

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:

  1. 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)

  2. 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.

  3. 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)

  4. 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)))

  5. Unary Operators
    • Unary operators are operators that need only one operand to perform any operation, such as: increment, decrement, negation, etc. It consists of various arithmetic, logical and other operators that operate on a single operand.
    • A few unary operators available in Java include:
      • + (Unary Plus - represents a positive value, e.g. +a)
      • - (Unary Minus - represents a negative value, e.g. -a)
      • ! (Greater than or equal to, e.g. a >= b)
      • ++ (Increment Operator - increments the value of a variable by 1, e.g. ++a OR a++)
      • -- (Decrement Operator - decrements the value of a variable by 1, e.g. --a OR a--)
    • The last previous two operators (increment and decrement) are especially important because not only are they incredibly useful in multiple cases but they also have two variants of these operators:
      1. Pre-increment/decrement (also known as prefix)
        • Here our code increments/decrements the value first, and then performs the specified operation.
        • // Prefix increment example
          public class PrefixExample {
              public static void main(String [] args) {
                  int a = 5;
                  System.out.println(++a);//this immediately increments to 5+1 and prints 6
                  System.out.println(a);//this reflects the modifcation in the previous line AND also prints 6
              }
          }
      2. 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
              }
          }

  6. Bitwise Operators

TO REMEMBER:


Precedence Table



Floating-Point Numbers

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.




Math Methods

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;



Types of Errors

There are three types of errors can occur in a program:

  1. Compiler errors
  2. Run-time errors
  3. 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.




The System class

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:

  1. 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.

  2. 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.

  3. 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 ref

    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
Escape Sequences

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.

escape sequences



Data Types

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.)

Strings (part 1)

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)


Operators for Strings

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)


The Scanner Bug

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?




Literals and Constants

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.



Putting it all Together

At this point, we have now seen enough Java to write useful programs that solve everyday problems.

We can:

  1. import Java library classes
  2. create a Scanner
  3. get input from the keyboard
  4. complete any necessary calculations
  5. print and format output to the screen



Program Structure

At this point, we have seen all of the elements that make up Java programs.

Figure 3.2 shows these organizational units.

Figure 3.2
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

program outline



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.




Relational Operators

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.




Logical Operators

Java has three logical operators:

  • &&
  • ||
  • and !

Which respectively stand for:

  • and
  • or
  • and not

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.


De Morgan's Laws

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
De Morgan's Law

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.

example



Conditional Statements

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 if 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.


An if/else Block

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.


Ternary Operator

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:

ternary operator

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);



Chaining and Nesting

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.


The else if Statement

We can use the else if statement to specify a new condition if the first condition is false.

else if

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.


Nested if Statements

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.




Flag Variables

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");
}



Validating Input

In our previous examples, we prompt the user to enter an int (whole number) that we will evaluate using our if/else block.

We use .nextInt(), so the Scanner is expecting to read data that is compatible with an int.

If the user enters a whole number, the Scanner accepts it and converts it to a int.

But if the user types anything else, i.e a floating-point number, the Scanner throws an InputMismatchException.

We can prevent this error by checking the input before parsing it.

The Scanner class has boolean methods that can validate whether the input is of a particular type, in our case we can use .hasNextInt() which will return true if the next token in this Scanner's input can be interpreted as an int value and false otherwise.




Switch Statements

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:

switch statement

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:

  1. Monday
  2. Tuesday
  3. Wednesday
  4. Thursday
  5. Friday
  6. Saturday
  7. Sunday

The break Keyword

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

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.



Combining Cases

If the same outcome applies to more than one case we can also combine cases.

switch statement


Arrow Notation

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);



The char Data Type

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);
ASCII Table

Reading in chars

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.




Reference

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