Lecture from: 27.09.2024 | Video: Videos ETHZ

Programs and Data Types

Programs operate on data. This data can be associated with different types. These types can have associated properties, methods, and operations.

Primitive Types

Java has eight primitive data types. These types store simple values and are not objects. They are:

  1. byte: 8-bit integer, ranging from -128 to 127.
  2. short: 16-bit integer, ranging from -32,768 to 32,767.
  3. int: 32-bit integer, commonly used for whole numbers.
  4. long: 64-bit integer, used for larger whole numbers.
  5. float: 32-bit floating-point number, used for decimal values.
  6. double: 64-bit floating-point number, commonly used for more precise decimal values.
  7. boolean: Represents two possible values: true or false.
  8. char: 16-bit Unicode character, used to store single characters.

Arithmetic Operations

In Java, the common arithmetic operators include:

  • + : Addition
  • - : Subtraction
  • * : Multiplication
  • / : Division
  • % : Modulo (remainder)

When using these operators between variables of the same type (TypA), the result will be of the same type (TypA).

Examples:

2 * 3 = 6

Questions:

  1. What happens with 5 / 2?

    • In Java, when both operands are integers, the result is integer division. This means 5 / 2 will return 2 (the fractional part is discarded).
  2. What about TypA Op TypB?

    • If TypA and TypB are different types (e.g., an integer and a floating-point number), Java will perform type promotion. The smaller type (like int) is promoted to the larger type (like double), and the result will be of the larger type.

    • For example:

      int a = 5;
      double b = 2.0;
      double result = a / b;  // Result will be 2.5

Wholenumber Division using /

  • When using / with whole numbers (integers), Java performs integer division.
    • For example:
      7 / 2 = 3  // the remainder is discarded
    • This type of division truncates any decimal value and only returns the quotient as an integer.

Modulo %

  • The modulo operator (%) returns the remainder of the division between two numbers.

    • For example:
      7 % 2 = 1  // 7 divided by 2 equals 3 with a remainder of 1

Usage of Modulo

The modulo operation is commonly used in various situations:

  1. Checking Even or Odd Numbers:

    • To check if a number is even or odd, use the modulo operator:
      if (n % 2 == 0) {
          // n is even
      } else {
          // n is odd
      }
  2. Extracting the Last 4 Digits of a Number:

    • To extract the last 4 digits of a number, you can use the modulo operator with 10000:
      int number = 12345678;
      int last4Digits = number % 10000;  // Result is 5678

Important Differences

Associativity

The associativity of an operator determines the order in which operations are performed when an expression has multiple operators of the same precedence.

For example, let “O” represent an operator:

Left-Associative Operators

  • Left-associativity means that the expression is evaluated from left to right.

  • For example, in the expression A O B O C, if the operator O is left-associative, it will be evaluated as:

    (A O B) O C

Right-Associative Operators

  • Right-associativity means that the expression is evaluated from right to left.

  • If O were a right-associative operator, the expression A O B O C would be evaluated as:

    A O (B O C)

Left-Associative Operators We’ve Seen So Far

  • The operators we’ve encountered so far (+, -, *, /, %) are left-associative. This means that operations with these operators are performed from left to right in expressions.

Example:

For an expression like 10 - 4 - 2:

  • Since - is left-associative, the evaluation will be:

    (10 - 4) - 2 = 6 - 2 = 4

If the operator were right-associative, the same expression would evaluate as:

10 - (4 - 2) = 10 - 2 = 8

Precedence

Precedence refers to the rules that determine which operators are evaluated first in an expression with different operators.

Operator Precedence Rules

  • Operators with higher precedence are evaluated before those with lower precedence.
  • For example, multiplication (*) and division (/) have higher precedence than addition (+) and subtraction (-).

Example:

In the expression 2 + 3 * 4, multiplication has higher precedence, so it is evaluated first:

