Demo zu List, Set, Map und weiterem

Hier ein vollständiges, ausführlich erläutertes Beispiel.
Packages und Imports sind selbst zu ergänzen:

1. Abstrakte Klasse Person

Abstrakte Klasse Person […​klicken zum Auf/Zuklappen…​]
public class TestApp {

    private static Company c1;

    public TestApp() {

    }

    public static void main(String[] args) throws DuplicateItemException {
        TestApp myTestApp = new TestApp();
        myTestApp.testNiceCases();
        myTestApp.testBadCases();
    }

    public void testNiceCases() throws DuplicateItemException {
        c1 = new Company();
        LocalDate birthDay1 = LocalDate.of(2006, 12, 31);
        LocalDate birthDay2 = LocalDate.of(1999, 3, 13);
        Employee e1, e2;
        try {
            e1 = new Employee("Evi Eder", birthDay1, 'f', "evi.eder@nixmail.at", 0);
            c1.addEmployee(e1);
            e2 = new Employee("Paul Pi", birthDay2, 'm', "paul.pi@kamail.at", 0);
            c1.addEmployee(e2);
        } catch (BadParamException | InvalidEmailException | DuplicateEmailException e) {
            System.out.format("TEST-ERR: %s with message %s should not happen here",
                    e.getClass().getSimpleName(), e.getMessage());
            e.printStackTrace();
        }
        c1.printEmployeeList();
        c1.printEmployeeSet();
        c1.printEmployeeEmailTxtMap();
        c1.printEmployeeEmailAddrMap();

    }

    public void testBadCases() {
        //TODO: implement cases which SHOULD throw exceptions
        LocalDate birthDay1 = LocalDate.of(2006, 12, 31);
        try {
            Employee e3 = new Employee("Evi Eder", birthDay1, 'x', "evi.eder@nixmail.at", 0);
        } catch (BadParamException e) {
            System.out.format("EXPECTED-ERR: %s with message %s",
                    e.getClass().getSimpleName(), e.getMessage());
        } catch (InvalidEmailException e) {
            e.printStackTrace();
        }
    }
}

2. Abstrakte Klasse EmailAddr

Abstrakte Klasse EmailAddr […​klicken zum Auf/Zuklappen…​]
public abstract class EmailAddr implements Comparable<EmailAddr> {
    private static final Class<?> CL = java.lang.invoke.MethodHandles.lookup().lookupClass();
    private static final Logger LOG = Logger.getLogger(CL.getName());

    private String emailAddrTxt;

    // All checks already done within method checkValid(..) called by factory method!!
    protected EmailAddr(String emailAddrTxt) {
        LOG.entering(CL.getSimpleName(), "Constructor(...)");
        this.emailAddrTxt = emailAddrTxt;
        LOG.exiting(CL.getSimpleName(), "Constructor(...)");
    }

    /**
     * Non-static factory method. To make it usable, all subclasses should have a constant
     * {@code  public static final EmailAddr NONE = new EmailAddrSubclass(null)},
     * which can be used to call {@code NONE.checkValid(..)} and
     * {@code NONE.emailAddrFactory(..)}this factory method
     *
     * @param emailAddrTxt the string representing the email address.
     * @return the created EmailAddr object
     * @throws InvalidEmailException if NONE.checkValid(..) throwed one.
     */
    public abstract EmailAddr emailAddrFactory(String emailAddrTxt) throws InvalidEmailException;

    /**
     * Does a validity check of the given email address text.
     * @param emailAddrTxt the email address text to check
     * @throws InvalidEmailException if NONE.checkValid(..) found a mistake
     */
    public abstract void checkValid(String emailAddrTxt) throws InvalidEmailException;

