Manipulació bàsica de dades

El propòsit principal de tot programa d’ordinador, en darrera instància, és processar dades de tota mena. Per assolir aquesta tasca, l’ordinador ha d’emmagatzemar les dades a la memòria, de manera que posteriorment el processador les pugui llegir i transformar d’acord amb els propòsits del programa. Per tant, un conjunt d’instruccions molt important dins de tot llenguatge de programació, i un bon lloc per on començar l’aprenentatge, és el que dóna a la memòria les ordres de desar-les o consultar-les. Un cop ja se saben gestionar dades a la memòria, és el moment de veure algunes de les transformacions bàsiques que permet fer el processador.

El terme dada indica tota informació que fa servir l’ordinador en les execucions dels programes.

Tot i que de vegades s’usa el terme dades, en plural, com si fos quelcom en general, cal tenir en compte que dins d’un programa cada dada que es vol tractar és un element individual i independent. Per exemple, en un programa que suma dos nombres qualssevol hi ha tres dades amb les quals treballa: els dos operands inicials i el resultat final.

D’altra banda, fins al moment s’ha dit que l’ordinador s’encarrega de processar i transformar cadascuna d’aquestes dades individuals, però tampoc no s’ha especificat exactament com es representen o classifiquen els diferents tipus de dades dins del programa, o de quina manera es poden transformar. Ha arribat el moment de concretar una mica més tots aquests detalls.

Tipus de dades

Estrictament parlant, qualsevol informació es pot transformar en dades que pugui entendre i manipular un ordinador. Una dada individual dins del vostre programa pot ser un document de text, una imatge, un plànol d’un edifici, una cançó, etc. Només cal veure la immensa varietat de programes que hi ha avui dia per fer-se’n una idea. Ara bé, un dels punts importants és que dins d’un programa les dades es poden classificar dins de diferents categories, els tipus de dades.

Un tipus de dada és la definició del conjunt de valors vàlids que poden prendre unes dades i el conjunt de transformacions que s’hi pot fer.

Per exemple, en el vostre dia a dia sovint tracteu amb dades que tenen com a característica comuna el fet que es poden representar mitjançant nombres: una distància, una edat, un període de temps, etc. Es pot dir que una ciutat està a 8 km d’una altra, que algú té 30 anys o que han passat 15 dies des d’un esdeveniment. Per tant, els valors 8, 30 o 15 formarien part d’un mateix tipus de dada.

També com a exemple, en contraposició a les dades de tipus numèric, una altra informació que tracteu habitualment és text en paraules: el nom d’una mascota, d’un carrer, d’una institució, etc. En aquest cas, podeu estar parlant d’en “Shiro”, del “Passeig de Gràcia”, de l’“Institut Obert de Catalunya”, etc. En aquest cas, les dades es representen amb símbols diferents i expressen unes altres idees. Per tant, formarien part d’un altre tipus de dada.

Una propietat important per veure si dues dades són de tipus diferents és si el conjunt de valors amb el qual es poden expressar també és diferent. En el dos exemples anteriors, la consideració que es tracta de tipus de dades diferents també es troba suportada per aquest fet. Per exemple, el conjunt de valors que pot prendre el nom d’un carrer o una persona no és qualsevol, ja que no es pot expressar exclusivament mitjançant valors numèrics.

Com que cada dada dins del vostre programa sempre ha de pertànyer a algun tipus, precisament part de la vostra tasca com a programadors és identificar quin és el tipus que s’apropa més a la informació que voleu representar i processar. Establir quin és el tipus d’una dada implica que s’estableixen un conjunt de condicions sobre aquella dada al llarg de tot el programa. Una de les més importants és l’efecte sobre la manera com aquesta dada es representarà, tant internament dins del maquinari de l’ordinador com a l’hora de representar-la en el codi font dels vostres programes. Recordeu que, per la seva naturalesa de sistema digital, totes les dades que hi ha dins d’un ordinador es codifiquen com a seqüències binàries. Quina seqüència de 0 i 1 cal usar per representar el valor de cada dada dependrà del tipus escollit.

Cada llenguatge de programació incorpora els seus tipus de dades propis i, a part, gairebé sempre ofereix mecanismes per definir-ne de nous partint de tipus de dades ja existents. Per tant, com a programadors, només heu de triar entre tots els que ofereix el llenguatge quin s’apropa més a la mena d’informació que voleu tractar. Ara bé, malauradament no es pot garantir que les instruccions bàsiques definides en la sintaxi d’un llenguatge de programació siguin capaces de tractar de manera directa qualsevol dada. Això només succeeix amb un conjunt limitat de tipus de dades molt simples, anomenats tipus primitius.

Els tipus primitius de dades són els que ja estan incorporats directament dins d’un llenguatge de programació, i són usats com a peces bàsiques per construir-ne de més complexos.

Els llenguatges de programació identifiquen cada tipus amb una paraula clau pròpia.

El suport a diferents tipus primitius pot variar també entre llenguatges. De totes maneres, n’hi ha quatre que es pot considerar que, d’una manera o d’una altra, tots els llenguatges els suporten. El motiu és que aquests tipus estan estretament lligats als tipus de seqüències binàries que normalment un ordinador pot processar i representar directament dins del seu maquinari. Es tracta dels nombres enters, els reals, els caràcters i els booleans.

Per començar, n’hi ha prou de veure quina mena de dades representen. Ara bé, per il·lustrar com podem usar dins del codi font d’un programa dades que pertanyen a tipus primitius, començareu treballant amb literals.

Un literal és un text usat per representar un valor fix dins del codi font d’un programa.

El tipus de dada booleà

La paraula clau per identificar aquest tipus de dada en Java és boolean.

El tipus de dada booleà representa un valor de tipus lògic per tal d’establir la certesa o falsedat d’un estat o afirmació.

  • Exemples de literals d’una dada booleana: true (cert) o false (fals). No n’hi ha cap altre.
  • Exemples de dades que se solen representar amb un booleà: interruptor encès o apagat, estar casat, tenir dret de vot, disposar de carnet de conduir B1, la contrasenya és correcta, etc.

Un literal de tipus booleà es representa simplement escrivint el text tal com s’ha descrit a dalt. En un IDE, normalment aquest text queda ressaltat en un color especial perquè quedi clar que s’ha escrit un literal de tipus booleà. Per exemple, el programa següent en Java mostra un seguit de literals d’aquest tipus per pantalla. Proveu-lo al vostre entorn de treball. Recordeu que ha d’estar dins d’un fitxer anomenat LiteralsBoolea, i que el nom no pot tenir accents.

  1. public class LiteralsBoolea {
  2. public static void main (String[] args) {
  3. System.out.println(true);
  4. System.out.println(false);
  5. }
  6. }

El tipus de dada enter

La paraula clau per identificar aquest tipus de dada en Java és int.

El tipus de dada enter representa un valor numèric, positiu o negatiu, sense cap decimal.

  • Exemples de literals enters: 3, 0, -345, 138764, -345002, etc.
  • Exemples de dades que se solen representar amb un enter: edat, dia del mes, any, nombre de fills, etc.

Un literal de tipus enter es representa simplement escrivint un nombre sense decimals. Per exemple, el programa següent en Java mostra un seguit de literals d’aquest tipus per pantalla. Proveu-lo al vostre entorn de treball. Recordeu que ha d’estar dins un fitxer anomenat LiteralsEnter.

  1. public class LiteralsEnter {
  2. public static void main (String[] args) {
  3. System.out.println(3);
  4. System.out.println(0);
  5. System.out.println(-345);
  6. System.out.println(138764);
  7. System.out.println(-345002);
  8. }
  9. }

El tipus de dada real

La paraula clau per identificar aquest tipus de dada a Java és double.

El tipus de dada real representa un valor numèric, positiu o negatiu, amb decimals.

  • Exemples de literals reals: 2.25, 4.0, -9653.3333, 100.0003, etc.
  • Exemples de dades que se solen representar amb un real: un preu en euros, el rècord mundial dels 100 m llisos, la distància entre dues ciutats, etc.

Per referir-se a una dada de tipus real, aquesta inclou sempre els seus decimals amb un punt (.). En els exemples noteu el detall del valor 4.0. Els nombres reals representen valors numèrics amb decimals, però en la seva definició res no impedeix que el decimal sigui 0. Per tant, estrictament, el valor 4 i el valor 4.0 corresponen a tipus de dades diferents. El primer és un valor per a un tipus de dada enter i el segon per a un de real.

Per exemple, el programa següent en Java mostra un seguit de literals d’aquest tipus per pantalla. Proveu-lo al vostre entorn de treball. Recordeu que ha d’estar dins un fitxer anomenat LiteralsReal.

  1. public class LiteralsReal {
  2. public static void main (String[] args) {
  3. System.out.println(2.25);
  4. System.out.println(4.0);
  5. System.out.println(-9653.3333);
  6. System.out.println(100.0003);
  7. }
  8. }

Si voleu saber més sobre les diferències entre la representació d’enters i reals, podeu cercar informació sobre els formats “Complement a Dos” (Two’s Complement) i “IEEE 754”.

Una pregunta que potser us podeu plantejar ara és quin sentit té el tipus enter si amb el tipus real ja podem representar qualsevol nombre, amb decimals i tot. No és una mica redundant? Bé, d’entrada pot semblar que sí, però hi ha un motiu per diferenciar-los. Sense entrar en detalls molt tècnics, representar i fer operacions amb enters internament dins l’ordinador (en binari) és molt més senzill i ràpid que no pas amb reals. A més a més, un enter requereix menys memòria. Evidentment, un programa no serà perceptiblement més lent o més ràpid pel simple fet de fer una operació entre dues dades de tipus real en lloc d’enter, però és un bon costum usar sempre el tipus de dada que s’adapti exactament a les vostres necessitats. Si una dada no té sentit que tingui decimals, com un número d’any o de mes, és millor usar el tipus enter.

El tipus de dada caràcter

La paraula clau per identificar aquest tipus de dada en Java és char.

El tipus de dada caràcter representa una unitat fonamental de text usada en qualsevol alfabet, un nombre o un signe de puntuació o exclamació.

  • Exemples de literals caràcter: ‘a’, ‘A’, ‘4’, '>', '?’, ‘Γ’ (lletra grega gamma majúscula), etc.
  • Exemples de dades que se solen representar amb un caràcter: cadascun dels símbols individuals d’un alfabet.

