This site is from a past semester! The current version will be here when the new semester starts.
TIC4001 2020
  • Full Timeline
  • Week 1 [Mon, Aug 10th]
  • Week 2 [Fri, Aug 14th]
  • Week 3 [Fri, Aug 21st]
  • Week 4 [Fri, Aug 28th]
  • Week 5 [Fri, Sep 4th]
  • Week 6 [Fri, Sep 11th]
  • Week 7 [Fri, Sep 18th]
  • Week 8 [Fri, Oct 2nd]
  • Week 9 [Fri, Oct 9th]
  • Week 10 [Fri, Oct 16th]
  • Week 11 [Fri, Oct 23rd]
  • Week 12 [Fri, Oct 30th]
  • Week 13 [Fri, Nov 6th]
  • Textbook
  • Admin Info
  • Report Bugs
  • Forum
  • Gitter (Chat)
  • Instructors
  • Announcements
  • Files
  • Java Coding Standard
  • Git Conventions
  • Participation Dashboard

  •  Individual Project (iP):
  • Individual Project Info
  • iP List
  • iP Upstream Repo
  • iP Code Dashboard
  • iP Progress Dashboard

  •  Team Project (tP):
  • Team Project Info
  • Team List
  • tP Code Dashboard
  • tP Progress Dashboard
  • Week 3 [Fri, Aug 21st] - Topics

    Similar to last week, most topics for this week are TIC2002 topics given here for reference. But there are some new topics too (they are marked by one of these icons: , , , : OPTIONAL)

    • [W3.1] OOP, Java: Class-Level Members
    • [W3.1a] Paradigms → OOP → Classes → Class-level members :

    • [W3.1b] C++ to Java → Classes → Class-level members :

    • [W3.2] Java: Useful Classes
    • [W3.2a] Implementation → Reuse → APIs → What :

    • [W3.2b] C++ to Java → Some Useful Classes → Java API :

    • [W3.2c] C++ to Java → Some Useful Classes → The String class :

    • [W3.2d] C++ to Java → Some Useful Classes → Wrapper Classes for primitive types :

    • [W3.2e] C++ to Java → Some Useful Classes → The Arrays class :

    • [W3.2f] C++ to Java → Some Useful Classes → The Scanner class :

    • [W3.3] OOP + Java: Inheritance
    • [W3.3a] Paradigms → OOP → Inheritance → What :

    • [W3.3b] Paradigms → OOP → Inheritance → Overloading :

    • [W3.3c] Paradigms → OOP → Inheritance → Overriding :

    • [W3.3d] C++ to Java → Inheritance → Inheritance (Basics) :

    • [W3.3e] C++ to Java → Inheritance → The Object class :

    • [W3.4] OOP + Java: Polymorphism
    • [W3.4a] Paradigms → OOP → Polymorphism → What :

    • [W3.4b] Paradigms → OOP → Inheritance → Substitutability :

    • [W3.4c] Paradigms → OOP → Inheritance → Dynamic and static binding

    • [W3.4d] Paradigms → OOP → Polymorphism → How

    • [W3.4e] C++ to Java → Inheritance → Polymorphism :

    • [W3.5] Java: Constants
    • [W3.5a] C++ to Java → Miscellaneous Topics → Constants :
    • [W3.6] OOP + Java: Enumerations
    • [W3.6a] Paradigms → OOP → Classes → Enumerations :

    • [W3.6b] C++ to Java → Miscellaneous Topics → Enumerations :

    • [W3.7] Code Quality: Coding Standards
    • [W3.7a] Implementation → Code Quality → Introduction → What :

    • [W3.7b] Implementation → Code Quality → Style → Introduction :

    • [W3.8] RCS: Pull, Push
    • [W3.8a] Tools → Git and GitHub → pull, fetch: Downloading data from other repos :

    • [W3.8b] Tools → Git and GitHub → Creating PRs

    • [W3.9] Automated Testing of Text UIs
    • [W3.9a] Quality Assurance → Testing → Introduction → What :

    • [W3.9b] Quality Assurance → Testing → Regression Testing → What :

    • [W3.9c] Quality Assurance → Testing → Test Automation → What :

    • [W3.9d] Quality Assurance → Testing → Test Automation → Automated testing of CLI applications :


    [W3.1] OOP, Java: Class-Level Members

    W3.1a :

    Paradigms → OOP → Classes → Class-level members

    Video

    Can explain class-level members

    While all objects of a class have the same attributes, each object has its own copy of the attribute value.

    All Person objects have the name attribute but the value of that attribute varies between Person objects.

    However, some attributes are not suitable to be maintained by individual objects. Instead, they should be maintained centrally, shared by all objects of the class. They are like ‘global variables’ but attached to a specific class. Such variables whose value is shared by all instances of a class are called class-level attributes.

    The attribute totalPersons should be maintained centrally and shared by all Person objects rather than copied at each Person object.

    Similarly, when a normal method is being called, a message is being sent to the receiving object and the result may depend on the receiving object.

    Sending the getName() message to the Adam object results in the response "Adam" while sending the same message to the Beth object results in the response "Beth".

    However, there can be methods related to a specific class but not suitable for sending messages to a specific object of that class. Such methods that are called using the class instead of a specific instance are called class-level methods.

    The method getTotalPersons() is not suitable to send to a specific Person object because a specific object of the Person class should not have to know about the total number of Person objects.

    Class-level attributes and methods are collectively called class-level members (also called static members sometimes because some programming languages use the keyword static to identify class-level members). They are to be accessed using the class name rather than an instance of the class.

    Which of these are suitable as class-level variables?

    • a. system: multi-player Pac Man game, Class: Player, variable: totalScore
    • b. system: eLearning system, class: Course, variable: totalStudents
    • c. system: ToDo manager, class: Task, variable: totalPendingTasks
    • d. system: any, class: ArrayList, variable: total (i.e., total items in a given ArrayList object)

    (c)

    Explanation: totalPendingTasks should not be managed by individual Task objects and is therefore suitable to be maintained as a class-level variable. The other variables should be managed at instance level as their value varies from instance to instance. e.g., totalStudents for one Course object will differ from totalStudents of another.

    W3.1b :

    C++ to Java → Classes → Class-level members

    Video

    Can use class-level members

    The content below is an extract from -- Java Tutorial, with slight adaptations.

    When a number of objects are created from the same class blueprint, they each have their own distinct copies of instance variables. In the case of a Bicycle class, the instance variables are gear, and speed. Each Bicycle object has its own values for these variables, stored in different memory locations.

    Sometimes, you want to have variables that are common to all objects. This is accomplished with the static modifier. Fields that have the static modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.

    Suppose you want to create a number of Bicycle objects and assign each a serial number, beginning with 1 for the first object. This ID number is unique to each object and is therefore an instance variable. At the same time, you need a field to keep track of how many Bicycle objects have been created so that you know what ID to assign to the next one. Such a field is not related to any individual object, but to the class as a whole. For this you need a class variable, numberOfBicycles, as follows:

    public class Bicycle {

    private int gear;
    private int speed;

    // an instance variable for the object ID
    private int id;

    // a class variable for the number of Bicycle objects instantiated
    private static int numberOfBicycles = 0;
    ...
    }

    Class variables are referenced by the class name itself, as in Bicycle.numberOfBicycles This makes it clear that they are class variables.

    The Java programming language supports static methods as well as static variables. Static methods, which have the static modifier in their declarations, should be invoked with the class name, without the need for creating an instance of the class, as in ClassName.methodName(args)

    The static modifier, in combination with the final modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change. For example, the following variable declaration defines a constant named PI, whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter): static final double PI = 3.141592653589793;

    Here is an example with class-level variables and class-level methods:

    public class Bicycle {

    private int gear;
    private int speed;

    private int id;

    private static int numberOfBicycles = 0;


    public Bicycle(int startSpeed, int startGear) {
    gear = startGear;
    speed = startSpeed;

    numberOfBicycles++;
    id = numberOfBicycles;
    }

    public int getID() {
    return id;
    }

    public static int getNumberOfBicycles() {
    return numberOfBicycles;
    }

    public int getGear(){
    return gear;
    }

    public void setGear(int newValue) {
    gear = newValue;
    }

    public int getSpeed() {
    return speed;
    }

    // ...

    }

    Explanation of System.out.println(...):

    • out is a class-level public attribute of the System class.
    • println is an instance level method of the out object.

    Consider the Circle class below:

    public class Circle {
    private int x;
    private int y;
    private double radius;

    public Circle(){
    this(0, 0, 0);
    }

    public Circle(int x, int y, double radius){
    setX(x);
    setY(y);
    setRadius(radius);
    }

    public int getX() {
    return x;
    }

    public void setX(int x) {
    this.x = x;
    }

    public int getY() {
    return y;
    }

    public void setY(int y) {
    this.y = y;
    }

    public double getRadius() {
    return radius;
    }

    public void setRadius(double radius) {
    this.radius = Math.max(radius, 0);
    }

    public int getArea(){
    double area = Math.PI * Math.pow(radius, 2);
    return (int)area;
    }
    }

    Update it as follows so that code given below produces the given output.

    • Add a class-level getMaxRadius method that returns the maximum radius that has been used in all Circle objects created thus far.
    public class Main {
    public static void main(String[] args) {
    Circle c = new Circle();
    System.out.println("max radius used so far : " + Circle.getMaxRadius());
    c = new Circle(0, 0, 10);
    System.out.println("max radius used so far : " + Circle.getMaxRadius());
    c = new Circle(0, 0, -15);
    System.out.println("max radius used so far : " + Circle.getMaxRadius());
    c.setRadius(12);
    System.out.println("max radius used so far : " + Circle.getMaxRadius());
    }
    }

    max radius used so far : 0.0
    max radius used so far : 10.0
    max radius used so far : 10.0
    max radius used so far : 12.0

    You can use a static variable maxRadius to track the maximum value used for the radius attribute so far.

    Partial solution:

    public void setRadius(double radius) {
    this.radius = Math.max(radius, 0);
    if (maxRadius < this.radius){
    // ...
    }
    }

    [W3.2] Java: Useful Classes

    W3.2a :

    Implementation → Reuse → APIs → What

    Can explain APIs

    An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.

    A class has an API (e.g., API of the Java String class, API of the Python str class) which is a collection of public methods that you can invoke to make use of the class.

    The GitHub API is a collection of web request formats that the GitHub server accepts and their corresponding responses. You can write a program that interacts with GitHub through that API.

    When developing large systems, if you define the API of each component early, the development team can develop the components in parallel because the future behavior of the other components are now more predictable.

    Choose the correct statements.

    • a. A software component can have an API.
    • b. Any method of a class is part of its API.
    • c. Private methods of a class are not part of its API.
    • d. The API forms the contract between the component developer and the component user.
    • e. Sequence diagrams can be used to show how components interact with each other via APIs.

    (a) (c) (d) (e)

    Explanation: (b) is incorrect because private methods cannot be a part of the API.

    Defining component APIs early is useful for developing components in parallel.

    True

    Explanation: Yes, once you know the precise behavior expected of each component, you can start developing them in parallel.

    W3.2b :

    C++ to Java → Some Useful Classes → Java API

    Video

    Can use Java API documentation about classes

    Java comes with a rich collection of classes that you can use. They form what is known as the Java API (Application Programming Interface). Each class in the API comes with documentation in a standard format.

    W3.2c :

    C++ to Java → Some Useful Classes → The String class

    Video

    Can use the String class

    String is a built-in Java class that you can use without importing. Given below are some useful String methods:

    Any class in the java.lang package can be used without importing.

    Find characters of a string

    Strings provide a method named charAt, which extracts a character. It returns a char, a primitive type that stores an individual character (as opposed to strings of them).

    String fruit = "banana";
    char letter = fruit.charAt(0);

    The argument 0 means that we want the letter at position 0. Like array indexes, string indexes start at 0, so the character assigned to letter is 'b'.

    You can convert a string to an array of characters using the toCharArray method.

    char[] fruitChars = fruit.toCharArray()
    Change a string to upper/lower case

    Strings provide methods, toUpperCase and toLowerCase, that convert from uppercase to lowercase and back.

    After these statements run, upperName refers to the string "ALAN TURING" but name still refers to "Alan Turing".

    String name = "Alan Turing";
    String upperName = name.toUpperCase();
    System.out.println(name);
    System.out.println(upperName);

    Alan Turing
    ALAN TURING

    Note that a string method cannot change the string object on which the method is invoked, because strings are once created, cannot be modifiedimmutable. For example, when you invoke toUpperCase on a string "abc", you get a new string object "ABC" as the return value rather than the string "abc" being changed to "ABC". As a result, for such string methods that seemingly modify the string but actually return a new string instead e.g., toLowerCase, invoking the method has no effect if you don’t assign the return value to a variable.

    String s = "Ada";
    s.toUpperCase(); // no effect
    s = s.toUpperCase(); // the correct way
    Replacing parts of a string

    Another useful method is replace, which finds and replaces instances of one string within another.

    This example replaces "Computer Science" with "CS".

    String text = "Computer Science is fun!";
    text = text.replace("Computer Science", "CS");
    System.out.println(text);

    CS is fun!
    Accessing substrings

    The substring method returns a new string that copies letters from an existing string, starting at the given index.

    • "banana".substring(0) "banana"
    • "banana".substring(2) "nana"
    • "banana".substring(6) ""

    If it’s invoked with two arguments, they are treated as a start and end index:

    • "banana".substring(0, 3) "ban"
    • "banana".substring(2, 5) "nan"
    • "banana".substring(6, 6) ""
    Searching within strings

    The indexOf method searches for a single character (or a substring) in a string and returns the index of the first occurrence. The method returns -1 if there are no occurrences.

    • "banana".indexOf('a') 1
    • "banana".indexOf('a', 2) 3 searches for 'a', starting from position 2
    • "banana".indexOf('x') -1
    • "banana".indexOf("nan") 2 searches for the substring "nan"
    Comparing strings

    To compare two strings, it is tempting to use the == and != operators.

    String name1 = "Alan Turing";
    String name2 = "Alan Turing";
    System.out.println(name1 == name2);

    This code compiles and runs, and most of the time it shows true. But it is not correct. The problem is, i.e., as opposed to comparing primitive values e.g., 2 == 2when used for comparing objects, the == operator checks whether the two variables refer to the same object (by comparing the references). If you give it two different string objects that contain the same letters, it is supposed to yield true because they are two distinct objects even if they contain the same text. However, because Java strings are immutable, in some cases (but not always) Java reuses existing string objects instead of creating multiple objects, which can cause the above code to yield true. Therefore, it is not safe to use == to compare strings if your intention is to check if they contain the same text.

    The right way to compare strings is with the equals method.

    This example invokes equals on name1 and passes name2 as an argument. The equals method returns true if the strings contain the same characters; otherwise it returns false.

    if (name1.equals(name2)) {
    System.out.println("The names are the same.");
    }

    If the strings differ, you can use compareTo to see which comes first in alphabetical order. The return value from compareTo is the difference between the first characters in the strings that differ. If the strings are equal, their difference is zero. If the first string (the one on which the method is invoked) comes first in the alphabet, the difference is negative. Otherwise, the difference is positive.

    In this example, compareTo returns positive 8, because the second letter of "Alan" comes 8 letters after the second letter of "Ada".

    String name1 = "Alan";
    String name2 = "Ada";
    int diff = name1.compareTo(name2);
    if (diff == 0) {
    System.out.println("The names are the same.");
    } else if (diff < 0) {
    System.out.println("name1 comes before name2.");
    } else if (diff > 0) {
    System.out.println("name2 comes before name1.");
    }

    Both equals and compareTo are case-sensitive. The uppercase letters come before the lowercase letters, so "Ada" comes before "ada". To check if two strings are similar irrespective of the differences in case, you can use the equalsIgnoreCase method.

    String s1 = "Apple";
    String s2 = "apple";
    System.out.println(s1.equals(s2)); //false
    System.out.println(s1.equalsIgnoreCase(s2)); //true

    Some more comparison-related String methods:

    • contains: checks if one string is a sub-string of the other e.g., Snapple and app
    • startsWith: checks if one string has the other as a substring at the beginning e.g., Apple and App
    • endsWith: checks if one string has the other as a substring at the end e.g., Crab and ab
    Printing special characters (line breaks, tabs, ...)

    You can embed a special character e.g., line break, tab, backspace, etc. in a string using an escape sequence.

    Escape sequence meaning
    \n newline character
    \t tab character
    \b backspace character
    \f form feed character
    \r carriage return character
    \" " (double quote) character
    \' ' (single quote) character
    \\ \ (back slash) character
    \uDDDD character from the Unicode character set, by specifying the Unicode as four hex digits in the place of DDDD

    An example of using escape sequences to print some special characters.

    System.out.println("First line\nSecond \"line\"");

    First line
    Second "line"

    As the behavior of the \n e.g., behavior differs between Windows and OS-Xdepends on the platform, the recommended way to print a line break is using the System.lineSeparator() as it works the same in all platforms.

    Using System.lineSeparator() to print a line break.

    System.out.println("First line" + System.lineSeparator() + "Second line");

    First line
    Second line
    String formatting

    Sometimes programs need to create strings that are formatted in a certain way. String.format takes a format specifier followed by a sequence of values and returns a new string formatted as specified.

    The following method returns a time string in 12-hour format. The format specifier \%02d means “two digit integer padded with zeros”, so timeString(19, 5) returns the string "07:05 PM".

    public static String timeString(int hour, int minute) {
    String ampm;
    if (hour < 12) {
    ampm = "AM";
    if (hour == 0) {
    hour = 12; // midnight
    }
    } else {
    ampm = "PM";
    hour = hour - 12;
    }
    return String.format("%02d:%02d %s", hour, minute, ampm); // returns "07:05 PM"
    }

    Implement the printPrice method in the code below to produce the given output. Its behavior:

    • The parameter item is a string in the format name--$price i.e., a name and a price of an item separated using a -- e.g., banana--$3/50
    • It prints the NAME: price where the name is in upper case. The price does not have a $ sign and has . in place of the /
      e.g., banana--$3/50 BANANA: 3.50
    • The name part of the input can have trailing/leading spaces which should be omitted from the output.
      e.g., banana --$3/50 BANANA: 3.50

    Do a Web search to find how to remove leading/trailing spaces. Suggested search terms java string remove leading trailing spaces

    public class Main {

    public static void printPrice(String item) {
    // TODO: add your code here

    }

    public static void main(String[] args) {
    printPrice("sandwich --$4/50");
    printPrice(" soda --$10/00");
    printPrice(" fries --$0/50");
    }
    }

    SANDWICH: 4.50
    SODA: 10.00
    FRIES: 0.50

    Partial solution:

    public static void printPrice(String item) {
    int dividerPosition = item.indexOf("--");
    String itemName = item.substring(0, dividerPosition);
    //...
    System.out.println(itemName.trim().toUpperCase() + ...);

    }

    W3.2d :

    C++ to Java → Some Useful Classes → Wrapper Classes for primitive types

    Video

    Can use wrapper classes for primitive

    Primitive values (like int, double, and char) do not provide methods.

    For example, you can’t call equals on an int:

    int i = 5;
    System.out.println(i.equals(5)); // compiler error

    But for each primitive type, there is a corresponding class in the Java library, called a wrapper class, as given in the table below. They are in the java.lang package i.e., no need to import.

    Primitive type Wrapper class
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    char Character
    boolean Boolean

    Double d = new Double(2.5);
    int i = d.intValue();
    System.out.println(d);
    System.out.println(i);

    2.5
    2

    Each wrapper class defines constants MIN_VALUE and MAX_VALUE.

    Accessing max and min values for integers:

    System.out.println(Integer.MIN_VALUE + " : " + Integer.MAX_VALUE);

    -2147483648 : 2147483647

    Wrapper classes provide methods for In this context, parse means something like “read and translate”parsing strings to other types e.g., Integer.parseInt converts a string to (you guessed it) an integer. The other wrapper classes provide similar methods, like Double.parseDouble and Boolean.parseBoolean.

    Integer.parseInt("1234") 1234

    Wrapper classes also provide toString, which returns a string representation of a value.

    Integer.toString(1234) "1234"

    Implement the printTotalScore method in the code below to produce the given output. Its behavior:

    • values is an array of strings, each string representing an integer e.g., ["5", "-1"]
    • The method prints the total of the numbers represented by the strings in the array
      ["5", "-1"] 4
    public class Main {

    public static void printTotalScore(String[] values){
    // TODO: add your code here
    }

    public static void main(String[] args) {
    printTotalScore(new String[]{});
    printTotalScore(new String[]{"0", "124", "-15"});
    }
    }

    0
    109

    You can use the Integer.parseInt() method to convert a String to the equivalent int value.

    Partial solution:

    public static void printTotalScore(String[] values){
    int total = 0;
    for (String value: values){
    // convert the value to an int and add to the total
    }
    System.out.println(total);
    }

    W3.2e :

    C++ to Java → Some Useful Classes → The Arrays class

    Video

    Can use the Arrays class

    java.util.Arrays provides methods for working with arrays. One of them, toString, returns a string representation of an array. It also provides a copyOf that copies an array.

    Using Arrays.copyOf and Arrays.toString:

    int[] a = new int[]{1,2,3,4};

    int[] b = Arrays.copyOf(a, 3); // copy first three elements
    System.out.println(Arrays.toString(b));

    int[] c = Arrays.copyOf(a, a.length); // copy all elements
    System.out.println(Arrays.toString(c));

    [1, 2, 3]
    [1, 2, 3, 4]

    Implement the following two methods in the code below to produce the given output.

    • filterEmailsfilterEmails(String[] items): String[]
      • items is an array of strings each of which may be an email address or some other random string
      • Returns a String[] containing email addresses that were in items. Any string containing @ is considered as an email.
        ["aaa@bbb", "xyz"] ["aaa@bbb"]
    • printItems(String[] items)
      • Prints items in the standard array format. e.g., ["aaa", "bbb"] [aaa, bbb]
    import java.util.Arrays;

    public class Main {
    public static String[] filterEmails(String[] items){
    // TODO: add your code here
    }

    public static void printItems(String[] items){
    // TODO: add your code here
    }

    public static void main(String[] args) {
    printItems(filterEmails(new String[]{}));
    printItems(filterEmails(new String[]{"abc"}));
    printItems(filterEmails(new String[]{"adam@example.com", "aab", "john@example.com", "some@"}));
    printItems(filterEmails(new String[]{"xyz", "@bee.com", "aab"}));
    }
    }

    []
    []
    [adam@example.com, john@example.com, some@]
    [@bee.com]

    • filterEmails(String[] items): String[]
      1. create a new array (say emails) of the same size as items
      2. go through the elements in the items and add to emails if the element contains @ (you can use the contains method of the String class here)
      3. Use Arrays.copyOf method to return the filled part of emails.
    • printItems(String[] items)
      • You can use the Arrays.toString() method for this.
    public static String[] filterEmails(String[] items){
    String[] results = new String[items.length];
    int matchCount = 0;
    for(String item: items){
    if (item.contains("@")){
    //...
    }
    }
    return Arrays.copyOf(results, matchCount);
    }

    public static void printItems(String[] items){
    System.out.println(Arrays.toString(items));
    }

    W3.2f :

    C++ to Java → Some Useful Classes → The Scanner class

    Video

    Can use the Scanner class

    Scanner is a class that provides methods for inputting words, numbers, and other data. Scanner provides a method called nextLine that reads a line of input from the keyboard and returns a String. The following example reads two lines and repeats them back to the user:

    import java.util.Scanner;

    public class Echo {

    public static void main(String[] args) {
    String line;
    Scanner in = new Scanner(System.in);

    System.out.print("Type something: ");
    line = in.nextLine();
    System.out.println("You said: " + line);

    System.out.print("Type something else: ");
    line = in.nextLine();
    System.out.println("You also said: " + line);
    }
    }

    Scanner class normally reads inputs as strings but it can read in a specific type of input too.

    The code below uses the nextInt method of the Scanner class to read an input as an integer.


    Scanner in = new Scanner(System.in);

    System.out.print("What is your age? ");
    int age = in.nextInt();
    in.nextLine(); // read the newline character the user enters following the integer
    System.out.print("What is your name? ");
    String name = in.nextLine();
    System.out.printf("Hello %s, age %d\n", name, age);

    Note the use of printf method for formatting the output.

    Write a program to ask the user for a description of overseas expenses (presumably, the user has just returned from an overseas trip) and calculate the total in local currency.

    • The conversion rate from overseas currency to local currency : overseas $1.0 = local $1.70
    • The user can describe expenses is in free form text, as one line. The program takes all amounts mentioned in the format $amount e.g., $1.50

    Here is one example output:

    Your expenses while overseas?beer $4.50 books $3.00 $5.00 for food, that's all
    Expenses in overseas currency:[$4.50, $3.00, $5.00]
    Total in local currency: $21.25

    Here is another:

    Your expenses while overseas?nothing. I lived off my friends all the time.
    Expenses in overseas currency:[]
    Total in local currency: $0.00

    One more:

    Your expenses while overseas? Just $10
    Expenses in overseas currency:[$10]
    Total in local currency: $17.00

    Here's the skeleton code to use as the starting point:

    public class Main {

    // You can add more methods here

    public static void main(String[] args) {
    String line;
    Scanner in = new Scanner(System.in);

    System.out.print("Your expenses while overseas?");
    // TODO: add your code here
    }
    }

    You can use the split method of the String class to convert a sentence into an array of words. e.g.,

    String sentence = "hello my dear";
    String[] words = sentence.split(" "); // split using the space as the delimiter
    System.out.println(Arrays.toString(words));

    [hello, my, dear]

    You can use String.format("%.2f", doubleValue) to format doubleValue to two decimal points.
    e.g., String.format("%.2f", 1.3334) 1.33

    import java.util.Arrays;
    import java.util.Scanner;

    public class Main {

    public static String[] filterAmounts(String sentence) {
    String[] words = sentence.split(" ");
    String[] result = new String[words.length];
    int wordCount = 0;
    for (String word : words) {
    if (word.startsWith("$")) {
    result[wordCount] = word;
    wordCount++;
    }
    }
    return Arrays.copyOf(result, wordCount);
    }

    public static void main(String[] args) {
    String line;
    Scanner in = new Scanner(System.in);

    System.out.print("Your expenses while overseas?");
    line = in.nextLine();

    String[] amounts = filterAmounts(line);
    System.out.println("Expenses in overseas currency:" + Arrays.toString(amounts));
    double total = 0;
    for (String amount : amounts) {
    // convert amount to double, multiply by currency conversion rate, and add to total
    }
    System.out.println("Total in local currency: $" + String.format("%.2f", total));

    }
    }

    [W3.3] OOP + Java: Inheritance

    W3.3a :

    Paradigms → OOP → Inheritance → What

    Can explain the meaning of inheritance

    The OOP concept Inheritance allows you to define a new class based on an existing class.

    For example, you can use inheritance to define an EvaluationReport class based on an existing Report class so that the EvaluationReport class does not have to duplicate data/behaviors that are already implemented in the Report class. The EvaluationReport can inherit the wordCount attribute and the print() method from the base class Report.

    • Other names for Base class: Parent class, Superclass
    • Other names for Derived class: Child class, Subclass, Extended class

    A superclass is said to be more general than the subclass. Conversely, a subclass is said to be more specialized than the superclass.

    Applying inheritance on a group of similar classes can result in the common parts among classes being extracted into more general classes.

    Man and Woman behave the same way for certain things. However, the two classes cannot be simply replaced with a more general class Person because of the need to distinguish between Man and Woman for certain other things. A solution is to add the Person class as a superclass (to contain the code common to men and women) and let Man and Woman inherit from Person class.

    Inheritance implies the derived class can be considered as a sub-type of the base class (and the base class is a super-type of the derived class), resulting in an is a relationship.

    Inheritance does not necessarily mean a sub-type relationship exists. However, the two often go hand-in-hand. For simplicity, at this point let us assume inheritance implies a sub-type relationship.

    To continue the previous example,

    • Woman is a Person
    • Man is a Person

    Inheritance relationships through a chain of classes can result in inheritance hierarchies (aka inheritance trees).

    Two inheritance hierarchies/trees are given below. Note that the triangle points to the parent class. Observe how the Parrot is a Bird as well as it is an Animal.

    Multiple Inheritance is when a class inherits directly from multiple classes. Multiple inheritance among classes is allowed in some languages (e.g., Python, C++) but not in other languages (e.g., Java, C#).

    The Honey class inherits from the Food class and the Medicine class because honey can be consumed as a food as well as a medicine (in some oriental medicine practices). Similarly, a Car is a Vehicle, an Asset and a Liability.

    Which of these are correct?

    • a. Superclass is more general than the subclass.
    • b. Child class is more specialized than the parent class.
    • c. A class can inherit behavior from its ancestor classes (ancestor classes = classes above it in the inheritance hierarchy).
    • d. Code reuse can be one benefit of inheritance.
    • e. A change to the superclass will not affect its subclasses.

    (a) (b) (c) (d)

    Explanation: (e) is incorrect. Because subclasses inherit behavior from the superclass, any changes to the superclass could affect subclasses.

    W3.3b :

    Paradigms → OOP → Inheritance → Overloading

    Can explain method overloading

    Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.

    Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.

    Example:

    Method Type Signature
    int add(int X, int Y) (int, int)
    void add(int A, int B) (int, int)
    void m(int X, double Y) (int, double)
    void m(double X, int Y) (double, int)

    In the case below, the calculate method is overloaded because the two methods have the same name but different type signatures (String) and (int).

    • calculate(String): void
    • calculate(int): void

    W3.3c :

    Paradigms → OOP → Inheritance → Overriding

    Can explain method overriding

    Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.

    Consider the following case of EvaluationReport class inheriting the Report class:

    Report methods EvaluationReport methods Overrides?
    print() print() Yes
    write(String) write(String) Yes
    read():String read(int):String No. Reason: the two methods have different signatures; This is a case of overloading (rather than overriding).

    Paradigms → OOP → Inheritance →

    Overloading

    Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.

    Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.

    Example:

    Method Type Signature
    int add(int X, int Y) (int, int)
    void add(int A, int B) (int, int)
    void m(int X, double Y) (int, double)
    void m(double X, int Y) (double, int)

    In the case below, the calculate method is overloaded because the two methods have the same name but different type signatures (String) and (int).

    • calculate(String): void
    • calculate(int): void

    Which of these methods override another method? A is the parent class. B inherits A.

    • a
    • b
    • c
    • d
    • e

    d

    Explanation: Method overriding requires a method in a child class to use the same method name and same parameter sequence used by one of its ancestors.

    W3.3d :

    C++ to Java → Inheritance → Inheritance (Basics)

    Can use basic inheritance

    Given below is an extract from the -- Java Tutorial, with slight adaptations.

    A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).

    A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.

    Every class has one and only one direct superclass (single inheritance), except the Object class, which has no superclass, . In the absence of any other explicit superclass, every class is implicitly a subclass of Object. Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object. Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object. Java does not support multiple inheritance among classes.

    The java.lang.Object class defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object, other classes derive from some of those classes, and so on, forming a single hierarchy of classes.

    The keyword extends indicates one class inheriting from another.

    Here is the sample code for a possible implementation of a Bicycle class and a MountainBike class that is a subclass of the Bicycle:

    public class Bicycle {

    public int gear;
    public int speed;

    public Bicycle(int startSpeed, int startGear) {
    gear = startGear;
    speed = startSpeed;
    }

    public void setGear(int newValue) {
    gear = newValue;
    }

    public void applyBrake(int decrement) {
    speed -= decrement;
    }

    public void speedUp(int increment) {
    speed += increment;
    }

    }
    public class MountainBike extends Bicycle {

    // the MountainBike subclass adds one field
    public int seatHeight;

    // the MountainBike subclass has one constructor
    public MountainBike(int startHeight, int startSpeed, int startGear) {
    super(startSpeed, startGear);
    seatHeight = startHeight;
    }

    // the MountainBike subclass adds one method
    public void setHeight(int newValue) {
    seatHeight = newValue;
    }
    }

    A subclass inherits all the fields and methods of the superclass. In the example above, MountainBike inherits all the fields and methods of Bicycle and adds the field seatHeight and a method to set it.

    Accessing superclass members

    If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super. You can also use super to refer to a when both the superclass and the subclass use the same variable name, the superclass variables is said to be hidden/shadowed by the subclass variablehidden field (although hiding fields is discouraged).

    Consider this class, Superclass and a subclass, called Subclass, that overrides printMethod():

    public class Superclass {

    public void printMethod() {
    System.out.println("Printed in Superclass.");
    }
    }
    public class Subclass extends Superclass {

    // overrides printMethod in Superclass
    public void printMethod() {
    super.printMethod();
    System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
    Subclass s = new Subclass();
    s.printMethod();
    }
    }

    Printed in Superclass.
    Printed in Subclass

    Within Subclass, the simple name printMethod() refers to the one declared in Subclass, which overrides the one in Superclass. So, to refer to printMethod() inherited from Superclass, Subclass must use a qualified name, using super as shown.

    Subclass constructors

    A subclass constructor can invoke the superclass constructor. Invocation of a superclass constructor must be the first line in the subclass constructor. The syntax for calling a superclass constructor is super() (which invokes the no-argument constructor of the superclass) or super(parameters) (to invoke the superclass constructor with a matching parameter list).

    The following example illustrates how to use the super keyword to invoke a superclass's constructor. Recall from the Bicycle example that MountainBike is a subclass of Bicycle. Here is the MountainBike (subclass) constructor that calls the superclass constructor and then adds some initialization code of its own (i.e., seatHeight = startHeight;):

    public MountainBike(int startHeight, int startSpeed, int startGear) {
    super(startSpeed, startGear);
    seatHeight = startHeight;
    }

    Note: If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the superclass does not have a no-argument constructor, you will get a compile-time error. Object does have such a constructor, so if Object is the only superclass, there is no problem.

    Access modifiers (simplified)

    Access level modifiers determine whether other classes can use a particular field or invoke a particular method. Given below is a simplified version of Java access modifiers, assuming you have not yet started placing your classes in different packages i.e., all classes are placed in the root level. A full explanation of access modifiers is given in a later topic.

    There are two levels of access control:

    1. At the class level:

      • public: the class is visible to all other classes
      • no modifier: same as public

    2. At the member level:

      • public : the member is visible to all other classes
      • no modifier: same as public
      • protected: the member is visible to sub classes only
      • private: the member is not visible to other classes (but can be accessed in its own class)

    Background: Suppose we are creating a software to manage various tasks a person has to do. Two types of such tasks are,

    • Todos: i.e., things that needs to be done some day e.g., 'Read the book Lord of the Rings'
    • Deadlines: i.e., things to be done by a specific date/time e.g., 'Read the text book by Nov 25th'

    The Task class is given below:

    public class Task {
    protected String description;

    public Task(String description) {
    this.description = description;
    }

    public String getDescription() {
    return description;
    }
    }
    1. Write a Todo class that inherits from the Task class.
      • It should have an additional boolean field isDone to indicate whether the todo is done or not done.
      • It should have a isDone() method to access the isDone field and a setDone(boolean) method to set the isDone field.
    2. Write a Deadline class that inherits from the Todo class that you implemented in the previous step. It should have,
      • an additional String field by to store the details of when the task to be done e.g., Jan 25th 5pm
      • a getBy() method to access the value of the by field, and a corresponding setBy(String) method.
      • a constructor of the form Deadline(String description, String by)

    The expected behavior of the two classes is as follows:

    public class Main {
    public static void main(String[] args) {
    // create a todo task and print details
    Todo t = new Todo("Read a good book");
    System.out.println(t.getDescription());
    System.out.println(t.isDone());

    // change todo fields and print again
    t.setDone(true);
    System.out.println(t.isDone());

    // create a deadline task and print details
    Deadline d = new Deadline("Read textbook", "Nov 16");
    System.out.println(d.getDescription());
    System.out.println(d.isDone());
    System.out.println(d.getBy());

    // change deadline details and print again
    d.setDone(true);
    d.setBy("Postponed to Nov 18th");
    System.out.println(d.isDone());
    System.out.println(d.getBy());
    }
    }

    Read a good book
    false
    true
    Read textbook
    false
    Nov 16
    true
    Postponed to Nov 18th

    Todo class is given below. You can follow a similar approach for the Deadline class.

    public class Todo extends Task {
    protected boolean isDone;

    public Todo(String description) {
    super(description);
    isDone = false;
    }
    }

    W3.3e :

    C++ to Java → Inheritance → The Object class

    Can use Object class

    As you know, all Java objects inherit from the Object class. Let us look at some of the useful methods in the Object class that can be used by other classes.

    The toString method

    Every class inherits a toString method from the Object class that is used by Java to get a string representation of the object e.g., for printing. By default, it simply returns the type of the object and its address (in hexadecimal).

    Suppose you defined a class called Time, to represent a moment in time. If you create a Time object and display it with println:

    class Time {
    int hours;
    int minutes;
    int seconds;

    Time(int hours, int minutes, int seconds) {
    this.hours = hours;
    this.minutes = minutes;
    this.seconds = seconds;
    }
    }
     Time t = new Time(5, 20, 13);
    System.out.println(t);

    Time@80cc7c0 (the address part can vary)

    You can override the toString method in your classes to provide a more meaningful string representation of the objects of that class.

    Here's an example of overriding the toString method of the Time class:

    class Time{

    //...

    @Override
    public String toString() {
    return String.format("%02d:%02d:%02d\n", this.hours, this.minutes, this.seconds);
    }
    }
     Time t = new Time(5, 20, 13);
    System.out.println(t);

    05:20:13

    @Override is an optional annotation you can use to indicate that the method is overriding a method from the parent class.

    The equals method

    There are two ways to check whether values are equal: the == operator and the equals method. With objects you can use either one, but they are not the same.

    • The == operator checks whether objects are identical; that is, whether they are the same object.
    • The equals method checks whether they are equivalent; that is, whether they have the same value.

    The definition of identity is always the same, so the == operator always does the same thing.

    Consider the following variables:

    Time time1 = new Time(9, 30, 0);
    Time time2 = time1;
    Time time3 = new Time(9, 30, 0);
    • The assignment operator copies references, so time1 and time2 refer to the same object. Because they are identical, time1 == time2 is true.
    • But time1 and time3 refer to different objects. Because they are not identical, time1 == time3 is false.

    By default, the equals method inherited from the Object class does the same thing as ==. As the definition of equivalence is different for different classes, you can override the equals method to define your own criteria for equivalence of objects of your class.

    Here's how you can override the equals method of the Time class to provide an equals method that considers two Time objects equivalent as long as they represent the same time of the day:

    public class Time {
    int hours;
    int minutes;
    int seconds;

    // ...

    @Override
    public boolean equals(Object o) {
    Time other = (Time) o;
    return this.hours == other.hours
    && this.minutes == other.minutes
    && this.seconds == other.seconds;
    }
    }
    Time t1 = new Time(5, 20, 13);
    Time t2 = new Time(5, 20, 13);
    System.out.println(t1 == t2);
    System.out.println(t1.equals(t2));

    false
    true

    Note that a proper equals method implementation is more complex than the example above. See the article How to Implement Java’s equals Method Correctly by Nicolai Parlog for a detailed explanation before you implement your own equals method.

    Suppose you have the following classes Task, Todo, Deadline:

    public class Task {
    protected String description;

    public Task(String description) {
    this.description = description;
    }

    public String getDescription() {
    return description;
    }
    }
    public class Todo extends Task {
    protected boolean isDone;

    public Todo(String description) {
    super(description);
    isDone = false;
    }

    public void setDone(boolean done) {
    isDone = done;
    }

    public boolean isDone() {
    return isDone;
    }
    }
    public class Deadline extends Todo {

    protected String by;

    public Deadline(String description, String by) {
    super(description);
    this.by = by;
    }

    public void setBy(String by) {
    this.by = by;
    }

    public String getBy() {
    return by;
    }
    }

    Override the toString method of the three classes to produce the following behavior.

    public class Main {
    public static void main(String[] args) {
    // create a todo task and print it
    Todo t = new Todo("Read a good book");
    System.out.println(t);

    // change todo fields and print again
    t.setDone(true);
    System.out.println(t);

    // create a deadline task and print it
    Deadline d = new Deadline("Read textbook", "Nov 16");
    System.out.println(d);

    // change deadline details and print again
    d.setDone(true);
    d.setBy("Postponed to Nov 18th");
    System.out.println(d);
    }
    }

    description: Read a good book
    is done? No
    description: Read a good book
    is done? Yes
    description: Read textbook
    is done? No
    do by: Nov 16
    description: Read textbook
    is done? Yes
    do by: Postponed to Nov 18th

    You can use the super.toString from the subclass to invoke the behavior of the method you are overriding. This is useful here because the overriding method is simply adding onto the behavior of the overridden method.

    toString() method of Task class and Todo class are given below. You can follow a similar approach for the Deadline class.

    public class Task {
    // ...

    @Override
    public String toString() {
    return "description: " + description;
    }
    }
    public class Todo extends Task {
    // ...

    @Override
    public String toString() {
    String status = null;
    if (isDone){
    status = "Yes";
    } else {
    status = "No";
    }
    return super.toString() + System.lineSeparator() + "is done? " + status;
    }
    }

    [W3.4] OOP + Java: Polymorphism

    W3.4a :

    Paradigms → OOP → Polymorphism → What

    Can explain OOP polymorphism

    Polymorphism:

    The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple

    Polymorphism allows you to write code targeting superclass objects, use that code on subclass objects, and achieve possibly different results based on the actual class of the object.

    Assume classes Cat and Dog are both subclasses of the Animal class. You can write code targeting Animal objects and use that code on Cat and Dog objects, achieving possibly different results based on whether it is a Cat object or a Dog object. Some examples:

    • Declare an array of type Animal and still be able to store Dog and Cat objects in it.
    • Define a method that takes an Animal object as a parameter and yet be able to pass Dog and Cat objects to it.
    • Call a method on a Dog or a Cat object as if it is an Animal object (i.e., without knowing whether it is a Dog object or a Cat object) and get a different response from it based on its actual class e.g., call the Animal class's method speak() on object a and get a "Meow" as the return value if a is a Cat object and "Woof" if it is a Dog object.

    Polymorphism literally means "ability to take many forms".

    W3.4b :

    Paradigms → OOP → Inheritance → Substitutability

    Can explain substitutability

    Every instance of a subclass is an instance of the superclass, but not vice-versa. As a result, inheritance allows substitutability: the ability to substitute a child class object where a parent class object is expected.

    An AcademicStaff is an instance of a Staff, but a Staff is not necessarily an instance of an AcademicStaff. i.e. wherever an object of the superclass is expected, it can be substituted by an object of any of its subclasses.

    The following code is valid because an AcademicStaff object is substitutable as a Staff object.

    Staff staff = new AcademicStaff(); // OK

    But the following code is not valid because staff is declared as a Staff type and therefore its value may or may not be of type AcademicStaff, which is the type expected by variable academicStaff.

    Staff staff;
    ...
    AcademicStaff academicStaff = staff; // Not OK

    W3.4c

    Paradigms → OOP → Inheritance → Dynamic and static binding

    Can explain dynamic and static binding

    Dynamic binding (There are subtle differences between late binding and dynamic binding, but they are beyond the scope of this documentaka late binding): a mechanism where method calls in code are deciding which method implementation to executeresolved at at the moment the method call is executedruntime, rather than at compile time.

    Overridden methods are resolved using dynamic binding, and therefore resolves to the implementation in the actual type of the object.

    Paradigms → OOP → Inheritance →

    Overriding

    Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.

    Consider the following case of EvaluationReport class inheriting the Report class:

    Report methods EvaluationReport methods Overrides?
    print() print() Yes
    write(String) write(String) Yes
    read():String read(int):String No. Reason: the two methods have different signatures; This is a case of overloading (rather than overriding).

    Paradigms → OOP → Inheritance →

    Overloading

    Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.

    Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.

    Example:

    Method Type Signature
    int add(int X, int Y) (int, int)
    void add(int A, int B) (int, int)
    void m(int X, double Y) (int, double)
    void m(double X, int Y) (double, int)

    In the case below, the calculate method is overloaded because the two methods have the same name but different type signatures (String) and (int).

    • calculate(String): void
    • calculate(int): void

    Which of these methods override another method? A is the parent class. B inherits A.

    • a
    • b
    • c
    • d
    • e

    d

    Explanation: Method overriding requires a method in a child class to use the same method name and same parameter sequence used by one of its ancestors.

    Consider the code below. The declared type of s is Staff and it appears as if the adjustSalary(int) operation of the Staff class is invoked.

    void adjustSalary(int byPercent) {
    for (Staff s: staff) {
    s.adjustSalary(byPercent);
    }
    }

    However, at runtime s can receive an object of any subclass of Staff. That means the adjustSalary(int) operation of the actual subclass object will be called. If the subclass does not override that operation, the operation defined in the superclass (in this case, Staff class) will be called.

    Static binding (aka early binding): When a method call is resolved at compile time.

    In contrast, overloaded methods are resolved using static binding.

    Paradigms → OOP → Inheritance →

    Overloading

    Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.

    Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.

    Example:

    Method Type Signature
    int add(int X, int Y) (int, int)
    void add(int A, int B) (int, int)
    void m(int X, double Y) (int, double)
    void m(double X, int Y) (double, int)

    In the case below, the calculate method is overloaded because the two methods have the same name but different type signatures (String) and (int).

    • calculate(String): void
    • calculate(int): void

    Note how the constructor is overloaded in the class below. The method call new Account() is bound to the first constructor at compile time.

    class Account {

    Account() {
    // Signature: ()
    ...
    }

    Account(String name, String number, double balance) {
    // Signature: (String, String, double)
    ...
    }
    }

    Similarly, the calculateGrade method is overloaded in the code below and a method call calculateGrade("A1213232") is bound to the second implementation, at compile time.

    void calculateGrade(int[] averages) { ... }
    void calculateGrade(String matric) { ... }

    W3.4d

    Paradigms → OOP → Polymorphism → How

    Can explain how substitutability operation overriding, and dynamic binding relates to polymorphism

    Three concepts combine to achieve polymorphism: substitutability, operation overriding, and dynamic binding.

    • Substitutability: Because of substitutability, you can write code that expects objects of a parent class and yet use that code with objects of child classes. That is how polymorphism is able to treat objects of different types as one type.
    • Overriding: To get polymorphic behavior from an operation, the operation in the superclass needs to be overridden in each of the subclasses. That is how overriding allows objects of different subclasses to display different behaviors in response to the same method call.
    • Dynamic binding: Calls to overridden methods are bound to the implementation of the actual object's class dynamically during the runtime. That is how the polymorphic code can call the method of the parent class and yet execute the implementation of the child class.

    Which one of these is least related to how OO programs achieve polymorphism?

    (c)

    Explanation: Operation overriding is the one that is related, not operation overloading.

    W3.4e :

    C++ to Java → Inheritance → Polymorphism

    Can use polymorphism in Java

    Java is a strongly-typed language which means the code works with only the object types that it targets.

    The following code PetShelter keeps a list of Cat objects and make them speak. The code will not work with any other type, for example, Dog objects.

    public class PetShelter {
    private static Cat[] cats = new Cat[]{
    new Cat("Mittens"),
    new Cat("Snowball")};

    public static void main(String[] args) {
    for (Cat c: cats){
    System.out.println(c.speak());
    }
    }
    }

    Mittens: Meow
    Snowball: Meow
    public class Cat {
    public Cat(String name) {
    super(name);
    }

    public String speak() {
    return name + ": Meow";
    }
    }

    This strong-typing can lead to unnecessary verbosity caused by repetitive similar code that do similar things with different object types.

    If the PetShelter is to keep both cats and dogs, you'll need two arrays and two loops:

    public class PetShelter {
    private static Cat[] cats = new Cat[]{
    new Cat("Mittens"),
    new Cat("Snowball")};
    private static Dog[] dogs = new Dog[]{
    new Dog("Spot")};

    public static void main(String[] args) {
    for (Cat c: cats){
    System.out.println(c.speak());
    }
    for(Dog d: dogs){
    System.out.println(d.speak());
    }
    }
    }

    Mittens: Meow
    Snowball: Meow
    Spot: Woof
    public class Dog {
    public Dog(String name) {
    super(name);
    }

    public String speak() {
    return name + ": Woof";
    }
    }

    A better way is to take advantage of polymorphism to write code that targets a superclass so that it works with any subclass objects.

    The PetShelter2 uses one data structure to keep both types of animals and one loop to make them speak. The code targets the Animal superclass (assuming Cat and Dog inherits from the Animal class) instead of repeating the code for each animal type.

    public class PetShelter2 {
    private static Animal[] animals = new Animal[]{
    new Cat("Mittens"),
    new Cat("Snowball"),
    new Dog("Spot")};

    public static void main(String[] args) {
    for (Animal a: animals){
    System.out.println(a.speak());
    }
    }
    }

    Mittens: Meow
    Snowball: Meow
    Spot: Woof
    public class Animal {

    protected String name;

    public Animal(String name){
    this.name = name;
    }
    public String speak(){
    return name;
    }
    }
    public class Cat extends Animal {
    public Cat(String name) {
    super(name);
    }

    @Override
    public String speak() {
    return name + ": Meow";
    }
    }
    public class Dog extends Animal {
    public Dog(String name) {
    super(name);
    }

    @Override
    public String speak() {
    return name + ": Woof";
    }
    }

    Explanation: Because Java supports polymorphism, you can store both Cat and Dog objects in an array of Animal objects. Similarly, you can call the speak method on any Animal object (as done in the loop) and yet get different behavior from Cat objects and Dog objects.

    Suggestion: try to add an Animal object (e.g., new Animal("Unnamed")) to the animals array and see what happens.

    Polymorphic code is better in several ways:

    • It is shorter.
    • It is simpler.
    • It is more flexible (in the above example, the main method will work even if we add more animal types).

    The Main class below keeps a list of Circle and Rectangle objects and prints the area (as an int value) of each shape when requested.

    Add the missing variables/methods to the code below so that it produces the output given.

    public class Main {
    //TODO add your methods here

    public static void main(String[] args) {
    addShape(new Circle(5));
    addShape(new Rectangle(3, 4));
    addShape(new Circle(10));
    printAreas();
    addShape(new Rectangle(4, 4));
    printAreas();
    }
    }

    78
    12
    314
    78
    12
    314
    16

    Circle class and Rectangle class is given below but you'll need to add a parent class Shape:

    public class Circle {

    private int radius;

    public Circle(int radius) {
    this.radius = radius;
    }

    public int area() {
    return (int)(Math.PI * radius * radius);
    }
    }
    public class Rectangle {
    private int height;
    private int width;

    public Rectangle(int height, int width){
    this.height = height;
    this.width = width;
    }

    public int area() {
    return height * width;
    }
    }

    You may use an array of size 100 to store the shapes.

    public class Main {
    private static Shape[] shapes = new Shape[100];
    private static int shapeCount = 0;

    public static void addShape(Shape s){
    shapes[shapeCount] = s;
    shapeCount++;
    }

    // ...

    }

    [W3.5] Java: Constants

    W3.5a :

    C++ to Java → Miscellaneous Topics → Constants

    Can use Java constants

    Java does not directly support constants. The convention is to use a static final variable where a constant is needed. The static modifier causes the variable to be available without instantiating an object. The final modifier causes the variable to be unchangeable. Java constants are normally declared in ALL CAPS separated by underscores.

    Here is an example of a constant named MAX_BALANCE which can be accessed as Account.MAX_BALANCE.

    public class Account{

    public static final double MAX_BALANCE = 1000000.0;

    }

    Math.PI is an example constant that comes with Java.

    [W3.6] OOP + Java: Enumerations

    W3.6a :

    Paradigms → OOP → Classes → Enumerations

    Can explain the meaning of enumerations

    An Enumeration is a fixed set of values that can be considered as a data type. An enumeration is often useful when using a regular data type such as int or String would allow invalid values to be assigned to a variable.

    Suppose you want a variable called priority to store the priority of something. There are only three priority levels: high, medium, and low. You can declare the variable priority as of type int and use only values 2, 1, and 0 to indicate the three priority levels. However, this opens the possibility of an invalid value such as 9 being assigned to it. But if you define an enumeration type called Priority that has three values HIGH, MEDIUM and LOW only, a variable of type Priority will never be assigned an invalid value because the compiler is able to catch such an error.

    Priority: HIGH, MEDIUM, LOW

    W3.6b :

    C++ to Java → Miscellaneous Topics → Enumerations

    Can use Java enumerations

    You can define an enum type by using the enum keyword. Because they are constants, the names of an enum type's fields are in uppercase letters e.g., FLAG_SUCCESS by convention.

    Defining an enumeration to represent days of a week (code to be put in the Day.java file):

    public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY
    }

    Some examples of using the Day enumeration defined above:

    Day today = Day.MONDAY;
    Day[] holidays = new Day[]{Day.SATURDAY, Day.SUNDAY};

    switch (today) {
    case SATURDAY:
    case SUNDAY:
    System.out.println("It's the weekend");
    break;
    default:
    System.out.println("It's a week day");
    }

    Note that while enumerations are usually a simple set of fixed values, Java enumerations can have behaviors too, as explained in this tutorial from -- Java Tutorial

    Define an enumeration named Priority. Add the missing describe method to the code below so that it produces the output given.

    public class Main {

    // Add your method here

    public static void main(String[] args) {
    describe("Red", Priority.HIGH);
    describe("Orange", Priority.MEDIUM);
    describe("Blue", Priority.MEDIUM);
    describe("Green", Priority.LOW);
    }
    }

    Red indicates high priority
    Orange indicates medium priority
    Blue indicates medium priority
    Green indicates low priority

    Use a switch statement to select between possible values for Priority.

        public static void describe(String color, Priority p) {
    switch (p) {
    case LOW:
    System.out.println(color + " indicates low priority");
    break;
    // ...
    }
    }

    Code for the enumeration is given below:

    public enum Priority {
    HIGH, MEDIUM, LOW
    }

    [W3.7] Code Quality: Coding Standards

    W3.7a :

    Implementation → Code Quality → Introduction → What

    Video

    Can explain the importance of code quality

    Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live. -- Martin Golding

    Production code needs to be of high quality. Given how the world is becoming increasingly dependent on software, poor quality code is something no one can afford to tolerate.

    Production Code
    Code being used in an actual product with actual users

    W3.7b :

    Implementation → Code Quality → Style → Introduction

    Video

    Can explain the need for following a standard

    One essential way to improve code quality is to follow a consistent style. That is why software engineers follow a strict coding standard (aka style guide).

    The aim of a coding standard is to make the entire code base look like it was written by one person. A coding standard is usually specific to a programming language and specifies guidelines such as the locations of opening and closing braces, indentation styles and naming styles (e.g. whether to use Hungarian style, Pascal casing, Camel casing, etc.). It is important that the whole team/company uses the same coding standard and that the standard is generally not inconsistent with typical industry practices. If a company's coding standard is very different from what is typically used in the industry, new recruits will take longer to get used to the company's coding style.

    IDEs can help to enforce some parts of a coding standard e.g. indentation rules.

    What is the recommended approach regarding coding standards?

    c

    What is the aim of using a coding standard? How does it help?

    [W3.8] RCS: Pull, Push

    W3.8a :

    Tools → Git and GitHub → pull, fetch: Downloading data from other repos

    Video

    Can pull changes from a repo

    Here's a scenario you can try in order to learn how to pull commits from another repo to yours.

    You can pull from one repo to another, to receive new commits in the second repo, if the repos have a shared history. Let's say some new commits were added to the upstream repo is a term used to refer to the repo you cloned fromupstream repo after you cloned it and you would like to copy over those new commits to your own clone i.e., sync your clone with the upstream repo. In that case, you pull from the upstream repo to your clone.

    1. Clone a repo (e.g., the repo used in [Git & GitHub → Clone]) to be used for this activity.

    2. Delete the last few commits to simulate cloning the repo a few commits ago.

    clone: Copying a repo

    Can clone a remote repo

    Given below is an example scenario you can try yourself to learn Git cloning.

    You can clone a repo to create a copy of that repo in another location on your computer. The copy will even have the revision history of the original repo i.e., identical to the original repo. For example, you can clone a remote repo onto your computer to create a local copy of the remote repo.

    Suppose you want to clone the sample repo samplerepo-things to your computer.

    Note that the URL of the GitHub project is different from the URL you need to clone a repo in that GitHub project. e.g.

    GitHub project URL: https://github.com/se-edu/samplerepo-things
    Git repo URL: https://github.com/se-edu/samplerepo-things.git (note the .git at the end)

    FileClone / New… and provide the URL of the repo and the destination directory.

    You can use the clone command to clone a repo.

    Follow the instructions given here.

    Right-click the target commit (i.e. the commit that is 2 commits behind the tip) and choose Reset current branch to this commit.

    Choose the Hard - … option and click OK.

    This is what you will see.

    Note the following (cross refer the screenshot above):

    Arrow marked as a: The local repo is now at this commit, marked by the master label.
    Arrow marked as b: The origin/master label shows what is the latest commit in the master branch in the remote repo.

    Use the reset command to delete commits at the tip of the revision history.

    git reset --hard HEAD~2

    Now, your local repo state is exactly how it would be if you had cloned the repo 2 commits ago, as if somebody has added two more commits to the remote repo since you cloned it.

    3. Pull from the other repo: To get those missing commits to your local repo (i.e. to sync your local repo with upstream repo) you can do a pull.

    Click the Pull button in the main menu, choose origin and master in the next dialog, and click OK.

    Now you should see something like this where master and origin/master are both pointing the same commit.

    git pull origin

    You can also do a fetch instead of a pull in which case the new commits will be downloaded to your repo but the working directory will remain at the current commit. To move the current state to the latest commit that was downloaded, you need to do a merge. A pull is a shortcut that does both those steps in one go.

    Working with multiple remotes

    When you clone a repo, Git automatically adds a remote repo named origin to your repo configuration. As you know, you can pull commits from that repo. As you know, a Git repo can work with remote repos other than the one it was cloned from.

    A repo can work with any number of other repositories as long as they have a shared history e.g., repo1 can pull from (or push to) repo2 and repo3 if they have a shared history between them.

    To communicate with another remote repo, you can first add it as a remote of your repo. Here is an example scenario you can follow to learn how to pull from another repo:

    1. Open the local repo in SourceTree. Suggested: Use your local clone of the samplerepo-things repo.

    2. Choose RepositoryRepository Settings menu option.

    3. Add a new remote to the repo with the following values.

      • Remote name: the name you want to assign to the remote repo e.g., upstream1
      • URL/path: the URL of your repo (ending in .git) that. Suggested: https://github.com/se-edu/samplerepo-things-2.git (samplerepo-things-2 is another repo that has a shared history with samplerepo-things)
      • Username: your GitHub username

    4. Now, you can pull from the added repo as you did before but choose the remote name of the repo you want to pull from (instead of origin):

      If the Remote branch to pull dropdown is empty, click the Refresh button on its right.

    5. If the pull from the samplerepo-things-2 was successful, you should have received one more commit into your local repo.

    1. Navigate to the folder containing the local repo.

    2. Set the new remote repo as a remote of the local repo.
      command: git remote add {remote_name} {remote_repo_url}
      e.g., git remote add upstream1 https://github.com/johndoe/foobar.git

    3. Now you can pull from the new remote.
      e.g., git pull upstream1 master

    W3.8b

    Tools → Git and GitHub → Creating PRs

    Can create PRs on GitHub

    Suppose you want to propose some changes to a GitHub repo (e.g., samplerepo-pr-practice) as a pull request (PR). Here is a scenario you can try in order to learn how to create PRs:

    A pull request (PR for short) is a mechanism for contributing code to a remote repo, i.e., "I'm requesting you to pull my proposed changes to your repo". For this to work, the two repos must have a shared history. The most common case is sending PRs from a fork to its upstream repo is a repo you forked fromupstream repo.

    1. Fork the repo onto your GitHub account.

    2. Clone it onto your computer.

    3. Commit your changes e.g., add a new file with some contents and commit it.

    • Option A - Commit changes to the master branch
    • Option B - Commit to a new branch e.g., create a branch named add-intro (remember to switch to the master branch before creating a new branch) and add your commit to it.

    4. Push the branch you updated (i.e., master branch or the new branch) to your fork, as explained here.

    Pushing a branch to a remote repo

    Here's how to push a branch to a remote repo:

    Here's how to push a branch named add-intro to your own fork of a repo named samplerepo-pr-practice:

    Normally: git push {remote repository} {branch}. Examples:

    • git push origin master pushes the master branch to the repo named origin (i.e., the repo you cloned from)
    • git push upstream-repo add-intro pushes the add-intro branch to the repo named upstream-repo

    If pushing a branch you created locally to the remote for the first time, add the -u flag to get the local branch to track the new upstream branch:
    e.g., git push -u origin add-intro

    See git-scm.com/docs/git-push for details of the push command.

    5. Initiate the PR creation:

    1. Go to your fork.

    2. Click on the Pull requests tab followed by the New pull request button. This will bring you to the 'Comparing changes' page.

    3. Set the appropriate target repo and the branch that should receive your PR, using the base repository and base dropdowns. e.g.,
      base repository: se-edu/samplerepo-pr-practice base: master

      Normally, the default value shown in the dropdown is what you want but in case your fork has e.g., the repo you forked from is also a fork of a another repo, which means both of those are considered upstream repos of your forkmultiple upstream repos, the default may not be what you want.

    4. Indicate which repo:branch contains your proposed code, using the head repository and compare dropdowns. e.g.,
      head repository: myrepo/samplerepo-pr-practice compare: master

    6. Verify the proposed code: Verify that the diff view in the page shows the exact change you intend to propose. If it doesn't, commit the new code and push to the branchupdate the branch as necessary.

    7. Submit the PR:

    1. Click the Create pull request button.

    2. Fill in the PR name and description e.g.,
      Name: Add an introduction to the README.md
      Description:

      Add some paragraph to the README.md to explain ...
      Also add a heading ...
    3. If you want to indicate that the PR you are about to create is 'still work in progress, not yet ready', click on the dropdown arrow in the Create pull request button and choose Create draft pull request option.

    4. Click the Create pull request button to create the PR.

    5. Go to the receiving repo to verify that your PR appears there in the Pull requests tab.

    The next step of the PR life cycle is the PR review. The members of the repo that received your PR can now review your proposed changes.

    • If they like the changes, they can merge the changes to their repo, which also closes the PR automatically.
    • If they don't like it at all, they can simply close the PR too i.e., they reject your proposed change.
    • In most cases, they will add comments to the PR to suggest further changes. When that happens, GitHub will notify you.

    You can update the PR along the way too. Suppose PR reviewers suggested a certain improvement to your proposed code. To update your PR as per the suggestion, you can simply modify the code in your local repo, commit the updated code to the same master branch, and push to your fork as you did earlier. The PR will auto-update accordingly.

    Sending PRs using the master branch is less common than sending PRs using separate branches. For example, suppose you wanted to propose two bug fixes that are not related to each other. In that case, it is more appropriate to send two separate PRs so that each fix can be reviewed, refined, and merged independently. But if you send PRs using the master branch only, both fixes (and any other change you do in the master branch) will appear in the PRs you create from it.

    To create another PR while the current PR is still under review, create a new branch (remember to switch back to the master branch first), add your new proposed change in that branch, and create a new PR following the steps given above.

    It is possible to create PRs within the same repo e.g., you can create a PR from branch feature-x to the master branch, within the same repo. Doing so will allow the code to be reviewed by other developers (using PR review mechanism) before it is merged.

    [W3.9] Automated Testing of Text UIs

    W3.9a :

    Quality Assurance → Testing → Introduction → What

    Video

    Can explain testing

    Testing: Operating a system or component under specified conditions, observing or recording the results, and making an evaluation of some aspect of the system or component. –- source: IEEE

    When testing, you execute a set of test cases. A test case specifies how to perform a test. At a minimum, it specifies the input to the software under test (SUT) and the expected behavior.

    Example: A minimal test case for testing a browser:

    • Input – Start the browser using a blank page (vertical scrollbar disabled). Then, load longfile.html located in the test data folder.
    • Expected behavior – The scrollbar should be automatically enabled upon loading longfile.html.

    Test cases can be determined based on the specification, reviewing similar existing systems, or comparing to the past behavior of the SUT.

    Other details a test case can contain extra A more elaborate test case can have other details such as those given below.
    • A unique identifier : e.g. TC0034-a
    • A descriptive name: e.g. vertical scrollbar activation for long web pages
    • Objectives: e.g. to check whether the vertical scrollbar is correctly activated when a long web page is loaded to the browser
    • Classification information: e.g. priority - medium, category - UI features
    • Cleanup, if any: e.g. empty the browser cache.

    For each test case you should do the following:

    1. Feed the input to the SUT
    2. Observe the actual output
    3. Compare actual output with the expected output

    A test case failure is a mismatch between the expected behavior and the actual behavior. A failure indicates a potential defect (or a bug), unless the error is in the test case itself.

    Example: In the browser example above, a test case failure is implied if the scrollbar remains disabled after loading longfile.html. The defect/bug causing that failure could be an uninitialized variable.

    A deeper look at the definition of testing extra

    Here is another definition of testing:

    Software testing consists of the dynamic verification that a program provides expected behaviors on a finite set of test cases, suitably selected from the usually infinite execution domain. -– source: Software Engineering Book of Knowledge V3

    Some things to note (indicated by keywords in the above definition):

    • Dynamic: Testing involves executing the software. It is not by examining the code statically.
    • Finite: In most non-trivial cases there are potentially infinite test scenarios but resource constraints dictate that we can test only a finite number of scenarios.
    • Selected: In most cases it is not possible to test all scenarios. That means we need to select what scenarios to test.
    • Expected: Testing requires some knowledge of how the software is expected to behave.

    Explain how the concepts of testing, test cases, test failure, and defects are related to each other.

    W3.9b :

    Quality Assurance → Testing → Regression Testing → What

    Video

    Can explain regression testing

    When you modify a system, the modification may result in some unintended and undesirable effects on the system. Such an effect is called a regression.

    Regression testing is the re-testing of the software to detect regressions. Note that to detect regressions, you need to retest all related components, even if they had been tested before.

    Regression testing is more effective when it is done frequently, after each small change. However, doing so can be prohibitively expensive if testing is done manually. Hence, regression testing is more practical when it is automated.

    Regression testing is the automated re-testing of a software after it has been modified.

    c.

    Explanation: Regression testing need not be automated but automation is highly recommended.

    Explain why and when you would do regression testing in a software project.

    W3.9c :

    Quality Assurance → Testing → Test Automation → What

    Can explain test automation

    An automated test case can be run programmatically and the result of the test case (pass or fail) is determined programmatically. Compared to manual testing, automated testing reduces the effort required to run tests repeatedly and increases precision of testing (because manual testing is susceptible to human errors).

    W3.9d :

    Quality Assurance → Testing → Test Automation → Automated testing of CLI applications

    Video

    Can semi-automate testing of CLIs

    A simple way to semi-automate testing of a CLI (Command Line Interface) app is by using input/output re-direction.

    • First, you feed the app with a sequence of test inputs that is stored in a file while redirecting the output to another file.
    • Next, you compare the actual output file with another file containing the expected output.

    Let's assume you are testing a CLI app called AddressBook. Here are the detailed steps:

    1. Store the test input in the text file input.txt.

      add Valid Name p/12345 valid@email.butNoPrefix
      add Valid Name 12345 e/valid@email.butPhonePrefixMissing
    2. Store the output you expect from the SUT in another text file expected.txt.

      Command: || [add Valid Name p/12345 valid@email.butNoPrefix]
      Invalid command format: add

      Command: || [add Valid Name 12345 e/valid@email.butPhonePrefixMissing]
      Invalid command format: add
    3. Run the program as given below, which will redirect the text in input.txt as the input to AddressBook and similarly, will redirect the output of AddressBook to a text file output.txt. Note that this does not require any code changes to AddressBook.

      java AddressBook < input.txt > output.txt
      • The way to run a CLI program differs based on the language.
        e.g., In Python, assuming the code is in AddressBook.py file, use the command
        python AddressBook.py < input.txt > output.txt

      • If you are using Windows, use a normal command window to run the app, not a PowerShell window.

      More on the > operator and the < operator extra

      A CLI program takes input from the keyboard and outputs to the console. That is because those two are the default input and output streams, respectively. But you can change that behavior using < and > operators. For example, if you run AddressBook in a command window, the output will be shown in the console, but if you run it like this,

      java AddressBook > output.txt 

      the Operating System then creates a file output.txt and stores the output in that file instead of displaying it in the console. No file I/O coding is required. Similarly, adding < input.txt (or any other filename) makes the OS redirect the contents of the file as input to the program, as if the user typed the content of the file one line at a time.

      Resources:

    4. Next, you compare output.txt with the expected.txt. This can be done using a utility such as Windows' FC (i.e. File Compare) command, Unix's diff command, or a GUI tool such as WinMerge.

      FC output.txt expected.txt

    Note that the above technique is only suitable when testing CLI apps, and only if the exact output can be predetermined. If the output varies from one run to the other (e.g. it contains a time stamp), this technique will not work. In those cases, you need more sophisticated ways of automating tests.

    CLI application: An application that has a Command Line Interface. i.e. user interacts with the app by typing in commands.

    Similar to last week, most topics for this week are TIC2002 topics given here for reference. But there are some new topics too (they are marked by one of these icons: , , , : OPTIONAL)