    /**
     * Does a raw check of emailAddrTxt address validity (null, maxLen 254, exactly 1 '@').
     *
     * @param emailAddrTxt
     * @return a 2-cells String array containing in cell 0 localPart, in cell 1 domainPart
     * @throws InvalidEmailException if null, > 254 chars, not exactly 1 '@'
     */
    protected static String[] splitAndRawCheck(String emailAddrTxt) throws InvalidEmailException {
        String[] parts = null;
        String msg = null;    // null value used to decide if there was a problem
        if (emailAddrTxt == null) {
            msg = "given emailAddrTxt address is null";
        } else if (emailAddrTxt.length() > 254) {
            msg = "Email address cannot exceed 254 chars, but given emailAddrTxt addr ["
                    + emailAddrTxt + "] has length " + emailAddrTxt.length();
        } else {
            parts = emailAddrTxt.split("@");  // if '@' at the end, arr.length is only 1!!!
            if (parts.length != 2) {
                msg = "Email address doesn't contain exactly one '@': [" + emailAddrTxt
                        + "] with something behind";
            }
        }
        if (msg == null) {
            String localPart = parts[0];
            String domainPart = parts[1];
            if (localPart.length() > 64) {
                msg = "LocalPart of [" + emailAddrTxt + "] is longer than the allowed 64 chars: "
                        + localPart.length();
            } else {
                parts[1] = domainPart.toLowerCase();
            }
        }
        if (msg != null) {
            throw new InvalidEmailException(msg);
        }
        return parts;
    }

    /**
     * Get the part before the '@'.
     *
     * @return the front part.
     */
    String getLocalPart() {
        int atPos = emailAddrTxt.indexOf('@');
        return emailAddrTxt.substring(0, atPos);
    }


    /**
     * Get the part after the '@'.
     *
     * @return the back part.
     */
    String getDomainPart() {
        int atPos = emailAddrTxt.indexOf('@');
        return emailAddrTxt.substring(atPos + 1);
//        String[] parts = emailAddrTxt.split("@");
//        return parts[1];
    }

    abstract String getEmailReverse();

    /**
     * diffenrences in upper/lower case are ignored - will cause same hash code,
     * consistent with equals(..)!
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((emailAddrTxt == null) ? 0 : emailAddrTxt.toLowerCase().hashCode());
        return result;
    }

    /**
     * checks ignoring case.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        EmailAddr other = (EmailAddr2) obj;
        if (this.emailAddrTxt == null) {
            if (other.emailAddrTxt != null)
                return false;
        } else if (!this.emailAddrTxt.equalsIgnoreCase(other.emailAddrTxt))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return emailAddrTxt;
    }

    /**
     * Use the most plausible way to sort emailAddrTxt addresses - the reverse EmailAddress (case
     * insensitive).
     */
    public int compareTo(EmailAddr other) {
        // makes more sense to use the getEmailReverse() for comparing:
        // return this.email.compareToIgnoreCase(other.email);
        return this.getEmailReverse().compareToIgnoreCase(other.getEmailReverse());
    }
}

3. Abstrakte Klasse EmailAddr1

Abstrakte Klasse EmailAddr1 […​klicken zum Auf/Zuklappen…​]
public class EmailAddr1 extends EmailAddr {
    private static final Class<?> CL = java.lang.invoke.MethodHandles.lookup().lookupClass();
    private static final Logger LOG = Logger.getLogger(CL.getName()); // my.application.engine_iface.EmailAddr
    public static final EmailAddr1 NONE = new EmailAddr1(null);

    private String emailAddrTxt;

    // since private, from outside only factory method can be used to instantiate!
    private EmailAddr1(String emailAddrTxt) {
        super(emailAddrTxt);
        LOG.exiting(CL.getSimpleName(), "Constructor(...)");
    }

    /**
     * Factory method to create EmailAddr1-objects after checking validity.
     *
     * @param emailAddrTxt the textual representation of an email address - will be checked
     * @return a ready to use, valid email address object. Never null!
     * @throws InvalidEmailException if validation fails
     */
    public EmailAddr emailAddrFactory(String emailAddrTxt) throws InvalidEmailException {
        LOG.entering(CL.getSimpleName(), "emailAddrFactory(...)");
        checkValid(emailAddrTxt);
        return new EmailAddr1(emailAddrTxt);
    }