Per referir-se a una dada de tipus caràcter, aquesta es rodeja de cometes simples ('). Per tant, no és el mateix el caràcter ‘4’ i que el valor enter 4, ja que pertanyen a tipus diferents. El primer l’usareu per referir-vos a una representació textual, mentre que el segon és el concepte pròpiament matemàtic. Aneu amb compte, ja que entre les cometes simples només hi pot haver un sol caràcter, o Java dirà que la sintaxi no és correcta.

Per exemple, el programa següent en Java mostra un seguit de literals d’aquest tipus per pantalla. Proveu-lo al vostre entorn de treball. Recordeu que ha d’estar dins un fitxer anomenat LiteralsCaracter, sense usar cap accent en el nom.

  1. public class LiteralsCaracter {
  2. public static void main (String[] args) {
  3. System.out.println('a');
  4. System.out.println('A');
  5. System.out.println('4');
  6. System.out.println('>');
  7. System.out.println('?');
  8. System.out.println('Γ');
  9. }
  10. }

El Java representa els seus caràcters internament usant la taula UNICODE.

Sistemes de representació dels caràcters

A l’hora de decidir com es representa internament un caràcter, s’utilitzen diferents taules de codis establerts, entre els quals destaquen els codis ASCII i UNICODE. El codi ASCII va ser un dels més estesos, sobretot per la gran proliferació d’ordinadors personals (anomenats en anglès personal computer, PC). El codi ASCII inicial representava 128 caràcters en lloc de 256. Quan es van necessitar caràcters especials per als diferents idiomes, es va ampliar amb 128 caràcters més, i es va constituir el codi ASCII estès.

Malauradament, el codi ASCII té una pega, i és que només permet representar alfabets occidentals, per la qual cosa els programes que l’usen per representar les seves dades de tipus caràcter són incompatibles amb sistemes amb altres alfabets, com tots els asiàtics, el ciríl·lic, etc. Per aquest motiu, posteriorment es va crear la taula de codificació UNICODE, que permet codificar fins a 65.536 caràcters, però mantenint la compatibilitat amb la codificació ASCII. Això permet donar suport a qualsevol llengua actual, i fins i tot d’antigues, com els jeroglífics egipcis. Aquest sistema és el que actualment usen la majoria d’aplicacions modernes.

Transformació de les dades

Dins d’un programa les dades s’acabaran representant usant algun dels quatre tipus primitius. Un cop heu escollit quin tipus usareu per representar una informació concreta, ja heu limitat el conjunt de valors que pot prendre. Ara bé, un altre punt molt important d’aquesta decisió és que també haureu fixat de quina manera és possible transformar-les. Per a cada tipus de dada, els seus valor només es pot transformar d’unes maneres molt concretes: mitjançant el que formalment es coneix com a operacions. Cada tipus de dada estableix el conjunt d’operacions admissibles sobre els valors que comprèn.

Un llenguatge de programació que només permet operacions entre dades del mateix tipus s’anomena fortament tipat. El Java ho és.

Normalment, només es poden fer operacions entre dades que pertanyin al mateix tipus. Com se sol dir, no es poden sumar pomes i taronges. Ara bé, sí que es pot donar el cas que una operació entre dos tipus de dades iguals doni com a resultat una dada d’un tipus diferent. Per exemple, no podreu sumar un nombre enter (el nombre 3, per exemple) amb un booleà (el valor true, per exemple). D’entrada, no tindria cap sentit.

Aplicar operacions sobre diverses dades us permet crear nous valors. Les operacions es divideixen en dos tipus: unàries o binàries, segons el nombre d’operands que usen (un o dos). La figura presenta un esquema de com les operacions ens permeten crear noves dades.

Figura Esquema d’aplicació d’operadors i operands per generar noves dades

El símbol com s’identifica cadascuna de les operacions dins d’un tipus de dada és el seu operador. Les dades sobre les quals s’aplica una operació són els seus operands.

Operacions entre booleans

Aquest tipus de dada deu el seu nom a l’àlgebra de Boole, que és el conjunt de normes i operacions que regeixen de quina manera es poden combinar els valors true i false.

Les taules de veritat són una representació que ens permet esbrinar, amb claredat i fiabilitat, tots els resultats possibles d’una operació a partir de totes les combinacions dels valors possibles dels operands.

Les operacions bàsiques que es poden fer sobre dades d’aquest tipus són de dos tipus: lògiques i relacionals. Els operadors lògics que es poden aplicar són la negació (!), la conjunció (&&) i la disjunció (||). Per a aquest tipus d’operacions, una manera senzilla de plasmar el seu resultat en ser aplicats sobre dades de tipus booleà és usar una taula de veritat.

La taula resumeix el resultat d’aplicar les diferents operacions lògiques. La negació té la particularitat que només s’aplica sobre una única dada.

Taula: Taula de veritat de les operacions booleanes
Operands Resultats de l’operació
A B A && B A || B ! A
false false false false true
true false false true false
false true false true true
true true true true false

Si es vol fer un resum de la taula, es veu que:

  • La negació dóna com a resultat el valor contrari.
  • La conjunció és certa si totes dos operands són certs.
  • La disjunció és certa si algun dels operands ho és.
  • La disjunció exclusiva és certa només si un dels operands és cert, però no si ho són tots dos.

Vist amb un exemple més concret, les operacions lògiques segueixen el raonament següent. Suposeu que A i B són interruptors que poden estar encesos (true) o no (false). La negació és com commutar un interruptor. Si estava encès passa a estar apagat i viceversa. La conjunció és com preguntar “És cert que els dos interruptores estan encesos?”. Només cal que un dels dos estigui apagat perquè la resposta sigui que no (false). La disjunció és com preguntar “És cert que algun dels dos interruptors està encès?”. Si un dels dos, el que sigui, ho està, la resposta serà si (true).

Respecte als operadors relacionals, aquests es refereixen a les operacions igual (==) i diferent (!=) , aplicats tal com indica la taula.

Taula: Taula de veritat de les operacions relacionals entre booleans
Operands Resultats de l’operació
A B A == B A != B
false false true false
true false false true
false true false true
true true true false

En aquest cas, si es torna a l’exemple dels interruptors A i B, l’igual és equivalent a preguntar “És cert que els dos interruptors estan en el mateix estat?” (els dos apagats o els dos encesos). L’operació diferent seria el cas contrari: “És cert que un està un apagat i l’altre encès?”.

El programa següent mostra, a mode d’exemple, el resultat de fer diverses operacions entre dades de tipus booleà. Us pot servir per veure més clarament els resultats expressats a les taules de veritat anteriors.

  1. public class OperacionsBoolea {
  2. public static void main (String[] args) {
  3. //Operacions lògiques: el resultat és un booleà
  4. System.out.println(!true);
  5. System.out.println(true && true);
  6. System.out.println(true && false);
  7. System.out.println(true || false);
  8. System.out.println(false || false);
  9. //Operacions relacionals: el resultat també és un booleà
  10. System.out.println(false == false);
  11. System.out.println(false != true);
  12. }
  13. }

Operacions entre enters

Les operacions bàsiques que es poden fer sobre dades d’aquest tipus són de dos tipus: aritmètiques i relacionals.

Els operadors aritmètics generalment suportats són el canvi de signe (-, operador unari), la suma (+), resta (-, operador binari), multiplicació (*) i divisió (/). Qualsevol operació entre dues dades de tipus enter sempre dóna com a resultat una nova dada també de tipus enter. Ara bé, com que els nombres enters no disposen de decimals, cal tenir present que, en el cas de la divisió, el resultat es trunca i es perden tots els decimals. En alguns llenguatges de programació també hi ha una cinquena operació anomenada mòdul (%). El resultat d’aplicar-la sobre dos enters retorna la resta de l’operació de divisió, tal com esquematitza la figura.

Figura Esquema del funcionament de les operacions de divisió i mòdul d’enters

La taula mostra alguns exemples de resultats de l’operació divisió i mòdul en dades de tipus enter.

Taula: Exemples de divisió i mòdul entre enters
Operands Resultat de les operacions
A B A / B A % B
17 4 4 , en lloc de 4.25 1
9 5 1 , en lloc de 1.8 4
23 10 2 , en lloc de 2.3 3

Els operadors relacionals inclouen l’igual (==), diferent (!=), major que (>), menor que (<), major o igual que (>=) i menor o igual que (). En aquest cas, el resultat de dur a terme aquesta operació és una dada de tipus booleà (cert/fals). La taula en mostra alguns exemples.

Taula: Exemples d’operacions relacionals amb enters
Operands Resultat de les operacions
A B A == B A > B A < B A >= B A ≤ B
4 3 false true false true false
14 -2 false true false true false
-78 34 false false true false true
12 12 true false false true true

El programa següent mostra, a mode d’exemple, el resultat de fer diverses operacions entre dades de tipus enter.

  1. public class OperacionsEnter {
  2. public static void main (String[] args) {
  3. //Operacions aritmètiques: el resultat és un enter
  4. System.out.println(3 + 2);
  5. System.out.println(4 - 10);
  6. System.out.println(3 * 8);
  7. System.out.println(10 / 3);
  8. System.out.println(10 % 3);
  9. //Operacions relacionals: el resultat és un booleà
  10. System.out.println(4 == 4);
  11. System.out.println(5 > 6);
  12. System.out.println(7 < 10);
  13. }
  14. }

Operacions entre reals

Les operacions que es poden fer entre dades de tipus real són exactament les mateixes que entre enters, tant aritmètiques com relacionals. L’única excepció és l’operació mòdul, que deixa de tenir sentit, ja que ara la divisió sí que es fa amb càlcul de decimals. La taula mostra com és la divisió entre dades de tipus real.

Taula: Exemples de divisió entre reals
Operands Resultat de l’operació
A B A / B
17.0 4.0 4.25
9.0 5.0 1.8
23.0 10.0 2.3

El programa següent mostra, a mode d’exemple, el resultat de fer diverses operacions entre dades de tipus real.

  1. public class OperacionsReal {
  2. public static void main (String[] args) {
  3. //Operacions aritmètiques: el resultat és un enter
  4. System.out.println(8.5 + 3.2);
  5. System.out.println(5.66 - 3.1);
  6. System.out.println(3.1 * 8.4);
  7. System.out.println(17.0 / 4.0);
  8. //Operacions relacionals: el resultat és un booleà
  9. System.out.println(1.2 == 1.2 );
  10. System.out.println(-4.3 > -12.45);
  11. System.out.println(3.14 < 5.126);
  12. }
  13. }

Operacions entre caràcters

Les dades d’aquest tipus només accepten operacions relacionals que els permetin comparar-los entre ells, de manera similar als enters o els reals: igual (==), diferent (!=), més gran que (>), menor que (<), més gran o igual que (>=) i menor o igual que (). En els casos de les operacions que inclouen “més gran que” o “menor que”, se’n considera l’ordre en la taula emprada per representar-los a l’ordinador.

El programa següent mostra, a mode d’exemple, el resultat de fer diverses operacions entre dades de tipus caràcter.

  1. public class OperacionsCaracter {
  2. public static void main (String[] args) {
  3. //Operacions relacionals: el resultat és un booleà
  4. System.out.println('a' == 'a');
  5. System.out.println('a' != 'a');
  6. System.out.println('z' > 'w');
  7. System.out.println('k' < 'b');
  8. }
  9. }

Construcció d'expressions

Com s’ha vist, donat un conjunt de dades d’un tipus concret, el mecanisme principal per transformar-les i obtenir-ne de noves és l’aplicació d’operacions entre diversos operands. Fins al moment, però, els exemples d’aplicació d’operacions s’han limitat a una única operació, amb el nombre d’operands corresponents segons si aquesta era unària o binària (un o dos). Ara bé, en molts casos, és útil poder aplicar d’una sola vegada un conjunt d’operacions diferents sobre un seguit de dades.

Una expressió és una combinació qualsevol d’operadors i operands.

Per construir una expressió, cal que aquesta sigui correcta en dos nivells, sintàcticament i semànticament, de manera que es respecti el significat de les dades usades com a operands i els seus operadors. En qualsevol cas, les expressions sempre s’escriuen en una sola línia, com qualsevol text llegible en català, ordenant els elements d’esquerra a dreta i de dalt a baix.

Des del punt de vista sintàctic, les normes bàsiques de construcció d’expressions usant literals són les següents:

  1. Es considera que un literal sol és en si mateix una expressió.
  2. Donada una expressió correcta E, també ho és escriure-la entre parèntesis: (E).
  3. Donada una expressió correcta E i un operador unari qualsevol op, opE és una expressió correcta.
  4. Donades dues expressions correctes E1 i E2 i un operador binari qualsevol op, E1 op E2 és una expressió correcta.

Aquestes normes són un cas general vàlid per representar qualsevol combinació de literals i operacions, sigui quina sigui la llargària. Exemples d’expressions que les segueixen, mostrades de manera que s’apliquen de manera acumulativa, són:

  • 6, per a la primera regla.
  • (6 + 5), per a la segona regla, en què E és 6 + 5.
  • -4, per a la tercera regla, en què op és - i E és 4.
  • 6 + 5, per a la quarta regla, en què E1 és 6, E2 és 5 i op és +.
  • (6 + 5) * -4, per a la quarta regla, en què E1 és (6 + 5), E2 és -4 i op és *.

Des del vessant semàntic, les normes que sempre ha de complir una expressió són les següents:

  1. Qualsevol operació sempre ha de ser entre dades del mateix tipus.
  2. L’operació usada ha d’existir per al tipus de dada.

El primer punt és molt important i cal tenir cura en aplicar-lo. Per exemple, si partim de literals, escriure un 6 correspon al literal que representa el valor numèric “sis” dins del tipus de dada enter. El text 6.0 és el mateix però per als reals, i ‘6’ (noteu les cometes simples) per als caràcters. Ara bé, tot i que representen el mateix concepte, no es poden fer operacions entre aquests tres literals, ja que pertanyen a tipus diferents. Tanmateix, noteu com amb petits detalls d’escriptura es pot establir a quin tipus de dada pertany cada literal dins del vostre codi font directament. De fet, tots els exemples de valors possibles donat un tipus de dada, mostrats a la secció anterior, són literals.

Les expressions següents són correctes sintàcticament, però no semànticament. Per tant, no es consideren expressions vàlides.

  • 5.3 == ‘4’ : tot i que reals i caràcters disposen de l’operació igualtat, els dos literals pertanyen a tipus diferents (primera regla).
  • true + false, l’operació suma no existeix per als booleans (segona regla).
  • -‘g’, l’operació canvi de signe no existeix per als caràcters (segona regla).
  • 5 == false, tot i que en booleans i enters existeix la igualtat, l’operació és entre tipus diferents (primera regla).
  • 5 || 4.0, es fa una operació entre tipus diferents i, a part, la disjunció no existeix en els enters ni en els reals (regla primera i segona).

Avaluació d'expressions

Un literal individual entre parèntesis és equivalent al mateix literal sense parèntesis.

Entenem com a avaluar una expressió anar aplicant tots els seus operadors sobre les diferents dades que la conformen fins arribar a un resultat final, que és la dada resultant. L’avaluació sempre es comença calculant les expressions que es troben entre parèntesis, en ordre de més intern a més extern. Un cop han desaparegut tots els parèntesis, llavors s’apliquen les operacions segons el seu ordre de precedència.

L’ordre de precedència d’un conjunt d’operadors és la regla usada per establir de manera no ambigua l’ordre com s’han de resoldre les operacions dins d’una expressió.

Les operacions amb ordre de precedència major s’avaluen abans que les d’ordre menor. Per a les operacions que s’han descrit fins ara, aquest ordre és el següent. En cas d’empat, es resol l’expressió ordenadament d’esquerra a dreta. La taula taula mostra aquest ordre, de més prioritari (1) a menys (7).

Taula: Ordre de precedència dels operadors
Ordre Operació Operador
1 Canvi de signe -(unari)
2 Producte, divisió i mòdul * / %
3 Suma i resta + -
4 Relacionals de comparació > < <= >=
5 Relacionals d’igualtat == !=
6 Negació ! (unari)
7 Conjunció &&
8 Disjunció ||

Tot i haver-hi aquest ordre, és molt recomanable que totes les expressions basades en un operador binari sempre s’incloguin entre parèntesis quan s’han de combinar amb nous operadors per generar expressions complexes, amb vista a millorar la llegibilitat de l’expressió general.

A la figura es mostra el procés d’avaluació d’una expressió de certa complexitat, que és la que teniu al programa següent:

  1. //Comprova una expressió complexa
  2. public class ProvaExpressio {
  3. public static void main(String[] args) {
  4. System.out.println(((3 + 4) == 7 )&&!(12.3 > 2.11)||('a' == 'b'));
  5. }
  6. }
Figura Esquema d’avaluació d’una expressió d’exemple

Fixeu-vos atentament com en aquesta expressió apareixen literals de tipus de dades totalment diferents. Tot i així, sempre que s’aplica una operació binària, sempre és entre dades del mateix tipus. De fet, el resultat d’avaluar-la és una dada de tipus booleà, però en lloc de l’expressió original hi havia dades booleanes.

Repte 1: feu un programa que avaluï una expressió que contingui literals dels quatre tipus de dades (booleà, enter, real i caràcter) i la mostri per pantalla.

Desbordaments i errors de precisió

Un fet molt important que heu de tenir en compte quan avalueu expressions aritmètiques entre dades de tipus numèric (enters i reals), és que aquests tipus tenen una limitació en els valors que poden representar. En haver-hi infinits nombres, això vol dir que per representar-los tots dins de l’ordinador caldrien seqüències binàries d’infinita llargària, cosa impossible. Per tant, els llenguatges de programació només poden representar un rang concret en tots dos casos. Si mai se supera aquest rang, es diu que heu patit un desbordament (overflow) i el resultat de qualsevol expressió serà sempre incorrecte.

En el cas dels reals, a més d’haver-n’hi en nombre infinit, hi ha la particularitat que, donat un nombre real, aquest pot tenir infinits decimals. Això significa que tampoc no és possible representar qualsevol seqüència de decimals amb un nombre real. Per a certs valors en realitat es fan arrodoniments. La conseqüència directa d’aquest fet és que en fer càlculs amb dades reals poden aparèixer errors de precisió. Ho haureu de tenir en compte en programes de càlcul complexos, en què cada decimal és important.

Tenint en compte la circumstància que el rang de dades que es pot representar usant tipus numèrics està fitat, molts llenguatges de programació en realitat divideixen els tipus enters i reals en diferents categories, cadascuna definida amb una paraula clau diferent. Cadascuna d’aquestes categories es considera un nou tipus primitiu diferent. El tret distintiu per a cada cas és el rang de valors que pot assolir i la llargària de la seva representació interna en binari (nombre de bits).

Per al llenguatge Java, la taula us mostra quins tipus primitius hi ha, preveient aquesta circumstància. Hi podeu apreciar quin és el rang acceptable per a cada cas i la seva mida. Més enllà d’aquests rangs, ja no és possible operar amb tipus primitius; calen solucions més complexes.

Els literals que usa Java per defecte, i els que s’han usat fins ara per descriure dades de tipus enter i real es corresponen als tipus enter simple (int) i real de doble precisió (double). Per tant, a partir d’ara, sempre que es parli de tipus de dades enters i reals en general, el text es referirà a aquests dos tipus primitius en concret.

Taula: Rangs i paraules clau dels tipus primitius numèrics en Java
Tipus Paraula clau Java Mida (bits) Rang
byte byte 8 -128 a 127
enter curt short 16 -32768 a 32767
enter simple int 32 -2147483648 a 2147483648
enter llarg long 64 -9,223,372,036,854,775,808 a 9,223,372,036,854,775,808
real de simple precisió float 32 -3.40292347*10^38 a 3.40292347*10^38
real de doble precisió double 64 -4.94065645841246544*10^24 a 1.79769313486231570*10^308

En Java, un float pot representar fidelment fins a 6 o 7 decimals. Un double fins a uns 15.

Si es vol indicar que un literal és d’un altre tipus diferent d’aquests dos, cal indicar-ho explícitament afegint al final el caràcter ‘L’, per escriure un long, o ‘F’, per un float. En Java no es pot explicitar que un literal és un enter curt. Per exemple:

  • 5400000000L
  • 3.141592F

Sempre que s’avalua una operació binària en què apareixen literals numèrics de tipus diferent, el tipus del resultat serà el mateix que l’operand amb més rang.

Per exemple, el resultat d’avaluar l’expressió (2 + 5) * 40L és un enter de tipus long (concretament, 280L).

Repte 2: en el vostre entorn de treball, creeu el programa següent. Observeu què passa exactament. Llavors, intenteu arreglar el problema.

  1. //Un programa que usa un enter moooolt gran
  2. public class TresMilMilions {
  3. public static void main (String[] args) {
  4. System.out.println(3000000000);
  5. }
  6. }

Gestió de dades a la memòria

Fins al moment s’ha treballat amb literals dins del codi font dels programes. Ara bé, aquesta només és la punta de l’iceberg a l’hora d’operar amb dades dins d’un programa. Si us limiteu a això, la funció de l’ordinador no va més enllà de ser una simple calculadora més complicada d’usar. De fet, els literals només són suficients en els casos que us cal representar dades amb un valor conegut en el moment d’escriure el programa i que mai variarà al llarg de l’execució, per a cap de les execucions.

Ara bé, en molts altres casos, el valor de les dades que es volen tractar dins d’un programa o bé és desconegut (per exemple, es tracta precisament del resultat que s’està intentant calcular o dependrà d’una resposta de l’usuari) o bé el valor anirà variant al llarg del programa (per exemple, el preu total per pagar en una botiga virtual quan encara no s’ha finalitzat la compra). Potser fins i tot en coneixeu el valor inicial (en una botiga virtual, el preu quan no s’ha comprat res encara segur que és zero), però no se’n pot predir l’evolució. Un altre cas que us podeu trobar és que vulgueu desar les dades resultants d’avaluar una expressió per usar-la més endavant, en un o diversos llocs dins del programa. En cap d’aquests casos no es poden usar literals i el que cal usar és una variable emmagatzemada dins de la memòria de l’ordinador.

Una variable és una dada emmagatzemada a la memòria que pot veure modificat el seu valor en qualsevol moment durant l’execució del programa.

Com funciona la memòria de l'ordinador

Abans de veure com podem usar variables, val la pena fer un repàs dels aspectes més importants del funcionament de la memòria de l’ordinador des del punt de vista de com la usareu a l’hora de fer un programa. Això us oferirà una certa perspectiva sobre alguns dels aspectes més importants en l’ús de les variables.

Suposeu que esteu a l’escola primària i que un bon dia el professor us fa sortir a la pissarra i us diu que haureu de resoldre una suma de 4 nombres enters de 8 xifres. Primer de tot, el que fareu és cercar un tros de pissarra que estigui lliure per poder escriure-hi vosaltres, que no impliqui haver d’esborrar aquelles coses que el professor ha escrit abans. Concretament, us cal prou espai per poder escriure els 4 nombres que us dictarà el professor i per poder escriure la solució final. Si no us veiéssiu capaços de calcular-ho tot de memòria, també podríeu cercar espais per poder-hi fer càlculs auxiliars.

Després, a mesura que el professor us va dictant els nombres, vosaltres els aneu escrivint a la pissarra per no oblidar-los. Un cop els teniu tots apuntats, ja podeu procedir a fer les operacions pertinents i veure si us en sortiu o no. Mentre feu els càlculs, podeu anar escrivint, si us va bé, nous nombres que representen càlculs parcials, sempre que tingueu espai a la pissarra. També podeu esborrar o modificar en tot moment la informació que heu escrit a la pissarra. Finalment, amb l’ajuda de totes aquestes dades, obteniu el resultat final. Un cop dieu el resultat final, i el professor us diu si és correcte o no, esborreu la part de la pissarra on heu escrit per deixar espai als vostres companys i torneu al vostre lloc.

Conceptualment, la memòria de l’ordinador tal com la faria servir un programa no és gaire diferent de vosaltres i d’aquesta pissarra. És un espai disponible on els diferents programes poden desar lliurement dades per poder dur a terme la seva tasca. Ara bé, els dos punts més importants que aquest símil té en comú amb la memòria d’un ordinador, i que heu de tenir en compte, són:

  • La memòria és un espai compartit entre tots els programes que es troben en execució, com el sistema operatiu. Per tant, abans de poder emmagatzemar cap dada, cal poder cercar un espai buit que no s’estigui usant.
  • Les dades emmagatzemades no són persistents, només existeixen mentre el programa està resolent la tasca en curs. En acabar, s’esborren. En el cas d’un programa, les seves dades només estan presents a la memòria mentre aquest està en execució. Quan acaba, les dades desapareixen totalment. L’única manera d’evitar-ho és que, abans d’acabar l’execució, siguin traspassades a un medi persistent (per exemple, en el cas de la pissarra, a un full de paper, i en el cas de l’ordinador, a un fitxer).

Ara bé, per la natura de sistema digital d’un ordinador, la manera com la seva memòria organitza totes les dades contingudes no és exactament com una pissarra. També hi ha dos aspectes en què el símil divergeix i que cal tenir ben presents:

Cada cel·la individual s’indexa amb un identificador numèric anomenat la seva adreça.

  • Recordeu que les dades es representen en format binari quan es troben dins de l’ordinador. Un ordinador només pot representar internament la informació en zeros i uns, d’acord amb l’estat dels transistors dels seus xips.
  • De fet, les dades s’organitzen en cel·les dins de la memòria, d’una manera més aviat semblant a un full de càlcul. Cadascuna pot contenir 8 bits d’informació. Ara bé, una dada pot ocupar més d’una cel·la i el nombre exacte que n’ocupa depèn del seu tipus i del llenguatge usat.

Declaració de variables

El llenguatge de programació ja s’encarrega internament de tots els aspectes de nivell baix vinculats a l’estructura real de la memòria.

De la descripció del funcionament de la memòria, el punt més important és que per poder disposar d’una variable dins del vostre programa, primer s’ha de cercar un espai lliure a la memòria, compost de diverses cel·les, que serà assignat a aquesta variable de manera exclusiva. Afortunadament, els llenguatges de programació de nivell alt ofereixen un sistema relativament senzill per fer-ho.

Tota variable dins del codi font d’un programa ha d’haver estat declarada prèviament pel programador abans de poder fer-la servir.

Per declarar una variable dins el vostre programa, només cal especificar tres coses: el tipus, un identificador o etiqueta únic i un valor inicial. Tot i que la sintaxi per declarar una variable pot variar segons el llenguatge de programació, gairebé sempre cal definir aquestes tres coses.

L’identificador ens permet diferenciar les diferents variables que hi hagi dins del codi font del programa, el tipus delimita quins valors pot emmagatzemar i quines operacions són aplicables, i el valor inicial el contingut que hi haurà tan bon punt s’ha declarat. Evidentment, el valor inicial ha de ser una dada del mateix tipus que la variable. Un cop completada correctament la declaració, immediatament després d’aquella línia de codi, és possible usar des de llavors en endavant l’identificador escollit per llegir el valor de les dades emmagatzemades o sobreescriure-les.

La importància d'inicialitzar les variables

No tots els llenguatges requereixen assignar un valor inicial a una variable. En aquests casos, simplement no es pot predir quin és el valor emmagatzemat a la memòria (la seqüència de zeros i uns en aquell espai). Pot ser qualsevol valor, per la qual cosa el resultat de qualsevol operació en què intervingui aquesta variable serà impredictible. Per aquest motiu, tot i que el compilador accepti la sintaxi com a correcta si no es fa, és un hàbit molt bo inicialitzar sempre les variables.

En cas de dubte, sempre podeu assignar el 0 com a valor inicial.

La sintaxi per declarar una variable en una línia de codi font Java és la següent. Recordeu que les paraules clau de Java per als quatre tipus primitius presentats són boolean, int, double i char:

  1. paraulaClauTipus identificadorVariable = valorInicial;

Estrictament, es considera que la part a l’esquerra del signe igual és la declaració pròpiament de la variable, mentre que la part dreta és la inicialització: l’especificació de quin és el valor inicial.

La manera més senzilla de veure-ho és mitjançant un exemple concret:

  1. //El programa que només declara una variable de tipus enter
  2. public class DeclararEnter {
  3. public static void main(String[] args) {
  4. int elMeuEnter = 2;
  5. //Ara hi ha una variable en memòria que conté el valor enter 2.
  6. }
  7. }

Tot just després d’executar aquesta instrucció, la memòria quedaria com mostra la figura. En aquest cas, aquesta variable ocupa dues cel·les, ja que Java emmagatzema els enters a la memòria usant 32 bits. En qualsevol cas, aquest fet és totalment transparent per al programador.

Figura Memòria de l’ordinador en declarar la variable elMeuEnter

També és possible declarar més d’una variable del mateix tipus de dada en una sola línia separant els identificadors i inicialitzacions amb comes. Per exemple:

  1. //El programa que declara de cop dues variables de tipus enter
  2. public class DeclararEnter {
  3. public static void main(String[] args) {
  4. int elMeuEnter = 2, elMeuAltreEnter = 4;
  5. //Ara hi ha dues variables a la memòria
  6. //Contenen els valors enters 2 i 4, respectivament.
  7. }
  8. }

Identificadors

Col·loquialment, per referir-se a un identificador també se sol usar el terme nom. Exemple: “El nom d’una variable”.

Un identificador pren la forma d’un text arbitrari. Podeu triar el que més us agradi. De totes formes, és molt i molt recomanable que sempre useu algun text que us ajudi a entendre fàcilment què s’està emmagatzemant a cada variable, ja que això ajuda a entendre què fa el codi font del vostre programa. Quan un programa és llarg o fa molt que no s’ha editat, fins i tot pot ser problemàtic per al seu propi creador tornar a entendre per a què servia cada variable.

Noteu les diferències entre els dos codis següents. Tot i que el codi de declaració de variables és totalment equivalent (es declaren tres variables de tipus real), segur que en un us resulta més fàcil saber què fa el programa i quin paper té cada variable en el procés sense ni tan sols haver de conèixer la resta de les instruccions.

  1. //Programa A. Un codi poc entenedor.
  2. public class DivideixISuma {
  3. public static void main(String[] args) {
  4. double x = 20.0;
  5. double y = 6.0;
  6. double z = 3.0;
  7. //Ara vindria la resta del codi
  8. ...
  9. }
  10. }
  1. //Programa B. Un codi més entenedor.
  2. public class DivideixISuma {
  3. public static void main(String[] args) {
  4. double dividend = 20.0;
  5. double divisor = 6.0;
  6. double sumarAlFinal = 3.0;
  7. //Ara vindria la resta del codi
  8. ...
  9. }
  10. }

No tingueu mandra d’usar identificadors de variables entenedors, encara que siguin una mica més llargs. Evidentment, la clau de tot plegat sempre està en l’equilibri. Usar un identificador que ocupi cent lletres i no es pugui llegir en una sola línia de text tampoc no ajuda gaire.

Malauradament, no qualsevol identificador es considera vàlid. Cada llenguatge de programació imposa unes condicions sobre el format que considera admissible. Tot i que aquí s’explica el cas concret del llenguatge Java, no hi ha gaires variacions entre els diferents llenguatges. Un identificador:

  • No pot contenir espais.
  • No pot començar amb un nombre.
  • Es desaconsella usar accents.
  • No pot ser igual que alguna de les paraules paraules clau del llenguatge.

Una paraula clau (o reservada) d’un llenguatge de programació és aquella que té un significat especial dins la seva sintaxi i s’usa per compondre certes parts o instruccions en el codi font d’un programa.

El conjunt de paraules reservades en Java s’enumera tot seguit:

abstract, continue, for, new, switch, assert, default, goto, package, synchronized, boolean, do, if, private, this, break, double, implements, protected, throw, byte, else, import, public, throws, case, enum, instanceof, return, transient, catch, extends, int, short, try, char, final, interface, static, void, class, finally, long, strictfp, volatile, const, float, native, super, while

Fixeu-vos que en aquesta llista hi ha algunes paraules que ja heu vist, usades per declarar variables o especificar on comença el codi font o el mètode principal del programa: boolean, int, double, char, public, class, void, etc.

Convencions de nomenclatura

Tot i que teniu llibertat total per triar els identificadors de les variables, igual que passa amb els noms de les classes de Java, hi ha una convenció de codi per anomenar-los en Java. En aquest cas cal usar lowerCamelCase (notació de camell minúscula). Aquesta notació és com UpperCamelCase, però en aquest cas la primera paraula sempre va en minúscula i no en majúscula.

Exemples d’identificadors de variables que segueixen la convenció són: divisor, resultatDivisio, elMeuEnter, etc.

Finalment, recordeu que el Java és sensible a majúscules i minúscules, per la qual cosa elMeuEnter i elmeuEnter es consideren identificadors totalment diferents.

Ús de variables

La principal vàlua d’una variable com a peça fonamental dins d’un programa és la seva capacitat per emmagatzemar dades i recuperar-ne el valor en qualsevol moment al llarg del codi font del programa, un cop declarades. La recuperació de les dades emmagatzemades és el mecanisme més directe.

Quan una variable ha estat declarada, el seu identificador es pot usar exactament igual que un literal dins de qualsevol expressió. El valor que representarà serà el del valor emmagatzemat en memòria en aquell instant per a aquella variable.

En el cas del Java, el delimitador de fi de bloc és la clau }.

