Uma boa parte dos programas
de computador precisam representar e manipular números e também
seqüências de caracteres. As necessidades numéricas são, grosso modo, atendidas
pelos tipos primitivos tais como long, int e double. As
linguagens de alto-nível têm diferentes abordagens para lidar com a necessidade
de representar e manipular as seqüências de caracteres. Na linguagem Java as
seqüências de caracteres podem ser representadas utilizando arranjos de
caracteres (char arrays) ou então as cadeias de caracteres (strings).
Aqui iremos dar ênfase no suporte de processamento de texto utilizando cadeias
de caracteres.
Quando discutimos arranjos
vimos que eles são objetos especiais. Os arranjos possuem
"inicializadores" e arranjos podem ser indexados. As cadeias de
caracteres são objetos especiais também, mas em um sentido um pouco diferente.
Uma cadeia de caractere é o único objeto que possui uma representação no
código fonte do programa, ou seja uma cadeia de caractere é o único objeto que
tem um literal na linguagem Java.
Considere o seguinte trecho
de programa:
System.out.println(123);
System.out.println(3.1415);
System.out.println("Ola!");
O tipo do literal 123 é int.
o tipo do literal 3.1415 é double. Em Java int e double
são tipos primitivos. O tipo da cadeia de caracteres "Ola!" é String.
Em Java String é uma classe(class). A classe é o elemento
da Linguagem Java que define a estrutura(estado) e o comportamento de seus
objetos(instâncias). Mais a frente na disciplina iremos apresentar o conceito classe com mais detalhes. Neste ponto
da disciplina iremos apenas estudar uma classe especial denominada String (uma
classe pré-definida) cujos objetos ou instâncias contêm cadeias de caracteres.
Uma classe define um tipo. A expressão “instanciar a classe C” deve ser
interpretada da mesma forma que a expressão “criar um objeto da classe C” ou
ainda “criar um objeto do tipo C”. Uma variável do tipo String é uma
variável do tipo referência, ou seja, contém um endereço de objeto (instância).
Uma variável de tipo primitivo P deverá conter um valor do tipo P, uma variável
do tipo referência deverá conter um endereço de um objeto (instância) do tipo
referenciado, em particular uma variável do tipo String deverá conter um
endereço de um objeto do tipo String.
O trecho de programa:
String s;
corresponde a definir uma variável com nome s e tipo String, mas o conteúdo de
s não é definido. O trecho de programa:
String s="abcde";
Corresponde a definir uma variável com nome s, tipo String e conteúdo
correspondendo a um endereço de memória onde o compilador montou um objeto
contendo a cadeia de caracteres "abcde".
Uma variável do tipo String
pode ser iniciada ou inicializada de forma semelhante a uma variável de tipo
primitivo além de poder ser iniciada através de construtores como será visto
mais à frente. A iniciação é feita com literais do tipo String . Um
literal do tipo String é uma seqüência de caracteres iniciada e terminada com
aspas. Podemos utilizar o operador de concatenação de cadeias (+). O operador
de concatenação permite representar uma cadeia em diferentes linhas do texto do
programa. Exemplo de declaração de variáveis do tipo cadeia de caracteres já inicializadas:
String s1="Universidade ";
String s2="Federal de";
String s3=" Minas "+
"Gerais";
String universidade=s1+s2+s3;
No trecho de programa acima
a variável universidade
é inicializada utilizando o operador de concatenação de cadeias de caracteres.
Os literais de tipo String podem ser usados em comandos de atribuição:
String universidade;
universidade="Universidade Federal de Minas Gerais";
No trecho acima a variável
universidade recebe o endereço de memória onde fica um objeto contendo a cadeia
de caracteres "Universidade Federal de Minas Gerais". A figura abaixo
mostra uma representação gráfica simplificada para o que discutimos acima:

