Johdatus ohjelmointiin, koe 21.6.1999, tehtävä 4 Tarkastusselostus ja esimerkkivastauksia, Harri Pulli 29.6.99 ======================================================= Tehtävässä piti käyttää annettua luokkaa Syottotiedosto. Muutama (n. 2) oli erehtynyt ohjelmoimaan tuota luokkaa. Pisteitä tehtävästä oli jaossa 12. ------------------------------------------------------------------------ Sisältö: Pistejako Tyypillisiä virheitä Pistejakauma Yksi mahdollinen ratkaisu Toteutus luokalle Syottotiedosto [testitarkoituksiin] ------------------------------------------------------------------------ Pistejako: Ohjelman rungosta: 2p Tiedostojen nimien luku: 3p Syottotiedostojen luonti ja tiedostojen vertailu 6p Loppuvertailut ja tulosteet 1p Tyypillisiä virheitä: - Sovelletaan jotakin metodia arvoon, joka voi olla null. Esim. jotakin "eka.equals(toka)" tai "eka.toCharArray()", kun eka voi olla null. - Toistoehdossa luetaan kahta tiedostoa sivuvaikutuksena ja käytetään silti ehdollista tai-operaatiota ||. Tällöin jälkimmäinen rivi jää lukematta. Esim. while ((ekarivi = eka.lueRivi()) != null || (tokarivi = toka.lueRivi()) != null ) ... lukee vain ekasta, jos sieltä löytyy ei-null. (Tilanteesta riippuen tästä on voinut mennä kaksikin pistettä..., vrt. alla.) - String-arvojen vertailu operaatioin == ja !=. - Tiedoston lukemisessa oli käytetty do-while -silmukkaa, joka olettaa että tiedostossa on ainakin yksi rivi luettavana. - Pääohjelman kutsuma apumetodi ei ole static. - Sisentelyä ei ole edes yritetty. - Joka toinen rivipari hukataan. Esim. jotakin while (eka.lueRivi() != null && toka.lueRivi() != null) { ekarivi = eka.lueRivi(); tokarivi = toka.lueRivi(); ... - Toisistaan poikkeavat rivit tulostetaan moneen kertaan. (Tyypillisesti char[]-taulukkoja for-lauseella vertailtaessa.) - Pidemmän tiedoston lopppurivit jätetään laskematta. (Näin voi käydä esim. kun käytetään lukemista sivuvaikutuksena ja ehdollisia loogisia operaatioita (&&, ||) ehdottomien (&, |) sijasta.) Fataaleja virheitä: - Ei luoda Syottotiedosto-olioita. - public class OvatkoSamat extends Syottotiedosto ... ^^^^^^^ Virheitä joista pisteitä ei mennyt: - "=" vertailuoperaationa "==":n sijaan. - Puolipisteiden puuttumiset yms. Virheestä menetti pisteitä 1 - 3 riippuen virheen vaikutuksesta. ---------------------------------------------------------------------- Pistejakauma: kpl 12 2 11 1 10 2 9 5 8 8 7 3 6 0 5 2 4 1 3 1 2 2 1 1 0 6 ========== yht. 34 Keskiarvo oli 7,4. ----------------------------------------------------------------------- // OvatkoSamat luokan esimerkkitoteutus: public class OvatkoSamat { public static void main(String[] args) { boolean ok; Syottotiedosto eka,toka; do{ System.out.print("Anna ensimmäisen tiedoston nimi:"); eka = new Syottotiedosto( Lue.rivi() ); ok = eka.onKunnossa(); if(!ok) System.out.println("Antamasi tiedoston nimi oli virheellinen"); } while(!ok); do{ System.out.print("Anna toisen tiedoston nimi:"); toka = new Syottotiedosto( Lue.rivi() ); ok = toka.onKunnossa(); if(!ok) System.out.println("Antamasi tiedoston nimi oli virheellinen"); } while(!ok); String rivi; int eroja = 0; while(!( eka.onLoppu() || toka.onLoppu()) ) if( !( eka.lueRivi().equals(toka.lueRivi()) ) ) eroja++; while(eka.onLoppu()==false){ rivi = eka.lueRivi(); eroja++; } while(toka.onLoppu()==false){ rivi = toka.lueRivi(); eroja++; } if(eroja == 0) System.out.println("Tiedostot ovat täsmälleen samanlaiset"); else System.out.println("Tiedostot erosivat " + eroja + ". kohdassa"); } } // Ja toinen kilpaileva versio: public class OvatkoSamat { public static void main(String[] args) { boolean ok; Syottotiedosto eka,toka; do{ System.out.print("Anna ensimmäisen tiedoston nimi:"); eka = new Syottotiedosto( Lue.rivi() ); ok = eka.onKunnossa(); if(!ok) System.out.println("Antamasi tiedoston nimi oli virheellinen"); } while(!ok); do{ System.out.print("Anna toisen tiedoston nimi:"); toka = new Syottotiedosto( Lue.rivi() ); ok = toka.onKunnossa(); if(!ok) System.out.println("Antamasi tiedoston nimi oli virheellinen"); } while(!ok); String ekarivi, tokarivi; int eroja = 0; while ((ekarivi = eka.lueRivi()) != null | (tokarivi = toka.lueRivi()) != null ) { if(ekarivi != null && tokarivi != null) if(!ekarivi.equals(tokarivi)) eroja++; if(ekarivi == null) eroja++; if(tokarivi == null) eroja++; } if(eroja == 0) System.out.println("Tiedostot ovat täsmälleen samanlaiset"); else System.out.println("Tiedostot erosivat " + eroja + ". kohdassa"); } } // Syottotiedostoa ei siis pitänyt toteuttaa. // Se on mukana ainoastaan testausta varten. import java.io.*; /** * Syottotiedosto-luokka avittaa tiedostojen lukemisessa. * * @version 0.1 * @author Jussi Sarkkinen * */ public class Syottotiedosto { private BufferedReader syotto; private String rivi; // "Seuraava rivi" private boolean auki; /** * Konstruktori, joka avaa parametrinaan saamansa tiedoston. * Avaamisen onnistuminen voidaan kysyä onkoAuki()-metodilla. */ public Syottotiedosto(String nimi) { auki = true; try { syotto = new BufferedReader( new InputStreamReader (new FileInputStream(nimi)) ); // Avataan tiedosto } catch (IOException e) { rivi = null; // Tuli virhe... auki = false; } this.rivi=""; this.lueRivi(); } /** * Lukee rivin tiedostosta. * @return Rivi Stringinä, null jos EOF */ public String lueRivi() { if ( !auki ) return null; // Jos tiedosto ei auennut, ei voi lukea. try { String vanharivi=rivi; if (rivi != null) { rivi = syotto.readLine(); return vanharivi; } else return null; } catch (Exception e) { rivi = null; // Virhe riviä luettaessa. return null; } } /** * Tiedoston lopun kyselyä. * @return true: on loppu, false: ei loppu. */ public boolean onLoppu() { return (rivi == null); } /** * Onnistuiko tiedoston avaaminen? * @return true: onnistui, false: ei onnistunut */ public boolean onKunnossa() { return auki; } }