Ara bé, cal tenir present que en la majoria de llenguatges de programació la declaració d’una variable només té validesa des de la línia de codi on es declara fins a trobar el delimitador que marca el final del bloc de codi on s’ha declarat. Fora d’aquest rang d’instruccions del codi font, el seu àmbit, és com si no estigués declarada.

L’àmbit d’una variable és el context sota el qual es considera declarada.

Si s’intenta fer ús d’un identificador que no correspon a cap variable declarada (ja sigui perquè mai no s’ha declarat o perquè se’n fa ús fora del seu àmbit), en compilar el codi font es produirà un error. Cal dir que l’ús de variables fora del seu àmbit no és un problema que ara mateix us hagi d’amoïnar gaire, ja que els programes que s’estan tractant es componen d’un únic bloc que engloba totes les instruccions (el mètode principal).

El programa següent divideix dues dades de tipus real i després en suma una tercera usant variables en lloc de literals:

  1. //Un programa que calcula una divisió i una suma, completat
  2. public class DivideixISuma {
  3. public static void main(String[] args) {
  4. double dividend = 20.0;
  5. double divisor = 6.0;
  6. double sumarAlFinal = 3.0;
  7. //Les variables es poden usar com a literals dins una expressió.
  8. //Aquí és equivalent a fer (20.0/6.0) + 3.0
  9. System.out.println((dividend/divisor) + sumarAlFinal);
  10. }
  11. }