    /**
     * Does a very primitive check of email address validity (no Regular Expressions).
     *
     * @param emailAddrTxt
     * @throws InvalidEmailException
     */
    @Override
    public void checkValid(String emailAddrTxt) throws InvalidEmailException {
        String[] parts = EmailAddr.splitAndRawCheck(emailAddrTxt); // static method of abstr.cl.
        String localPart = parts[0];
        String domainPart = parts[1];  // already converted to all lower case

        String msg = null;    // 'null' value used to express there was NO problem
        if (localPart.isBlank()) {
            msg = "LocalPart of [" + emailAddrTxt + "] has no content";
        } else if (domainPart.isBlank()) {
            msg = "DomainPart of [" + emailAddrTxt + "] has no content";
        } else if (domainPart.contains(" ")) {
            msg = "DomainPart of [" + emailAddrTxt + "] contains space";
        } else if (!domainPart.contains(".")) {
            msg = "DomainPart of [" + emailAddrTxt + "] has no '.'";
        }
        if (msg != null) {
            throw new InvalidEmailException(msg);
        }
    }

    @Override
    String getEmailReverse() {
        return new StringBuilder(getDomainPart()).reverse().toString() + "@" + getLocalPart();
    }

    public EmailAddr emailAddrFactory2(String emailAddrTxt) throws InvalidEmailException {
        return new EmailAddr1(emailAddrTxt);
    }

    public static void main(String[] args) {
        String[] stringsToCheck = {"evi@nix.net", "evi@nix@net", "evi@", "@nix.net", "evi.nix.net",
                "evi@nix net", null, "   @   ", "evi@   ", "evi@ nix.net", "e@nix.net"};
        for (String emailAddrTxt : stringsToCheck) {
            try {
                EmailAddr emailAddr = NONE.emailAddrFactory(emailAddrTxt);
                System.out.format("Valid: '%s', reversed: '%s'%n",
                        emailAddr, emailAddr.getEmailReverse());

            } catch (InvalidEmailException e) {
                System.out.format("ERR: %s%n", e.getMessage());
            }

        }
    }
}

4. Abstrakte Klasse EmailAddr2

Abstrakte Klasse EmailAddr2 […​klicken zum Auf/Zuklappen…​]
/**
 * Class representing an e-mail address including validity checks and smart Comparator
 * implementation.
 *
 * @author Maximilian_Renkin
 * @version 2020-02-22
 */
public class EmailAddr2 extends EmailAddr {
    private static final Class<?> CL = java.lang.invoke.MethodHandles.lookup().lookupClass();
    private static final Logger LOG = Logger.getLogger(CL.getName()); // my.application.engine_iface.EmailAddr
    public static final EmailAddr2 NONE = new EmailAddr2(null);

    private EmailAddr2(String emailAddrTxt) {
        super(emailAddrTxt);
        LOG.exiting(CL.getSimpleName(), "Constructor(...)");
    }

    /**
     * Factory method to create EmailAddr2-objects after checking validity.
     *
     * @param emailAddrTxt the textual representation of an email address - will be checked
     * @return a ready to use, valid email address object. Never null!
     * @throws InvalidEmailException if validation fails
     */
    public EmailAddr emailAddrFactory(String emailAddrTxt) throws InvalidEmailException {
        checkValid(emailAddrTxt);
        return new EmailAddr2(emailAddrTxt);
    }

    private static final String DOMAIN_CHARS = "[-0-9a-z]"; // domain part converted to lower case

    /**
     * Does a check of emailAddrTxt address validity (slightly simplified, but useful). Details
     * and specification see e.g.: E-Mail-Adresse – Wikipedia —
     * https://de.wikipedia.org/wiki/E-Mail-Adresse
     *
     * @param emailAddrTxt TODO: move to emailTxt ?
     * @throws InvalidEmailException
     */
    @Override
    public void checkValid(String emailAddrTxt) throws InvalidEmailException {
        String[] parts = EmailAddr.splitAndRawCheck(emailAddrTxt); // static method of abstr.cl.
        String localPart = parts[0];
        String domainPart = parts[1];  // already converted to all lower case
        String msg = null;    // null value used to express there was NO problem
        if (!localPart.matches("[-._0-9A-Za-z]{1,64}")) {
            msg = "Local part of [" + emailAddrTxt + "] has to consist of min 1 letter,"
                    + " all letters one of 0-9, A-Z, a-z, '-', '.', '_'";
        } else if (!domainPart.matches(DOMAIN_CHARS + "{2,}(\\." + DOMAIN_CHARS + "{2,})+")) {
            msg = "Domain part must have min. one dot separating 'words' (len>1 ,"
                    + " only letters, digits and minus)\n - e.g. 'aa.bb', 'a-b.mn.xyz' "
                    + "or 'abc-de.m-n.xy', but given was: [" + emailAddrTxt + "]";
        }
        if (msg != null) {
            throw new InvalidEmailException(msg);
        }
    }

