Browse Source

Add missing assingments; update README

master
Pekka Helenius 4 years ago
parent
commit
668494892e
10 changed files with 2159 additions and 1 deletions
  1. +732
    -0
      11_object-oriented_programming/1_PainonVartijat.md
  2. +460
    -0
      11_object-oriented_programming/2_Yhteystiedot-ja-osoitekirja-1.md
  3. +255
    -0
      11_object-oriented_programming/3_Yhteystiedot-ja-osoitekirja-2.md
  4. +294
    -0
      11_object-oriented_programming/4_Yhteystiedot-ja-osoitekirja-3.md
  5. +55
    -0
      12_exception/1_KysyUudestaan.md
  6. +37
    -0
      12_exception/2_PoikkeuksenHeitto.md
  7. +66
    -0
      12_exception/3_VirheellinenSyote.md
  8. +150
    -0
      12_exception/4_Kello-ja-Poikkeukset.md
  9. +97
    -0
      13_files/1_WordCount.md
  10. +13
    -1
      README.md

+ 732
- 0
11_object-oriented_programming/1_PainonVartijat.md View File

@ -0,0 +1,732 @@
Tässä tehtävässä harjoitellaan olioiden luomista ja niiden metodien kutsumista käyttäen apuna alla olevaa valmista Auto-luokkaa. Tallenna tehtävänannon lopussa oleva luokka itsellesi tiedostoon Auto.java ja luo uusi luokka AutoOhjelma, jonka main-metodissa teet seuraavat toimenpiteet:
1) Luo uusi "Tesla"-merkkinen auto ja laita viittaus tähän olioon talteen muuttujaan
2) Luo toinen "BMW"-merkkinen auto ja laita viittaus tähän olioon talteen eri muuttujaan
3) Kutsu Teslan aja-metodia arvolla 100.
4) Kutsu BMW:n aja-metodia arvolla 98.
5) Kutsu Teslan aja-metodia arvolla 23.
6) Tulosta Teslan merkkijonoesitys (toString) omalle rivilleen
7) Tulosta BMW:n merkkijonoesitys (toString) omalle rivilleen
Huom! Koska kilometrit on yksityinen muuttuja, sen arvoa ei voida muuttaa luokan ulkopuolelta. Sinun on siis muutettava arvoa julkisen aja-metodin kautta.
# Toteuta painonhallintasovellus
Tarkoituksena on, että sovellukseen voi noin päivittäin syöttää oman painonsa ja painot tallentuvat myös tiedostoon. Painokehityksestä saa niin sanotulla ASCII grafiikalla piirrettyä kuvaajia käyttäen # merkkiä piirtosymbolina. Kuvassa uusimmat mittaukset näkyvät alimpana. Päivämäärät merkitään muodossa, josta tässä esimerkki: 9.9.2019. Eli päivä.kuukausi.vuosi.
Tarkoitus on palauttaa vain yksi luokka PainoSovellus, jonka sisässä ovat muut tarvittavat luokat: PainoMittaus, Pvm, Piirturi ja TiedostoTyokalut. Yhden Java-luokan sisään voi laittaa muita luokkia siten, että ennen PainoMittaus-luokan viimeistä sulkevaa aaltosuljetta }, lisätään muut luokat omista tiedostoistaan siten, että "public class" alkuinen muoto on muodettu muotoon "class". Kaikki import-määreet, mitä eri luokissa tarvitaan laitetaan PainoSovellus-ohjelman alkuun.
## PainoSovellus
Sisältää ohjelman valikon tulostamisen silmukassa. Toteuta esimerkiksi while-silmukalla.
## PainoMittaus
Mallintaa yhtä painomittausta model-luokka, joka sisältää päivämäärän ja varsinaisen painon kiloina. Päivämäärä on luettavissa ja tuotettavissa Pvm-luokan avulla. Tee siis 1) yksityinen (private) attribuutti tietotyyppiä int, joka kertoo paljonko päivän paino oli 2) yksityinen (private) attribuutti tietotyyppiä Pvm (tarkoittaa paivamaaraa). Pvm-luokka toteutetaan seuraavassa kohdassa.
## Pvm
Pvm-luokka (model-luokka), jossa on attribuutit: 1) yksityinen (private) attribuutti tietotyyppiä int, jossa on kuukauden päivä 2) yksityinen (private) attribuutti tietotyyppiä int, jossa on kuukauden numero 3) yksityinen (private) attribuutti tietotyyppiä int, jossa on vuoden arvo.
Parametrittömän konstruktorin kautta voidaan luoda päivämäärä, jonka arvoksi tulee automaattisesti tällähetkellä kuluva päivä. Eli parametritön konstruktori public Pvm() { } asettaa sisällään attribuuttien arvoiksi tämänhetkisen päivämäärän.
Parametrillinen konstruktori pystyy asettamaan päivämäärän arvot sille tulleesta merkkijonosta, eli se pystyy käsittelemään tilanteen, jossa käyttäjä antaa merkkijonona päivämääräksi esimerkiksi 10.3.2020. Java-koodissa tämä käsitellään esimerkiksi seuraavasti:
```
public Pvm(String pvm) {
String rivi = pvm;
String[] pvmKirjaus = new String[3];
pvmKirjaus = rivi.split("\\.");
this.pp = Integer.parseInt(pvmKirjaus[0]);
this.kk = Integer.parseInt(pvmKirjaus[1]);
this.vv = Integer.parseInt(pvmKirjaus[2]);
}
```
## Piirturi
Luokka, joka kykenee skaalautuvasti tuottamaan näyttöä sopivasti käyttävän kuvaajan perustuen annettuun tietoon.
## TiedostoTyokalut
Luokka, joka kykenee kirjoittamaan tiedostoon talteen mitattuja painoja.
Painot pitää tallentaa tiedostoon nimeltään painotcsv.txt
**Toteuta PainoSovellus-luokkaan metodi:**
`public void tulostaValikko()`
Metodi tulostaa numeroilla valittavan valikon.
**Toteuta Piirturi-luokkaan metodi:**
`public void piirraNaytolle()`
Metodi tulostaa sopivasti skaalatun kuvaajan painon kehityksestä.
**Toteuta TiedostoTyokalut-luokkaan metodi:**
`public PainoMittaus[] lue()`
Metodi pystyy lukemaan tiedostosta painotietoja ohjelman ymmärtämään muotoon, niin että ne saadaan osaksi kuvaajaa. Toisin sanoen palautetaan siis taulukko PainoMittaus-olioita, joissa kuhunkin olioon on asetettu päivämäärä ja painolukema.
Ohjelmiston tulisi toimia muun muassa kuten seuraavassa.
```
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
1
Anna paino (muodossa 9.9.2019,85):
1.9.2019,52
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
1
Anna paino (muodossa 9.9.2019,85):
2.9.2019,48
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
1
Anna paino (muodossa 9.9.2019,85):
3.9.2019,49
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3): 1
Anna paino (muodossa 9.9.2019,85):
4.9.2019,55
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
1
Anna paino (muodossa 9.9.2019,85):
5.9.2019,48
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
1
Anna paino (muodossa 9.9.2019,85):
6.9.2019,53
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
2
############
########
#########
###############
########
#############
Valikko
0) Lopeta
1) Lisää painokirjaus menneelle päivälle
2) Tulosta painokuvaaja
3) Lisää painokirjaus tälle päivälle
Anna valintasi (0, 1, 2 tai 3):
0
```
**PainoSovellus.java**
```
import java.io.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class PainoSovellus {
public static String tiedostopolku = System.getProperty("user.dir");
public static String tiedosto = "painotcsv.txt";
private static int painoMin = 40;
private static int painoMax = 300;
private static String piirturiSymbol = "#";
// NOTE: Private preferred, as this is being read just after the application entry point
private static TiedostoTyokalut tiedostoTyokalu = new TiedostoTyokalut(tiedostopolku, tiedosto);
public static void main(String[] args) {
while (true) {
tulostaValikko();
}
}
// Method prints a numerical menu with various options.
public static void tulostaValikko() {
String kayttajaArvo;
Scanner syote = new Scanner(System.in);
System.out.printf(
"Valikko\n" +
"0) Lopeta\n" +
"1) Lisää painokirjaus menneelle päivälle\n" +
"2) Tulosta painokuvaaja\n" +
"3) Lisää painokirjaus tälle päivälle\n" +
"Anna valintasi (0, 1, 2 tai 3):\n"
);
try {
int valinta = syote.nextInt();
switch (valinta) {
case 0:
System.exit(0);
case 1:
System.out.println("Anna paino (muodossa 9.9.2019,85):");
kayttajaArvo = syote.next();
tiedostoTyokalu.kirjoita(kayttajaArvo);
break;
case 2:
//Piirturi piirturi = new Piirturi();
Piirturi.piirraNaytolle();
break;
case 3:
System.out.println("Anna paino (muodossa 85):");
// Since this variable can also use syntax [date,weight] in case 1, we don't use nextInt() method.
// Input check is done by TiedostoTyokalut.inputValidator() method.
kayttajaArvo = syote.next();
tiedostoTyokalu.kirjoita(kayttajaArvo);
break;
//////////
default:
System.err.println("Antamasi valinta ei ole kelvollinen");
}
} catch (Exception e) {
System.err.printf("Luettu syöte ei ole kelvollinen. Syy:\n%s\n", e);
// Do not exit the application here
}
}
static class PainoMittaus {
private Pvm pvm;
private int paino;
private Object[] data = new Object[2];
public Pvm getPvm() {
return this.pvm;
}
public int getPaino() {
return this.paino;
}
public Object[] getObject() {
this.data[0] = this.pvm;
this.data[1] = this.paino;
return this.data;
}
public PainoMittaus(Pvm pvm, int paino) {
if (paino >= PainoSovellus.painoMin && paino <= PainoSovellus.painoMax) {
this.paino = paino;
this.pvm = pvm;
}
}
}
static class Pvm {
// NOTE: These variables are not used in critical date checks.
// WARNING:
// Doing manual check for these is error-prone as we need to validate ranges of vuosi, then kuukausi
// and then paiva
// Therefore, the manual check methods were replaced with built-in Java date classes.
// Old Pvm class code with manual checks is as commented-out attachment in this document
private int vuosi;
private int kuukausi;
private int paiva;
private LocalDate hetki;
private DateTimeFormatter paivaMuoto = DateTimeFormatter.ofPattern("d.M.yyyy");
public void setVuosi(int vuosi) {
this.vuosi = vuosi;
}
public void setKuukausi(int kuukausi) {
this.kuukausi = kuukausi;
}
public void setPaiva(int paiva) {
this.paiva = paiva;
}
public int getVuosi() {
return this.vuosi;
}
public int getKuukausi() {
return this.kuukausi;
}
public int getPaiva() {
return this.paiva;
}
public void setHetki(LocalDate hetki) {
this.hetki = hetki;
}
public void setHetki(String hetki) {
this.hetki = paivaTarkistaja(hetki);
}
public LocalDate getHetki() {
return this.hetki;
}
// Parameterless constructor automatically gets the current day
public Pvm() {
this.hetki = LocalDate.now();
}
// Read day from user input string
// Validate the format/syntax
public Pvm(String paivamaara) {
this.hetki = paivaTarkistaja(paivamaara);
}
private LocalDate paivaTarkistaja(String paivamaara) {
LocalDate hetkiTestaaja = LocalDate.parse(paivamaara, paivaMuoto);
LocalDate nykyHetki = LocalDate.now();
// Can't add values for future days
if (!hetkiTestaaja.isAfter(nykyHetki)) {
return hetkiTestaaja;
}
// TODO add proper error message, avoid unclear NullPointerException
return null;
}
@Override
public String toString() {
return this.hetki.format(paivaMuoto);
}
}
static class Piirturi {
public Piirturi() {}
public static void piirraNaytolle() {
PainoMittaus[] painot = PainoSovellus.tiedostoTyokalu.lue();
List<Pvm> paivaArvot = new ArrayList<Pvm>();
List<Integer> painoArvot = new ArrayList<Integer>();
// Extract and map gathered painoMittaus objects
for (int i = 0; i < painot.length; i++) {
// These variables have been validated by TiedostoTyokalut.inputValidator
// Mixed data types are not recommended in general in Java.
Pvm paiva = (Pvm) painot[i].getObject()[0];
int paino = (int) painot[i].getObject()[1];
paivaArvot.add(paiva);
painoArvot.add(paino);
}
tulostaDiagrammi(jarjestaArvot(paivaArvot, painoArvot));
}
// Sort mapped painoMittaus object [key,value] pairs by keys (dates)
// Outputs values (weights) in sorted order
private static List<Integer> jarjestaArvot(List<Pvm> paivaArvot, List<Integer> painoArvot) {
List<LocalDate> paivaArvotValid = new ArrayList<LocalDate>();
List<LocalDate> paivaErrors = new ArrayList<LocalDate>();
Map<LocalDate, Integer> piirturiMap = new HashMap<LocalDate, Integer>();
for (int i = 0; i < paivaArvot.size(); i++) {
LocalDate syoteHetki = paivaArvot.get(i).getHetki();
// This is not needed for Map data type as it automatically sorts out
// duplicate keys. This condition was added to be more informative
// for the end user about errors in value parsing operations
if (paivaArvotValid.contains(syoteHetki)) {
if (!paivaErrors.contains(syoteHetki)) {
paivaErrors.add(syoteHetki);
System.err.printf("Varoitus: päivälle %s on määritelty useampi painoarvo.\n" +
"Hyväksytään ensimmäisenä luettu arvo.\n", syoteHetki);
}
}
paivaArvotValid.add(syoteHetki);
}
for (int i = 0; i < paivaArvot.size(); i++) {
piirturiMap.put(paivaArvot.get(i).getHetki(), painoArvot.get(i));
}
// Use TreeMap to sort key, value pairs by keys (dates in our case)
Map<LocalDate, Integer> piirturiTreeMap = new TreeMap<LocalDate, Integer>(piirturiMap);
// Java garbage collector
paivaArvotValid = null;
paivaErrors = null;
return getValues(piirturiTreeMap);
}
// Get values from sorted input Map (sorted by date)
private static <Key, Value> List<Value> getValues(Map<Key, Value> inMap) {
List<Value> painoArvotSorted = new ArrayList<Value>();
for (Map.Entry<Key, Value> entry : inMap.entrySet()) {
painoArvotSorted.add(entry.getValue());
}
return painoArvotSorted;
}
private static void tulostaDiagrammi(List<Integer> syoteArvot) {
List<String> hashSymbols = new ArrayList<String>();
for (int i = 0; i < syoteArvot.size(); i++) {
for (int a = 0; a < (syoteArvot.get(i) - PainoSovellus.painoMin); a++) {
hashSymbols.add(PainoSovellus.piirturiSymbol);
}
System.out.println(String.join("", hashSymbols));
hashSymbols.clear();
}
}
}
static class TiedostoTyokalut {
private String tiedostoPolku, tiedosto;
private File tiedostoKohde;
public TiedostoTyokalut(String tiedostopolku, String tiedosto) {
this.tiedostoPolku = tiedostopolku;
this.tiedosto = tiedosto;
}
// Write a line to the file
public void kirjoita(String newLine) throws Exception {
try {
// TODO Add charset encoding check (UTF-8)
this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
FileWriter kirjoitin = new FileWriter(this.tiedostoKohde, true);
// TODO Add proper error handling
Object[] painoDataParsed = inputValidator(newLine, false).getObject();
// Alternatively:
// PainoMittaus painoDataParsed = inputValidator(newLine, false);
// painoDataParsed.getPvm() + "," + painoDataParsed.getPaino();
if (!painoDataParsed.toString().isEmpty()) {
kirjoitin.append(painoDataParsed[0] + "," + painoDataParsed[1]);
kirjoitin.append(System.getProperty("line.separator"));
kirjoitin.close();
}
} catch (IOException o) {
System.err.printf("Ei voitu luoda tai avata tiedostoa: %s (polku: %s)", this.tiedosto, this.tiedostoPolku);
}
}
// Read lines from the file
public PainoMittaus[] lue() {
this.tiedostoKohde = new File(this.tiedostoPolku, this.tiedosto);
try {
FileReader lukija = new FileReader(tiedostoKohde);
BufferedReader lukijaVirta = new BufferedReader(lukija);
String lukijaRivi;
int entryCount = 0;
List<PainoMittaus> painoEntries = new ArrayList<PainoMittaus>();
try {
lukijaRivi = lukijaVirta.readLine();
while (lukijaRivi != null) {
// TODO Add proper error handling
PainoMittaus painoData = inputValidator(lukijaRivi, true);
// Skip invalid entries
if (painoData == null) {
System.err.println("Varoitus: luettu arvo oli tyhjä");
continue;
} else {
painoEntries.add(painoData);
entryCount++;
//System.out.println((String.valueOf(painoData.getPaino())));
}
// Move to the next line
lukijaRivi = lukijaVirta.readLine();
}
// Add object painoTauluOlio which contains PainoMittaus objects
PainoMittaus[] painoTauluOlio = new PainoMittaus[entryCount];
// Add PainoMittaus objects
for (int i = 0; i < painoEntries.size(); i++) {
painoTauluOlio[i] = painoEntries.get(i);
}
/*
for (int s = 0; s < painoTauluOlio.length; s++) {
System.out.println(painoTauluOlio[s]);
}
*/
return painoTauluOlio;
} catch (IOException e) {
System.err.printf("Ei voitu lukea tiedostoa: %s\n", this.tiedostoKohde);
}
} catch (FileNotFoundException e) {
System.err.printf("Ei voitu avata tiedostoa: %s\n", this.tiedostoKohde);
}
// TODO add proper error message, avoid unclear NullPointerException
return null;
}
private PainoMittaus inputValidator(String paivaPaino, boolean onLukuOperaatio) {
List<String> inData = Arrays.asList(paivaPaino.split(","));
Pvm nykyHetki = new Pvm();
if (inData.size() == 2) {
Pvm tamaHetki = new Pvm(inData.get(0));
// Only past days accepted in write operations when [date,weight] input used
if (!onLukuOperaatio && nykyHetki.equals(tamaHetki)) {
// TODO add proper error message, avoid unclear NullPointerException
return null;
}
return new PainoMittaus(tamaHetki, Integer.parseInt(inData.get(1)));
// Date from current date
} else if (inData.size() == 1) {
return new PainoMittaus(nykyHetki, Integer.parseInt(paivaPaino));
}
// TODO add proper error message, avoid unclear NullPointerException
return null;
}
}
}
////////////////////////////////////////////////////////////
// OLD Pvm class
// Has manual check methods for input date validity (format and ranges of year, month and day)
/*
class Pvm {
// NOTE: Using DateTimeFormatter without additional getter/setter filler code instead
// is highly recommended! This code contains a lot of overhead due to task requirements
// to have these attributes individually defined in this class.
private int paiva;
private int kuukausi;
private int vuosi;
private String hetki;
private boolean isPast;
private boolean isCurrentDate;
private String formattedDate(int paiva, int kuukausi, int vuosi) {
return paiva + "." + kuukausi + "." + vuosi;
}
private boolean inValueChecker(int input, int min, int max) {
try {
if (input < min || input > max) {
//System.out.printf("min: %s, max: %s, input: %s", min,max,input);
System.err.println("Annettu päivämäärä ei ole hyväksytyllä arvovälillä");
return false;
// TODO what to do next? Ask date again or abort execution?
} else {
return true;
}
} catch (InputMismatchException e) {
System.err.println("Annettu päivämäärä ei ole kelvollisessa muodossa");
}
return false;
}
public boolean setVuosi(int vuosi) {
if (this.inValueChecker(vuosi, PainoSovellus.minVuosi, LocalDate.now().getYear())) {
this.vuosi = vuosi;
return true;
}
return false;
}
public boolean setKuukausi(int kuukausi) {
int maxMonth;
if (this.getVuosi() < LocalDate.now().getYear()) {
this.isPast = true;
maxMonth = 12;
} else {
maxMonth = LocalDate.now().getMonthValue();
this.isPast = false;
}
if (this.inValueChecker(kuukausi, PainoSovellus.minKuukausi, maxMonth)) {
this.kuukausi = kuukausi;
return true;
}
return false;
}
public boolean setPaiva(int paiva) {
int maxDay;
//if (this.getKuukausi() < LocalDate.now().getMonthValue()) {
if(this.isPast) {
switch(this.getKuukausi()) {
case 2:
maxDay = 29;
break;
case 4:
case 6:
case 9:
case 11:
maxDay = 30;
break;
default:
maxDay = 31;
}
} else {
// Exclude the current day. Should be yesterday, at maximum
maxDay = LocalDate.now().getDayOfMonth() - 1;
}
if (this.inValueChecker(paiva, PainoSovellus.minPaiva, maxDay)) {
this.paiva = paiva;
return true;
}
return false;
}
public int getVuosi() {
return this.vuosi;
}
public int getKuukausi() {
return this.kuukausi;
}
public int getPaiva() {
return this.paiva;
}
// Empty constructor
public Pvm() {}
// Date from LocalDate variable
public Pvm(LocalDate date) {
this.vuosi = date.getYear();
this.kuukausi = date.getMonthValue();
this.paiva = date.getDayOfMonth();
this.hetki = this.formattedDate(this.paiva, this.kuukausi, this.vuosi);
}
// Date from manual input. Has additional checks
public Pvm(int paiva, int kuukausi, int vuosi) {
boolean isValidDate = false;
if (this.setVuosi(vuosi)) {
if (this.setKuukausi(kuukausi)) {
if (this.setPaiva(paiva)) {
isValidDate = true;
}
}
if (isValidDate) {
paiva = this.paiva;
kuukausi = this.kuukausi;
vuosi = this.vuosi;
this.hetki = this.formattedDate(paiva, kuukausi, vuosi);
}
}
}
public String toString() {
if (!this.hetki.isEmpty()) {
return this.hetki;
} else {
return null;
}
}
}
*/
```