En aquest exemple l’ús de les variables és correcte, ja que s’accedeix al seu identificador en una línia de codi que es troba entre la línia on s’han declarat i la clau de final de bloc del mètode principal. L’ús s’ha fet dins el seu àmbit.

Ara bé, l’ús de variables no té gaire sentit quan per fer una operació és podrien usar literals directament i el resultat seria el mateix, com en l’exemple anterior. La vertadera utilitat consisteix a poder canviar el valor que hi ha emmagatzemat en memòria. El valor d’una variable es pot modificar en qualsevol moment dins d’un programa amb l’operador d’assignació (=):

  1. identificadorVariable = expressió;

Aquesta sintaxi ja l’havíeu vista, ja que és pràcticament igual a la usada per establir el valor inicial en declarar la variable. De fet, una variable també es pot inicialitzar a partir d’una expressió, no cal usar un únic literal.

Si el tipus del resultat de l’expressió i el de la variable en què s’assigna no és el mateix hi haurà un error de compilació.

En fer una assignació a una variable, el primer que succeeix és que s’avalua l’expressió que hi ha a la banda dreta. El resultat obtingut llavors es converteix immediatament en el nou valor emmagatzemat dins de la variable. El valor anterior es perd per sempre, ja que queda sobreescrit. Ara bé, recordeu que l’operador d’assignació (=), com qualsevol altre operador binari, sempre requereix que els seus operands pertanyin al mateix tipus de dada. Per tant, només és correcte assignar expressions que avaluïn un tipus idèntic a l’utilitzat en declarar la variable.

