0% found this document useful (0 votes)
36 views

Java Exam Notes

Uploaded by

philipshen1969
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views

Java Exam Notes

Uploaded by

philipshen1969
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

SWITCH STATEMENTS

import java.util.Scanner;

public class Switch {


public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter a number: ");
int num = scanner.nextInt();
switch (num) {
case 10:
System.out.println("10");
break;
case 200:
System.out.println("Two hundred");
break;
case 100:
System.out.println("100");
break;
default:
System.out.println("Other");
}
}
}

• switch statements jump to the case that matches to the expression


• Then, they execute lines of code until they hit a break
• If there is no matching case, they go to default
• The expression needs to be byte, short, int, char, an Enumeration, or a String
* missing break statements can cause bugs
* switch is faster than 'if', it doesn't need to check in order
* switch can only handle branching on a single expression to specific values
* to express an expression like x > y/2, you would need to map the expression to specific values
that the switch can handle

int conditionValue = (y / 2); // Calculate the condition value


int caseValue = (x > conditionValue) ? 1 : 0; // Map the condition to specific values

switch (caseValue) {
case 1:
// Code to handle x > y/2
break;
case 0:
// Code to handle x <= y/2
break;
default:
// Default case
}
MISSING INITIALISATIONS AND FOR-LOOP

import java.util.Scanner;

public class EmptyForLoop {


public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
for (; x > 0; --x) {
System.out.println("x=" + x);
}
}
}
###
* loops allow empty statements: in this case since x is not initialised inside -> x will start at its value
outside the loop
* A minimal infinite loop: for (;;)

CONTINUE and RETURN

* continue ends execution early and goes to the next iteration of the loop
* different to break which ends the entire loop

• return is used to return a value for non-void methods


• The return value must be of the correct type (e.g., int)
• The return statement allows us to end the execution of a method -> analogous to break

STORED BY REFERENCE
public class BankExample3 {
public static void main(String[] args) {
BankAccount account1, account2;
account1 = new BankAccount();
account2 = account1; // Same object
account1.ownerName = "Donald Knuth";
account1.balance = 1000;
account1.balance -= 15;
System.out.println(account1.ownerName + " has $" + account1.balance);
System.out.println(account2.ownerName + " has $" + account2.balance);
}
}

* Non-primitive variables store references -> hence default value for variable storing object is null
* objects (instance of a class) are stored by reference
• account1 and account2 store a reference to the object, not the object itself
ARRAYS
* JAGGED ARRAY -> arr = new type[x][]
• Arrays can be initialised with specific values -> String[] names = {"Donald", "Alan"}; int[][]
myArray = {{1,2},{3,4,5}};
public class ArrayFor {
public static void main(String[] args) {
int[][] multiples = new int[5][5];
// for loops go well with arrays
for (int i = 0; i < multiples.length; i++) {
for (int j = 0; j < multiples[i].length; j++) {
multiples[i][j] = (i + 1) * (j + 1);
}
} // So do for-each loops
for (int[] row : multiples) {
for (int num : row) {
System.out.print(num + "\t");
}
System.out.println();
}
}
ARRAYLIST
import java.util.ArrayList;
public class ClassDatabase {
### ArrayList uses .get(_index_) to get a value at an index
private ArrayList<StudentCourse> entry;
public ClassDatabase() {
this.entry = new ArrayList<>();
}

public void addCourseStudent(String student, String course) {


StudentCourse current = new StudentCourse(student,course);
entry.add(current);
}
public int countStudents(String course) {
int count = 0;
for (StudentCourse data : entry) {
if (data.course == course) {
count ++;
}
}
return count;
}

public static void main(String[] args) {


ClassDatabase db = new ClassDatabase();
db.addCourseStudent("Alan Turing", "CITS2005");
System.out.println(db.countStudents("CITS2005"));
}
CONSTRUCTORS

* Can have multiple constructors: but each need different parameter lists
• Gives all numeric types a value of 0, boolean a value of false, and all objects null
• null is a special value for reference (object) variables that means they do not have any reference
yet

public class StudentCourse {

String student;
String course;

public StudentCourse(String student, String course) {


this.student = student;
this.course = course;
}
}

STRINGS

METHODS: .length() .charAt() .toCharArray() .substring(start_index, end_index) s1.equals(s2)

--> s = s.concatenate(new MyString("World".toCharArray())) #where MyString has a method


concatenate

modifying strings:
char[] chars = s.toChaArray()
new_string =new String(chars)

• we can only read the characters in a string, A string cannot be changed once it has been created’

public static boolean isScarcasmCaseWord(String string) {


char[] data = string.toCharArray();
int index = 0;
do {
if (Character.isLowerCase(data[index])) {
if (Character.isLowerCase(data[index+1])) {
return false;
}
index++;

} else {
if (Character.isUpperCase(data[index+1])) {
return false;
}
index++;
}
} while (index < data.length - 1);
return true;
ABSTRACTION

-DATA HIDING-
• Data hiding and encapsulation achieve abstraction
* Data Hiding: Data hiding is the practice of restricting access to certain parts of objects (attributes,
methods) from outside the objects' scope
* prevents unintended or unauthorized access to the object's internal state.
* Achieve data hiding by using access modifiers like private, protected, and public.

-ENCAPSULATION-
* bundling of data (attributes) and methods that operate on the data into a single unit (class)
* hide the internal state of an object and only expose a controlled interface to interact with the
object

public class ParticleCollider {


public static Particle runCollisions(Particle start, Particle[] collisions) {
Particle current = start;
for (int i = 0; i < collisions.length; i++) {
System.out.println("Colliding" + current + "with" + collisions[i]);
current = current.collide(collisions[i]);
}
return current;
}

public static void main(String[] args) {


ParticleCollider pc = new ParticleCollider();
Particle[] collisions = {pc.new ParticleC(), pc.new ParticleD(), pc.new ParticleA(), pc.new
ParticleB()};
Particle start = pc.new ParticleA();
Particle result = runCollisions(start, collisions);
System.out.println(result);
}

abstract class Particle {


public abstract Particle collide(Particle other);
}

class ParticleA extends Particle {


@Override
public Particle collide(Particle other) {
if (other instanceof ParticleB || other instanceof ParticleA) {
return new ParticleA();
} else {
return new ParticleB();
}
}
}
PASS BY VALUE, PASS BY REFERENCE

* Methods and constructors can have parameters that are classes, meaning the values passed in
are objects.
* Variables storing objects or arrays store references, while variables that store primitive types
store the value directly

RECURSION

public class Fibonacci { public class WinningArrangements {


public static void main(String[] args) { private static int numWays(int coins, int limit) {
Fibonacci f = new Fibonacci(); if (coins == 0) {
for (int i = 1; i <= 10; i++) { return 1;
System.out.println(f.fib(i)); }
} if (limit == 0) {
} return 0;
}
public int fib(int n) { limit = Math.min(limit, coins);
if (n <= 2) int result = 0;
return 1; for (int pile = 1; pile <= limit; pile++) {
else result += numWays(coins - pile,
return fib(n - 1) + fib(n - 2); Math.min(limit, pile));
} }
} return result;
}

import java.util.Scanner; public static int numWays(int coins) {


return numWays(coins, coins);
}

public static void main(String[] args) {


Scanner scanner = new Scanner(System.in);
int coins = scanner.nextInt();
System.out.println(numWays(coins));
}
}
STATIC FIELDS AND METHODS

-ACCESSING STATIC MEMBERS-


* ClassName.staticField, ClassName.staticMethod()

* Static Fields: These are variables that belong to the class. All instances of the class share the
same static field.
* Static Methods: These are methods that belong to the class. Declared using the static keyword.
• These methods make sense when they do not need to use fields

public class MyClass {


public static int staticField = 10;

public static void staticMethod() {


System.out.println("Static method");
}
public static void main(String[] args) {
System.out.println(MyClass.staticField);
MyClass.staticMethod();
}
}

ABSTRACT CLASSES & METHODS & FINAL

-ABSTARCT CLASS AND METHODS-


• Any class can be declared abstract
• An abstract class cannot be instantiated
• Any class with an abstract method must be abstract
• Abstract methods need no body
• Abstract methods must be overridden by subclasses (unless the subclass is abstract too)
• Any class with an abstract method must also be declared as abstract

-FINAL-
• Abstract methods must be overidden
• final methods can never be overidden. They are “final”
• final classes can never be inherited from. They are “final” too

METHOD HIDING

Method hiding occurs when a subclass defines a static method with the same name
and signature as a static method in the superclass.
Unlike method overriding, which involves instance methods and allows for
polymorphic behavior,
method hiding involves static methods and does not allow polymorphism.
When a method is hidden, the superclass's method is called, and the subclass's
equivalent is hidden.
INHERITANCE

class Animal {
public void talk() {
System.out.println("Animal talks");
}
}

class Dog extends Animal {


@Override
public void talk() {
System.out.println("Dog barks");
super.talk(); // Call the talk() method of the superclass (Animal)
}
OUTPUT IS: Dog barks Animal Talks

* super.(...) gives access to superclass members that shares same name as the subclass members

EXAMPLES OF INHERITANCE CHAINS

public class Point3D extends Point2D { public class Point2D {

private double z; private double x;


private double y;
public Point3D(double x, double y, double z) {
// constructors are defined without return types: public Point2D(double x, double y) {
super(x, y); this.x = x;
this.z = z; this.y = y;
} }
public double get_x() {
return this.x;
public double get_z() {
}
return this.z;
public double get_y() {
} return this.y;
}
} public static double getMaxX(Point2D[] Point2D) {
}
double max;
// handle empty arrays: if Point2D.length = 0, return Double.NEGATIVE_INFINITY
max = Point2D[0].get_x();
for (int i = 0; i < Point2D.length; i++) {
if (Point2D[i].get_x() > max) {
max = Point2D[i].get_x();
}
}
return max;
}
// runtime polymorphism: method overriding

public static void main(String[] args) {


Point2D[] coord = {new Point3D(1, 0, 3), new Point2D(2,3)};
double best;
best = MaximumXCoordinate.getMaxX(coord);

System.out.println(best);
}
}
DYNAMIC DISPATCH
class Animal {
// ...
}
class Goose extends Animal {
// ...
}
public class SuperGoose {
public static void main(String[] args) {
// What if we did this ??
Animal a = new Goose();
a.talk();
}
OUTPUT IS: Goose.talk() because it’s the runtime type of the object being called that determines
which method gets used
Packages and access modifiers
Modifier | Class | Package | Subclass| World
public Yes | Yes | Yes | Yes
protected Yes | Yes | Yes | No
(default) Yes | Yes | No | No
private Yes | No | No | No

Exceptions
• Let’s understand what happens when an exception gets thrown
• First, something goes wrong (e.g., array index out of bounds)
• An exception is an object. One is created and “thrown”
• If the exception is not caught, the method immediately terminates
• The exception goes up to the parent method that called the current method
• This continues until either the exception is caught, or we run out of methods
• If it remains uncaught, the program crashes and reports the error

Compile-time (checked)

public class StackFull extends Exception {


public StackFull(String message) {
super(message);
}
}

public void push(String s) {


if (top == data.length - 1) {
throw new StackFull("Stack can only hold " + data.length + " elements");
}
data[top++] = s;
}

Run-time (unchecked)

int[] a = {882, 2, 11};


Scanner sc = new Scanner(System.in);
System.out.print("Enter index: ");
int index = sc.nextInt();
try {
System.out.println(a[index]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught ArrayIndexOutOfBoundsException");
}
Interfaces
multiple abstract classes can’t be inherited simultaneously: leads to method ambiguity
Multiple interfaces can be inherited, since their methods are meant to be overriden

class D extends A implements B { ... }


class E implements B { ... }
class F implements B, C { ... }

public interface HasLegs {


int countLegs();
}

class Chair implements HasLegs {


public int countLegs() {
return 4;
}
}

abstract class Insect implements HasLegs {


@Override
public int countLegs() {
return 6;
}
}

class Cricket extends Insect implements MakesSounds {


@Override
public String sound() {
return "Chirp";
}
}

class SqueakyChair implements HasLegs, MakesSounds {


@Override
public int countLegs() {
return 4;
}

@Override
public String sound() {
return "Squeak";
}
}
Enums
• Enums can be public, protected, private, or default like any class
• All enums implicitly extend the built-in Enum class: default methods{ values(), ordinal(),
valueOf() }

enum Transport {
BUS(50), TRAIN(100), FERRY(20), TRAM(30);

private final int typicalSpeed;

Transport(int typicalSpeed) {
this.typicalSpeed = typicalSpeed;
}

public int getTypicalSpeed() {


return typicalSpeed;
}
}

public class EnumConstructor {


public static void main(String[] args) {
System.out.println(Transport.BUS.getTypicalSpeed());
System.out.println(Transport.TRAIN.getTypicalSpeed());
}
}

Autoboxing/unboxing
• Autoboxing: automatic conversion from primitive to object type
• Unboxing: automatic conversion from object to primitive type
Useful for data structures or methods that only accept object types

public class AutoboxList {


public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
int sum = 0;
for (int i : list) {
sum += i;
}
Integer four = 4;
sum += four;
System.out.println(sum);
}
}

Character Class
isDigit(), isLetter(), isUpperCase(), isLowerCase(), isWhiteSpace()
toUpperCase(), toLowerCase()
public static <T> Iterator<T> reversed(DoubleEndedIterator<T> it) {
return new Iterator<T>() {
Generics
public class GenericPair<T, V> { @Override
public T first; public boolean hasNext() {
public V second; return it.hasNext();
}
public GenericPair(T first, V second) {
this.first = first; @Override
this.second = second; public T next() {
}
} if (hasNext()) {
return it.reverseNext();
} else {
Generic Interfaces and Methods throw new NoSuchElementException();
}
}
};
}
Interface
public class RunningMaximum<T extends Comparable<T>> {
private T currentMax;

public RunningMaximum(T initial) {


currentMax = initial;
}

public void addNumber(T number) {


if (number.compareTo(currentMax) > 0) {
currentMax = number;
}
}

public T getCurrentMax() {
return currentMax;
}
}

Method
public class GenericMax {
public static <T extends Comparable<T>> T max(T a, T b) {
if (a.compareTo(b) > 0) {
return a;
} else {
return b;
}
}
}
Interfaces (II)
Bounded Type parameters
<T extends SuperClass>
This type parameter will accept any type argument that is either SuperClass or a subclass of
SuperClass

They are bounded since SuperClass is an upper bound on the type of <T>
Usually, <T> would accept any type --- this is equivalent to <T extends Object>

abstract class Bird {}


class Emu extends Bird {}
class Hawk extends Bird {}

class BirdPair<T extends Bird> {


public T first;
public T second;

public BirdPair(T first, T second) {


this.first = first;
this.second = second;
}
}

public class BoundedType {


public static void main(String[] args) {
var emuPair = new BirdPair<Emu>(new Emu(), new Emu());
var hawkPair = new BirdPair<Hawk>(new Hawk(), new Hawk());
var birdPair = new BirdPair<Bird>(new Emu(), new Hawk());
// var badPair = new BirdPair<String>("Hello", "World");
}
}
OOP Principles SOLID
Single-responsibility principle
* A class should have only one reason to change
* Each class should focus on a single responsibility

violation
public class FileManager {
public String readFile(String filePath) { ... }
public void writeFile(String filePath, String content) { ... }
public String compressFile(String contents) { ... }
}

Open–closed principle
* Software entities should be open for extension but closed for modification
* Add new features by making new classes/methods, not by modifying existing code

Liskov substitution principle


* Subtypes should be substitutable for their base types without affecting the correctness of the
program
* Translation: a subclass should be able to pretend to be its superclass without breaking anything

Interface segregation principle


* Clients should not be forced to depend on methods they do not use
* Translation: lots of smaller interfaces are usually better than one big class/interface

Dependency inversion principle


High-level modules should not depend on low-level modules; both should depend on abstractions
• Abstractions should not depend on details; details should depend on abstractions
• Translation: don’t store classes, store interfaces
• Makes code more flexible. If we change a class, we don’t need to check all the classes that depend
on it
Concurrency and Threads (I)
• A single process can have multiple threads of execution
• Each program can run as its own process

• Java supports multi-threaded code via the Thread class


• Each instance represents a new thread of execution. If we create multiple threads, we can have
multiple parts of our code executed simultaneously

Once we call start(), the run() method executes concurrently in a new thread
class MyThread extends Thread {
private int number;

public MyThread(int number) {


this.number = number;
}
@Override
public void run() {
System.out.println("MyThread (" + number + ") running");
}
}
public class ThreadExample {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyThread t = new MyThread(i);
t.start();
}
}
}
Race condition
A race condition occurs when two threads try to do the same thing concurrently. The simplest
example is modification of some variable. If two threads are trying to increment an integer by 1 at
the same time, the result may not be the same as it being incremented twice

Synchronised
Java offers the synchronised statement
When a thread enters the block, it attempts to get a lock on the object
The thread will wait until the object is available if it is locked
• First, it locks the object >>>Then, it executes the block >>> Then, the object is unlocked

class AddThread extends Thread { // SpecialInt is an integer object with increment() method
private SpecialInt specialInt;
Synchronised(obj){…} ensures the object
being updated inside the synchronised block
public AddThread(SpecialInt specialInt) {
is inaccessible to other concurrently running
this.specialInt = specialInt;
threads.
}
Multiple threads might be running
public void run() {
concurrently and accessing specialInt to
for (int i = 0; i < 1000000; i++) {
increment, but synchronised ensure only one
synchronized (specialInt) {
thread is allowed to modify the object at a
specialInt.increment();
time
}
}
Concurrency and Threads (II)
Join
Once we start a thread, how do we wait until it has finished?
This may happen if you depend on the result

For example, if you create two threads to read from 2 different databases you may wait for a result
from both to continue with the program • Thread has a join() method • When the thread finishes
executing run(), it will join with the calling thread • Calling join() waits for that to happen

public class JoinExample {


public static void main(String[] args) { In top for-loop, the threads[1] which
Thread[] threads = new Thread[10]; threads[1].start(), which should output
for (int i = 0; i < 10; i++) { ‘1’ begins its execution in a thread.
threads[i] = new MyThread(i);
threads[i].start(); While thread[1] runs, the second for-
} loop uses threads[1].join() to wait for
for (int i = 0; i < 10; i++) { threads[1] to actually prints. Then the
try { second iteration of the top for-loop
threads[i].join(); begins again etc.
} catch (InterruptedException e) {
}
}
System.out.println("All threads finished");
}
}
wait(), notify()
inter-thread communication is facilitated using wait() and notify() methods. These methods are
part of the Object class and are used when a thread needs to wait until an object is updated by
another thread before it can continue its execution.

When a thread calls wait() on an object, it releases the lock it holds on that object and enters a
waiting state. It will remain in this state until another thread calls notify() or notifyAll() on the
same object.

notify() : When a thread calls notify() on an object, it wakes up one of the threads that are waiting
on that object. The awakened thread will then compete for the object's lock and continue its
execution.

class ProduceConsume {
private String sharedResource;

public synchronized void produce(String value) throws InterruptedException {


while (sharedResource != null) {
wait(); // wait for the consumer to consume the resource
}
sharedResource = value;
System.out.println("Produced: " + value);
notify(); // notify the consumer that the resource is ready
}

public synchronized void consume() throws InterruptedException {


while (sharedResource == null) {
wait(); // wait for the producer to produce the resource
}
System.out.println("Consumed: " + sharedResource);
sharedResource = null;
notify(); // notify the producer that the resource has been consumed
}
}
Anonymous
Anonymous classes are a way to create a new custom object without declaring a class explicitly.
They are useful when you need a one-of-a-kind object. For example, when sorting an array using
the Arrays.sort(array, comparator) method, you can pass an instance of the Comparator<T>
interface. Instead of creating a new class that implements Comparator<T> , you can use an
anonymous class to define the comparison logic inline, making your code more concise. Here's an
example comparing strings based on the number of 'e' characters:

String[] strings = {"elevated", "banana", "elephant", "early"};


Arrays.sort(strings, new Comparator<String>() {
public int compare(String s1, String s2) {
return countOccurences(s1, 'e') - countOccurences(s2, 'e');
}
});

You might also like