Einführung in die Programmierung (252-0027), ETH Zürich. Slides: “3. Kontrollstrukturen”, sections 3.1–3.4. Lecturers: Malte Schwerhoff (3.1), Manuela Fischer (3.2–3.3).
Sources and coverage
Week 3 Tuesday, second half: start of 3.1: if, if-else, boolean, relational operators, = vs ==.
Week 3 Thursday: rest of 3.1: logical operators (recap, since they were already covered in the exercise session), separate ifs vs else if, the three if constructs, style, ternary operator, braces. Short-circuit evaluation was only referenced: “covered in the exercise session, details on the slides”.
Week 3 Friday: 3.2 (all loops of part I) and 3.3.1–3.3.2 (parameters, return values) up to “missing return statement”.
3.3.3 (scope), 3.3.4 (overloading) and all of 3.4 were announced for the following Tuesday. No recording of them was provided, so these parts are built from the slides alone; all slide examples were worked out and checked.
Where slides contain bugs or the lecturer misspoke, the correct version is given and the discrepancy is flagged.
1. Executive Overview & Core Objectives
Until now programs ran linearly, top to bottom. This chapter introduces control structures (Kontrollstrukturen):
loops (Schleifen: for, while, do-while) for repetition.
It completes methods with parameters (passed by value), return values, scope (Namensraum) and overloading (Methodenüberladung). It ends with increment/decrement and their side effects, typical loop errors (fencepost, off-by-one, infinite loops) and input-controlled sentinel loops.
Learning objectives. After this week you should be able to:
evaluate boolean expressions with relational and logical operators, including precedence, short-circuit evaluation and De Morgan’s laws;
choose between independent ifs, if/else if/else, and else if without a final else, depending on whether the cases must exclude each other;
write and trace for, nested for, while and do-while loops, and derive loop bounds from a table of the output pattern;
write methods with parameters and return values, explain value semantics, and determine where each variable is visible;
detect “missing return statement” by thinking like the compiler, and resolve overloaded calls;
evaluate expressions with ++/-- (pre/post) and compound assignments, and recognise when short-circuiting suppresses a side effect;
solve fencepost problems and write sentinel loops that don’t process the sentinel.
2. Core Concepts & Formal Definitions
3.1 Branches (Verzweigungen)
Definition (if statement, If-Anweisung)
Executes a group of statements A only if a condition test is true (conditional execution, bedingte Ausführung). Execution then continues with C either way.
if (test) { A // if-block}C
Control-flow diagram:test branches to “yes”, which runs A and then C, or “no”, which runs C directly.
Definition (if-else statement, If-else-Anweisung)
Executes either A or B, depending on test. Then C.
if (test) { A // if-block} else { B // else-block}C
Intuition from the lecture: in the morning you check the fridge. If there is milk, you drink it; else you go to the supermarket.
double punkte = console.nextDouble();if (punkte >= 50.0) { System.out.println("Prüfung bestanden");} else { // punkte < 50.0 System.out.println("Prüfung nicht bestanden");}
Not every operator makes sense for every type.1 < true is a compiler error. Java does not give integers a truth value, unlike C/C++ or Python. Java is strict: the code might be a mistake, so you must state your intention clearly.
Relational operators have lower precedence than arithmetic ones.1 + 1 == 2 means (1 + 1) == 2, not 1 + (1 == 2); the latter would be int + boolean, a compile error. The lecturer demonstrated both: the explicit 1 + (1 == 2) is rejected.
Discrepancy: the lecturer said he believed all relational operators have the same precedence. Correctly, < <= > >= bind tighter than == !=. It rarely matters, because chaining them is a type error anyway (next callout).
Exam Trap: No chained comparisons
2 <= x <= 10 is common in maths and allowed in Python, but not in Java. The operators are left-associative, so this means (2 <= x) <= 10, i.e. true <= 10 or false <= 10. That is a compile error (“bad operand types for operator <=”). Write 2 <= x && x <= 10. Chaining is just notation; nothing is lost.
Definition (= vs ==)
== is the comparison operator: a truth value stating whether both sides are equal.
= is the assignment operator: x = e has the type of x and evaluates to the new value of x. In general, therefore, it is not a truth value.
int x = 0;if (x = 0) { … } // ✗ compile error: incompatible types: int cannot be converted to boolean
Example: The degenerate boolean case (lecture demo)
boolean b = false;if (b = true) { System.out.println("This will never be printed.");} else { System.out.println("This will always be printed.");}// Output: This will never be printed.
The class predicted a compile error. But since b is a boolean, the assignment b = true evaluates to true and is a valid condition. It is equivalent to b = true; if (b) …. With an int variable the same slip is a compile error. In languages without a strict int/boolean separation, this mistake is even easier to make.
Boolean expressions are combined with logical operators into new boolean expressions:
Operator
Meaning
Example
Value
&&
and
(2 == 3) && true
false
||
or
(2 == 3) || (-1 < 5)
true
!
not
!(2 == 3)
true
Precedence:! before && before ||. && and || are left-associative; ! (unary) is right-associative.
Truth table (Wahrheitstabelle):
p
q
p && q
p || q
!p
true
true
true
true
false
true
false
false
true
false
false
true
false
true
true
false
false
false
false
true
Remark (XOR and NAND; Thursday student discussion)
The lecturer: Java doesn’t provide XOR or NAND, but &&, || and ! suffice to express every boolean function, a simple provable fact. If needed often, you can wrap the combination in a method:
XOR: (a || b) && !(a && b)
NAND: !(a && b)
Correction: Java does have ^ (XOR) for booleans, and a != b also computes XOR. The course works with &&, || and !.
Operator precedence table, so far (highest first):
Level
Operators
Associativity
1
postfix x++x--
2
unary ++x--x+-!(type)
right
3
*/%
left
4
+- (incl. string concatenation)
left
5
<<=>>=
(not chainable)
6
==!=
left
7
&&
left
8
||
left
9
? :
right
10
=+=-=*=/=%=
right
Definition (if-else-if, If-else-if-Anweisung)
A test cascade. Tests are checked in order, and the first true one wins. Its block runs, and only that block. The final else is entered only if all previous tests failed. Any number of else if blocks is possible.
if (test1) { A // if-block} else if (test2) { B // else-if-block} else { C // else-block}D
Student question: what is D? Just the next statement after the whole cascade. It is always executed, regardless of the tests.
if (x > 0) { System.out.println("positive");} else if (x < 0) { System.out.println("negative");} else { // x == 0 System.out.println("zero");}if (place == 1) { System.out.println("Gold!");} else if (place == 2) { System.out.println("Silver!");} else if (place == 3) { System.out.println("Bronze!");} else { System.out.println("No luck!");}
“If you win gold, you don’t get silver and bronze thrown in as well.”
The three if constructs (characterised together with the class)
Construct
Shape
Property
if / else if / else
if (t1) {A} else if (t2) {B} else {C}
mutual exclusion, exactly one of A, B, C
if / else if (no final else)
if (t1) {A} else if (t2) {B} else if (t3) {C}
mutual exclusion, at most one of A, B, C (possibly none)
independent ifs
if (t1) {A}if (t2) {B}if (t3) {C}
no mutual exclusion; any number of A, B, C, no guarantee of even one
Definition (Ternary operator, ternärer Operator)
test ? valueTrue : valueFalse
test is a boolean expression. valueTrue and valueFalse are expressions, typically of the same type T.
The result has type T: valueTrue if test is true, otherwise valueFalse.
It is an expression, while if is a statement.
if (test) { System.out.println("A"); } else { System.out.println("B"); }System.out.println(test ? "A" : "B"); // same effect
Use: remove redundancy when both branches differ only in a value. Trade-off: simpler, more local control flow, but more complex expressions. It isn’t always better.
Definition (Omitting braces)
If a block consists of exactly one statement, the braces may be omitted: if (test) statementTrue; else statementFalse;
Recommendation: always use braces. If someone later adds a second statement, or merely indents it, it is outside the braces and no longer protected by the test. In the Java community, braces are standard; in C++ less so.
“If you think you’d never make this mistake, search for Apple goto fail”: very well-paid engineers made exactly this mistake and caused a serious security bug.
For p && q and p || q, Java evaluates the left operand pfirst, and stops as soon as the result is known:
p && q: if p is false, the result is false, so q is not evaluated.
p || q: if p is true, the result is true, so q is not evaluated.
In other words, q is evaluated for && only if p is true, and for || only if p is false.
Consequences of short-circuiting
&& and || are not commutative in Java. p && q is not necessarily equivalent to q && p, and the same for ||. This is discussed later.
Possible side effects of q happen only conditionally.
Reasons for the design:
Safety:q may only be evaluated under certain conditions p (p && q).
Efficiency:q needn’t be evaluated if p already succeeded (p || q).
Definition (De Morgan's laws)
Distribution rules for negating boolean expressions: ¬(a∧b)≡¬a∨¬b¬(a∨b)≡¬a∧¬b
Original
Negated
Negated (De Morgan)
a && b
!(a && b)
!a || !b
a || b
!(a || b)
!a && !b
Negating a comparison flips it: !(x == 7) is x != 7; !(y > 3) is y <= 3. So these are equivalent:
if (x == 7 && y > 3) { A } else { B }if (x != 7 || y <= 3) { B } else { A }
3.2 Loops I (Schleifen I)
Definition (Loop, Schleife; iteration)
A loop executes a section of code repeatedly. Each execution of the section is an iteration (Iteration).
Motivation: writing a line 5, 100 or 1000 times by copy-paste is tedious and error-prone. You have to count the copies, and a change such as numbers up to 100 instead of 10 must be made everywhere. The lecture’s examples were The Shining, the Simpsons chalkboard gag, and a random times-table quiz copied ten times.
3.2.1 The (simple) for loop
Definition (for loop, for-Schleife)
Syntax (simplified):
for (initialization; test; update) { // head (Kopf) body // body (Rumpf, Körper)}statement;
initialization: a variable declaration or assignment.
test: a boolean expression, the continuation condition (Fortsetzungsbedingung).
update: an expression with a side effect.
Semantics:
Execute initializationonce.
Repeat until termination:
Check whether test evaluates to true. If not, terminate: jump to statement.
Execute the body.
Execute update.
Typical use (intended behaviour; the compiler does not check it)
initialization initialises the loop variable (Schleifen-/Laufvariable). A variable declared there is valid only inside the loop.
test checks whether the loop variable has reached a certain value, typically with < <= > >= == !=.
update changes the loop variable and brings it closer to the termination value.
Inside the body you can read the loop variable like a normal variable, e.g. print i to count from 1 to 100.
An already existing variable can also be used as the loop variable.
The compiler does not enforce the intended use. true as the test, or updating j instead of i, gives an infinite loop. IntelliJ sometimes warns, e.g. “Condition j <= 100 is always true”.
for (int i = 1; i <= 5; i = i + 1) { System.out.println(i); // 1 2 3 4 5 (one per line)}
Changing the 5 to 100 repeats the body 100 times; no copying is needed. Python’s for looks different, but the principle is the same.
3.2.2 Nested for loops (verschachtelte Schleifen)
Definition (Nested loop)
A loop inside the body of another loop, typically with a different loop variable (here j). For each iteration of the outer loop, the inner loop is executed completely. With 5 outer and 10 inner iterations, the inner body runs 50 times.
Picture a 2D table: i indexes the rows, j the columns.
Any statement can be in a loop body: if/else, other loops, method calls. The inner loop’s bounds may depend on the outer variable.
3.2.3 The while loop
Definition (Definite vs. indefinite loops)
Definite loop (bestimmte Schleife), also counting loop (Zählschleife): the number of iterations is known before the loop starts. Examples: print the first 10 squares; print all odd numbers between 7 and 91. Typically a for loop: one glance at the head shows how often it runs.
Indefinite loop (unbestimmte Schleife): the number of iterations is not known beforehand; it may depend on a complex condition or on user input. Examples:
read input until the user types a negative number (or 42);
read a file until three consecutive sentences end with ”!”;
accept deposits until a certain amount is exceeded.
Definition (while loop, while-Schleife)
while (test) { body}statement;
The body runs as long astest evaluates to true; the loop stops as soon as test is false. test is checked before every iteration, so the body may run zero times.
Unlike for, there is no initialisation and no update in the head. If you need a loop variable, initialise it before the loop and update it inside the body; you are responsible for both.
Minor slip: the lecturer once said “we jump out of the for loop” while explaining the while loop.
A counting loop can be written as while (int i = 1; while (i <= 100) { …; i = i + 1; }), but people would wonder why you didn’t use for.
3.3 Methods II (Methoden II)
Recap: a method is a named sequence of statements that runs whenever the method is called. A method call is also a control structure: jump to the body, execute it, jump back.
3.3.1 Parameters (Parameter)
Motivation:
Two methods vorsatzManuela() and vorsatzMalte() differ in exactly one place, the name.
Changing the vow (“I will always do the exercises”) means editing both.
A third person needs a third method. This is not maintainable.
“Two methods that do the same thing except for one value: that screams for methods with parameters.”
Definition (Parameter, Parametrisierung)
Parametrising a method means giving it parameters. A parameter is a value that the called method receives from the caller.
Declaration:public static void methodName(parameterType parameterName) { … }. The method states that it needs a parameter, with its type (int, double, boolean, String, …) and name.
Call:methodName(parameterValue);. The value is any expression of type parameterType. It is stored in the parameter variable, and the method’s statements run with that value.
Inside the method: the parameter is used like a variable.
Definition (Formal vs. actual parameter)
Formal parameter (formaler Parameter): the parameter variable declared by the method, e.g. String name.
Actual parameter (tatsächlicher Parameter), also argument (Argument): the value passed at a call. It can differ per call: "Manuela", "Malte", "Hans", or a variable.
At each call, the formal parameter is initialised with the value of the actual parameter. This is nothing but an assignment, so the usual assignment and conversion rules apply.
Arguments are matched to parameters by position, not by name.
Definition (Value semantics, Wertübergabe)
For primitive parameter types (int, boolean, …):
The caller evaluates the argument expression and copies the result into the parameter variable.
The called method only receives a copy. It does not know where the value came from, and it does not know the caller’s variables.
Changes to the parameter variable have no effect on the calling method.
The parameter variable is a new variable, even if it happens to have the same name as a variable in the caller. It disappears when the call ends.
More general types come later.
No choice between by-value and by-reference in Java (student question)
In C++ you choose per parameter whether to pass by value or by reference. In Java the language decides: primitive types are always passed by value (copied), and certain other types are always passed by reference. You can simulate other behaviour, but you can’t choose it. References are covered later.
3.3.2 Return values (Rückgabewerte)
Definition (Return value, Rückgabewert)
The counterpart to parameters:
Parameters send values from the caller into the called method.
Return values send a value from the called method back to the caller.
The return type is written before the method name. void means “no return value”; that explains the void seen all along.
The value is returned with a return statement inside the method.
public static returnType name(…) { … return expression;}
Definition (Return statement, Rückgabeanweisung)
return expression;
Evaluates expression.
Returns the value to the caller (a copy, for primitive types).
Terminates the called method.
The call is “replaced” by the return value, so a call can be part of an expression, like f(x)+7 in maths. Parentheses around the returned expression are optional; return x + 1; is fine.
Definition (Local variables)
Variables declared inside a method, including its parameters, are local. They are not visible to the caller and disappear when the call terminates. The caller only receives a copy of the return value.
Rules for return statements
Methods without a return value (void) need no return, but may contain return; to leave the method early. It sends no value back.
Methods with a return value need at least onereturn. Otherwise it is a compile error.
More than one return is allowed and sometimes sensible, especially for case distinctions with if-else.
Every execution path through the method must end in a return. The compiler checks this by looking at the structure of the code (§3, “how the compiler thinks”).
You can’t continue computing after return. If you need more work, store intermediate results and return at the end.
Definition (Ignored return values)
Return values may be ignored. squareNumber(3); computes 9 and discards it, with no warning or error. This is not useful yet, but becomes so for methods with side effects, where you only want the effect.
3.3.3 Scope (Namensräume) (slides only)
Definition (Scope)
The scope of a name is the part of the program in which the name/symbol is visible.
A variable can only be read or modified within its scope.
A method can only be called within its scope (more on method visibility later).
The rules below are an approximation; further aspects follow later.
Scope rules for variables
General: from the declaration to the end of the current block ({ … }), including all enclosed (nested) blocks.
In methods: a variable declared in a method, including parameter variables, exists only in that method.
In branches: a variable declared in an if/else block exists only in that block.
In a for head: a variable declared in the head of a for loop exists only in that loop (head and body).
In a loop body: a variable declared in the body exists for one iteration only. The next iteration creates a new variable with the same name.
Unique declaration: the scopes of variables with the same name must not overlap. Variables with non-overlapping scopes may share a name; they are two different variables that happen to be called the same.
Why scopes?
Readability: the same names can be reused in different places and contexts.
Simpler memory management (not relevant for EProg).
The signature (Signatur) of a method is its name plus the list of its parameter types: methodName(type1, type2, …, typeN). Parameter names and return type are not part of it.
Several methods with the same signature are not allowed. Compiler error: “method is already defined”.
Several methods with the same name are allowed if they have different parameter types and/or a different number of parameters. This is method overloading.
At a call, the most fitting method is chosen. There must be a unique best method, otherwise it is a compiler error.
3.4 Loops II (Schleifen II) (slides only)
3.4.1 Increment and decrement (Inkrement und Dekrement)
Definition (Shorthand forms)
Form
Name
Effect
Value of the expression
x++
post-increment (Post-Inkrement)
x = x + 1
the old x: read first, then change
x--
post-decrement
x = x - 1
the old x
++x
pre-increment (Prä-Inkrement)
x = x + 1
the new x: change first, then read
--x
pre-decrement
x = x - 1
the new x
x += v
x = x + v
the new x
x -= v, x *= v, x /= v, x %= v
analogous
the new x
These work for double too: double y = 2.4; y--; gives 1.4.
Exam Trap: =+ is not +=
x =+ 3 parses as x = (+3): an assignment of positive 3, not an increment. It compiles silently.
Definition (Side effect, Seiteneffekt)
Strongly simplified, there are two kinds of code components:
those that have a value: literals, expressions;
those that have an effect: statements, which change the program’s state.
Expressions with side effects change the state too: increment/decrement (x++, x--), assignments, operations that raise errors. Methods with side effects come later.
Recommendations for shorthands
Shorthands are nice but dangerous, especially with short-circuit evaluation.
The goal is understandable programs; reading code should not be a puzzle. You must still recognise increments and decrements.
Recommendation: use the shorthand only as a stand-alone statement, never inside larger expressions. Use it mainly in loop updates: for (int i = 0; i < n; i++).
3.4.2 Typical errors (Typische Fehler)
Definition (Fencepost error, Zaunpfahlfehler)
A fence of nposts (Pfosten, Pfähle) has only n−1rails (Latten, Querstreben). A loop that always does “post + rail” (or “rail + post”) produces one rail too many.
Solution: move one post outside the loop, the first or the last one, and loop n−1 times.
for (int i = 1; i <= n - 1; i++) { post(); rail(); }post(); // last post outsidepost(); // first post outsidefor (int i = 1; i <= n - 1; i++) { rail(); post(); }
Definition (Off-by-one error)
The loop runs once too often or once too few, or the loop variable is off by one (an indexing problem). The fencepost error is a special case.
Tip:
Trace a small example with 2–3 iterations by hand.
Input can control not only the computation but also the termination of loops. A sentinel loop is an indefinite loop that runs until a sentinel is read: a value that signals something, typically the end of the input. The sentinel is not data.
3.4.4 The do-while loop
Definition (do-while loop, do-while-Schleife)
do { body} while (test); // note the semicolonstatement;
The test is evaluated only at the end of the body, to decide whether another iteration is needed. So there is always at least one iteration.
Equivalence of the three loop types
for, while and do-while are equally expressive: each can simulate the others. Depending on context, one is more sensible:
for for counting loops;
while for indefinite loops that may run zero times;
do-while when at least one iteration is required.
The slides leave the proof as an exercise; see §3.
Remark (break)
break; leaves a loop immediately. The slides show it once and state that it is not discussed further and not exam-relevant.
3. Step-by-Step Derivations
3.1 Evaluation order of a mixed boolean expression (slide 10)
This is the only bracketing that matches the textual requirement; any other would contradict it.
Checks:
1992: divisible by 4, not by 100, so true.
1900: divisible by 100 and not by 400, so false.
2000: divisible by 400, so true.
The lecturer would add the brackets around the && part even though precedence makes it unnecessary, for readers. Full bracketing of every comparison is “one notch too much”.
3.3 Short-circuiting as a guard (slide 29)
int a = …;int b = …;if (a / b > 2) { … } // what if b == 0? Runtime errorif (b != 0 && a / b > 2) { … } // safe
With b == 0:
b != 0 is false.
false && (a / b > 2): the right side is not evaluated, so no division by zero happens. The result is false.
The order of operands is relevant:if (a / b > 2 && b != 0) evaluates the division first, so a division by 0 is possible, which is a runtime error.
3.4 Executing a for loop (slides 37–38)
for (int i = 1; i <= 5; i = i + 1) { System.out.println(i);}
Step
i
test: i <= 5
Action
init
1
1
1
true
print 1; update: i = 2
2
2
true
print 2; i = 3
3
3
true
print 3; i = 4
4
4
true
print 4; i = 5
5
5
true
print 5; i = 6
6
6
false
leave the loop
The body runs 5 times and the test is evaluated 6 times.
3.5 Deriving inner-loop bounds with a table (slides 51–53)
Target:
....1
...2
..3
.4
5
Outer loop: one iteration per row, row = 1, 2, 3, 4, 5.
Inner loop: the pattern of the row: some dots (depending on row), then the number row.
How many dots? Tabulate:
row
# dots
(−1)⋅ row
(−1)⋅ row +5
1
4
−1
4
2
3
−2
3
3
2
−3
2
4
1
−4
1
5
0
−5
0
The count decreases by 1 per row, so the formula contains −row. The values −1,…,−5 are shifted from the target by 5, so add 5: dots=5−row.
“Maybe overkill here, but it helps with harder patterns.” Descriptive loop variable names (row, pt) make it clear which variable iterates over what.
3.6 Value semantics, traced with “boxes” (slides 76–80)
public static void plusOne(int x) { x = x + 1; System.out.println(x); // extra output (slide 80)}int x = 6;int y = 7;plusOne(x);System.out.println(x);plusOne(y);System.out.println(y);
Step
main’s x
main’s y
plusOne’s x (parameter box)
Output
int x = 6; int y = 7;
6
7
(none)
plusOne(x): copy 6 into a new box
6
7
6
x = x + 1 inside
6
7
7
7
return: the parameter box disappears
6
7
(none)
println(x) in main
6
7
6
plusOne(y): copy 7
6
7
7
x = x + 1 inside
6
7
8
8
println(y) in main
6
7
7
Output: 7, 6, 8, 7. The lecture asked the class to predict println(x) after plusOne(x); the answer is 6. The two variables named x are unrelated memory cells.
How to communicate a result back: return it, and assign it at the caller.
public static int plusOne(int x) { x = x + 1; // variant 1 return x;}public static int plusOne(int x) { return (x + 1); // variant 2; parentheses recommended but optional}System.out.println(plusOne(6)); // 7
Further variant from the lecture: int y = x + 1; return y;. “Artistic freedom.”
3.7 How the compiler thinks about return paths (slides 93–96, Friday demo)
Rule: every execution path through a non-void method must reach a return. The compiler does not reason about whether your conditions are logically exhaustive. An if without else can always be skipped.
public static int max(int a, int b) { if (a > b) { return a; } else if (b > a) { return b; }} // ✗ "missing return statement": genuinely no return when a == bpublic static int max(int a, int b) { if (a >= b) { return a; } else if (b > a) { return b; }} // ✗ logically complete, but the compiler sees a path through the "implicit else"public static int silly(int x) { if (x > 0) { return x; } else { if (x <= 0) { return -x; } }} // ✗ the inner if has no else, so there is a path without return
Control-flow view of silly:
x > 0 true leads to return x ✓.
x > 0 false leads to the test x <= 0:
true leads to return -x ✓;
false (impossible logically, but structurally present) leads to the end of the method without a return ✗.
Example: Friday demo, abs
public static int abs(int x) { if (x < 0) { return -x; } else if (x > 0) { return x; }}
First reaction from the class: the case x == 0 is missing. Adding else if (x == 0) return 0;still gives “missing return statement”.
A student: “maybe returns inside if/else are the problem”. Returns inside branches are allowed; the issue is the missing unconditional path.
Fixes: make the last case a plain else, or add return …; after the cascade. “Even if you know all cases are covered, the compiler doesn’t have to know it. Sometimes you are smarter than the compiler, sometimes it is smarter than you.”
The lecturer’s preferred style (less nesting):
public static int abs(int x) { if (x < 0) { return -x; } return x; // reaching here means we didn't return above, so x >= 0}
Method: draw the control-flow graph and check every branch for a return.
Two styles for max, both correct:
public static int max(int a, int b) { // variant 1: single return int result; if (a >= b) { result = a; } else { result = b; } return result;}public static int max(int a, int b) { // variant 2: one return per case if (a >= b) { return a; } else { return b; }}
Some languages enforce a single return; Java doesn’t, and multiple returns often simplify code.
3.8 Unrolling post-increment (slides 119–121)
Execution of an assignment
The right side (an expression) is evaluated. Any ++ side effects happen during this step.
The result is stored in the variable on the left side.
int y = x++; with x = 2:
x++ reads 2 (the expression’s value), then sets x = 3.
y = 2.
So one line changes two variables: x == 3, y == 2.
println(x++) twice, starting with x = 2: prints 2 (x becomes 3), then prints 3 (x becomes 4).
a = a++; with a = 1:
aOld = a, which is 1 (the value of the expression a++).
a = a + 1, which is 2 (the side effect of ++).
a = aOld, which is 1 (the assignment, executed last).
Result: a stays 1.
int c = b-- - b--; with b = 10:
bOld1 = b (10), then b = 9.
bOld2 = b (9), then b = 8.
c = bOld1 - bOld2 = 1.
Result: c = 1, b = 8.
3.9 Pre- and post-increment in one expression (slide 123)
int x = 2; System.out.println(++x + x++ + " " + x + ++x + x);
Evaluate left to right:
Sub-expression
Value
x afterwards
++x
3
3
x++
3
4
3 + 3 (still int addition)
6
6 + " "
"6 "
+ x
"6 4"
4
+ ++x
"6 45"
5
+ x
"6 455"
5
Output: 6 455. “From here on it’s strings.”
3.10 Short-circuit suppresses a side effect (slide 124)
int x = 2;boolean q = p && x++ < 3;
p = true: evaluate x++ < 3, which is 2 < 3 = true, and x becomes 3. So q = true, x is 3.
p = false: false && … is false without evaluating the right side. So q = false, x stays 2.
3.11 Equivalence of the loop types (slide 154, exercise)
Simulate
By
for (init; test; update) { body }
init; while (test) { body; update; } (difference: the scope of the loop variable)
while (test) { body }
for ( ; test ; ) { body } (the head parts may be empty)
do { body } while (test);
body; while (test) { body; }
while (test) { body }
if (test) { do { body } while (test); }
Hence all three are equally expressive.
4. Worked Examples & Applications
4.1 Boolean expressions (slide 14)
With int x = 42, y = 17, z = 25;:
#
Expression
Steps
Result
1
y < x && y <= z
17 < 42 true, 17 <= 25 true
true
2
x % 2 == y % 2 || x % 2 == z % 2
0 == 1 false, 0 == 1 false
false
3
x <= y + z && x >= y + z
y + z = 42: 42 <= 42 true, 42 >= 42 true
true (this is how to “chain”)
4
!(x < y && x < z)
42 < 17 false, so && short-circuits to false; !false
Output for 80:Note 5. and Note 4., because each test is independent.
In the lecture the value was raised to 90, giving Note 6., Note 5., Note 4.. “If you see 6, 5, 4, 3, 2, 1 as your grade in myStudies, that doesn’t help you much.” The control-flow diagram makes it obvious: a decision on one level has no effect on the next.
Repair while keeping separate ifs (class discussion):
Exclude the cases already handled. The second test becomes points >= 80 && !(points >= 90), i.e. points < 90.
Likewise points >= 70 && points < 80.
Still wrong: the unconditional else depends only on the last test, so 80 or 90 points also print “Fail.”. The else must be replaced by a real condition, if (points < 70).
A student suggested dropping the first if and printing “6” in the last else. This fails for the same reason: the else still depends only on its own if, so 80 points print “5” and “6”. Swapping which cases go wrong doesn’t remove the problem.
else if (previousTime <= timeGroup3) instead of else. Using <= at the boundary is a requirement question. More importantly, runners slower than the group-3 limit then get no group, whereas with else everyone, “even someone taking 300 years”, lands in group 3. Which is right must be clarified with the requirements.
Swapping the tests so that < timeGroup2 comes first leaves group 1 empty. Since t1<t2, every time below t1 is also below t2 and is caught first.
Giving every test both bounds also works, but duplicates the limits again.
Example: Medal or certificate
Task: a medal if the average is above 5.9; else a certificate if above 5.75 (made-up rules). At most one, so if / else if with no final else:
boolean medal = false;boolean award = false;if (avgGrade > 5.9) { medal = true;} else if (avgGrade > 5.75) { award = true;}
A final else { medal = false; award = false; } (student suggestion) is unnecessary here, because both flags were initialised with false. If they were only declared, it would be needed.
medal = avgGrade > 5.9; award = avgGrade > 5.75; assigns boolean expressions directly. That is a nice technique, but it is not equivalent: a 6.0 would get both.
An int status (0 = nothing, 1 = certificate, 2 = medal) set in an if / else if is an equivalent alternative to two flags.
Example: Divisible by 2, 3 or 5
Task: any combination is possible, so independent ifs:
Keeping only the first test (student suggestion) is not equivalent. 5 is divisible by 5 but not by 2, so it would give false.
For 30, divisible is set to true three times, which is redundant. A student suggested skipping the remaining tests once it is already true.
Better:boolean divisible = number % 2 == 0 || number % 3 == 0 || number % 5 == 0;. It needs no branching, and short-circuiting stops at the first true test, the same efficiency gain. Trade-off: less branching, a more complex expression.
4.4 Style: comparing booleans with true/false (slides 23–24)
Redundant
Elegant
if (isPrime == true)
if (isPrime)
if (test == false)
if (!test)
test already evaluates to true or false; comparing it with a literal adds nothing. It isn’t faster either, since the compiler optimises it away. But it immediately signals “someone is still learning”. “You don’t want to give that impression in a team; get rid of it right away.”
4.5 for-loop examples (slides 40–43)
for (int i = 1; i <= 12; i = i + 1) { System.out.println("I WILL NOT WASTE CHALK.");}int mistakes = 0, first, second; // three variables declared; only mistakes initialisedfor (int i = 1; i <= 10; i = i + 1) { first = 1 + rand.nextInt(10); second = 1 + rand.nextInt(10); System.out.print(first + " * " + second + " = "); if (console.nextInt() != (first * second)) { mistakes = mistakes + 1; }}System.out.println((10 - mistakes) + " out of 10 correct!");
Countdown (count down instead of up):
System.out.print("T minus ");for (int i = 10; i >= 1; i = i - 1) { System.out.print(i + ", ");}System.out.println("blastoff!");// T minus 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, blastoff!
You could also count up and compute the output in reverse, but letting the loop variable run backwards is more natural. The step size may also be 2 or anything else.
Exam Trap: Infinite loop (Endlosschleife)
for (int i = 10; i % 2 == 0; i = i + 2) { System.out.print(i + " ");}// 10 12 14 16 18 20 22 … never terminates
i stays even forever, so the test never becomes false. In IntelliJ you stop the program with the red stop button in the Run tab.
Live demo: a loop testing j <= 100 while updating i; IntelliJ warned “Condition j <= 100 is always true”.
4.6 Nested loops (slides 45–50)
Rectangle 5 × 10:
for (int i = 1; i <= 5; i = i + 1) { for (int j = 1; j <= 10; j = j + 1) { System.out.print("*"); } System.out.println();}
The intermediate version, ten print("*") statements inside the outer loop, is exactly the copying that loops should eliminate.
Triangle (class suggestion: make the inner bound depend on i):
for (int i = 1; i <= 5; i = i + 1) { for (int j = 1; j <= i; j = j + 1) { System.out.print("*"); } System.out.println();}
Print "*"
Print i
Print j
*
1
1
**
22
12
***
333
123
****
4444
1234
*****
55555
12345
Exam Trap: Wrong variable in a nested head
for (int i = 1; i <= 5; i = i + 1) { for (int j = 1; j <= 10; i = i + 1) { … } // updates i, not j: j <= 10 is always true}for (int i = 1; i <= 5; i = i + 1) { for (int j = 1; i <= 10; j = j + 1) { … } // tests i, which doesn't change inside}
Both inner loops are infinite. The demo printed endless stars in the first outer iteration. i and j look alike: “when you see j and then suddenly i in a head, all alarm bells should ring.”
4.7 while examples (slides 57–58)
int num = 1; // initialisation (before the loop)while (num * num <= 2000) { // test System.out.print(num + " "); num = num * 2; // update (inside the body)}// 1 2 4 8 16 32
The loop stops because 322=1024≤2000 but 642=4096>2000. “If you’re good at mental arithmetic you know the number of iterations; I’d rather work with the condition.”
int n = 91;int factor = 2; // 1 is a trivial divisor, so start at 2while (n % factor != 0) { // as long as factor does not divide n factor = factor + 1;}System.out.println("first divisor is " + factor); // first divisor is 7 (91 = 7 · 13)
4.8 Methods with parameters (slides 61–75)
public static void vorsatz(String name) { // parameter declaration System.out.print(name + " verspricht: "); // parameter used like a variable System.out.println("\"Ich werde die Übungen machen.\"");}public static void main(String[] args) { vorsatz("Manuela"); // value passed at the call vorsatz("Malte");}// Manuela verspricht: "Ich werde die Übungen machen."// Malte verspricht: "Ich werde die Übungen machen."
In the demo, a variable holding "Hans" was also passed, and a missing space was added after the name.
public static void echoPIN(int pin) { System.out.println("Die Geheimnummer ist " + pin);}echoPIN(123456); // pin = 123456echoPIN(40 + 2); // the argument expression is evaluated first: pin = 42
Possible errors:
echoPIN(); is a compile error: an int argument is required.
echoPIN(1.67); is a compile error: “possible lossy conversion”. The conversion rules are the same as before, because parameter passing is an assignment.
public static void sumFromTo(int from, int to) { int sum = 0; for (int i = from; i <= to; i = i + 1) { sum = sum + i; } System.out.println("the sum is " + sum);}sumFromTo(10, 11); // the sum is 21sumFromTo(1, 100); // the sum is 5050 (= 100 · 101 / 2)
Example: "Mystery function" quiz (Friday, EduApp)
A method with parameters in the order (x, z, y) was called from main with variables named x, y, z in a different order. The output in the lecture was 2493; the code is in the uploaded quiz slides, which were not part of the material provided here.
Takeaway:the order of the arguments matters, not their names. Whatever the caller’s first argument is goes into the first parameter, regardless of what either is called. The lecturer called this “terrible code you’d never want to write or read”, but a typical error source.
celsius is local: invisible and irrelevant to the caller, who only gets a copy of the return value.
5 / 9 would be integer division, giving 0, which is why 5.0 / 9.0 is used.
public static int abs(int x) { if (x < 0) { x = -x; // only the local copy changes } return x;}int x = -6;System.out.println("absolute value of " + x + ": " + abs(x)); // absolute value of -6: 6
Primality test (slide 88):
public static boolean isPrime(int n) { boolean foundDivisor = false; int d = 2; while (!foundDivisor) { if (n % d == 0) { foundDivisor = true; } else { d = d + 1; } } // d is the smallest divisor of n that is >= 2 return d == n; // prime iff the smallest divisor >= 2 is n itself}
For a prime n the loop tries 2,3,… and only stops at d=n, so it returns true.
For a composite n it stops at a divisor d<n, so it returns false.
Precondition: n≥2 (not stated on the slide). For n=1 no divisor ≥2 exists, so the loop runs practically forever.
Example: Quiz "What happens here?" (slide 90)
public static void main(String[] args) { squareNumber(3); System.out.println("3^2 = " + result);}public static int squareNumber(int x) { int result = x * x; return result;}
Answer: compile error, “cannot find symbol result”.result is a local variable of squareNumber, invisible from main.
A student suspected missing parentheses around the returned value. That is not the reason; return result; without parentheses is fine.
Fix:int result = squareNumber(3); in main, which is a new local variable of main.
public static void nonSense() { System.out.println("This is reached."); return; System.out.println("This is not reached.");}nonSense(); // This is reached.
4.10 Scope examples (slides 104–108)
public static void example() { int x = 3; x = x * x; for (int i = 1; i <= 10; i = i + 1) { System.out.println(x + i); // x is visible (outer block) } // i no longer exists here System.out.println(x); System.out.println(i); // ✗ ERROR: cannot find symbol "i"}
for (int i = 1; i <= 100; i = i + 1) { int i = 2; // ✗ ERROR: scopes overlap System.out.print(i);}for (int i = 1; i <= 100; i = i + 1) { for (int i = 1; i <= 100; i = i + 1) { … } // ✗ ERROR: inner i overlaps outer i}for (int i = 1; i <= 100; i = i + 1) { System.out.print(i); }int i = 5; // ✓ allowed: the loop's i is already gone
public static void example(int x) { for (int i = 1; i <= 10; i = i + 1) { for (int j = i; j <= 10; j = j + 1) { System.out.print(x + i + j + " "); } // j no longer exists here System.out.println(i); if (i % 2 == 0) { int j = 4; // ✓ a new j; the old j's scope has ended System.out.print(i * j); } // j no longer exists here } // i no longer exists here System.out.println(x);} // x no longer exists here
4.11 Overloading examples (slides 112–115)
public static int max(int a, int b) { return a >= b ? a : b; }public static boolean max(boolean a, boolean b) { return a || b; }max(3, 2); // 3: calls the first methodmax(true, false); // true: calls the second methodpublic static int sum(int from, int to) { int sum = 0; for (int i = from; i <= to; i = i + 1) { sum = sum + i; } return sum;}public static int sum(int to) { return sum(1, to); } // reuses the general versionsum(2, 3); // 5: first methodsum(3); // 6: second method
Inside the first sum, a local variable is also named sum. That is legal, because variables and methods are different kinds of names.
Not allowed:
int f(int a) and int f(int b): only the parameter names differ.
int f(int a) and void f(int a): only the return type differs.
Any combination of these.
public static String foo(int a, double b) { return "int double"; }public static String foo(double a, int b) { return "double int"; }foo(1, 1.0); // "int double": only the first fits (1.0 can't become an int)foo(1.0, 1); // "double int": only the second fitsfoo(1.0, 1.0); // ✗ no matching method: one argument would need double → intfoo(1, 1); // ✗ ambiguous: both fit by widening one int to double; neither is better
4.12 The fencepost problem in practice (slides 128–135)
Task:printNumbers(int n) prints 1, 2, 3, 4, 5, 6 for n=6.
Candidate body
Output for n=5
Problem
print(", " + i + ", ")
, 1, , 2, , 3, , 4, , 5,
commas everywhere
print(i + ", ")
1, 2, 3, 4, 5,
trailing comma
print(", " + i)
, 1, 2, 3, 4, 5
leading comma
Numbers are posts, commas are rails: n numbers need n−1 commas.
public static void printNumbers(int n) { // last post outside for (int i = 1; i <= n - 1; i++) { System.out.print(i + ", "); } System.out.print(n);}public static void printNumbers(int n) { // first post outside System.out.print(1); for (int i = 2; i <= n; i++) { System.out.print(", " + i); }}
Edge cases:
The first version runs no iteration for n<1, but still prints n (e.g. 0).
public static void printPrimes(int n) { if (n < 2) { return; // nothing to print for n < 2 } System.out.print(2); // first post: 2 is always the first prime for (int i = 3; i <= n; i++) { if (isPrime(i)) { System.out.print(", " + i); // rail + post } }}public static boolean isPrime(int n) { // counting variant int numberOfDivisors = 0; for (int d = 1; d <= n; d++) { if (n % d == 0) { numberOfDivisors++; } } return numberOfDivisors == 2; // exactly 1 and n; also correct (false) for n < 2}
4.13 Sentinel loops step by step (slides 138–147)
Task: read numbers until the user types 0, then print the sum.
Enter a number (0 to quit): 10
Enter a number (0 to quit): 20
Enter a number (0 to quit): 30
Enter a number (0 to quit): 0
The total is 60
First attempt:
int sum = 0;int number;while (number != 0) { … } // ✗ compile error: number not initialised
Second attempt:int number = -1; // dummy, anything but 0. Inside the loop: prompt, read, sum = sum + number.
This is correct, but bad style: the sentinel itself is added to the sum. That is harmless only because adding 0 changes nothing.
Switching the sentinel to −1 exposes the bug: inputs 10, 20, 30, −1 give The total is 59. The code should work independently of the choice of sentinel.
Ugly fixes:
Start with sum = 1 to “compensate”. A hack that depends on the sentinel value.
Add if (number != -1) sum = sum + number; inside the loop. This tests the same condition twice per iteration.
Proper solution: it is a fencepost problem. We read n numbers but add only n−1. So: read once before the loop (first post); in the loop, add (rail), then read (post).
sum = 0
print prompt and read input
while input is not sentinel:
add input to sum
print prompt and read input
print sum
int sum = 0;Scanner console = new Scanner(System.in);System.out.print("Enter a number (-1 to quit): ");int number = console.nextInt();while (number != -1) { sum = sum + number; System.out.print("Enter a number (-1 to quit): "); // redundancy: prompt + read appear twice number = console.nextInt();}System.out.print("The total is " + sum);
Removing the redundancy with a method:
int sum = 0;int number = readNumber();while (number != -1) { sum = sum + number; number = readNumber();}System.out.print("The total is " + sum);public static int readNumber() { Scanner console = new Scanner(System.in); System.out.print("Enter a number (-1 to quit): "); return console.nextInt();}
With do-while.Slide bug: slides 150–152 write readNumber(); without storing the result, so number is never set. Corrected:
int sum = 0;int number;do { number = readNumber(); if (number != -1) { sum = sum + number; }} while (number != -1);System.out.print("The total is " + sum);
No dummy initialisation is needed: the compiler can see that number is assigned before the test.
With break (not exam-relevant):
do { number = readNumber(); if (number == -1) { break; // leaves the loop immediately } sum = sum + number;} while (true);
The same task with three loop types (n inputs, the last being −1):
Loop
Structure
Iterations / reads
for
read once; for ( ; number != -1; ) { add; read; }
n−1 iterations, n reads
while
read once; while (number != -1) { add; read; }
n−1 iterations, n reads
do-while
do { read; if (number != -1) add; } while (number != -1);
n iterations, n reads
The fencepost with three loop types (n posts, n−1 rails):
post(); // for: n-1 iterationsfor (int i = 2; i <= n; i++) { rail(); post(); }post(); // while: n-1 iterationsint i = 2;while (i <= n) { rail(); post(); i++; }int i = 1; // do-while, variant 1: n-1 iterationsdo { post(); rail(); i++; } while (i < n);post();int i = 1; // do-while, variant 2: n iterationsdo { post(); if (i < n) { rail(); } i++; } while (i <= n);
4.14 Exam traps collection
Exam Trap: if (a = b)
With ints it is a compile error. With booleans it compiles and silently assigns.
Exam Trap: else binds to the nearest if
Several independent ifs plus one trailing else means “else of the last test only”, not “else of all”.
Exam Trap: Short-circuit and side effects
In p && q / p || q, side effects of q (x++, a division by zero) may or may not happen. Determine p first.
Exam Trap: a = a++
a is unchanged: the old value is reassigned last.
Exam Trap: Parameters are copies
A method changing its primitive parameter does not change the caller’s variable. Changes only “come back” through return plus an assignment: x = plusOne(x);.
Exam Trap: Loop variable scope
A variable declared in the for head doesn’t exist after the loop. Declare it before the loop if you need its final value.
Exam Trap: missing return
“Missing return statement” appears even when your conditions are logically exhaustive. The compiler only sees an if without else.
Exam Trap: Empty loop body
A ; right after the head, for (…); or while (…);, makes the body empty. The block after it is not part of the loop, and while (…); often loops forever.
5. Intuition & Conceptual Bridges
Intuition: EBNF control forms ↔ Java control structures
Week 1’s sequence, selection/option and repetition correspond directly to statement sequences, if/else and loops. Week 1 also showed that EBNF repetition cannot count, whereas a Java for loop can: it has a counter variable. That is why recursion and iteration are equally powerful in programming languages but not in EBNF.
Intuition: Loops are methods for time
Methods remove repetition in space: the same code at different places in the program. Loops remove repetition in time: the same code many times in a row. Parameters make method bodies flexible, just as the loop variable makes loop bodies flexible.
Intuition: Value semantics in the memory-cell picture
A call copies the argument’s value into a fresh cell that belongs to the method and is discarded afterwards. That is why the method can’t touch the caller’s cells. With arrays and objects (next chapter), the copied value is a reference, and the picture gets more interesting.
Intuition: Short-circuiting is a hidden branch
p && q behaves like p ? q : false, and p || q like p ? true : q. Seen that way, conditional side effects and the guard pattern b != 0 && a / b > 2 are no surprise.
Intuition: Thinking in fences
“How many posts, how many rails?” comes up everywhere: separators between list elements, sentinel input, array indices (next chapter). Trace n=0,1,2 before trusting a loop.
Connections:
Prior: week 2’s operator precedence table is extended with relational and logical operators. Assignment is an expression with a value, which is why if (b = true) compiles. Variables, scope and static methods are generalised.
Next (chapter 4, Sequenzen): strings and arrays, processed with exactly these loops; reference semantics, where passing an array lets a method modify the caller’s data (completing value semantics); the allTrue/early-return and max patterns.
Later: recursion (cf. week-1 EBNF recursion); the debugger in the week-5 exercises; break is mentioned but not exam-relevant.
6. Summary & Self-Assessment Questions
Cheat sheet
Topic
Key facts
if / else
condition must be boolean; else attaches to the nearest if
Cascade
if … else if … else: first true test wins; exactly one block runs
Constructs
if/else if/else: exactly one; if/else if: at most one; separate ifs: any number
Relational ops
== != < > <= >=, lower precedence than arithmetic; no chaining
Logical ops
! > && > ||; short-circuit: && skips if left is false, || skips if left is true
De Morgan
!(a && b) = !a || !b; !(a || b) = !a && !b
Ternary
test ? a : b is an expression
= vs ==
assignment (evaluates to the new value) vs. comparison
Style
if (b) not if (b == true); always use braces
for
init once; then test, body, update, test, …; loop variable scoped to the loop
while
test before each iteration; 0 or more iterations
do-while
test after each iteration; at least 1; while (test);
Nested loops
the inner loop runs fully per outer iteration; derive bounds from a row/count table
Parameters
formal = variable, actual = argument value; copied (value semantics); matched by position
return
ends the method; every path of a non-void method must return; the call is replaced by the value
Scope
from declaration to end of block; scopes of equal names must not overlap
Overloading
same name, different parameter types/count; a unique best match or a compile error
++ / --
post: use the old value; pre: use the new value; use only as stand-alone statements
Compound
x += v etc.; beware x =+ v
Fencepost
n posts, n−1 rails: move one post out of the loop
Sentinel
read before the loop; inside: process, then read
Self-assessment questions
Question 1. Give the value of every variable after this code.
int x = 5;boolean b = (x > 3) || (x++ > 10);boolean c = (x < 3) && (++x > 0);boolean d = !(x == 5) || x-- == 5;int e = x++ + ++x;
Solution to Question 1
b: x > 3 is 5 > 3, true. || short-circuits, so x++ is not executed. b = true, x = 5.
c: x < 3 is false. && short-circuits, so ++x is not executed. c = false, x = 5.
d: !(x == 5) is !true, false. || needs its right side: x-- == 5 compares the old value 5 with 5, giving true, and then x becomes 4. d = true, x = 4.
e: left to right. x++ gives 4, and x becomes 5. ++x makes x 6 and gives 6. e = 4 + 6 = 10, x = 6.
Final: x = 6, b = true, c = false, d = true, e = 10.
Question 2. What does this print? Then rewrite the outer loop as a while loop.
for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 4 - i; j++) { System.out.print(" "); } for (int k = 1; k <= 2 * i - 1; k++) { System.out.print("*"); } System.out.println();}
Solution to Question 2
For each row i: 4−i spaces, then 2i−1 stars.
i
spaces
stars
line
1
3
1
*
2
2
3
***
3
1
5
*****
4
0
7
*******
The output is a pyramid:
*
***
*****
*******
As a while loop, the init moves before the loop and the update to the end of the body:
int i = 1;while (i <= 4) { for (int j = 1; j <= 4 - i; j++) { System.out.print(" "); } for (int k = 1; k <= 2 * i - 1; k++) { System.out.print("*"); } System.out.println(); i++;}
Difference: i remains in scope after the loop, with value 5.
Question 3. What does this print? Explain using value semantics.
public static int f(int a, int b) { a = a + b; b = a - b; return a * b;}public static void main(String[] args) { int a = 2; int b = 3; int c = f(b, a); System.out.println(a + " " + b + " " + c);}
Solution to Question 3
Arguments are matched by position, not by name.f(b, a) evaluates to f(3, 2), so f’s parameter a = 3 and f’s parameter b = 2. These are new variables that happen to share names with main’s variables.
Inside f:
a = a + b, so a = 3 + 2 = 5.
b = a - b, so b = 5 − 2 = 3.
return a * b returns 5 · 3 = 15.
Back in main, its own a and b were never touched, so they are still 2 and 3. Then c = 15.
Output: 2 3 15. In a + " " + b + " " + c, the first + is 2 + " ", which already makes a String, so all later + concatenate.
Question 4. Write int digitSum(int n) returning the sum of the decimal digits of n, e.g. digitSum(4096) = 19 and digitSum(-35) = 8. Use a while loop, % and /, and trace it for 4096.
Solution to Question 4
Idea:n % 10 is the last digit and n / 10 removes it (week 2). Repeat while digits remain. This is an indefinite loop: the number of digits isn’t known in advance.
public static int digitSum(int n) { if (n < 0) { n = -n; // changes only the local copy } int sum = 0; while (n > 0) { sum = sum + n % 10; // add the last digit n = n / 10; // drop the last digit } return sum;}
Trace for 4096:
n before
n%10
sum
n after (n/10)
4096
6
6
409
409
9
15
40
40
0
15
4
4
4
19
0
Now 0 > 0 is false, so the loop ends and returns 19 ✓
Edge cases:
n=0: the loop never runs, returning 0. Correct.
n=−35: becomes 35, so 5 + 3 = 8 ✓
Integer.MIN_VALUE cannot be negated within int (overflow, week 2); ignore it here.
Question 5. Find every compile error and explain why.
public static int classify(int n) { if (n > 0) { int sign = 1; } else if (n <= 0) { int sign = -1; } for (int i = 0; i < 3; i++) { int sign = 0; } System.out.println(i); return sign;}public static void classify(int m) { }
Solution to Question 5
System.out.println(i);: i was declared in the for head, so its scope ended with the loop. “cannot find symbol i”.
return sign;: every sign was declared inside a block (the if-block, the else-if-block, the loop body) and disappeared at its closing brace. No sign is in scope here. “cannot find symbol sign”.
Return path (conceptual): if the returns were placed inside the if / else if blocks, the compiler would still complain. else if (n <= 0) without a final else leaves an “implicit else” path without a return. Use a plain else.
public static void classify(int m): same name and same parameter types (int) as int classify(int n). Parameter names and return type don’t count for the signature. “method classify(int) is already defined”.
Not errors: the three separate int sign declarations are fine, since their scopes don’t overlap. The int sign = 0 in the loop body creates a fresh variable each iteration.
One possible fix:
public static int classify(int n) { int sign; // declared in the method's block, so visible at the return if (n > 0) { sign = 1; } else { sign = -1; } return sign;}