Alerta. No és el mateix l’operador d’assignació (=) que el d’igualtat (==). Cal no confondre’ls.

És molt important que sigueu conscients de l’ordre per resoldre una assignació. Primer cal avaluar l’expressió i després realment modificar el valor de la variable, ja que dins d’una expressió es pot usar l’identificador de la mateixa variable a la qual s’està fent l’assignació! Per tant, a la banda dreta s’usa el seu valor original, i un cop feta l’assignació, aquesta s’haurà modificat d’acord amb el resultat d’avaluar l’expressió.

La millor manera de veure el comportament d’una variable quan se’n modifica el valor és amb un exemple:

  1. //Un programa que calcula una divisió i una suma, però a poc a poc
  2. public class DivideixISumaDetallat {
  3. public static void main(String[] args) {
  4. double dividend = 20.0;
  5. double divisor = 6.0;
  6. double sumarAlFinal = 3.0;
  7. double resultat = 0.0;
  8. //El resultat serà una variable que s'anirà modificant.
  9. resultat = dividend/divisor;
  10. //Una expressió que usa la mateixa variable que es modifica!
  11. resultat = resultat + sumarAlFinal;
  12. System.out.println(resultat);
  13. }
  14. }

La figura mostra un esquema de com es va modificant la variable resultat a cada pas.

Figura Canvi del valor de la variable resultat

Per al cas de la conjunció i disjunció, els operadors d’aquest tipus són &= i |=.

Operació i assignació simultània

Modificar el valor d’una variable a partir d’aplicar una operació sobre el seu valor és quelcom que passa molt sovint dins un programa. Per aquest motiu, alguns llenguatges (com el Java) ofereixen un seguit de dreceres anomenades operadors d’operació, aritmètica o lògica, i d’assignació. En aquest es combina una única operació directament amb l’assignació d’acord amb el format següent:

Donat un operador binari op, x op= y equival a x = x op y.

Per tant, en l’exemple anterior també es podria calcular el resultat final fent:

resultat += sumarAlFinal;

Repte 3: feu un programa amb dues variables que, sense usar cap literal enlloc excepte per inicialitzar aquestes variables, vagi calculant i imprimint successivament els 5 primers valors de la taula de multiplicar del 4. Podeu usar operadors aritmètics i d’assignació, si voleu.

Constants

Un codi ben comentat també ajuda a entendre el codi font d’un programa.

En definir variables, hi ha la possibilitat d’assignar un identificador que us permet donar una certa semàntica al valor que emmagatzema. Si trieu bé aquest identificador, pot ser molt més senzill entendre què fa el programa quan llegiu el codi font. Malauradament, en usar literals es perd una mica aquesta expressivitat, ja que només es disposa del valor tal qual, però no se’n pot saber exactament la funció sense inspeccionar el codi amb atenció. Per solucionar aquest detall, els llenguatges de programació permeten reemplaçar l’ús de literals per constants.

Una constant és un tipus especial de variable que té la particularitat que dins del codi del programa el seu valor només pot ser llegit, però mai modificat.

L’ús d’aquest terme és semblant al que se’n fa en matemàtiques o física, on s’usa per indicar certs valors universals i immutables: π (3,1415…), c (300.000 m/s, la velocitat de la llum), etc.

Hi ha diverses maneres de definir constants en el Java, però veureu la més popular. En aquest cas, les constants es defineixen fora del mètode principal, però dins del bloc que identifica un codi font Java. La sintaxi és idèntica a la definició de la variable, però abans de la paraula clau per al tipus de dada cal afegir les paraules reservades private static final , en aquest ordre. O sigui:

  1. private static final paraulaClauTipus identificadorVariable = valorInicial;

Igual que amb les variables, el valor inicial pot ser tant un literal com una expressió. Ara bé, aquesta expressió no pot contenir variables. De totes maneres, el més normal és usar literals, per la qual cosa ens hi referirem al llarg de les explicacions.

Convencions de nomenclatura

Pel cas de les constants, les convencions de codi existents en el Java són diferents. En aquest sempre s’usen majúscules per a totes les lletres i, en cas d’anomenar-les amb una composició de diferents paraules, aquestes se separen amb un símbol de subratllat (underscore, _).

Exemples d’identificadors de constants que segueixen la convenció són: CONSTANT, UNA_CONSTANT, LA_MEVA_CONSTANT, etc.

Compareu la llegibilitat del codi dels dos programes següents. En el primer sembla que s’apliqui una multiplicació arbitrària al final de codi, però en el segon queda ben clar per què cal fer-la.

  1. //Calcula el preu de comprar diverses coses
  2. public class CalculaPreu {
  3. public static void main(String[] args) {
  4. double preuFinal = 0.0;
  5. //Va sumant preus de productes
  6. ...
  7. preuFinal = preuFinal + (preuFinal * 0.18);
  8. System.out.println(preuFinal);
  9. }
  10. }
  1. //Calcula el preu de comprar diverses coses, i queda més clar com ho fa
  2. public class CalculaPreu {
  3. private static final double IVA = 0.18;
  4. public static void main(String[] args) {
  5. double preuFinal = 0.0;
  6. //Va sumant preus de productes
  7. ...
  8. preuFinal = preuFinal + (preuFinal * IVA);
  9. System.out.println(preuFinal);
  10. }
  11. }

Les constants són també especialment útils quan, a part de voler etiquetar certs literals de manera que el codi sigui més fàcil d’entendre, el literal en qüestió apareix diverses vegades dins el codi font. Suposeu que en una versió posterior del programa cal fer un canvi en el valor d’aquest literal. Per exemple, l’IVA que cal aplicar dins d’un programa de botiga virtual passa de 0,18% a 0,16%. Si no s’ha declarat com a constant, caldrà cercar aquest valor línia per línia i assegurar-se que s’ha usat per fer un càlcul d’IVA (potser s’ha usat aquest mateix literal, però amb una altra finalitat). Si l’heu declarat, n’hi ha prou de modificar només la declaració de la constant. El canvi es produeix automàticament en totes les expressions en què s’usa la constant amb aquest identificador.

La declaració de constants en lloc d’usar directament un literal quan aquest apareix en molts llocs dins el codi també ofereix un altre avantatge subtil. Suposeu que us equivoqueu en escriure alguna de les aparicions del literal. En lloc de 0,18% d’IVA escriviu 0,28% (ja que les tecles ‘1’ i ‘2’ estan juntes). En aquest cas, el programa compilarà perfectament, però no farà el que ha de fer correctament. El pitjor és que, d’entrada, no tindreu ni la menor idea de per què passa això i haureu de repassar tot el codi fins trobar el literal incorrecte. En canvi, amb una constant, el literal l’escriviu una única vegada i ja està. En canvi, si mai us equivoqueu en escriure l’identificador de la constant, el compilador considerarà que esteu fent ús d’un identificador no declarat prèviament i us donarà un error, i indicarà exactament el lloc dins el codi font on us heu equivocat. Arreglar-ho serà molt fàcil.

