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 definição de
classe define campos(fields), métodos(methods) e constructores(
constructors). São ainda itens de classes os inicializadores estáticos (static initializers) e os
inicializadores de instância (instance
initializers), mas não iremos concentrar muito nestes elementos, em
particular a classe String não os utiliza. 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 além disso métodos são membros (members)de classe e construtores não
são considerados membros. 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 trecho abaixo
String cordao;
cordao= new String();
equivale a
String cordao= "";
Ou seja o construtor String() retorna o endereço de um bloco de
memória representando uma seqüência vazia de caracteres.
O trecho abaixo
char[] arranjoCar;
arranjoCar= new char[]{'a', 'b', 'c ', 'd
', 'e ', 'f '};
String cordao;
cordao= new String(arranjoCar, 1, 4);
equivale a
String cordao= "bcde";
Ou seja o construtor String(char[]
v,int 0, int c)
retorna o endereço de um bloco de memória representando um subarranjo de v
contendo c caracteres a partir do caractere de índice o.
O operador new é que
aloca e estrutura 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. Uma instância de arranjo é um tipo especial de objeto, 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”] sem referência, 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., ou , mais especificamente o programador faz a
gerência de alocação de blocos de memória utilizando o operador new mas não faz a gerência de
desalocação ( na linguagem C o programador deve cuidar também da desalocação!).
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