2 + (3 * 4) = 2 + 12 = 14
  • Parentheses can be used to override precedence:
    • For example, (2 + 3) * 4 will first evaluate the expression inside the parentheses:

      (2 + 3) * 4 = 5 * 4 = 20

Expression Trees

An expression tree is a binary tree used to represent mathematical expressions. Each leaf node represents an operand (such as a constant or variable), and each internal node represents an operator. The tree structure reflects the order of operations in the expression.

How It Works:

  • The root of the tree is the main operator of the expression.
  • The left and right children of each internal node are sub-expressions that represent the operands of the operator at that node.
  • The tree is traversed in post-order (left, right, root) to evaluate the expression.

Example Expression:

For the expression (1 * 2) + ((3 * 5) % 4), the corresponding expression tree is:

Type Casting

Type casting in Java allows you to convert a variable from one data type to another. This can be necessary when you’re working with different types of data (e.g., converting a double to an int or an int to a double). There are two types of type casting:

  1. Implicit Casting (Widening): Automatically converting a smaller type to a larger type (e.g., int to double). No data is lost in this process.
  2. Explicit Casting (Narrowing): Manually converting a larger type to a smaller type (e.g., double to int). This might lead to a loss of data.

Syntax:

(type) expression

Here, type is the data type you want to cast the expression to. The cast operator is a unary operator, and it is right-associative. This means that if you have multiple casts in an expression, they are evaluated from right to left.

Examples:

  1. Implicit Casting (Widening):

    int num = 10;
    double result = num;  // int is automatically converted to double
  2. Explicit Casting (Narrowing):

    double value = 10.99;
    int num = (int) value;  // double is explicitly cast to int, fractional part is lost
    System.out.println(num);  // Output: 10
  3. Casting Between Integer Types:

    long largeNum = 100000L;
    int smallerNum = (int) largeNum;  // long to int
  4. Casting Between Floating-Point and Integer Types:

    float decimal = 9.7f;
    int wholeNum = (int) decimal;  // float to int, results in 9 (fractional part is lost)

Right-Associative:

Type casts are unary and right-associative. They also have the highest precedence. For example in an expression like this:

int result = (int) (float) (double) 5.5;

The casts are applied from right to left, so:

  1. First, 5.5 is cast from double to float.
  2. Then, the resulting float is cast to int.

Important Notes:

  • Widening conversions are done automatically, but narrowing conversions require explicit casting because there’s a risk of data loss.
  • Casting between types that aren’t compatible (e.g., boolean to int) isn’t allowed.

Strings

A String in Java is a sequence of characters. Strings are not primitive types but objects in Java. They are instances of the String class and are used to represent text. Strings are immutable, meaning once a string is created, it cannot be changed.

String Creation

You can create strings using double quotes:

String greeting = "Hello, World!";

Operations

There are several operations that can be performed on strings, including:

  1. Concatenation
  2. Length
  3. Substring
  4. Comparison
  5. Character Access

Concatenation

String concatenation allows you to join two or more strings together. This can be done using the + operator.

Example:
String firstName = "John";
String lastName = "Doe";
String fullName = firstName + " " + lastName;  // "John Doe"

When you use the + operator between strings, it combines them into a new string.

Casting Non-Strings

If a non-string value is concatenated with a string, Java automatically casts the non-string value to a string and then performs the concatenation.

Example:
int age = 30;
String message = "I am " + age + " years old.";  // "I am 30 years old."

In this example, the integer age is automatically converted to a string "30" and concatenated with the other strings.

Another example:

double number = 1.2;
String result = "The value is " + number;  // "The value is 1.2"

Associativity and Precedence of String Concatenation

  • The + operator for string concatenation is left-associative, meaning it is evaluated from left to right.
  • The + operator for strings has lower precedence than arithmetic operators like *, /, and %.
Example of Precedence:
String result = "Sum: " + 2 + 3 * 4;

Here’s the evaluation process:

  1. First, 3 * 4 = 12 is evaluated because * has higher precedence than +.
  2. Then, "Sum: " + 2 is evaluated, resulting in "Sum: 2".
  3. Finally, "Sum: 2" + 12 is evaluated, resulting in "Sum: 212".