    /**
     * Get String with reversed parts of emailAddress. Uses {@link #getDomainPartAsArray()}.<br/>
     * The purpose is to allow sorting emailAddrTxt addresses in a smart way - e.g. all
     * co.at.* together.
     * E.g. maximilian.renkin@eine-firma.co.at -> at.co.eine-firma@maximilian.renkin
     *
     * @return E.g. 'huber@xmail.co.at' -> 'at.co.xmail@huber' .
     */
    @Override
    public String getEmailReverse() {
        String[] domainPieces = getDomainPartAsArray();
        String reverseDomain = domainPieces[0];
        for (int i = 1; i < domainPieces.length; i++) {
            reverseDomain = domainPieces[i] + "." + reverseDomain;
        }
        return reverseDomain + "@" + getLocalPart();
    }

    /**
     * Get the dot-separated pieces of the domain part as array.
     *
     * @return
     */
    private String[] getDomainPartAsArray() {
        String[] pieces = getDomainPart().split("\\.");
        return pieces;
    }

    public static void main(String[] args) {
        String[] stringsToCheck = {"evi@nix.net", "evi@nix@net", "evi@", "@nix.net", "evi.nix.net",
                "evi@nix net", null, "   @   ", "evi@   ", "evi@ nix.net", "e@nix.net"};
        for (String emailAddrTxt : stringsToCheck) {
            try {
                EmailAddr emailAddr = NONE.emailAddrFactory(emailAddrTxt);
                System.out.format("Valid: '%s', reversed: '%s'%n",
                        emailAddr, emailAddr.getEmailReverse());

            } catch (InvalidEmailException e) {
                System.out.format("ERR: %s%n", e.getMessage());
            }

        }
    }
}

5. Interface Worker

Interface Worker […​klicken zum Auf/Zuklappen…​]
public interface Worker {
    public abstract float calcIncome(float workHours);
}

6. Klasse Freelancer

Klasse Freelancer […​klicken zum Auf/Zuklappen…​]
public class Freelancer extends Person implements Worker {

    private float hourlyRate;

    public Freelancer(String name, LocalDate birthDate, char gender, String email, float hourlyRate)
            throws BadParamException, InvalidEmailException {
        super(name, birthDate, gender, email);
        setHourlyRate(hourlyRate);
    }

    public void setHourlyRate(float hourlyRate) throws BadParamException {
        if (hourlyRate <= 0) {
            throw new BadParamException("non-positive hourlyRate: %f".formatted(hourlyRate));
        }
        this.hourlyRate = hourlyRate;
    }

    @Override
    public float calcIncome(float workHours) {
        return hourlyRate*workHours;
    }
}

7. Interface SickLeaveable

Interface SickLeaveable […​klicken zum Auf/Zuklappen…​]
public interface SickLeaveable { // krankenstandsfähig
    void setSickDays(int sickDays) throws BadParamException, BadParamException;
    int getSickDays();
}

8. Interface ChildSupportable

Interface ChildSupportable […​klicken zum Auf/Zuklappen…​]
public interface ChildSupportable {
    static final double AMOUNT_PER_CHILD = 300;
    int getNumChildren();  // kann nicht als default implem. werden - keine InstanzVars!
    void setNumChildren(int numChildren) throws BadParamException;  // detto
    default double calcChildSupport() {   // nur Konstante und deklarierte Meth. erlaubt
        return AMOUNT_PER_CHILD * getNumChildren(); // auch noch unimplem. Meth. nutzbar
    }
}

9. Klasse Employee

Klasse Employee […​klicken zum Auf/Zuklappen…​]
public class Employee extends Person implements Worker, SickLeaveable, ChildSupportable {
    public static final int MIN_SALERY_LEVEL = 0;
    public static final int MAX_SALERY_LEVEL = 20;
    private int saleryLevel; // e.g. 0 .. 20
    private int sickDays = 0;
    private int numChildren = 0;
    //more classes see:
    // ~/WORK_GitRepos_RX-htlw5/sj2021_2Xhif_pos1/Lab+Demo/
    //  ->   RX-2020-09-30-2xHIF_LabEx-AbstrCl-Ifaces-Const/src/my/labex

    public Employee(String name, LocalDate birthDate, char genderCode, String email,
                    int saleryLevel) throws BadParamException, InvalidEmailException {
        super(name, birthDate, genderCode, email);
        setSaleryLevel(saleryLevel);
    }

    public void setSaleryLevel(int saleryLevel) throws BadParamException {
        if (saleryLevel < MIN_SALERY_LEVEL || saleryLevel > MAX_SALERY_LEVEL) {
            throw new BadParamException("saleryLevel outside (%d, %d): %d"
                    .formatted(MIN_SALERY_LEVEL, MAX_SALERY_LEVEL, saleryLevel));
        }
        this.saleryLevel = saleryLevel;
    }

    @Override
    public void setSickDays(int sickDays) throws BadParamException {
        if (sickDays < 0 || sickDays > 365) {
            throw new BadParamException("sickDays outside (%d, %d): %d"
                    .formatted(0, 365, sickDays));
        }
        this.sickDays = sickDays;

    }

    @Override
    public void setNumChildren(int numChildren) throws BadParamException {
        if (numChildren < 0 || numChildren > 15) {
            throw new BadParamException("numChildren outside (%d, %d): %d"
                    .formatted(0, 15, numChildren));
        }
        this.numChildren = numChildren;
    }

    public static final float COMMON_WORK_HOURS = 168.0f;

    @Override
    public float calcIncome(float workHrs) {
        float effWage = 1500.0f + saleryLevel * 120.0f;
        float hourlyWage = effWage/ COMMON_WORK_HOURS;
        float overtime = workHrs - COMMON_WORK_HOURS;
        if (overtime > 0) {
            effWage += overtime*1.5f*hourlyWage;
        }
        return effWage;
    }

    public int getSaleryLevel() {
        return saleryLevel;
    }

    @Override
    public int getSickDays() {
        return sickDays;
    }

    @Override
    public int getNumChildren() {
        return numChildren;
    }

    @Override
    public String toString() {
        return String.format("%s%n        saleryLevel=%s, sickDays=%s, numChildren=%s",
                super.toString(), saleryLevel, sickDays, numChildren);
    }
}

10. Klasse Company

Klasse Company […​klicken zum Auf/Zuklappen…​]
public class Company {

    private List<Employee> employeeList;
    private Set<Employee> employeeSet;
    private Map<String, Employee> employeeEmailTxtMap;
    private Map<EmailAddr, Employee> employeeEmailAddrMap;

    public Company() {
        this.employeeList = new ArrayList<>();
        this.employeeSet = new HashSet<>();
        this.employeeEmailTxtMap = new HashMap<>();
        this.employeeEmailAddrMap = new HashMap<>();
    }

    /**
     * Adds given employee. There has to be a null check
     *
     * @param employee check for null
     * @return the added employee or null if adding failed
     * @throws BadParamException       if employee null or
     * @throws DuplicateEmailException
     * @throws InvalidEmailException
     * @throws DuplicateItemException
     */
    public Employee addEmployee(Employee employee) throws BadParamException, InvalidEmailException,
            DuplicateEmailException, DuplicateItemException {
        if (employee == null) {
            throw new BadParamException("employee was null");
        }
        Employee successEmployee = null;
        successEmployee = addEmployeeToList(employee);
        if (successEmployee != null) {
            successEmployee = addEmployeeToSet(employee);
        }
        if (successEmployee != null) {
            try {
                successEmployee = putEmployeeToEmailTxtMap(employee);
                successEmployee = putEmployeeToEmailAddrMap(employee);
            } catch (DuplicateEmailException e) {
                //e.printStackTrace();
                // remove employees from the other containers to ensure consistency (the last one
                // couldn't be successful, so no attempt to remove needed):
                employeeList.remove(employee);  // if was not added, nothing happens
                employeeSet.remove(employee);  // if was not added, no prblm.
                employeeEmailTxtMap.remove(employee.getEmail());  // if was not added, no prblm.
                throw new DuplicateEmailException(e);  // throw exception again after work
            }
        }
        return successEmployee;
    }

