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);
}
}