La millor manera de veure-ho és amb un exemple, que per fer-lo més interessant incorpora l’ús de variables. Tot seguit hi ha dues versions diferents del codi font d’un programa que fa exactament el mateix. Si us diuen que en tots dos hi ha una errada, en quin dels dos casos us seria més senzill identificar-la?

  1. //Què deu fer aquest programa? Compila perfectament, però té una errada!
  2. public class Conversio {
  3. public static void main(String[] args) {
  4. double valor = 12.0;
  5. System.out.println(valor*1.3656);
  6. valor = 300.0;
  7. System.out.println(valor*1.3756);
  8. valor = 189.0;
  9. System.out.println(valor*1.3656);
  10. }
  11. }
  1. //Potser ja està més clar què fa el programa. Per trobar l'errada, només cal compilar-lo.
  2. public class ConversioError {
  3. public static final double CONVERSIO_EURO_A_DOLAR = 1.3656;
  4. public static void main(String[] args) {
  5. double valor = 12.0;
  6. System.out.println(valor*CONVERSIO_EURO_A_DOLAR);
  7. valor = 300.0;
  8. System.out.println(valor*CONEVRSIO_EURO_A_DOLAR);
  9. valor = 189.0;
  10. System.out.println(valor*CONVERSIO_EURO_A_DOLAR);
  11. }
  12. }

Proveu de compilar els dos programes. De fet, per trobar l’errada en el segon ni tan sols cal una inspecció detallada del codi per part vostra, ja que apareixerà un error de compilació i us dirà exactament on es troba. Ara suposeu també que el valor de conversió ha canviat i l’euro val 1,4 dòlars. En quin dels dos programes és més senzill fer el canvi? Doncs imagineu-vos el mateix en un programa amb centenars o milers de línies de codi.

Una pregunta que us podeu fer és si no es pot assolir exactament el mateix simplement usant una variable, i no modificar-ne mai el valor dins el programa. És una bona pregunta, i la resposta és que a l’efecte funcional seria el mateix. Ara bé, definir constants té dos avantatges, però per entendre-ho cal tenir una mica d’idea de com funciona un compilador. El primer avantatge és que el compilador controla explícitament que cap constant declarada és modificada en alguna instrucció del codi font programa. Si això succeeix, considera que el codi font és erroni i genera un error de compilació. Això pot ser molt útil per detectar errors i garantir que les vostres constants realment mai no veuen modificat el seu valor al llarg de l’execució del programa a causa d’alguna distracció. El segon és que, en alguns llenguatges, abans de processar el codi font, el compilador reemplaça totes les aparicions de l’identificador de cada constant pel literal que se li ha assignat com a valor. Per tant, al contrari que les variables, les constants no ocupen espai realment a la memòria mentre el programa s’executa.

Per tant, és un bon costum cenyir-se a l’ús de constants quan s’està tractant amb dades generals que mai no canvien de valor.

Repte 4: feu dos programes, un que mostri per pantalla la taula de multiplicar del 3, i un altre, la del 5. Els dos han de ser exactament iguals, lletra per lletra, excepte en un únic literal dins de tot el codi.

Conversions de tipus

Fins al moment s’ha dit que sempre que hi ha una assignació d’un valor, ja sigui en establir el valor d’una variable o el d’una constant, els tipus ha de ser exactament igual en totes dues bandes de l’assignació. Bé, en realitat això no és estrictament cert. Sota certes condicions és possible fer assignacions entre tipus de dades diferents si sobre el valor en qüestió es pot fer una conversió de tipus.

Aquest procés també es coneix com a casting.

Una conversió de tipus és la transformació d’un tipus de dada en un altre de diferent.

Dins dels llenguatges de programació hi ha dos tipus diferents de conversions de tipus: les implícites i les explícites. El primer cas correspon a aquelles conversions fàcils de resoldre i que el llenguatge de programació és capaç de gestionar automàticament sense problemes. El segon cas és més complex i el força el programador, si és possible.

Conversió implícita

Aquest cas és el més simple i es fonamenta en el fet que hi ha certes compatibilitats entre tipus primitius de dades diferents. La millor manera d’il·lustrar-ho és amb el programa següent:

  1. public class ConversioTipus {
  2. public static void main (String[] args) {
  3. //El literal "100" és un enter i "real" és un float. Són tipus diferents, no?
  4. float real = 100;
  5. //Però, "doble" i "real" són dos variables de tipus diferent, no?
  6. double doble = real;
  7. System.out.println(doble);
  8. }
  9. }

La sintaxi d’aquest programa no es correspon al que s’ha explicat fins ara. En canvi, si proveu de compilar-lo i executar-lo, funcionarà correctament. Això és perquè en aquest codi hi ha els dos casos de conversions implícites més habituals.

El primer cas es correspon al fet que tots els valors que pertanyen al tipus de dada enter es poden traduir molt fàcilment a real. Simplement cal considerar que els seus decimals són .0 i ja està. Això és el que passa aquí. El literal 100 és un enter (concretament, un int), però s’assigna sobre una variable de tipus float, per la qual cosa en principi seria incorrecte. Caldria usar el literal que expressa aquest valor com un real de precisió simple, el 100.0F. Tot i això, el compilador de Java considera que l’assignació és correcta, ja que és capaç de fer automàticament aquesta conversió des del tipus enter a real.

El segon cas està vinculat al rang del tipus de dada numèric. Si un valor és d’un tipus primitiu numèric concret, aquest pot ser interpretat fàcilment com una dada dels tipus que accepta un rang més gran de valors. Per exemple, qualsevol valor de tipus int que us pugueu imaginar segur que també està dins dels valors acceptats entre els long. Igual passa entre els float i els double. El compilador de Java també s’adona d’això i per aquest motiu accepta una assignació d’un valor float (menys rang) a un double (més rang) sense problemes, ja que sap fer la conversió.

La figura fa un resum de les relacions entre tipus numèrics. Les relacions són transitives, de manera que tota dada d’un tipus numèric pot ser assignada a variables o constants de qualsevol dels tipus posteriors en la relació.

Figura Relacions de compatibilitat entre tipus numèrics

En qualsevol cas, al llarg del text sempre s’usaran els literals del tipus correcte en fer assignacions.

Conversió explícita

En qualsevol cas, on hi hagi assignacions entre dades de tipus diferent però el llenguatge triat sigui incapaç de fer una conversió implícita, el compilador sempre donarà error. Ja de per si, l’aparició d’aquest error sol significar que alguna cosa no està bé i que cal modificar el programa. Però hi ha casos en què és permissible eliminar aquest error especificant al codi que es vol forçar la conversió de les dades a un nou tipus de totes formes. Bàsicament, el que fa és obligar el compilador a transformar una dada d’un tipus en una de diferent “tan bé com pugui”. Per indicar-li-ho, davant de l’expressió per convertir es posa el tipus de dada resultant que es vol obtenir entre parèntesis. La sintaxi és la següent:

  1. identificador = (paraulaClauTipus)expressió;

En fer això, el resultat de l’expressió s’intentarà convertir al tipus de dada especificat, sigui quin sigui el tipus en què avaluï originalment. El valor final obtingut dependrà molt de la situació sota la qual es fa la conversió, ja que és evident que convertir dades a tipus totalment diferents del seu origen no és una acció directa. En qualsevol cas, el tipus triat per a la conversió ha de ser compatible amb el de la variable en què s’assigna, com sempre.

La conversió explícita s’ha d’usar amb molt de compte i tenint molt clar què s’està fent.

La situació més freqüent en què s’usa la conversió explícita és per invertir l’ordre establert en les relacions de la figura (que mostra un esquema de què està succeint a cada línia del codi), ja que normalment un llenguatge de programació no permet fer assignacions en direcció oposada a les fletxes.

Figura Conversió explícita entre variables

Això es deu al fet que, per exemple, un real o un valor de tipus long molt gran no es poden traduir normalment com un enter (int). En el primer cas no hi ha manera de representar els decimals i en el segon se surt fora del rang admissible. Ara bé, sí que hi ha casos en què aquesta assignació té sentit: quan els decimals del real són exactament .0 o quan el valor del long està dins del rang acceptable per als enters. Llavors, usant la conversió explícita podeu evitar que el compilador doni un error i fer que intenti convertir de manera correcta els valors en enters. Si podeu garantir que el valor original compleix les condicions perquè la traducció sigui correcta, la conversió es completarà amb èxit. En cas contrari, el valor final assignat serà de ben segur erroni.

Tot seguit es mostra un exemple de conversions explícites entre tipus que se suposa que no són compatibles. Ara bé, com que el programador pot garantir que el valor real per convertir, 300.0, és representable en diferents tipus d’enter, tot funciona correctament. Al final es veu per pantalla el valor 300 (o sigui, un literal de tipus enter). Però si esborreu les conversions explícites, (long) i (int), el programa no compilarà.

  1. public class ConversioExplicita {
  2. public static void main (String[] args) {
  3. double realLlarg = 300.0;
  4. //Assignació incorrecta. Un real té decimals, no?
  5. long enterLlarg = (long)realLlarg;
  6. //Assignació incorrecta. Un enter llarg té un rang major que un enter, no?
  7. int enter = (int)enterLlarg;
  8. System.out.println(enter);
  9. }
  10. }

Perquè aquest mecanisme funcioni és molt important que el programador estigui segur que el valor per convertir realment pertany al tipus de dada de destinació. En cas contrari, el resultat final no s’adaptarà fidelment a la dada original. Per exemple, en el cas de la conversió de qualsevol tipus real en enter es perdran tots els decimals (es truncarà el nombre), i en el cas d’assignar un valor fora del rang possible entre qualsevol tipus de dada obtindreu un desbordament.

Repte 5: experimenteu què passa si en el programa anterior s’inicialitza la variable realLlarg amb un valor amb diversos decimals. El programa continua compilant? Quin resultat dóna? Després proveu-ho assignant un valor superior al rang dels enters (per exemple, 3000000000.0).

Conversió amb tipus no numèrics

En certs llenguatges de programació també es pot intentar fer conversions de tipus en què alguna o totes dues dades són de tipus no numèric. Llavors el que es demana és fer una operació a baix nivell amb la representació interna, en binari, de la dada. Per entendre què succeeix en aquesta situació, cal recordar que la combinació de zeros i uns com es representa una dada concreta depèn del seu tipus. Ara bé, també cal tenir present que una mateixa seqüència binària pot representar valors diferents per a tipus de dades diferents. Per exemple, el valor de tipus caràcter ‘a’ es codifica internament amb la seqüència binària 0000000001100001, però aquesta seqüència també és usada per codificar el valor de tipus enter ‘97’. El tipus de dada proporciona un context sobre el significat de les dades en binari quan es llegeix una dada dins de l’ordinador.

El llenguatge de l'ordinador i el llenguatge natural