Example of Left-Associativity:
String result = "A" + "B" + "C";

Since + is left-associative, it evaluates from left to right:

  1. First, "A" + "B" is evaluated, resulting in "AB".
  2. Then, "AB" + "C" is evaluated, resulting in "ABC".

If string concatenation were right-associative, the expression would evaluate as "A" + ("B" + "C"), but in Java, it’s evaluated left-to-right.

Important Notes:

  • Since strings are immutable, concatenating strings creates a new string every time.
  • To efficiently handle many concatenations, consider using StringBuilder or StringBuffer.

Other examples to try out

  • 12 -3 + 5
  • 2 + 3 + " Zehn"
  • "Nummer " + 3 + 2
  • "Nummer " + 2 * 3
  • "Note " + (4.8 + 5.2)/2
  • "Note " + 4.8 + 5.2/2

Variables

A variable in Java is a container that holds data of a specific type. Variables allow programs to store, modify, and retrieve values. Each variable has a name, a type, and a value. The type determines what kind of data the variable can hold, such as integers, floating-point numbers, or characters. Variables are essential for making programs dynamic and interactive.

Variables in Java undergo the following steps:

  1. Declaration
  2. Initialization
  3. Assignment
  4. Evaluation

Declaration

Declaration is the process of defining a variable’s type and name. It ensures that enough memory is allocated to store the variable’s value, based on the type. Declaration reserves space but does not assign any value.

  • For primitive types, a default initialization (e.g., int a → a = 0) does not automatically take place during declaration. This means that after declaring a primitive variable, its value is undefined until explicitly initialized.
Example:
int a;  // Declaration of an integer variable

Here, a is declared but not yet assigned any value.

Initialization

Initialization assigns a value to the variable for the first time. This is necessary before using the variable in operations or expressions. Once initialized, the variable’s memory location holds the assigned value.

Example:
int a = 5;  // Declaration and initialization of variable a

In this case, a is both declared and initialized with the value 5.

Possible Mistakes

  1. Missing Declaration:

    Trying to use a variable before it is declared.

    b = 10;  // Error: variable b is not declared
  2. Initialization Before Declaration:

    Using a variable without assigning an initial value.

    int c;
    System.out.println(c);  // Error: c might not have been initialized
  3. Multiple Declarations in the Same Context:

    Declaring the same variable multiple times, possibly with different types.

    int a;
    double a;  // Error: variable a is already declared
  4. Missing Initialization:

    Declaring a variable but not initializing it before usage.

    int d;
    System.out.println(d);  // Error: d might not have been initialized
  5. Initialization with the Wrong Data Type:

    Assigning a value of an incompatible type to a variable.

    int e;
    e = "hi";  // Error: incompatible types: String cannot be assigned to int

Assignment

Assignment involves updating the value of a variable after it has been initialized. It writes a new value to the corresponding storage location.

Example:
int a = 5;  // Initialization
a = 10;     // Assignment of a new value to a

After assigning 10 to a, the previous value 5 is replaced, and a now holds 10.

Evaluation

Evaluation retrieves the value stored in the variable at the current point in the program. It allows you to access the variable’s value in expressions and calculations.

Example:
int x = 5;
int y = x + 2;  // Evaluation of x (which holds 5), resulting in y = 7

The variable x is evaluated, and its value 5 is used in the expression x + 2.

Evaluation and Assignment

In Java, you can assign a new value to a variable based on its current value. This is known as evaluation followed by assignment.

Example:
int a = 10;
a = a + 1;  // First, a is evaluated (10), then 1 is added, and the result (11) is assigned back to a

This expression might seem mathematically incorrect, but in programming, it is valid because first, the variable a is evaluated, and then the result of a + 1 is assigned back to a. This is not an equivalency ( in programming is an assignment operator, not a mathematical equality).

Continue here: 04 Java (Input, Random, Control Flow)