Minulla on Asiakkaat -luokka, joka perii geneerisen Tiedot -luokan. Asiakkaat -luokka lähettää Tiedot -luokalle parametrina Asiakas -olion. Tämä olio toteuttaa luokan, joka sisältää parse niminen metodin, jota pitäisi käyttää Tiedot -luokassa. Sen käyttäminen ei kuitenkaan onnistu, koska eihän Tiedot -luokka voi tietää, että parametrina on saatu olio, joka toteuttaa parse nimisen metodin. Saan siis ilmoituksen "The method parse(StringBuilder) is undefined for the type DATA". Miten saisin käytettyä tuota parse metodia eli kerrottua Tiedot -luokalle, että se saatu olio toteuttaa aina parse nimisen metodin?
public class Asiakkaat extends Tiedot<Asiakas>
public class Tiedot<DATA> { /** * Lukee tiedot tiedostosta * @param kansio josta luetaan * @return Tiedostosta luetut tiedot taulukossa * @throws SailoException jos lukeminen epäonnistuu */ @SuppressWarnings("unchecked") public DATA[] lueTiedostosta(String kansio) throws SailoException { tyhjennaTiedot(); // TODO Lue tiedosto DATA tieto = (DATA)(new Object()); tieto.parse(new StringBuilder("")); lisaa(tieto); return getTiedot(); } }
public class Asiakas { /** * Parsii tiedostosta luetun rivin * @param rivi tiedostosta luettu rivi */ public void parse(StringBuilder rivi) { // TODO Parsi rivi } }
Koko sun kuvauksesi on kyllä aivan jotain käsittämätöntä. Oliot eivät "toteuta" luokkkia. Olio on instanssi luokasta. Luokat toteuttavat rajapintoja ja kantaluokassa abstrakteiksi esiteltyjä funktioita. Tiedot.lueTiedostosta ei edes saa parametrina oliota, toisin kuin väität. Sen sijaan kyseisessä metodissa luodaan Object-olio, jonka sitten mustaa magiaa käyttäen castaat joksikin muuksi. Sinun pitää ymmärtää, että kyseinen olio on edelleen vain Object-tyyppiä eikä jotain muuta.
Sinun täytyy kirjoittaa lueTiedostosta-metodi uusiksi Asiakkaat-luokassa.
No juu tämä terminologia on vielä aika hakusessa. Minun täytyisi siis tehdä tuo lueTiedostosta -metodi jokaisessa Tiedot -luokan perivässä luokassa erikseen, vaikka kyseinen metodi tulisi olemaan täsmälleen samanlainen joka paikassa?
Ja tuon mustan magian käyttäminen opetettiin meillä luennolla, kun Javassa ei voi tehdä
DATA tieto = new DATA();
Tosin esimerkkinä oli kylläkin taulukko, että en tiedä sovelsinko sitä johonkin, jota ei voi tehdä..
// DATA[] tiedot = new DATA[]; DATA[] tiedot = (DATA[])(new Object[]);
Haetko jotain tämän tapaista?
/** */ public interface DataParser { void parse(StringBuilder stringBuilder); } /** */ public class Tiedot<DATA extends DataParser> { private DATA tieto; public Tiedot(DATA tieto) { this.tieto = tieto; } public DATA[] lueTiedostosta(String kansio) { DATA[] parsed = null; //tyhjennaTiedot(); // TODO Lue tiedosto StringBuilder sb = new StringBuilder(); tieto.parse(sb); System.out.println("--> " + sb.toString()); //lisaa(tieto); return parsed; } public static void main(String[] args) { Tiedot<Asiakas> asiakasTiedot = new Tiedot<Asiakas>(new Asiakas()); asiakasTiedot.lueTiedostosta("foo/"); } } /** */ public class Asiakas implements DataParser { @Override public void parse(StringBuilder stringBuilder) { stringBuilder.append("Asiakas parsed data"); } }
Minä en kyllä ymmärrä, mitä tässä edes yritetään tehdä. Meille ainakin opetettiin javan kanssa olioiden tallentaminen tiedostoon javan vakiota serialisointia käyttäen. Se on hyvin simppeli ja ennen kaikkea geneerinen tapa olioiden käsittelyyn. Miksi jokaisen luokan pitäisi toteuttaa oma parse-funktionsa, kun kaikki oliot luetaan samalla tavalla? Nähdäkseni ainoan ongelman pitäisi olla, että miten tiedostosta luetun olion voi castata sen oikeaan tyyppiin (eli esimerkiksi Asiakas-tyyppiseksi). Nythän tämä ap:n antama koodi ei edes yritä lukea mitään yhtään mistään vaan keksii ongelmia kaikesta epäolennaisesta.
The Alchemist, tarkoitus siis olisi, että ensin lukisin kaikkien asiakkaiden tiedot tiedostosta taulukkoon käskyllä asiakkaat.lueTiedosto(kansio);
ja tämän jälkeen voisin esimerkiksi hakea viitteen ensimmäisen asiakkaan tietoihin (olioon) asiakkaat.anna(0);
ja lisätä uuden asiakkaan asiakkaat.lisaa(asiakas);
. Ihan tarkoituksella tuo lueTiedostosta -metodi ei tee vielä mitään.
Juuri tuon tyylistä ratkaisua hain mitä __Pete__ näytti. Koitin soveltaa tuota omiin tarkoituksiini sopivaksi, mutta en vain vieläkään saanut toimimaan.
Nyt Asiakas -luokka perii Tieto -luokan, joka toteuttaa TietoRajapinnan. Saan virheen java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LwbKiti.TietoRajapinta; at wbKiti.Tiedot.<init>(Tiedot.java:16)
Tuo Tiedot -luokan 16. rivi on
private DATA[] alkiot = (DATA[])(new Object[MAX_TIETOJA]);
Kumpikohan on oikein
public class Tiedot<DATA extends Tieto>
vai
public class Tiedot<DATA extends TietoRajapinta>
ja miksi? Vai ovatko molemmat ihan väärin?
Tällä hetkellä viritelmäni näyttää tältä:
public class Tiedot<DATA extends TietoRajapinta> { private static final int MAX_TIETOJA = 5; private int lkm = 0; @SuppressWarnings("unchecked") private DATA[] alkiot = (DATA[])(new Object[MAX_TIETOJA]); /** * Lisää uuden tiedon tietorakenteeseen * @param tieto viite lisättävään tietoon */ public void lisaa(DATA tieto) { if (lkm >= alkiot.length) suurennaAlkiotTaulua(MAX_TIETOJA); alkiot[lkm] = tieto; lkm++; } /** * Lukee tiedot tiedostosta * @param kansio josta luetaan * @return Tiedostosta luetut tiedot taulukossa * @throws SailoException jos lukeminen epäonnistuu */ @SuppressWarnings("unchecked") public DATA[] lueTiedostosta(String kansio) throws SailoException { // TODO lue tiedostosta tyhjennaAlkiot(); if (kansio.equalsIgnoreCase("sunkirppix")) { for (int i = 0; i < 5; i++) { DATA tieto = (DATA)(new Object()); tieto.parse(new StringBuilder("")); lisaa(tieto); } } return getAlkiot(); } }
public class Asiakkaat extends Tiedot<Asiakas>
public class Asiakas extends Tieto
public class Tieto implements TietoRajapinta { private static final int MAX_DATAA = 5; private int lkm = 0; private String[] datat = new String[MAX_DATAA]; private static int seuraavaNro = 1; /** * Parsii tiedostosta luetun rivin datan tiedoiksi * @param rivi tiedostosta luettu rivi */ @Override public void parse(StringBuilder rivi) { // TODO Parsi rivi if (rivi.length() != 0) { while (rivi.length() != 0) { lisaa(Mjonot.erota(rivi, '|', "")); } } } /** * Lisää uuden datan data-tauluun * @param data viite lisättävään dataan */ @Override public void lisaa(String data) { if (lkm >= datat.length) suurennaDatatTaulua(MAX_DATAA); datat[lkm] = data; lkm++; } }
public interface TietoRajapinta { /** * Parsii tiedostosta luetun rivin tiedoiksi * @param rivi tiedostosta luettu rivi */ public void parse(StringBuilder rivi); /** * Lisää uuden datan data-tauluun * @param data viite lisättävään dataan */ public void lisaa(String data); }
Et lukenut viestiäni kunnolla. Minä kysyin, että miksi jokainen tyyppi tarvitsee parse-funktion, koska parsiminen on joka tapauksessa järkevintä toteuttaa universaalilla tavalla vaikkei käyttäisikään javan olioiden serialisointia. Noh, tuo koko viritelmäsi näyttää niin hullulta, etten yhtään osaa arvata tarkoitusperiä.
Eikö se nyt sitten ole jossain määrin "universaali" tapa toteuttaa parse, jos se toteutetaan Tieto -luokassa. Tällöinhän luokat, jotka perivät sen, saavat parsen automaattisesti käyttöönsä?
Asiakas asiakas = new Asiakas(); asiakas.parse(new StringBuilder("1|Matti|Meikäläinen")); Ostos ostos = new Ostos(); ostos.parse(new StringBuilder("3|iPad|400"));
public class Asiakas extends Tieto
public class Ostos extends Tieto
public class Tieto public void parse(StringBuilder rivi)
No jokatapauksessa.. Mistähän tuo virhe java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LwbKiti.TietoRajapinta; at wbKiti.Tiedot.<init>(Tiedot.java:16)
johtuu. Miten saisin tämän viritelmäni toimimaan halutulla tavalla? Mielestäni kyllä selitin tarkoitusperäni edellisen viestini ensimmäisessä kappaleessa enkä kyllä keksi miten selittäisin sen vielä paremmin.
Eivät nämä koodipätkät tässä ehkä näytä kovin järkeviltä, mutta ne sisältävät oikeasti paljon enemmän koodia ja mielestäni kokonaisuutena tämä toimii melko järkevästi. Toki tässä on vielä paljon opettelemista, joten eiköhän tämä tästä pikkuhiljaa muovaudu kuntoon.
AkeMake kirjoitti:
Mistähän tuo virhe java.lang.ClassCastException – – johtuu.
Siihähän lukee selvästi, mistä se johtuu. Yrität muuttaa Object-tyyppistä oliota väkisin TietoRajapinta-tyyppiseksi. Ei niin voi tehdä. Sinun pitää luoda new'llä oikeanlaisia olioita.
Muunnoksia saa käyttää vain silloin, kun varmasti tietää, että olio on oikeasti sitä tyyppiä, joksi sen yrittää muuttaa, yleensä siis näin:
if (x instanceof Tieto) { Tieto y = (Tieto) x; }
En kyllä vieläkään saa tästä kiinni. Meille ei vielä ole opetettu tuota <DATA extends TietoRajapinta> asiaa. Onko tuo siis normaali perintä vai mitä tuossa tapahtuu? Miksi aiemmin pystyin pakottaa tuon Object-tyyppisen olion toiseen muotoon, mutta en enää? Miksen siis voi pakottaa Object-tyyppistä oliota TietoRajapinta-tyyppiseksi? Jos joutuisin luomaan new:llä oikeanlaisia olioita, niin silloinhan menettäisin sen edun, jota nyt tässä havittelen eli että Tiedot-luokka voi käsitellä monen eri tyyppisiä olioita (kunhan ne ovat instansseja luokista, jotka lupaavat toteuttaa saman rajapinnnan).
Ajatus siis olisi, että voisin Tiedot-luokassa käyttää esimerkiksi asiakas- ja ostos-tyyppisiä olioita riippuen siitä kumpaa Tiedot-luokan perivää luokkaa Asiakkaat vai Ostokset käytän. Ja Tiedot-luokka voi aina käsitellä näitä asiakas- ja ostos-tyyppisiä olioita samalla tavalla, koska luokat, joista ne ovat, toteuttavat saman rajapinnan.
Tuntui, että selitin todella sekavasti, joten toivottavasti edes joku tajusi.
TietoRajapinta-tyyppinen olio on toki myös Object-tyyppinen, mutta sun täytyy olla 100% varma kun yrität castata Object-tyyppistä oliota TietoRajapinta-tyyppiseksi, että se on myös TietoRajapinta-tyyppinen. Jos seuraava analogia auttaisi ymmärtämään asian. Koira ja Kissa ovat molemmat Eläin-tyyppisiä, mutta et sä silti pysty Eläin-tyyppistä oliota muuttamaan Koira-tyyppiseksi, jos se on alun perin Kissa-tyyppinen, koska ei kissa voi muuttua koiraksi:
Elain elain1 = new Kissa(); Elain elain2 = new Koira(); Kissa kissa = ( Kissa ) elain1; // Toimii Koira koira = ( Koira ) elain2 // Toimii Kissa kissa1 = ( Kissa ) elain2; // Ei toimi
Edit.
Tämä notaatio <T extends Luokka> meinaa sitä, että T:n tyyppinä voidaan käyttää Luokka tai Luokan aliluokkia.
AkeMake kirjoitti:
Miksen siis voi pakottaa Object-tyyppistä oliota TietoRajapinta-tyyppiseksi?
Koska Object ei ole TietoRajapinta. Kun luot Object-tyyppisen olion, muistista varataan tilaa sen verran, kuin Objectin jäsenet vievät, ja olioon liitetään Object-tyypin metodit. Jos se tuolla tavalla muuttuisi TietoRajapinta-tyyppiseksi, mitä sen jäsenmuuttujissa olisi, missä ne jäsenmuuttujat fyysisesti sijaitsisivat ja minkä luokan TietoRajapinta-metodit olioon liittyisivät?
Tätä viestiketjua lukiessa kyllä ihmettelee nykysuuntausta, että aivan kaikesta pitäisi tehdä luokkia ja objekteja. Niillä on toki käyttönsä, mutta liiallisuuksiin mentäessä, ne mielestäni huonontavat koodin luettavuutta ja uudelleen käyttöä.
Tämän tyyppinen tehtävä missä käsitellään asiakastietoja, tallennetaan, luetaan ja haetaan niitä onnistuu yksinkertaisimmin määrittelemällä tarvittavat asiakastiedot ja kirjoittamalla muutama funktio.
Aihe on jo aika vanha, joten et voi enää vastata siihen.