Per exemplificar què vol dir que una mateixa representació d’una dada la poden compartir molts contextos diferents, es pot fer novament un símil amb el llenguatge natural. Suposem que trobeu un paper on hi ha escrita la paraula clau. En aquest símil, el paper representa la memòria, i clau, la dada emmagatzemada. Sense cap altra informació podeu ser capaços de saber-ne el significat? En realitat, amb aquestes quatre lletres un es pot referir a moltes coses: una clau per obrir una porta, un clau per penjar un quadre, la clau per descobrir un misteri (una pista), una clau d’arts marcials, etc. De fet, la paraula clau té més de quinze significats. El tipus de dada correspondria al context sota el qual s’ha d’interpretar aquesta paraula.

Fer una conversió amb dades de tipus no numèric indica a l’ordinador que ha d’agafar el valor binari trobat a la memòria tal qual i interpretar-lo com si fos un altre tipus de dada totalment diferent.

En la majoria de casos, aquestes s’han de fer explícitament, però en situacions molt concretes el llenguatge fins i tot és capaç d’interpretar-les implícitament. A mode d’exemple, Java és capaç de dur a terme la conversió de caràcter a enter, i viceversa, de manera implícita. En altres llenguatges s’ha de fer explícitament o simplement no es pot fer.

  1. public class ConversioCaracter {
  2. public static void main (String[] args) {
  3. int enter = 'a';
  4. char caracter = 98;
  5. //Escriu el valor en binari del literal 'a' interpretat com un enter: 97.
  6. System.out.println(enter);
  7. //Escriu el valor en binari del literal 98 interpretat com un caràcter: 'b'.
  8. System.out.println(caracter);
  9. }
  10. }

En altres llenguatges, com C, es pot intentar amb pràcticament qualsevol tipus de dada.

Cal dir que l’ús d’aquest cas és molt marginal. De fet, tot i aquesta darrera opció d’operar a baix nivell, en cada llenguatge hi ha conversions explícites que simplement són impossibles. Si les intenteu fer, el compilador dirà que hi ha un error igualment. En Java per exemple, és el cas de convertir una dada del tipus booleà a qualsevol altre.

Visualització i entrada de les dades en Java

La veritat és que no té cap sentit que un programa dugui a terme el processament d’unes dades si aquesta tasca després no té cap repercussió en el sistema d’entrada/sortida. Sobretot tenint en compte que un cop el programa finalitza, tots els valors tractats s’esborren de la memòria. El mínim que pot fer és mostrar el resultat de la tasca per pantalla, de manera que l’usuari que l’ha posada en marxa sap quin ha estat el resultat de tot plegat. Tampoc no té sentit usar variables si en aquestes sempre s’emmagatzemen expressions fruit d’operacions entre literals. A efectes pràctics, es podrien usar ja directament literals sempre. Altres accions més complexes ja serien desar les dades en un fitxer de manera persistent, imprimir-les o que aquestes serveixin com a senyal de control sobre algun perifèric extern.

  • Tota dada mostrada per un perifèric està representada per dades dins de l'ordinador./-23
  • Tota dada mostrada per un perifèric està representada per dades dins de l'ordinador.

Hi ha moltes maneres d’entrar o mostrar les dades a l’usuari, només cal veure les interfícies gràfiques dels sistemes moderns, però aquí veureu la més simple: cadenes de text en pantalla o per teclat.

Instruccions de sortida de dades per pantalla

Tot i que fins al moment ja us heu trobat en els exemples d’apartats anteriors algunes instruccions que serveixen per mostrar coses per pantalla, ha arribat el moment d’inspeccionar-les amb una mica més de deteniment. Abans de començar, però, val la pena dir que cada llenguatge de programació té les seves pròpies instruccions per visualitzar dades per pantalla. Si bé la idea general pot tenir certes similituds, la sintaxi pot ser totalment diferent. Per tant, cal tenir en compte que les descripcions se centren exclusivament en com funciona el llenguatge Java.

Principalment, Java proporciona dues instruccions per mostrar dades a la pantalla:

  1. //Mostra a la pantalla el resultat d'avaluar l'expressió //x//.
  2. System.out.print(x);
  3. //Fa el mateix, però al final afegeix un salt de línia.
  4. System.out.println(x);

Quan escriviu amb el teclat, el text es va mostrant en el mateix ordre com aneu cridant les instruccions de sortida per pantalla. No es pot demanar un posicionament absolut a qualsevol lloc de la pantalla. Per tant, cada instrucció començarà a escriure immediatament després d’on l’ha deixada la instrucció anterior.

Un dels aspectes més importants de representar dades per pantalla és que aquest perifèric només tracta amb text. O sigui, conjunts de caràcters en Java (char). Per tant, en el procés de sortida a la pantalla tota dada que es vol mostrar ha de patir una transformació des de la representació original fins al conjunt de caràcters que la representen en format text.

D'enter a text

Suposeu que tenim una variable de tipus enter en Java (int), en què hi ha emmagatzemat el valor 1309. Això internament es representa amb el valor binari: 0000010100010100. Ara bé, per la pantalla el que s’haurà de mostrar són els 4 caràcters (char) ‘1’, ‘3’, ‘0’, ‘9’. És a dir, s’ha d’ordenar al sistema d’entrada/sortida per pantalla que mostri els quatre valors de la representació en binari:

  • ‘1’: 000000000000000000110001
  • ‘3’: 000000000000000000110011
  • ‘0’: 000000000000000000110000
  • ‘9’: 000000000000000000111001

Altres llenguatges ofereixen altres mecanismes, alguns no tan simples, per fer aquesta transformació.

Per tant, si es vol escriure un enter, caldria un pas de transformació previ a representar-lo en diferents caràcters individuals. Pel que fa a aquest tipus d’instruccions, un dels grans avantatges de Java, com a llenguatge de nivell alt en relació amb altres llenguatges de nivell més baix, és que les dues instruccions bàsiques de què disposa poden acceptar qualsevol expressió que en ser avaluada resulti en qualsevol tipus primitiu, sigui quin sigui (enter, real, booleà o caràcter). Automàticament s’encarreguen elles mateixes de transformar el valor emmagatzemat en la seva representació en format text, i la mostren per pantalla.

A mode d’exemple, en fer:

  1. //Mostra diferents expressions simples per pantalla.
  2. public class MostrarExpressions {
  3. public static void main(String[] args) {
  4. //Mostra el text "3" per pantalla.
  5. System.out.println(3);
  6. //Mostra el text "a" per pantalla.
  7. System.out.println('a');
  8. //Mostra el text "3.1416" per pantalla.
  9. double x = 3.1416;
  10. System.out.println(x);
  11. //Mostra el text "true" per pantalla.
  12. boolean a = true;
  13. boolean b = false;
  14. System.out.println((a||b)&&(true));
  15. }
  16. }

Es mostra per pantalla el següent:

  1. 3
  2. a
  3. 3.1416
  4. true

Tipus de dada avançats: cadenes de text

En molts casos, segurament no en tindreu prou només escrivint la representació textual d’un tipus de dada simple. Us agradaria escriure una frase sencera de l’estil “El resultat obtingut és…”. És a dir, escriure blocs de text. Partint d’aquesta necessitat, si reflexioneu una mica, potser arribareu a la conclusió que el tipus de dada caràcter no és gaire útil, ja que per mostrar frases llargues, amb el que heu vist fins al moment, caldria fer-ho caràcter per caràcter, de manera molt molesta.

En realitat, el tipus de dada caràcter no se sol usar directament, sinó que els llenguatges de programació l’usen com a base per generar un nou tipus de dada més avançat que serveixi per processar text.

La paraula clau per identificar aquest tipus de dada en Java és String.

Una cadena de text (String) és un tipus de dada que permet representar qualsevol seqüència de caràcters de longitud arbitrària.

  • Exemples de valors d’una cadena de text: “Hola, món!”, “El resultat final és…”, etc.
  • Exemples de dades que se solen representar amb una cadena de text: qualsevol missatge de text per pantalla.

Per referir-nos a una dada de tipus cadena de text, l’escrivim entre cometes dobles (”). Aneu amb molt de compte, ja que no és el mateix ‘a’ que “a”. La primera és un literal que representa una dada de tipus caràcter i la segona és de tipus cadena de text.

Les cadenes de text no són un tipus primitiu.

Cada llenguatge ofereix la seva manera de gestionar cadenes de text i representar-les per pantalla. Des de l’exemple del programa “Hola, món!”, ja heu vist que les instruccions de sortida bàsica de Java per pantalla també poden mostrar cadenes de text de Java sense problemes, exactament igual que es fa amb els tipus primitius.

  1. System.out.println("Hola, món!");

Finalment, recordeu que, com a tipus de dada, també es poden declarar variables usant la paraula clau, igual que amb qualsevol altre tipus primitiu:

  1. //Mostra el text "Hola, món!" per pantalla.
  2. public class MostrarHolaMon {
  3. public static void main(String[] args) {
  4. String holaMon = "Hola, món!";
  5. System.out.println(holaMon);
  6. }
  7. }

Operadors de les cadenes de text

En contraposició dels tipus primitius, no es pot generalitzar en parlar d’operacions de cadenes de text. Cada llenguatge en pot oferir de diferents (o cap). En el cas del Java, les cadenes de text accepten l’operador suma (+), de manera que es poden generar expressions de la mateixa manera que es pot fer amb els tipus primitius. En aquest cas, el resultat d’aplicar-lo en una operació entre cadenes de text és que aquestes es concatenen. Es genera una nova cadena de text formada per la unió de cadascun dels operands de manera consecutiva.

  1. //Mostra el text "Hola, món!" per pantalla usant concatenació de cadenes de text.
  2. public class HolaMonConcatenat {
  3. public static void main(String[] args) {
  4. String hola = "Hola,";
  5. String mon = "món";
  6. String exclamacio = "!";
  7. //Mostra el text "Hola, món!" per pantalla
  8. System.out.println(hola + mon + exclamacio);
  9. }
  10. }

Noteu que les cadenes de text s’enganxen directament l’una darrere de l’altra, sense afegir cap espai.

De fet, per facilitar encara més la feina del programador, Java va una mica més enllà i permet aplicar aquest operador entre una cadena de text i qualsevol tipus primitiu. El resultat sempre és una nova cadena de text en què el tipus primitiu es transforma en la seva representació textual.

  1. //Mostra el resultat d'una divisió simple entre reals.
  2. public class Dividir{
  3. public static void main(String[] args) {
  4. double dividend = 18954.74;
  5. double divisor = 549.12;
  6. double resultatDivisio = dividend/divisor;
  7. String missatge = "El resultat obtingut és " + resultatDivisio + ".";
  8. System.out.println(missatge);
  9. }
  10. }

Les dues darreres línies també es poden convertir en una de sola si s’usa directament l’expressió sobre la instrucció de sortida:

  1. System.out.println("El resultat obtingut és " + resultatDivisio + ".");

Caràcters de control

En alguns llenguatges de programació, com en Java, hi ha un conjunt de literals dins dels caràcters que són especialment útils dins de les cadenes de text, ja que permeten representar símbols o accions amb un significat especial. Aquests s’anomenen seqüències d’escapament i sempre es componen d’una contrabarra (\) i un caràcter addicional. La taula mostra una llista dels més habituals.