    public Employee addEmployeeToList(Employee employee) throws DuplicateItemException {
        // here we do no check for duplicate email!
        if (employeeList.contains(employee)) {
            throw new DuplicateItemException("Employee %s already in company!".formatted(employee));
        }
        employeeList.add(employee);
        return employee;
        //        if (employeeList.add(employee)) {
        //            return employee;
        //        }
        //        return null;
    }

    public Employee addEmployeeToSet(Employee employee) throws DuplicateItemException {
        // here we do no check for duplicate email too!
        // Doublette check not necessary, since set is doing that automatically!!
        if (employeeSet.add(employee)) {
            return employee;
        }
        throw new DuplicateItemException("Employee %s already in company!".formatted(employee));
        //return null;
    }

    public Employee putEmployeeToEmailTxtMap(Employee employee) throws DuplicateEmailException {
        if (employeeEmailTxtMap.containsKey(employee.getEmail())) {
            throw new DuplicateEmailException(
                    "Email address of new employee %s already in use by member employee %s"
                            .formatted(employee,
                                    this.employeeEmailTxtMap.get(employee.getEmail())));
        }
        employeeEmailTxtMap.put(employee.getEmail(), employee);
        return employee;
    }

    public Employee putEmployeeToEmailAddrMap(Employee employee)
            throws InvalidEmailException, DuplicateEmailException {
        if (employeeEmailAddrMap.containsKey(employee.getEmailAddr())) {
            throw new DuplicateEmailException(
                    "Email address of new employee %s already in use".formatted(employee));
        }
        employeeEmailAddrMap.put(employee.getEmailAddr(), employee);
        return employee;
    }

    public void printEmployeeList() {
        System.out.format("EmployeeList with currently %d Members:%n", employeeList.size());
        System.out.println("  Output with classic 'for':");
        for (int i = 0; i < employeeList.size(); i++) {
            Employee emp = employeeList.get(i);
            System.out.format("    Element %3d: %s%n", i, emp);
        }
        int i = 0;
        System.out.println("  Output with 'foreach':");
        for (Employee emp : employeeList) {
            System.out.format("    Element %3d: %s%n", i++, emp);
        }
        System.out.println();
    }

    public void printEmployeeSet() {
        System.out.format("EmployeeSet with currently %d Members (unordered):%n",
                employeeList.size());
        for (Employee emp : employeeSet) {
            System.out.format("    %s%n", emp);
        }
        System.out.println();
    }

    public void printEmployeeEmailTxtMap() {
        System.out.format("EmployeeEmailTxtMap with currently %d Members (unordered):%n",
                employeeEmailTxtMap.size());
        for (String key : employeeEmailTxtMap.keySet()) {
            System.out.format("    %s%n", employeeEmailTxtMap.get(key));
        }
        System.out.println();
    }

    public void printEmployeeEmailAddrMap() {
        System.out.format("EmployeeEmailAddrMap with currently %d Members (unordered):%n",
                employeeEmailAddrMap.size());
        for (EmailAddr emailAddr : employeeEmailAddrMap.keySet()) {
            System.out.format("%s%n", employeeEmailAddrMap.get(emailAddr));
        }
        System.out.println();
    }

    public Employee getFromList(int index) {
        return employeeList.get(index);
    }

    // no way to get single element from set via something like an index - only iterating possible

    public Employee getFromEmailTxtMap(String key) {
        return employeeEmailTxtMap.get(key);
    }

    public Employee getFromEmailAddrMap(EmailAddr1 key) {
        return employeeEmailAddrMap.get(key);
    }
}