Observe que a organização
do bloco de memória correspondente a uma cadeia de caracteres é diferente da
organização de um bloco de memória correspondente a um arranjo de caracteres. No
caso de arranjo de caracteres cada caractere pode ser indexado através do
operador [] (abre e fecha colchete), o operador de indexação não pode ser usado em cadeias de
caracteres. A manipulação de caracteres individuais de uma cadeia de caracteres
tem que ser feita através de métodos conforme explicado abaixo. Os
métodos da classe String permitem vários tipos de operações sobre os caracteres
e substrings (trechos de cadeias de
caracteres).
O conteúdo da variável universidade é um endereço de um bloco de memória.
Este bloco de memória é o objeto que contém a cadeia de caracteres. A distinção
entre a cadeia de caracteres (seqüência de caracteres) e o objeto (o bloco de
memória onde fica a seqüência de caracteres) não é muito reforçada em alguns
textos. Um objeto é também referido como uma instância, o objeto acima é
uma instância da classe String. Não são mostrados todos os campos do
objeto, apenas um campo (este campo com uma referência para a classe String não
é disponível para o programador!) e que contém uma referência para a
classe String. A classe String não é detalhada, mas uma classe pode definir
campos e métodos (methods), dentre outros elementos.
Um método corresponde a um
código que implementa alguma funcionalidade relacionada à classe ou a um objeto.
Os métodos que implementam uma funcionalidade relacionada à classe são
denominados métodos estáticos (static) ou métodos de classe e os
métodos que implementam uma funcionalidade relacionada ao objeto são
denominados não-estáticos ou métodos de instância. Uma chamada
ou invocação de método corresponde a um comando que altera o fluxo de
controle. Uma chamada ou invocação de método desvia o fluxo de controle para o
código de máquina do método, um mecanismo, denominado mecanismo de retorno,
permite, terminada a execução do método, que o controle retorne ao comando
seguinte à chamada. Os métodos estáticos são invocados utilizando o nome da
classe seguido do operador '.' seguido do nome do método. Os métodos
não-estáticos são invocados utilizando uma referência para o objeto seguido do
operador '.', seguido do nome do método. Abaixo são mostrados alguns dos
métodos da classe String:
O trecho de programa abaixo
imprime o comprimento, em termos de números de caracteres, de uma cadeia de
caracteres referenciada pela variável cordao:
String cordao="abcdefg";
System.out.println(cordao.length());
No trecho acima o método não-estático length() da classe String é invocado.
Invocar um método consiste em desviar o fluxo de controle para o código
correspondente a este método, mas antes que ocorra o desvio, de alguma forma, é
salvo o ponto no qual o fluxo de controle deverá retornar. O println() também é um método. A ordem de
invocação é a seguinte: o compilador primeiramente gera código para invocar o
método length(), após a invocação de length() é gerado o código para invocar println().
A execução do código
correspondente a length()determina o número de caracteres e retorna este valor. O valor retornado
por um método tem que ter um tipo. O método length() da classe String é um método do
tipo int, ou seja retorna valores inteiros. Mais a frente na disciplina
iremos ver como definir classes e em particular como definir métodos. Observe
que os objetos correspondentes a arranjos têm um campo length enquanto que objetos correspondentes
a cadeias de caracteres compartilham o método length() da classe String. A execução
de um método pode receber valores através de parâmetros ou argumentos.
Os parâmetros são sempre de um determinado tipo. O método length() não tem parâmetros. O parâmetro do
método println()
deve ser do tipo String, print() e println() serão discutidos mais a frente na
disciplina. O valor retornado pelo método length() é do tipo int, o compilador
cuida para que seja feita a conversão do valor de tipo int para tipo String.
O trecho de programa abaixo
utiliza o método charAt(). Este método possui um parâmetro do tipo int e retorna um
valor do tipo char:
String cordao="abcdefg";
for(int i=0; i<cordao.length(); i++)
System.out.print(cordao.charAt(i));
System.out.println();
O trecho acima tem efeito
semelhante ao seguinte trecho:
String cordao="abcdefg";
System.out.println(cordao);
O método charAt() recebe como parâmetro um valor
inteiro que deve estar entre 0 e length()-1. Se o valor do parâmetro não atender a estes
limites haverá um erro em tempo de execução. O método devolve o (valor do
parâmetro+1)-ésimo caractere da cadeia de caracteres. Se o argumento de
charAt() for menor que 0 ou maior ou igual ao comprimento da cadeia ocorre um
erro de execução: IndexOutOfBoundsException.
Uma classe define
campos(fields), métodos(methods) e constructores( constructors).
Os campos e métodos já foram parcialmente discutidos: os campos correspondem a
posições de memória e os métodos são códigos que recebem 0 ou mais valores e
devolvem 0 ou 1 valor. Os construtores têm similaridades com métodos
(especificamente métodos de instância) mas sua função está ligada à iniciação
dos objetos quando de sua instanciação. Um exemplo de similaridade entre
construtores e métodos é o fato de construtores poderem receber parâmetros, mas
construtores, diferentemente de métodos, não podem ter nomes arbitrários. Os
nomes de construtores são sempre o nome
da classe correspondente. O trecho de programa abaixo ilustra como utilizar um
arranjo de caracteres para construir uma cadeia de caracteres
correspondente à seqüência do arranjo:
char[] arranjoCar;
arranjoCar= new char[]{'a', 'b', 'c ', 'd ', 'e ', 'f '};
String cordao;
cordao= new String(arranjoCar);
Este trecho corresponde ao seguinte uso de literais:
String cordao=”abcdef”;
No trecho acima é utilizado o operador new
no contexto de arranjos e no contexto de classes. A expressão new char[]{...} aloca um bloco de memória contendo
um arranjo do tipo char inicializado conforme especificado acima. A
expressão new String(...) irá alocar um objeto do tipo String e retornar o endereço do
bloco de memória onde foi alocado este objeto.
O operador new é que
estrutura e aloca todo o bloco de memória para instâncias de arranjo e classes
fazendo todas as iniciações necessárias e retornando o endereço do bloco de
memória. Veremos mais a frente na disciplina que um arranjo é um tipo especial
de objeto e que a alocação de um objeto e o retorno de seu endereço é feito
pelo operador new. Podemos fazer quantas atribuições quisermos a uma
variável do tipo referência. Quando um bloco de memória (arranjo ou objeto)
previamente referido por uma ou mais variáveis fica sem “ser referido” um
mecanismo da Máquina Virtual Java denominado Coletor de Lixo irá cuidar de reciclar a memória e o bloco de
memória poderá ser reutilizado. O ambiente de execução da linguagem Java não
exige que o programador faça a gerência dos blocos de memória.
A expressão String(arranjoCar) acima, junto ao operador new corresponde
a uma chamada ou invocação de construtor (constructor), na
verdade um dos vários contrutores definidos pela classe String. Somente o
operador new pode invocar um construtor, ou seja, um construtor só pode ser
invocado quando da instanciação de um objeto e serve para iniciar ou a
inicializar este objeto. A classe String tem vários construtores. Os
contrutores têm o mesmo nome da classe e só diferem entre si pelo número e tipo
dos parâmetros. A classe String define um construtor com apenas um parâmetro do
tipo arranjo de caractere que, por definição, aloca um objeto do tipo String
onde a cadeia de caractere é formada pela concatenação dos caracteres do
arranjo correspondente.
Arranjos de referências para Objetos do tipo String (Arranjos de String)
Podemos definir arranjos de
String, ou sem vício de linguagem, arranjos de referências para objetos do tipo
String:
String[] meses={"", "Janeiro",
"Fevereiro", "Março",...,"Dezembro"};
No arranjo acima foi incluído, na posição de índice 0, um string de comprimento
zero. Uma outra solução seria o uso de null.
Abaixo exemplificamos a
utilização de um método estático da classe String. Considere que temos
uma variável do tipo int contendo o dia do mês e uma variável também do
tipo int contendo o número do mês.. No trecho abaixo é construido um
string do dia do mês utilizando o método estático valueOf() da classe String:
int numDoDia=...;
int numDoMes=...;
String data="dia " + String.valueOf(numDoDia)+
" de " + meses[numDoMes];
No trecho acima invocamos o método valueOf() da classe String. Este
método cria um objeto contendo uma cadeia de caracteres correspondente à
conversão do valor inteiro do parâmetro numDoDia e retorna a referência para
este objeto. Descrito de outra maneira: valueOf() retorna a representação em
cadeia de caracteres do argumento numDoDia.
Uma chamada de método que
retorne uma referência pode ser qualificada, utilizando o operador '.', com
nova chamada de método. Considere o trecho de programa abaixo:
String universidade= "Universidade Federal de Minas Gerais";
String s=universidade.substring(0,20);
A invocação do método substring(0,20) retorna uma referência para um objeto contendo
a subcadeia "Universidade Federal". O primeiro parâmetro é o índice
de início (inclusive) e o segundo parâmetro é o índice final da subcadeia
(exclusivo) que é copiada da instância. Como o método substring() devolve uma referência para uma
instância de String, podemos invocar qualquer método de instância utilizando
esta referência:
String universidade= "Universidade Federal de Minas Gerais";
String x=universidade.substring(24,36).substring(0,5);
O trecho acima mostra duas
chamadas do método substring(). A primeira chamada retorna a cadeia "Minas
Gerais" e a segunda chamada retorna a cadeia "Minas".
Para comparar cadeias
existem os métodos: equals(), equalsIgnorecase(), compareTo() e
compareToIgnoreCase(). Se for utilizada o operador == da Linguagem Java com
variáveis do tipo String a comparação será feita com as referências:
String ab="ab";
String bc="bc";
String s1=ab+"c";
String s2="a"+bc;
if(s1==s2) System.out.println("valores de s1 e s2 iguais!");
No trecho acima o conteúdo de s1 e s2 são duas referências diferentes (foi
preciso alguma prestidigitação - sem trocadilhos - porque o compilador tenta
reutilizar cadeias iguais!). Se quisermos comparar o conteúdo das instâncias
devemos utilizar os métodos mencionados:
String ab="ab";
String bc="bc";
String s1=ab+"c";
String s2="a"+bc;
if(s1==s2) System.out.println("Endereço em s1 igual ao endereço em
s2!");
if(s1.equals(s2)) System.out.println("Instancias com cadeias
iguais!")
O método compareTo() é um método de instância que tem um argumento do tipo
String e retorna um valor do tipo int. O valor retornado é (i) menor que
zero quando a cadeia do argumento é lexicograficamente maior que a cadeia da
instância, (ii) zero quando a cadeia do argumento é a mesma da instância e
(iii) maior que zero quando a cadeia do argumento é menor que a cadeia da
instância:
String abc="abc";
String bcd="bcd";
if(abc.compareTo(bcd)==0)
System.out.println("cadeias iguais:"+abc);
if(abc.compareTo(bcd)<0)
System.out.println(abc+ " precede lexicograficamente "+
bcd);
if(abc.compareTo(bcd)>0)
System.out.println(bcd+ "precede lexicograficamente
"+abc);
Os métodos
equalsIgnoreCase() e compareToIgnoreCase() são similares à descrição acima mas
ignoram se as letras são maiúsculas ou minúsculas.
O método toLowerCase() da classe String retorna uma (referência para
uma) instância de
String na qual todos os caracteres foram convertidos para minúsculas. O método toUpperCase() retorna uma instância de String na
qual todos os caracteres (letras) foram convertidos para letras maiúsculas:
String universidade= "Universidade Federal de Minas Gerais";
String uniMinusculas=universidade.toLowerCase();
String unimaiusculas=universidade.toUpperCase();
A linguagem Java
define a classe Character que possui vários métodos estáticos úteis no tratamento de caracteres
individuais. Os seguintes métodos recebem um caractere como argumento e devolvem
um valor booleano: isDigit(), isLetter(), isLetterOrDigit(), isLowerCase(),
isSpaceChar(), isUpperCase().
Os comandos abaixo testam
se o primeiro caractere de uma instância de String é letra ou dígito:
String s="...";
if(Character.isDigit(s.charAt(0)) | Character.isLetter(s.charAt(0)))
System.out.println(...
if(Character.isLetterOrDigit(s.charAt(0)))
System.out.println(...
Exercícios