+ 460
- 0
11_object-oriented_programming/2_Yhteystiedot-ja-osoitekirja-1.md View File

@ -0,0 +1,460 @@
# Yhteystiedot ja osoitekirja 1/3
Tämä harjoitus pohjautuu seuraavaan kahteen luokkaan sekä erikseen toteutettavaan `AddressBookApp`-luokkaan.
**Contact-luokka:**
```
// tiedosto Contact.java
public class Contact {
private String name;
private String email;
private String phone;
public Contact(String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
}
public String getName() {
return this.name;
}
public String toString() {
// Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)"
return this.name + " (email: " + this.email + ", phone: " + this.phone + ")";
}
}
```
**AddressBook-luokka:**
```
// tiedosto AddressBook.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class AddressBook {
private List<Contact> contacts;
public AddressBook() {
this.contacts = new ArrayList<>();
}
public boolean add(Contact newContact) {
this.contacts.add(newContact);
return true;
}
public Contact search(String keyword) {
for (Contact current : this.contacts) {
String name = current.getName();
if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) {
return current; // palautetaan löytynyt arvo heti
}
}
return null; // palautetaan null, jos ei löytynyt
}
@Override
public String toString() {
// TODO: toteuta tämä metodi. Metodin tulee muodostaa merkkijono,
// joka sisältää kaikki yhteystiedot omilla riveillään.
return "TODO";
}
}
```
## Ohjelman komentorivikäyttöliittymä
Tehtävänä on tällä kertaa rakentaa uusi ohjelmaluokka `AddressBookApp`, joka mahdollistaa yhteystietojen käsittelyn `AddressBook` ja `Contact`-olioiden kanssa seuraavilla komennoilla:
```
This is an address book application. Available commands:
list
help
add <name>, <email>, <phone>
search <name>
exit
```
Ohjelma tulee jakaa osiin siten, että vain `AddressBookApp`-luokka pyytää käyttäjältä syötteitä ja tulostaa tekstiä. `Contact` ja `AddressBook` ovat vain datan varastointia ja käsittelyä varten.
## Syötteiden pyytäminen
Tämän sovelluksen käyttöliittymässä syötettä pyydettäessä ruudulle tulostetaan `>` -merkki syötteen odottamisen merkiksi:
```
System.out.print("> ");
```
Koska `>`-merkki tulostettiin `print` eikä `println`-metodilla, syöttää käyttäjä komentonsa samalle riville. Käyttäjän komento saattaa koostua yhdestä tai useasta osasta. Yhden sanan komentoja ovat esimerkiksi:
```
> list
```
ja
```
> exit
```
Moniosaisia komentoja ovat puolestaan esimerkiksi:
```
> add Maija Meikäläinen, maija@example.com, +35850555556
```
```
> search Matti
```
## Syötteiden lukeminen Scannerilla
Syötteet kannattaa tässä sovelluksessa lukea kahdessa osassa. Ensin luetaan syötteen ensimmäinen sana ja sen jälkeen koko loppurivi:
```
boolean running = true;
while (running) {
System.out.print("> ");
String command = input.next();
String theRest = input.nextLine().trim();
// ... toimintalogiikka
}
```
Rivin ensimmäinen sana on aina komento (`command`), jonka perusteella valitaan mikä operaatio osoitekirjalle suoritetaan. Mahdollisesti annettua loppuriviä käytetään `add`- ja `search`-komentojen tapauksessa uuden yhteystiedon tietoina tai hakusanana.
Jos käyttäjän syöte on `add Maija Meikäläinen, maija@example.com, +35850555556`, tulee muuttujien arvoiksi seuraavat:
| command | theRest |
|---------|----------------------------------------------------|
| add | Maija Meikäläinen, maija@example.com, +35850555556 |
`theRest`-muuttuja siis sisältää rivin koko loppuosan, joka `add`-komennon tapauksessa kannattaa pilkkoa String-luokan `split`-metodilla pilkkujen kohdalta:
```
String[] parts = theRest.split(",");
String name = parts[0].trim();
String email = parts[1].trim();
String phone = parts[2].trim();
```
## Ohjelman logiikan haarauttaminen
Syötteen lukemisen jälkeen voidaan koodi haarauttaa joko Javan perusteista tutulla `if-else if-else`-ketjulla tai `switch case`-rakenteella:
```
switch (command) {
case "help":
// tulosta ohje
break;
case "list":
// tulosta osoitekirjan koko sisältö
break;
case "add":
// käytä annettua nimeä, emailia ja puhelinnumeroa luodaksesi uuden yhteystiedon
break;
case "search":
// etsi yhteystietoa ja tulosta se
break;
case "exit":
// poistu ohjelmasta
break;
default:
// tunnistamaton komento:
System.out.println("Unknown command");
break;
}
```
`Switch-case` -rakenne ei ole tässä välttämätön, mutta se sopii tilanteeseen hyvin ja sen opettelu voi olla hyödyksi sinulle myös myöhemmin. Koska ohjelman halutaan toimivan toistaiseksi ja kysyvän käyttäjän syötettä aina uudelleen, on edellä esitetyt syötteen kyselyt ja ehtorakenteet toteutettava jonkin toistorakenteen sisään.
## Käyttöliittymän ominaisuudet
Sinun ei tarvitse toteuttaa kaikkia komentoja ohjelman ensimmäiseen versioon, vaan **toteuta ominaisuudet yksittäin sitä mukaan kun tarvitset niitä.**
### ADD
add-komennon tulee lisätä yhdelle riville lisätyt yhteystiedot uutena oliona `AddressBook`-osoitekirjaan:
```
> add Maija Meikäläinen, maija@example.com, +35850555556
Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
```
### SEARCH
Jos annettu hakusana löytyy jostain osoitelistan yhteystiedosta, ohjelman tulee tulostaa kyseinen yhteystieto. Jos yhteystietoa ei löydy, tulostetaan siitä kertova viesti:
```
> search Maija
Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> search Ville
Ville does not match any contact.
```
### LIST
Komennon `list` jälkeen ohjelman tulee tulostaa kukin yhteystieto omalla rivillään:
```
> list
Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
```
### HELP
Käyttäjän syötettyä komennon `help` ohjelman tulee tulostaa sama ohjeteksti kuin ohjelman käynnistyksessä:
```
This is an address book application. Available commands:
list
help
add <name>, <email>, <phone>
search <name>
exit
```
### EXIT
Käyttäjän kirjoittaessa syötteeksi "exit" ohjelman tulee tulostaa teksti "Bye!" ja lopettaa suorituksensa:
```
> exit
Bye!
```
### Esimerkki
```
This is an address book application. Available commands:
list
help
add <name>, <email>, <phone>
search <name>
exit
> add Maija Meikäläinen, maija@example.com, +35850555556
Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> add Matti Meikäläinen, matti@example.com, +35850555555
Added Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
> search Matti
Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
> search Maija
Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> list
Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
> search Ville
Ville does not match any contact.
> exit
Bye!
```
**AddressBookApp.java
```
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class AddressBookApp {
private static String welcomeText = "This is an address book application. Available commands:\n";
private static String commandMenu = " list\n" +
" help\n" +
" add <name>, <email>, <phone>\n" +
" search <name>\n" +
" exit\n";
// Keep here instead of translating to a local variable
private static String inputCLIPrefix = "> ";
// Keep added addressBook values during application runtime
private static AddressBook addressBook = new AddressBook();
public static void main(String[] args) {
System.out.print(welcomeText + commandMenu);
while (true) {
runApp();
}
}
private static void runApp() {
Scanner inputPrompt = new Scanner(System.in);
Object[] getInput = parseInput(inputCLIPrefix, inputPrompt);
String inputCmd = String.valueOf(getInput[0]);
// TODO Avoid risk of ClassCastException
List<String> inputArgs = (ArrayList<String>)getInput[1];
int argsCount = 3;
switch(inputCmd) {
case "list":
System.out.println(addressBook.toString());
break;
case "help":
System.out.print(welcomeText + commandMenu);
break;
case "add":
// TODO Should this be checked in AddressBook.add method instead?
if (inputArgs.size() != argsCount) {
System.err.printf("Invalid count of input arguments (expected %d).\n", argsCount);
break;
}
String name = String.valueOf(inputArgs.get(0));
String email = String.valueOf(inputArgs.get(1));
String phone = String.valueOf(inputArgs.get(2));
Contact contact = new Contact(name, email, phone);
// NOTE: Else condition not needed as 3 arguments is always passed
// to this object method call
if(addressBook.add(contact)) {
System.out.printf("Added %s\n", contact.toString());
}
break;
case "search":
String searchTerm = String.valueOf(inputArgs.get(0));
Contact match = addressBook.search(searchTerm);
if (match == null) {
System.out.printf("%s does not match any contact.\n", searchTerm);
} else {
System.out.println(match.toString());
}
break;
case "exit":
System.out.print("Bye!\n");
System.exit(0);
default:
System.err.println("Command not found.");
//break;
}
}
private static Object[] parseInput(String prefix, Scanner inputRaw) {
System.out.print(prefix);
String inputR = inputRaw.nextLine();
String command = inputR.split(" ")[0];
String[] theRest = inputR.split(",");
theRest[0] = theRest[0].replaceAll("^\\s*" + command + "\\s*","");
List<String> arguments = new ArrayList<String>();
for (int i = 0; i < theRest.length; i++) {
arguments.add(theRest[i].trim());
}
Object[] parsedOutput = new Object[2];
parsedOutput[0] = command;
parsedOutput[1] = arguments;
return parsedOutput;
}
}
```
**AddressBook.java**
```
import java.util.ArrayList;
import java.util.List;
public class AddressBook {
private List<Contact> contacts;
public AddressBook() {
this.contacts = new ArrayList<>();
}
public boolean add(Contact newContact) {
this.contacts.add(newContact);
return true;
}
public Contact search(String keyword) {
for (Contact current : this.contacts) {
String name = current.getName();
if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) {
return current; // palautetaan löytynyt arvo heti
}
}
return null; // palautetaan null, jos ei löytynyt
}
@Override
public String toString() {
String returnString = "";
for (Contact contact : this.contacts) {
returnString += contact + "\n";
}
return returnString;
}
}
```
**Contact.java**
```
public class Contact {
private String name;
private String email;
private String phone;
// NOTE: This does not validate or check input String values
// (Is name actually a name, email a valid email, and phone number an actual phone number?)
public Contact(String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
}
public String getName() {
return this.name;
}
public String toString() {
// Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)"
return this.name + " (email: " + this.email + ", phone: " + this.phone + ")";
}
}
```