Taula: Seqüències d’escapament típiques
Seqüència d’escapament Acció o símbol representat
\t Una tabulació.
\n Un salt de línia i retorn de carro.
El caràcter “cometa simple”.
\" El caràcter “cometes dobles” (").
\ El caràcter “contrabarra” (\).

Com es pot apreciar, per als dos primers casos cal usar una seqüència especial, ja que són accions que trobem en un teclat, però no són representables gràficament. Per exemple, el codi:

  1. //Mostra text usant caràcters de control.
  2. public class CaractersControl{
  3. public static void main(String[] args) {
  4. System.out.println("Linia 1\n\tLínia2\nLínia 3");
  5. }
  6. }

mostraria per pantalla:

  1. Línia 1
  2. Línia 2
  3. Línia 3

En la resta de casos, la seva existència es deu a la manera com es representen els literals de caràcters o cadenes de text. Com que el literal d’un caràcter s’envolta amb dues cometes simples i el d’una cadena de text amb cometes dobles, és l’única manera de distingir dins el text si aquest símbol s’usa per delimitar un literal o com a text que en forma part. La contrabarra es representa d’aquesta manera per distingir-la respecte de quan s’usa, precisament, per especificar una seqüència d’escapament. Així doncs, el codi:

  1. //Mostra una cadena de text que inclou cometes dobles.
  2. public class HolaMonCometes{
  3. public static void main(String[] args) {
  4. System.out.println("Hola \"món\"!");
  5. }
  6. }

mostraria per pantalla:

  1. Hola "món"!

Repte 6: feu un programa que mostri en pantalla de manera tabulada la taula de veritat d’una expressió de disjunció entre dues variables booleanes.

Entrada simple de dades per teclat

Explicar l’entrada de dades per teclat quan tot just s’ha començat a aprendre a programar usant Java té un seguit de complicacions, ja que per entendre exactament la sintaxi de cada instrucció usada cal conèixer nombrosos conceptes que encara no s’han explicat. A més a més, per acabar-ho de complicar, també hi intervé l’aspecte de llenguatge orientat a objectes de Java. Per aquest motiu, de moment n’hi ha prou que simplement aprengueu les instruccions des d’un punt de vista purament pràctic i funcional, però sense haver de saber exactament què està passant o quina sintaxi concreta s’està usant. Sempre que us calgui, podeu usar com a plantilla el codi font d’exemple en aquest apartat.

Per llegir dades de teclat cal usar una biblioteca, un conjunt d’extensions a les instruccions disponibles per defecte en el llenguatge. Per usar alguna de les extensions disponibles en una biblioteca, cadascuna identificada amb un nom, primer cal importar-la. Llavors, el primer que es fa és inicialitzar l’extensió, assignant-li un identificador, i a partir de llavors ja la podeu usar dins del vostre codi font.

Recordeu que el codi font ha d’estar en un fitxer anomenat Sumar.java.

Escriviu i executeu l’exemple següent per veure com podeu entrar per teclat dos nombres enters i fer una operació entre ells.

  1. //Aquesta línia fa que la biblioteca estigui disponible.
  2. import java.util.Scanner;
  3. //Un programa que llegeix un enter i el mostra per pantalla.
  4. public class Sumar {
  5. public static void main (String[] args) {
  6. //S'inicialitza la biblioteca.
  7. Scanner lector = new Scanner(System.in);
  8. //Es posa un missatge de benvinguda.
  9. System.out.println("Anem a sumar dos nombres enters");
  10. //Es llegeix un valor enter per teclat.
  11. //S'espera que es pitgi la tecla de retorn.
  12. System.out.print("Escriu un nombre i pitja la tecla de retorn: ");
  13. int primerEnter = lector.nextInt();
  14. lector.nextLine();
  15. //Es torna a fer...
  16. System.out.print("Torna a fer-ho: ");
  17. int segonEnter = lector.nextInt();
  18. lector.nextLine();
  19. //Fem l'operació.
  20. int resultat = primerEnter + segonEnter;
  21. //Imprimeix el resultat per pantalla!
  22. //S'usa l'operador suma entre una cadena de text i un enter
  23. System.out.println("La suma dels dos valors és " + resultat + ".");
  24. }
  25. }

En realitat, lector és un identificador intercanviable amb qualsevol altre.

Un cop l’extensió està inicialitzada (en aquest cas, només es fa un sol cop), el programa està preparat per llegir dades de diferents tipus des del teclat. De moment, n’hi ha prou que aprengueu com podeu llegir dades d’una en una, usant diferents línies de text per entrar cada dada individual. Per fer-ho, cal alternar les instruccions de lectura enumerades a la taula, segons el tipus de dada que es vol llegir des del teclat, i la instrucció lector.nextLine();, tal com es veu a l’exemple.

Taula: Instruccions per a la lectura de dades per teclat
Instrucció Tipus de dada llegida
lector.nextByte() byte
lector.nextShort() short
lector.nextInt() int
lector.nextLong() long
lector.nextFloat() float
lector.nextDouble() double
lector.nextBoolean() boolean
lector.next() String

Per no haver d’entrar en detalls, podeu considerar que cadascuna de les instruccions de la taula es comporta exactament igual que una expressió que com a resultat avalua la dada introduïda pel teclat. La instrucció mateixa és la que s’encarrega de transformar el text escrit, una cadena de text, en una dada del tipus corresponent. Normalment, tota dada llegida serà assignada a alguna variable prèviament declarada. Si us resulta més còmode, es pot fer en una sola línia com en el codi d’exemple, assignant el valor llegit directament com a part de la inicialització de la variable.

Usant aquest procediment, si en una mateixa línia s’introdueix text amb més d’una dada, només es llegeix la primera de totes. La resta s’ignora.

Aquest mecanisme no permet llegir caràcters individuals. Recordeu, però, que des del punt de vista del tipus de dada, una cadena de text que conté un únic caràcter no és el mateix que un caràcter individual. Un altre aspecte important és que la manera com reconeix els valors reals, si interpreta els decimals separats amb un punt o una coma (per exemple, 9.5 o 9,5), depèn de la configuració local del sistema. En les configuracions catalana i espanyola se separen amb una coma.

Si feu proves us adonareu que en aquest programa, si us equivoqueu i introduïu un valor diferent d’un enter, el programa s’atura immediatament i mostra un missatge d’error per pantalla. Això és normal, ja que la dada escrita per teclat ha d’encaixar amb el tipus de dada que llegeix la instrucció usada. També podeu comprovar que, igual que en avaluar expressions, intentar assignar una dada que excedeix el rang admissible per al tipus d’una variable resultarà en un desbordament. En el cas d’entrada pel teclat, tots aquests fets són especialment complicats de controlar, ja que mai no sabeu què escriurà realment l’usuari, expressament o per equivocació. De moment, podeu considerar que tot això no és cap problema. Per ara, simplement, procureu no entrar mai dades incorrectes pel teclat.

Aquest missatge indica que ha succeït un error d’execució (error de run-time).

Repte 7: feu un programa que mostri per pantalla la multiplicació de tres nombres reals entrats per teclat.

Solucions dels reptes proposats

Repte 1:

  1. public class AvaluarExpressionsLiterals {
  2. public static void main (String[] args) {
  3. //booleà == ((enter > (enter + enter))&&(caràcter == caràcter))||(real < real)
  4. System.out.println(false == ((6 > (5 + 3))&&('a' == 'b'))||(4.4 < 10.54) );
  5. }
  6. }

Repte 2:

  1. public class TresMilMilions {
  2. public static void main (String[] args) {
  3. //Cal dir que el literal es correspon a un "long" (afegir una "l" al final)
  4. System.out.println(3000000000l);
  5. }
  6. }

Repte 3:

  1. public class IniciTaulaMultiplicarQuatre {
  2. public static void main (String[] args) {
  3. int a = 4;
  4. int b = 4;
  5. System.out.println(a);
  6. a = a + b;
  7. System.out.println(a);
  8. a = a + b;
  9. System.out.println(a);
  10. a = a + b;
  11. System.out.println(a);
  12. a = a + b;
  13. System.out.println(a);
  14. }
  15. }

Repte 4:

  1. public class TaulaTresICinc {
  2. //Per fer la del 5, canviar el 3 per un 5.
  3. public static final int TAULA = 3;
  4. public static void main (String[] args) {
  5. System.out.println(1*TAULA);
  6. System.out.println(2*TAULA);
  7. System.out.println(3*TAULA);
  8. System.out.println(4*TAULA);
  9. System.out.println(5*TAULA);
  10. System.out.println(6*TAULA);
  11. System.out.println(7*TAULA);
  12. System.out.println(8*TAULA);
  13. System.out.println(9*TAULA);
  14. System.out.println(10*TAULA);
  15. }
  16. }

Repte 5:

  1. public class ConversioExplicitaDiversosDecimals {
  2. public static void main (String[] args) {
  3. double realLlarg = 3000000000.0;
  4. //Assignació incorrecta. Un real té decimals, no?
  5. long enterLlarg = (long)realLlarg;
  6. //Assignació incorrecta. Un enter llarg té un rang més gran que un enter, no?
  7. int enter = (int)enterLlarg;
  8. System.out.println(enter);
  9. }
  10. }

Repte 6:

  1. public class TaulaVeritatDisjuncio {
  2. public static void main(String[] args) {
  3. System.out.println("A \tB \t(A || B)");
  4. System.out.println("false\tfalse\tfalse");
  5. System.out.println("true \tfalse\ttrue");
  6. System.out.println("false\ttrue \ttrue");
  7. System.out.println("true \ttrue \ttrue");
  8. }
  9. }

Repte 7:

  1. import java.util.Scanner;
  2. public class MultiplicarEntradaTeclat {
  3. public static void main(String[] args) {
  4. //S'inicialitza la biblioteca.
  5. Scanner lector = new Scanner(System.in);
  6. //Es posa un missatge de benvinguda.
  7. System.out.println("Anem a multiplicar tres nombres reals");
  8. //Es llegeix un valor enter per teclat.
  9. //S'espera que es pitgi la tecla de retorn.
  10. System.out.print("Escriu un nombre i pitja la tecla de retorn: ");
  11. double primerReal = lector.nextDouble();
  12. lector.nextLine();
  13. //Es torna a fer...
  14. System.out.print("Torna a fer-ho: ");
  15. double segonReal = lector.nextDouble();
  16. lector.nextLine();
  17. //Es torna a fer...
  18. System.out.print("I un altre cop: ");
  19. double tercerReal = lector.nextDouble();
  20. lector.nextLine();
  21. //Fem l'operació.
  22. double resultat = primerReal * segonReal * tercerReal;
  23. //Imprimeix el resultat per pantalla!
  24. System.out.println("La multiplicació dels valors és " + resultat + ".");
  25. }
  26. }
Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats