Tutorial 4
Material from Alvin Cherk
Use .equal() instead of == for comparison between objects
public class EqualsExample {
public static void main(String[] args) {
String s1 = "Hello"; // string literal
String s2 = new String("Hello"); // string class
System.out.println(s1 == s2); // false, as its comparing the addresses of s1 and s2
System.out.println(s1.equals(s2)); // true, compares the values at the addresses
// ^ do this
}
}Correctness + style issue
Avoid magic numbers
Assign them to static final variables (readability - style issue)
public class MagicNumbers {
public void doSomething() {
if (files.size() == 5) {} // what does 5 mean here?
}
}
public class MagicNumbers {
private final static int MAX_FILES = 5;
// declare constant with good name ^
public void doSomething() {
if (files.size() == MagicNumbers.MAX_FILES) {};
// ^ use constant here
}
}
Use instanceof for type comparison (open/close - design issue)
public abstract class Shape {
public abstract String getType();
}
public class Rectangle extends Shape {
public void doSomething(List<Shape> shapes) {
for (Shape s : shapes) {
if (s.getClass().equals(Rectangle.class)) { // v1: kinda ok
// do something only on exactly the Rectangle class
}
if (s.getType().equals("Rectangle")) { // v2: very bad
// do something on all rectangles
}
if (s instanceof Rectangle) { // v3: best choice here
// do something on all rectangles
}
}
}
}
public class Square extends Rectangle { ... }Polymorphism is preferred over type-checking when performing a specific action (open closed principle - design issue)
// Example: What to do
public abstract class Shape {
public abstract String getType();
public abstract double area();
// ^ declare method in superclass
}
public class Rectangle extends Shape {
public double area() {
// calculate the area this way
}
}
public class Square extends Rectangle {
public double area() {
// calculate the area a different way
}
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Rectangle());
shapes.add(new Square());
for (Shape s : shapes) {
s.area(); // no more type checking
}
}
}Try to achieve this, but we know it's not always possible
What is it?
A contract must specifiy the following 3 conditions:
Software design approach that specifies how a software component should interact with other software components
What does it really mean?
If someone attempts to use my system without following my contracts, then the system doesn't need to ensure correctness.
i.e. system is allowed to crash
Implications for this course
public class Calculator {
public static Double add(Double a, Double b) {
return a + b;
}
public static Double divide(Double a, Double b) {
return a / b;
}
public static Double sin(Double angle) {
return Math.sin(angle);
}
public static Double tan(Double angle) {
return Math.tan(angle);
}
}Exercise: specify a contract for every method in the Calculator class
/**
* @preconditions: angle != null
* @postconditions: sin(angle)
* @invariant: a, b will remain unchanged
*/
public static Double sin(Double angle) {
return Math.sin(angle);
}
/**
* @preconditions: angle != null, angle != Math.PI / 2
* @postconditions: tan(angle)
* @invariant: a, b will remain unchanged
*/
public static Double tan(Double angle) {
return Math.tan(angle);
}/**
* @preconditions: a, b != null
* @postconditions: a + b
* @invariant: a, b will remain unchanged
*/
public static Double add(Double a, Double b) {
return a + b;
}/**
* @preconditions: a, b != null, b != 0
* @postconditions: a / b
* @invariant: a, b will remain unchanged
*/
public static Double divide(Double a, Double b) {
return a / b;
}add
divide
sin
tan
"Principle of least knowledge"
What is it?
Design principle that states an object should have minimal knowledge about other objects
It aims to achieve loose coupling
Purpose
How to uphold the Law of Demeter?
A method m of an object o should only call methods from:
o itselfo
m
a.getB().getC()*Sometimes it is unavoidable
Avoid situations like below*
Law of Demeter
In the unsw.training package there is some code for a training system
The bookTraining method in the TrainingSystem class violates the Law of Demeter. How?
bookTraining method violate the LawThe Seminar class is a "stranger" of the TrainingSystem classgetStart() is a method of the Seminar class and it is invokedTrainingSystem classTrainingSystem has low cohesion as it depends onSeminar class is poorly encapsulated. It relies on the TrainingSystem class to enforce its contract (i.e. capacity)
/**
* An in person all day seminar with a maximum of 10 attendees.
*/
public class Seminar {
private LocalDate start;
private List<String> attendees;
public LocalDate getStart() {
return start;
}
/**
* Try to book this seminar if it occurs on one of the available days and
* isn't already full
* @param employee
* @param availability
* @return The date of the seminar if booking was successful, null otherwise
*/
public LocalDate book(String employee, List<LocalDate> availability) {
for (LocalDate available : availability) {
if (start.equals(available) &&
attendees.size() < 10) {
attendees.add(employee);
return available;
}
}
return null;
}
}
public class TrainingSystem {
public List<Trainer> trainers;
/**
* Try to booking training for an employee, given their availability.
*
* @param employee
* @param availability
* @return The date of their seminar if booking was successful, null there
* are no empty slots in seminars on the day they are available.
*/
public LocalDate bookTraining(String employee, List<LocalDate> availability) {
for (Trainer trainer : trainers) {
LocalDate booked = trainer.book(employee, availability);
if (booked != null)
return booked;
}
return null;
}
}
/**
* A trainer that runs in person seminars.
*/
public class Trainer {
private String name;
private String room;
private List<Seminar> seminars;
public List<Seminar> getSeminars() {
return seminars;
}
/**
* Try to book one of this trainer's seminars.
* @param employee
* @param availability
* @return The date of the seminar if booking was successful, null if the
* trainer has no free slots in seminars on the available days.
*/
public LocalDate book(String employee, List<LocalDate> availability) {
for (Seminar seminar : seminars) {
LocalDate booked = seminar.book(employee, availability);
if (booked != null)
return booked;
}
return null;
}
}LSP is violated when either
What is it?
Design principle that states objects of a superclass should be replaceable with objects of its subclasses
Formal definition
Let Φ(x) be a provable property of objects x of class T. Then, Φ(y) should be true for objects y of class S where S is a subclass of T
Common signs of potential LSP violations
Solve the problem by minimising / removing inheritance
Design suggestion: favour composition over inheritance
Violations of LSP are typically caused by inheritance
/**
* An online seminar is a video that can be viewed at any time by employees. A
* record is kept of which employees have watched the seminar.
*/
public class OnlineSeminar extends Seminar {
private String videoURL;
private List<String> watched;
}
/**
* Precondition: each seminar can have a maximum of 10 attendees.
* Postcondition: an attendee is added to the seminar.
*/
public class Seminar {
private LocalDate start;
private List<String> attendees;
public LocalDate getStart() {
return start;
}
public List<String> getAttendees() {
return attendees;
}
}
How does OnlineSeminar violate LSP?
Suppose the capacity precondition is currently true, adding a new attendee to an OnlineSeminar will not satisfy the postcondition because presumably the attendee is added to a watched list as opposed to the attendees list from the superclass
What it is?
Java's way of enabling functional programming
Common stream methods:
forEach, filter, map, reduce
Why bother?
forEach makes index out of bounds impossible)If a variable can be null, use Optional<>.
Try to never set variables to be null because dealing with NullPointerExceptions is never fun :(
public class OptionalExample {
public static void main(String[] args) {
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
Optional<String> res = strings.stream().filter(x -> x.equals("1")).findAny();
if (res.isPresent()) {
System.out.println(res.get());
} else {
// It doesn't exist
// Handle error?
}
}
}
.findAny() actually returns Optional<type>
Material from Alvin Cherk
Streams
Convert the following to use streams
package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
// Exercise 1
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
for (String string : strings) {
System.out.println(string);
}
// Exercise 2
List<Integer> ints = new ArrayList<Integer>();
for (String string : strings) {
ints.add(Integer.parseInt(string));
}
System.out.println(ints);
}
}package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
// Exercise 1 Solution
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
strings.stream().forEach(x -> System.out.println(x));
strings.stream().forEach(x -> {
System.out.println(x);
});
// Exercise 2 Solution
List<Integer> parsedStrings = strings.stream().map(x -> Integer.parseInt(x)).collect(Collectors.toList());
strings2.stream().map(x -> Integer.parseInt(x)).forEach(x -> System.out.println(x));
}
}package stream;
public abstract class GenericSatellite {
private double linearSpeed; // in KM/minute
private String name; // name of satellite
public GenericSatellite(double linearSpeed, String name) {
this.linearSpeed = linearSpeed;
this.name = name;
}
public String getName() {
return this.name;
}
public double getLinearSpeed() {
return this.linearSpeed;
}
}
package stream;
public class FastSatellite extends GenericSatellite {
private static final double speed = 100.0;
public FastSatellite(String name) {
super(speed, name);
}
}
package stream;
public class SlowSatellite extends GenericSatellite {
private static final double speed = 50.0;
public SlowSatellite(String name) {
super(SlowSatellite.speed, name);
}
}
package stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Controller {
private List<GenericSatellite> satellites = new ArrayList<>();
public void addSatellite(GenericSatellite satelliteToAdd) {
this.satellites.add(satelliteToAdd);
}
public List<GenericSatellite> getSatelliteList() {
return this.satellites;
}
public static void main(String[] args) { ... }
}public static void main(String[] args) {
Controller c = new Controller();
c.addSatellite(new FastSatellite("Fast Satellite 1"));
c.addSatellite(new FastSatellite("Fast Satellite 2"));
c.addSatellite(new SlowSatellite("Slow Satellite 1"));
List<GenericSatellite> allSatellites = c.getSatelliteList();
// What if I just want FastSatellites only?
// Method 1, normal for-in/for-each
List<GenericSatellite> fast1 = new ArrayList<>();
for (GenericSatellite x : allSatellites) {
if (x instanceof FastSatellite) {
fast1.add(x);
}
}
// Method 2, streams
List<GenericSatellite> fast2 = allSatellites
.stream()
.filter(x -> x instanceof FastSatellite)
.collect(Collectors.toList());
// Same, but I typecast at the same time
List<FastSatellite> fast3 = allSatellites
.stream()
.filter(x -> x instanceof FastSatellite)
.map(x -> (FastSatellite) x)
.collect(Collectors.toList());
}public static void main(String[] args) {
Controller c = new Controller();
List<GenericSatellite> allSatellites = c.getSatelliteList();
// What if im trying to look for a satellite with name == "Fast Satellite 2"?
// Method 1, normal for-in/for-each
GenericSatellite g1 = null;
for (GenericSatellite x : allSatellites) {
if (x.getName().equals("Fast Satellite 2")) {
g1 = x;
break;
}
}
// Method 2, streams
Optional<GenericSatellite> g2 = allSatellites
.stream()
.filter(x -> x.getName().equals("Fast Satellite 2"))
.findAny();
}Reminder to talk about code smell