+ 255
- 0
11_object-oriented_programming/3_Yhteystiedot-ja-osoitekirja-2.md View File

@ -0,0 +1,255 @@
# Yhteystiedot ja osoitekirja 2/3 - Olioiden vertaileminen
Kuten merkkijonoja käsiteltäessä olemme huomanneet, merkkijonojen vertailu `==`-vertailuoperaattorilla vertailee olioviittauksia eikä merkkijonojen sisältöä. Samalla tavoin käy vertailtaessa Contact-olioita. Merkkijonoille on toteutettu oma `equals`-metodinsa, jonka avulla pystytään vertailemaan kahden eri merkkijonon sisältöä. Tässä tehtävässä sinun tulee toteuttaa vastaavasti `Contact`-luokkaan `equals`-metodi, joka vertailee, ovatko kaksi `Contact`-oliota sisällöiltään samanlaiset.
Lue sivu [Olioiden samankaltaisuus](https://ohjelmointi-19.mooc.fi/osa-6/3-olioiden-samankaltaisuus) saadaksesi lisätietoa olioiden vertailusta ja `equals`-metodin toteuttamisesta.
## ADD-KOMENTO
Jatkokehitä myös `AddressBook`-osoitekirjan ominaisuutta yhteystietojen lisäämiseksi siten, että osoitekirjaan ei lisätä kahta sisällöiltään täysin samanlaista yhteystietoa:
```
> add Maija Meikäläinen, maija@example.com, +35850555556
Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> add Maija Meikäläinen, maija@example.com, +35850555556
That contact already exists.
```
Jos käyttäjän syöttämä yhteystieto löytyy jo valmiiksi osoitekirjasta, sitä ei pidä lisätä, vaan ohjelman tulee tulostaa teksti `That contact already exists.`
## ESIMERKKI
```
This is an address book application. Available commands:
list
help
add <name>, <email>, <phone>
search <name>
exit
> add Maija Meikäläinen, maija@example.com, +35850555556
Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> add Matti Meikäläinen, matti@example.com, +35850555555
Added Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
> add Maija Meikäläinen, maija@example.com, +35850555556
That contact already exists.
...
```
**AddressBookApp.java**
```
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class AddressBookApp {
private static String welcomeText = "This is an address book application. Available commands:\n";
private static String commandMenu = " list\n" +
" help\n" +
" add <name>, <email>, <phone>\n" +
" search <name>\n" +
" exit\n";
// Keep here instead of translating to a local variable
private static String inputCLIPrefix = "> ";
// Keep added addressBook values during application runtime
private static AddressBook addressBook = new AddressBook();
public static void main(String[] args) {
System.out.print(welcomeText + commandMenu);
while (true) {
runApp();
}
}
private static void runApp() {
Scanner inputPrompt = new Scanner(System.in);
Object[] getInput = parseInput(inputCLIPrefix, inputPrompt);
String inputCmd = String.valueOf(getInput[0]);
// TODO Avoid risk of ClassCastException
List<String> inputArgs = (ArrayList<String>)getInput[1];
int argsCount = 3;
switch(inputCmd) {
case "list":
System.out.println(addressBook.toString());
break;
case "help":
System.out.print(welcomeText + commandMenu);
break;
case "add":
// TODO Should this be checked in AddressBook.add method instead?
if (inputArgs.size() != argsCount) {
System.err.printf("Invalid count of input arguments (expected %d).\n", argsCount);
break;
}
String name = String.valueOf(inputArgs.get(0));
String email = String.valueOf(inputArgs.get(1));
String phone = String.valueOf(inputArgs.get(2));
Contact contact = new Contact(name, email, phone);
// NOTE: Else condition not needed as 3 arguments is always passed to this object method call
if(addressBook.add(contact)) {
System.out.printf("Added %s\n", contact.toString());
}
break;
case "search":
String searchTerm = String.valueOf(inputArgs.get(0));
Contact match = addressBook.search(searchTerm);
if (match == null) {
System.out.printf("%s does not match any contact.\n", searchTerm);
} else {
System.out.println(match.toString());
}
break;
case "exit":
System.out.print("Bye!\n");
System.exit(0);
default:
System.err.println("Command not found.");
//break;
}
}
private static Object[] parseInput(String prefix, Scanner inputRaw) {
System.out.print(prefix);
String inputR = inputRaw.nextLine();
String command = inputR.split(" ")[0];
String[] theRest = inputR.split(",");
theRest[0] = theRest[0].replaceAll("^\\s*" + command + "\\s*","");
List<String> arguments = new ArrayList<String>();
for (int i = 0; i < theRest.length; i++) {
arguments.add(theRest[i].trim());
}
Object[] parsedOutput = new Object[2];
parsedOutput[0] = command;
parsedOutput[1] = arguments;
return parsedOutput;
}
}
```
**AddressBook.java**
```
import java.util.ArrayList;
import java.util.List;
public class AddressBook {
private List<Contact> contacts;
public AddressBook() {
this.contacts = new ArrayList<>();
}
public boolean add(Contact newContact) {
// Create a temporary ArrayList, if we have contacts already
// Get list of all contacts, put them into the new ArrayList contactsTemp
// Loop through all contacts in contactsTemp
// Check the current contact in contactsTemp against newContact
if (this.contacts.size() > 0) {
List<Contact> contactsTemp = new ArrayList<>(this.contacts);
for (Contact tempContact : contactsTemp) {
if (tempContact.equals(newContact)) {
System.out.println("That contact already exists.");
return false;
}
}
}
this.contacts.add(newContact);
return true;
}
public Contact search(String keyword) {
for (Contact current : this.contacts) {
String name = current.getName();
if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) {
return current; // palautetaan löytynyt arvo heti
}
}
return null; // palautetaan null, jos ei löytynyt
}
@Override
public String toString() {
String returnString = "";
for (Contact contact : this.contacts) {
returnString += contact + "\n";
}
return returnString;
}
}
```
**Contact.java**
```
public class Contact {
private String name;
private String email;
private String phone;
// NOTE: This does not validate or check input String values
// (Is name actually a name, email a valid email, and phone number an actual phone number?)
public Contact(String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object contact) {
if (this == contact) { return true; }
if (!(this instanceof Contact)) { return false; }
Contact contactT = (Contact) contact;
if (this.name.equals(contactT.name) && this.email.equals(contactT.email) && this.phone.equals(contactT.phone)) {
return true;
}
return false;
}
public String toString() {
// Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)"
return this.name + " (email: " + this.email + ", phone: " + this.phone + ")";
}
}
```

+ 294
- 0
11_object-oriented_programming/4_Yhteystiedot-ja-osoitekirja-3.md View File

@ -0,0 +1,294 @@
# Yhteystiedot ja osoitekirja 3/3 - Yhteystietojen järjestäminen aakkosjärjestykseen
Tässä tehtävässä sinun tehtäväsi on järjestää yhteystiedot nimien mukaiseen aakkosjärjestykseen. Tämä tehtävä on ohjelmointi 1 -kurssin näkökulmasta edistynyttä lisäsisältöä, minkä vuoksi se on julkaistu bonustehtävänä.
## Taustatietoa
Jos yritämme järjestää oman Contact-luokan oliot Collections.sort-metodin avulla, saamme seuraavan virheilmoituksen:
```
"The method sort(List) in the type Collections is not applicable for the arguments (List)"
```
Tämä johtuu siitä, että luokkamme ei ole yhteensopiva `Comparable`-tyypin kanssa. `Collections.sort`-metodi ei siis pysty vertailemaan olioitamme keskenään ilman, että määrittelemme sille lisäksi käytettävän vertailuoperaation.
Tutustu Javan `Comparator.comparing`-metodiin, jonka avulla voit määritellä vertailijan kutsumaan mitä tahansa oman luokkasi metodia olioiden järjestämiseksi: [https://www.baeldung.com/java-8-comparator-comparing](https://www.baeldung.com/java-8-comparator-comparing). Tällä kurssilla sinun kannattaa lukea artikkelista kohta [3.1. Key Selector Variant](https://www.baeldung.com/java-8-comparator-comparing#1-key-selector-variant) ja sitä aikaisemmat kappaleet, mutta ei välttämättä tätä kappaletta pidemmälle.
Kuten artikkelissa kerrotaan, `Comparator.comparing`-metodille voidaan antaa metodiviittaus mihin tahansa metodiin. `Collections.sort` käyttää tällöin vertailussa juuri haluamasi metodin palauttamia arvoja, joita vertaillaan keskenään. Voisimme siis esimerkiksi järjestää merkkijonot pituusjärjestykseen vertailemalla merkkijonojen pituuksia, jotka selviävät `length()`-metodin avulla: `Comparator.comparing(String::length)`.
`Collections.sort` käyttää tässä esimerkissä järjestelemiseen `Comparator`-oliota, joka vertaa merkkijonojen pituuksia toisiinsa:
```
List nesteet = Arrays.asList("maito", "Vesi", "ketsuppi", "bensa", "Limu");
// tehdään järjestely merkkijonojen length-metodin mukaan:
Collections.sort(nesteet, Comparator.comparing(String::length));
System.out.println(nesteet); // lyhimmästä merkkijonosta pisimpään: [Limu, Vesi, bensa, maito, ketsuppi]
```
## Tehtävä
Jatkokehitä nyt edellisissä tehtävissä käyttämiäsi kolmea osoitekirja- ja yhteystietoluokkaa siten, että osoitekirjan sisältöä listattaessa "list"-komennolla yhteystiedot esitetään aakkosjärjestyksessä nimen mukaan.
Sinun kannattaa hyödyntää edellä käsiteltyä `Collections.sort`-metodia sekä `Comparator.comparing`-metodia siten, että annat comparing-metodille metodiviittauksen `Contact`-luokkasi `getName`-metodiin.
## ESIMERKKI
```
This is an address book application. Available commands:
list
help
add <name>, <email>, <phone>
search <name>
exit
> add Maija Meikäläinen, maija@example.com, +35850555556
Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
> add Matti Meikäläinen, matti@example.com, +35850555555
Added Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
> add Benjamin Meikäläinen, benjamin@example.com, +35850555557
Added Benjamin Meikäläinen (email: benjamin@example.com, phone: +35850555557)
> add Abraham Meikäläinen, abraham@example.com, +35850555558
Added Abraham Meikäläinen (email: abraham@example.com, phone: +35850555558)
> add Wolverine Meikäläinen, wolverine@example.com, +35850555559
Added Wolverine Meikäläinen (email: wolverine@example.com, phone: +35850555559)
> list
Abraham Meikäläinen (email: abraham@example.com, phone: +35850555558)
Benjamin Meikäläinen (email: benjamin@example.com, phone: +35850555557)
Maija Meikäläinen (email: maija@example.com, phone: +35850555556)
Matti Meikäläinen (email: matti@example.com, phone: +35850555555)
Wolverine Meikäläinen (email: wolverine@example.com, phone: +35850555559)
> exit
Bye!
```
**AddressBookApp.java**
```
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class AddressBookApp {
private static String welcomeText = "This is an address book application. Available commands:\n";
private static String commandMenu = " list\n" +
" help\n" +
" add <name>, <email>, <phone>\n" +
" search <name>\n" +
" exit\n";
// Keep here instead of translating to a local variable
private static String inputCLIPrefix = "> ";
// Keep added addressBook values during application runtime
private static AddressBook addressBook = new AddressBook();
public static void main(String[] args) {
System.out.print(welcomeText + commandMenu);
while (true) {
runApp();
}
}
private static void runApp() {
Scanner inputPrompt = new Scanner(System.in);
Object[] getInput = parseInput(inputCLIPrefix, inputPrompt);
String inputCmd = String.valueOf(getInput[0]);
// TODO Avoid risk of ClassCastException
List<String> inputArgs = (ArrayList<String>)getInput[1];
int argsCount = 3;
switch(inputCmd) {
case "list":
System.out.println(addressBook.toString());
break;
case "help":
System.out.print(welcomeText + commandMenu);
break;
case "add":
// TODO Should this be checked in AddressBook.add method instead?
if (inputArgs.size() != argsCount) {
System.err.printf("Invalid count of input arguments (expected %d).\n", argsCount);
break;
}
String name = String.valueOf(inputArgs.get(0));
String email = String.valueOf(inputArgs.get(1));
String phone = String.valueOf(inputArgs.get(2));
Contact contact = new Contact(name, email, phone);
// NOTE: Else condition not needed as 3 arguments is always passed to this object method call
if(addressBook.add(contact)) {
System.out.printf("Added %s\n", contact.toString());
}
break;
case "search":
String searchTerm = String.valueOf(inputArgs.get(0));
Contact match = addressBook.search(searchTerm);
if (match == null) {
System.out.printf("%s does not match any contact.\n", searchTerm);
} else {
System.out.println(match.toString());
}
break;
case "exit":
System.out.print("Bye!\n");
System.exit(0);
default:
System.err.println("Command not found.");
//break;
}
}
private static Object[] parseInput(String prefix, Scanner inputRaw) {
System.out.print(prefix);
String inputR = inputRaw.nextLine();
String command = inputR.split(" ")[0];
String[] theRest = inputR.split(",");
theRest[0] = theRest[0].replaceAll("^\\s*" + command + "\\s*","");
List<String> arguments = new ArrayList<String>();
for (int i = 0; i < theRest.length; i++) {
arguments.add(theRest[i].trim());
}
Object[] parsedOutput = new Object[2];
parsedOutput[0] = command;
parsedOutput[1] = arguments;
return parsedOutput;
}
}
```
**AddressBook.java**
```
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AddressBook {
private List<Contact> contacts;
public AddressBook() {
this.contacts = new ArrayList<>();
}
public boolean add(Contact newContact) {
// Create a temporary ArrayList, if we have contacts already
// Get list of all contacts, put them into the new ArrayList contactsTemp
// Loop through all contacts in contactsTemp
// Check the current contact in contactsTemp against newContact
if (this.contacts.size() > 0) {
List<Contact> contactsTemp = new ArrayList<>(this.contacts);
for (Contact tempContact : contactsTemp) {
if (tempContact.equals(newContact)) {
System.out.println("That contact already exists.");
return false;
}
}
}
this.contacts.add(newContact);
return true;
}
public Contact search(String keyword) {
for (Contact current : this.contacts) {
String name = current.getName();
if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) {
return current; // palautetaan löytynyt arvo heti
}
}
return null; // palautetaan null, jos ei löytynyt
}
@Override
public String toString() {
String returnString = "";
// Sort by contact names. Define new Comparator object
Collections.sort(this.contacts, new Comparator<Contact>() {
public int compare(Contact contact1, Contact contact2) {
return contact1.getName().compareTo(contact2.getName());
}
});
for (Contact contact : this.contacts) {
returnString += contact + "\n";
}
return returnString;
}
}
```
**Contact.java**
```
public class Contact {
private String name;
private String email;
private String phone;
// NOTE: This does not validate or check input String values
// (Is name actually a name, email a valid email, and phone number an actual phone number?)
public Contact(String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object contact) {
if (this == contact) { return true; }
if (!(this instanceof Contact)) { return false; }
Contact contactT = (Contact) contact;
if (this.name.equals(contactT.name) && this.email.equals(contactT.email) && this.phone.equals(contactT.phone)) {
return true;
}
return false;
}
public String toString() {
// Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)"
return this.name + " (email: " + this.email + ", phone: " + this.phone + ")";
}
}
```

+ 55
- 0
12_exception/1_KysyUudestaan.md View File

@ -0,0 +1,55 @@
# Kysy Uudestaan
Kirjoita luokka `KysyUudestaan` ja lisää siihen `main`-metodi.
Main-metodissa sinun tulee kysyä käyttäjältä kokonaislukutyyppistä syötettä. Jos käyttäjä antaa syötteen, joka ei ole kelvollinen kokonaisluku, ohjelmasi tulee kysyä syötettä uudelleen esimerkkisuorituksen mukaisesti.
Kun käyttäjä syöttää kelvollisen kokonaisluvun, ohjelmasi tulee tulostaa annettu luku esimerkkisuorituksen mukaisesti ja sen suorituksen tulee päättyä.
Totetutustavastasi riippuen sinun tulee varautua tehtävässä joko InputMismatchException tai NumberFormatException -tyyppisiin poikkeuksiin.
**Huom:** ratkaisutavastasi riippuen saatat tässä tehtävässä törmätä ikuiseen silmukkaan, jossa ohjelmasi ei kysy virheellistä arvoa uudelleen. Tämä johtuu siitä, että virheellinen syöte jää System.in-syötepuskuriin ja seuraavalla kerralla ohjelmasi yrittää lukea samaa virheellistä arvoa uudelleen. Stack Overflow -palvelussa on tarkempi keskustelu tästä aiheesta.
```
Syötä kokonaisluku: 1_000
Virheellinen luku!
Syötä kokonaisluku: ?
Virheellinen luku!
Syötä kokonaisluku: +
Virheellinen luku!
Syötä kokonaisluku: a b c
Virheellinen luku!
Syötä kokonaisluku: -8
Syötit luvun -8.
```
**KysyUudestaan.java**
```
import java.util.InputMismatchException;
import java.util.Scanner;
public class KysyUudestaan {
public static void main(String[] args) {
while (true) {
System.out.print("Syötä kokonaisluku: ");
Scanner syote = new Scanner(System.in);
try {
int luku = syote.nextInt();
System.out.printf("Syötit luvun %d.", luku);
System.exit(0);
} catch (InputMismatchException e) {
System.out.println("Virheellinen luku!");
}
}
}
}
```

+ 37
- 0
12_exception/2_PoikkeuksenHeitto.md View File

@ -0,0 +1,37 @@
# Poikkeuksen heittäminen
Kirjoita ohjelma `ArvonTarkastus`, joka kysyy käyttäjältä yhden luvun. Ohjelmasi tulee luvun kysymisen jälkeen tarkastaa, että luku on vähintään 0 ja korkeintaan 23.
Mikäli luku on sallittu, tulee ohjelmasi tulostaa teksti "Luku X on sallittu." ja ohjelman suorituksen pitää päättyä. Mikäli luku ei ole sallittu, tulee ohjelmasi heittää Javan valmis [IllegalArgumentException-poikkeus](https://docs.oracle.com/javase/9/docs/api/java/lang/IllegalArgumentException.html), minkä jälkeen ohjelmasi "kaatuu". Voit antaa poikkeukselle konstruktoriparametrina minkä tahansa virheilmoituksen tai jättää merkkijonon antamatta.
```
Syötä luku väliltä 0-23: -1
Exception in thread "main" java.lang.IllegalArgumentException
```
**ArvonTarkastus.java**
```
import java.util.Scanner;
public class ArvonTarkastus {
public static void main(String[] args) throws IllegalArgumentException {
Scanner syote = new Scanner(System.in);
int min=0, max=23;
System.out.printf("Syötä luku väliltä %d-%d: ", min, max);
int luku = syote.nextInt();
if (luku >= min && luku <= max) {
System.out.printf("Luku %d on sallittu.", luku);
System.exit(0);
} else {
throw new IllegalArgumentException();
}
}
}
```

+ 66
- 0
12_exception/3_VirheellinenSyote.md View File

@ -0,0 +1,66 @@
# Virheellisen syötteen lukeminen
Kirjoita luokka `Summaaja` ja siihen `main`-metodi, jossa lasket käyttäjän syöttämien kokonaislukujen summan. Lukujen pyytäminen lopetetaan, kun käyttäjä syöttää luvun 0. Lopuksi ohjelmasi tulee tulostaa annettujen lukujen summa.
Tässä tehtävässä ohjelmasi täytyy käsitellä virheelliset syötteet kaatumatta. Jos käyttäjä syöttää arvon, joka ei ole kelvollinen kokonaisluku, tulee ohjelman tulostaa teksti "Virheellinen syöte!" ja jatkaa lukujen kysymistä.
**Huom:** ratkaisutavastasi riippuen saatat tässä tehtävässä törmätä ikuiseen silmukkaan, jossa ohjelmasi ei kysy virheellistä arvoa uudelleen. Tämä johtuu siitä, että virheellinen syöte jää System.in-syötepuskuriin ja seuraavalla kerralla ohjelmasi yrittää lukea samaa virheellistä arvoa uudelleen. [Stack Overflow -palvelussa on tarkempi keskustelu tästä aiheesta](https://stackoverflow.com/a/3572233).
```
Syötä seuraava luku (0 lopettaa): 5
Syötä seuraava luku (0 lopettaa): 4
Syötä seuraava luku (0 lopettaa): kolme
Virheellinen syöte!
Syötä seuraava luku (0 lopettaa): 3
Syötä seuraava luku (0 lopettaa): a b c d
Virheellinen syöte!
Syötä seuraava luku (0 lopettaa): 1
Syötä seuraava luku (0 lopettaa): 0
Lukujen summa on 13.
```
**Summaaja.java**
```
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.ArrayList;
public class Summaaja {
public static void main(String[] args) {
int i, sum = 0;
ArrayList<Integer> lukulista = new ArrayList<Integer>();
while (true) {
System.out.print("Syötä seuraava luku (0 lopettaa): ");
Scanner syote = new Scanner(System.in);
try {
int luku = syote.nextInt();
if (luku == 0) {
break;
} else {
lukulista.add(luku);
}
} catch (InputMismatchException e) {
System.out.println("Virheellinen syöte!");
}
}
for (i = 0; i < lukulista.size(); i++) {
sum += lukulista.get(i);
}
System.out.printf("Lukujen summa on %d.\n", sum);
}
}
```

+ 150
- 0
12_exception/4_Kello-ja-Poikkeukset.md View File

@ -0,0 +1,150 @@
# Kello ja poikkeukset
## 1. Kello-luokka
Kirjoita luokka `Kello`. Kello-luokassa on oltava seuraavat ominaisuudet:
- yksityiset oliomuuttujat `tunnit` (int) ja `minuutit` (int)
- konstruktori `Kello(int tunnit, int minuutit)`, joka asettaa annetut arvot oliomuuttujiin
- getterit ja setterit molemmille oliomuuttujille
- `public void lisaaMinuutit(int mins)` -metodi, joka kasvattaa kellonaikaa annettujen minuuttien verran. Jos minuutit ovat lisäyksen jälkeen 60 tai yli, kasvatetaan myös tunteja siten, että minuuttiosuus pysyy aina välillä 0 ≤ minuutit ≤ 59. Jos vastaavasti tunnit ovat lisäyksen jälkeen 24 tai enemmän, pyörähtää kello ympäri ja tunnit alkavat jälleen nollasta.
- `toString`-metodi, joka palauttaa kellonajan merkkijonoesityksen muodossa "22:52". Jos kellonajan minuutit ovat alle 10, tulee minuuttien edessä olla etunolla, esim. "0:06".
## 2. Parametriarvojen tarkastaminen ja poikkeusten heittäminen
Toteuta `Kello`-luokkaan tarkastukset, joissa tarkastetaan, että luokalle annetut tunnit ovat väliltä `0` ≤ tunnit ≤ `23` ja minuutit väliltä `0` ≤ minuutit ≤ `59`. Mikäli kellolle annetaan aika sallitun välin ulkopuolelta, tulee sen heittää `IllegalArgumentException`-poikkeus. Joudut huomioimaan oikeellisen ajan tarkastamisen sekä konstruktorissa että set-metodeissa.
Tarkasta myös `lisaaMinuutit`-metodissa, että kelloon ei yritetä lisätä negatiivista aikaa. Mikäli lisättävät minuutit on negatiivinen luku, heitä `IllegalArgumentException`.
`IllegalArgumentException`-luokan konstruktorille tulee antaa parametrina virheilmoitus. Tämän tehtävän kannalta ei ole merkitystä, minkä virheviestin annat poikkeuksille, esim:
```
throw new IllegalArgumentException("Virheellinen kellonaika");
```
Tässä tehtävässä sinun ei tarvitse toteuttaa tekstikäyttöliittymää, mutta sinun kannattaa testata luokkasi erillisellä main-metodilla.
## ESIMERKKI
```
=== Suoritettava testikoodi ===
public class KelloTest {
public static void main(String[] args) {
Kello kello = new Kello(22, 12);
System.out.println(kello);
kello.lisaaMinuutit(40);
System.out.println(kello);
kello.lisaaMinuutit(29);
System.out.println(kello);
kello.lisaaMinuutit(45);
System.out.println(kello);
}
}
=== Ohjelman tulosteet ===
22:12
22:52
23:21
0:06
```
**Kello.java**
```
public class Kello {
private int tunnit, minuutit;
private static int hour_min = 0;
private static int hour_max = 23;
private static int mins_min = 0;
private static int mins_max = 59;
public void setTunnit(int tunnit) {
this.tarkistaKello(tunnit,hour_min,hour_max);
this.tunnit = tunnit;
}
public void setMinuutit(int minuutit) {
this.tarkistaKello(minuutit, mins_min, mins_max);
this.minuutit = minuutit;
}
public int getTunnit(){
return this.tunnit;
}
public int getMinuutit() {
return this.minuutit;
}
public void lisaaMinuutit(int mins) throws IllegalArgumentException {
// kasvattaa kellonaikaa annettujen minuuttien verran
// jos minuutit yli 60, kasvattaa tunteja siten, että minuutit pysyvät välillä 0-59
//
// jos tunnit ovat lisäyksen jälkeen 24 tai enemmän, "pyörähtää kello ympäri" ja
// tunnit alkavat jälleen nollasta.
if (mins < 0) {
throw new IllegalArgumentException("Virheellinen minuuttiarvo");
}
this.minuutit += mins;
if (this.minuutit > 60) {
// kokonaiset tunnit kaikista minuuteista
int hours = (int)((double)this.minuutit/60.0);
this.tunnit += hours;
// (minuutit/60 - kokonaiset tunnit) * 60 = jäljelle jäävät minuutit
this.minuutit = (int)((((double)this.minuutit/60.0) - hours) * 60);
}
if (this.tunnit >= 24) {
this.tunnit = 0;
}
}
public Kello(int tunnit, int minuutit) {
// konstruktori, joka asettaa annetut arvot (tunnit, minuutit) oliomuuttujiin 'tunnit' ja 'minuutit'
this.tarkistaKello(minuutit, mins_min, mins_max);
this.tarkistaKello(tunnit,hour_min,hour_max);
this.setTunnit(tunnit);
this.setMinuutit(minuutit);
}
private void tarkistaKello(int tyyppi, int min, int max) {
if (!(tyyppi >= min && tyyppi <= max)) {
throw new IllegalArgumentException("Virheellinen kellonaika");
}
}
@Override
public String toString() {
// palauttaa kellonajan merkkijonoesityksen muodossa "22:52"
// jos minuutit alle 10, tulee minuuttien edessä olla etunolla, esim. "0:06".
if (this.minuutit < 10) {
return this.tunnit + ":" + "0" + this.minuutit;
} else {
return this.tunnit + ":" + this.minuutit;
}
}
}
```

+ 97
- 0
13_files/1_WordCount.md View File

@ -0,0 +1,97 @@
# WordCount
Kirjoita ohjelma `WordCount`, joka kysyy käyttäjältä tiedoston nimeä ja tulostaa kyseisessä tiedostossa olevien rivien, sanojen ja merkkien määrän.
Luettavan tiedoston on oltava Java-projektin juuressa. Esimerkiksi suorituksesta:
```
Anna tiedoston nimi: loremipsum.txt
Tiedostossa on:
2 riviä
8 sanaa
55 merkkiä
```
Esimerkissä on käytetty tiedostoa `loremipsum.txt`:
```
Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
```
Riveksi lasketaan myös tyhjät rivit ja merkeiksi myös välilyönnit. Sanojen laskemiseksi voit käyttää String-luokan `split`-metodia, jolla pilkot kunkin rivin välilyöntien kohdalta. Huomaa kuitenkin, että tyhjällä rivillä ei saa laskea yhtään sanaa, vaikka `split`-metodi palauttaakin yhden pituisen taulukon.
Voit hyödyntää omaa ohjelmaa testatessasi myös [tekstitiedosto.txt](https://gist.githubusercontent.com/swd1tn002/92d5a1082c978e2d0e90a082be1fca0b/raw/c5199dda06f2e34590301b1b711d4b157cc02c76/tekstitiedosto.txt)-tiedostoa.
```
Anna tiedoston nimi: loremipsum.txt
Tiedostossa on:
2 riviä
8 sanaa
55 merkkiä
```
**WordCount.java**
```
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.BufferedReader;
import java.util.Arrays;
import java.util.Scanner;
public class WordCount {
public static void main(String[] args) {
Scanner syote = new Scanner(System.in);
System.out.print("Anna tiedoston nimi: ");
String tiedostoNimi = syote.next();
try {
File tiedosto = new File(System.getProperty("user.dir"), tiedostoNimi);
int i, rivit = 0, sanat = 0, merkit = 0;
FileReader lukija = new FileReader(tiedosto);
BufferedReader riviVirta = new BufferedReader(lukija);
String nykyRivi = riviVirta.readLine();
while (nykyRivi != null) {
// Laske rivit
rivit++;
if (!nykyRivi.isEmpty()) {
// Laske sanat
i = 0;
while (i < Arrays.asList(nykyRivi.split(" ")).size()) {
sanat++;
i++;
}
// Laske merkit
i = 0;
while (i < Arrays.asList(nykyRivi.split("")).size()) {
merkit++;
i++;
}
}
// Siirry seuraavaan riviin
nykyRivi = riviVirta.readLine();
}
lukija.close();
System.out.printf("Tiedostossa on:\n%d riviä\n%d sanaa\n%d merkkiä\n", rivit, sanat, merkit);
} catch (IOException e) {
System.err.printf("Tiedostoa %s ei löydy tai sen lukemisessa kävi virhe", tiedostoNimi);
}
}
}
```

+ 13
- 1
README.md View File

@ -6,7 +6,7 @@ Contents of this repository are based on [Java programming course material](http
**NOTE**: Assignments and their descriptions are in Finnish for meanwhile. I have a plan to translate them to English.
**NOTE**: Code indentation in markdown (`.md`) files is incorrectly rendered in Gitea. Click `Raw` to see the correct indentation.
**NOTE**: Code indentation in markdown (`.md`) files is incorrectly rendered in Gitea. Click `Raw` to see the correct indentation of the provided Java code.
## Contents
@ -49,3 +49,15 @@ Contents of this repository are based on [Java programming course material](http
- [Classes and objects](src/branch/master/10_classes-and-objects)
- Description: Custom classes and objects in Java
- [Object-oriented programming](src/branch/master/11_object-oriented_programming)
- Description: Multi-class Java programs
- [Exception](src/branch/master/12_exception)
- Description: Exception handling in Java
- [Files](src/branch/master/13_files)
- Description: File reading in Java

Loading…
Cancel
Save