Informações gerais sobre construtores
Конструктор
é uma estrutura semelhante a um método, cujo objetivo é criar uma instância de uma classe. Características do designer:
- O nome do construtor deve corresponder ao nome da classe (por convenção, a primeira letra é maiúscula, geralmente um substantivo);
- Existe um construtor em qualquer classe. Mesmo que você não escreva um, o compilador Java criará um construtor padrão, que estará vazio e não fará nada além de chamar o construtor da superclasse.
- Um construtor é semelhante a um método, mas não é um método, nem mesmo é considerado membro da classe. Portanto, não pode ser herdado ou substituído em uma subclasse;
- Os construtores não são herdados;
- Pode haver vários construtores em uma classe. Neste caso, diz-se que os construtores estão sobrecarregados;
- Se uma classe não definir um construtor, o compilador adicionará automaticamente um construtor sem parâmetros ao código;
- Um construtor não tem um tipo de retorno; nem pode ser um tipo
void
; se um tipo for retornado void
, então ele não é mais um construtor, mas um método, apesar da coincidência com o nome da classe.
- O operador é permitido no construtor
return
, mas apenas vazio, sem nenhum valor de retorno;
- O construtor permite o uso de modificadores de acesso; você pode definir um dos modificadores:
public
, protected
ou private
sem modificador.
- Um construtor não pode ter os modificadores
abstract
, final
, ou ;native
static
synchronized
- A palavra-chave
this
refere-se a outro construtor na mesma classe. Se usado, a chamada deve ser a primeira linha do construtor;
- A palavra-chave
super
chama o construtor da classe pai. Se utilizado, a referência a ele deverá ser a primeira linha do construtor;
- Se o construtor não fizer uma chamada ao
super
construtor da classe ancestral (com ou sem argumentos), o compilador adicionará automaticamente código para chamar o construtor da classe ancestral sem argumentos;
Construtor padrão
Existe um construtor em qualquer classe. Mesmo que você não escreva um, o compilador Java criará um construtor padrão. Este construtor está vazio e não faz nada além de chamar o construtor da superclasse. Aqueles. se você escrever:
public class Example {}
então isso é equivalente a escrever:
public class Example
{
Example()
{
super;
}
}
Nesse caso, a classe ancestral não é especificada explicitamente e, por padrão, todas as classes Java herdam a classe,
Object
portanto, o construtor da classe é chamado
Object
. Se uma classe define um construtor com parâmetros, mas não há nenhum construtor sobrecarregado sem parâmetros, então chamar o construtor sem parâmetros é um erro. Porém, em Java desde a versão 1.5, é possível usar construtores com argumentos de comprimento variável. E se houver um construtor que tenha um argumento de comprimento variável, chamar o construtor padrão não será um erro. Não acontecerá porque o argumento de comprimento variável pode estar vazio. Por exemplo, o exemplo a seguir não será compilado, mas se você descomentar o construtor com um argumento de comprimento variável, ele será compilado e executado com êxito e resultará em uma linha de código em execução
DefaultDemo dd = new DefaultDemo()
; o construtor será chamado
DefaultDemo(int ... v)
. Naturalmente, neste caso é necessário utilizar JSDK 1.5. Arquivo
DefaultDemo.java
class DefaultDemo
{
DefaultDemo(String s)
{
System.out.print("DefaultDemo(String)");
}
public static void main(String args[])
{
DefaultDemo dd = new DefaultDemo();
}
}
O resultado da saída do programa com o construtor não comentado:
DefaultDemo(int ...)
Porém, no caso comum em que a classe não define nenhum construtor, será necessário chamar o construtor padrão (sem parâmetros), uma vez que a substituição do construtor padrão ocorre automaticamente.
Criação de objetos e construtores
Ao criar um objeto, as seguintes ações são executadas sequencialmente:
- A classe do objeto é pesquisada entre as classes já utilizadas no programa. Caso não esteja, é pesquisado em todos os catálogos e bibliotecas disponíveis para o programa. Depois que uma classe é descoberta em um diretório ou biblioteca, os campos estáticos da classe são criados e inicializados. Aqueles. Para cada classe, os campos estáticos são inicializados apenas uma vez.
- A memória é alocada para o objeto.
- Os campos da classe estão sendo inicializados.
- O construtor da classe é executado.
- Um link para o objeto criado e inicializado é formado. Esta referência é o valor da expressão que cria o objeto. Um objeto também pode ser criado chamando um método
newInstance()
de classe java.lang.Class
. Neste caso, é utilizado um construtor sem lista de parâmetros.
Sobrecarregando construtores
Construtores da mesma classe podem ter o mesmo nome e assinaturas diferentes. Esta propriedade é chamada de combinação ou sobrecarga. Se uma classe tiver vários construtores, a sobrecarga do construtor estará presente.
Construtores parametrizados
A assinatura de um construtor é o número e os tipos de parâmetros, bem como a sequência de seus tipos na lista de parâmetros do construtor. O tipo de retorno não é levado em consideração. O construtor não retorna nenhum parâmetro. Esta declaração explica, de certa forma, como Java distingue entre construtores ou métodos sobrecarregados. Java distingue métodos sobrecarregados não pelo tipo de retorno, mas pelo número, tipos e sequência de tipos de parâmetros de entrada. Um construtor não pode nem retornar um type
void
, caso contrário ele se transformará em um método regular, mesmo sendo semelhante ao nome da classe. O exemplo a seguir demonstra isso. Arquivo
VoidDemo.java
class VoidDemo
{
VoidDemo()
{
System.out.println("Constructor");
}
void VoidDemo()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo m = new VoidDemo();
}
}
Como resultado, o programa produzirá:
Constructor
Isso prova mais uma vez que um construtor é um método sem parâmetros de retorno. No entanto, o construtor pode receber um dos três modificadores
public
,
private
ou
protected
. E o exemplo agora ficará assim: Arquivo
VoidDemo2.java
class VoidDemo2
{
public VoidDemo2()
{
System.out.println("Constructor");
}
private void VoidDemo2()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo2 m = new VoidDemo2();
}
}
É permitido escrever um operador em um construtor
return
, mas apenas vazio, sem nenhum valor de retorno. Arquivo
ReturnDemo.java
class ReturnDemo
{
public ReturnDemo()
{
System.out.println("Constructor");
return;
}
public static void main(String s[])
{
ReturnDemo r = new ReturnDemo();
}
}
Construtores parametrizados com argumentos de comprimento variável
Java SDK 1.5 introduziu uma ferramenta há muito esperada - argumentos de comprimento variável para construtores e métodos. Anteriormente, um número variável de documentos era processado de duas maneiras inconvenientes. O primeiro deles foi concebido para garantir que o número máximo de argumentos seja limitado a um pequeno número e seja conhecido antecipadamente. Neste caso foi possível criar versões sobrecarregadas do método, uma para cada versão da lista de argumentos passada ao método. O segundo método é projetado para algo desconhecido antecipadamente e um grande número de argumentos. Neste caso, os argumentos foram colocados em um array, e esse array foi passado para o método. Argumentos de comprimento variável estão mais frequentemente envolvidos em manipulações subsequentes com inicializações de variáveis. É conveniente substituir a ausência de alguns dos argumentos esperados do construtor ou do método por valores padrão. O argumento de comprimento variável é uma matriz e é tratado como uma matriz. Por exemplo, o construtor de uma classe
Checking
com um número variável de argumentos ficaria assim:
class Checking
{
public Checking(int ... n)
{
}
}
A combinação de caracteres ... informa ao compilador que um número variável de argumentos será usado e que esses argumentos serão armazenados em um array cujo valor de referência está contido na variável n. O construtor pode ser chamado com um número diferente de argumentos, incluindo nenhum argumento. Os argumentos são automaticamente colocados em um array e passados por n. Se não houver argumentos, o comprimento da matriz será 0. A lista de parâmetros, juntamente com argumentos de comprimento variável, também pode incluir parâmetros obrigatórios. Neste caso, um parâmetro contendo um número variável de argumentos deve ser o último da lista de parâmetros. Por exemplo:
class Checking
{
public Checking(String s, int ... n)
{
}
}
Uma limitação muito óbvia diz respeito ao número de parâmetros de comprimento variável. Deve haver apenas um parâmetro de comprimento variável na lista de parâmetros. Dados dois parâmetros de comprimento variável, é impossível para o compilador determinar onde um parâmetro termina e o outro começa. Por exemplo:
class Checking
{
public Checking(String s, int ... n, double ... d)
{
}
}
Arquivo
Checking.java
Por exemplo, existem equipamentos capazes de reconhecer placas de automóveis e lembrar os números das praças da área por onde cada um dos carros passou durante o dia. É necessário selecionar da massa total de carros registrados aqueles que durante o dia visitaram duas praças determinadas, digamos 22 e 15, de acordo com o mapa da área. É bastante natural que um carro possa visitar muitas praças durante o dia, ou talvez apenas uma. Obviamente, o número de praças visitadas é limitado pela velocidade física do carro. Vamos criar um pequeno programa onde o construtor da classe tomará como argumentos o número do carro como parâmetro obrigatório e os números de quadrados visitados da área, cujo número pode ser variável. O construtor verificará se um carro apareceu em dois quadrados; se sim, exibirá seu número na tela.
Passando parâmetros para o construtor
Existem basicamente dois tipos de parâmetros em linguagens de programação:
- tipos básicos (primitivos);
- referências a objetos.
O termo chamada por valor significa que o construtor recebe o valor passado a ele pelo módulo de chamada. Por outro lado, chamar por referência significa que o construtor recebe o endereço da variável do chamador. Java usa chamada apenas por valor. Por valor de parâmetro e por valor de link de parâmetro. Java não usa chamada por referência para objetos (embora muitos programadores e autores de alguns livros afirmem isso). Ao passar objetos para Java, os parâmetros são passados
não por referência , mas
pelo valor da referência do objeto ! Em ambos os casos, o construtor recebe cópias dos valores de todos os parâmetros. O construtor não pode fazer nada com seus parâmetros de entrada:
- o construtor não pode alterar os valores dos parâmetros de entrada dos tipos principais (primitivos);
- o construtor não pode alterar as referências dos parâmetros de entrada;
- o construtor não pode reatribuir referências de parâmetros de entrada a novos objetos.
O construtor pode fazer isso com seus parâmetros de entrada:
- alterar o estado do objeto passado como parâmetro de entrada.
O exemplo a seguir prova que em Java, os parâmetros de entrada para um construtor são passados pelo valor de referência do objeto. Este exemplo também reflete que o construtor não pode alterar as referências dos parâmetros de entrada, mas na verdade altera as referências das cópias dos parâmetros de entrada. Arquivo
Empoyee.java
class Employee
{
Employee(String x, String y)
{
String temp = x;
x = y;
y = temp;
}
public static void main(String args[])
{
String name1 = new String("Alice");
String name2 = new String("Mary");
Employee a = new Employee(name1, name2);
System.out.println("name1="+name1);
System.out.println("name2="+name2);
}
}
A saída do programa é:
name1=Alice
name2=Mary
Se Java usasse chamada por referência para passar objetos como parâmetros, o construtor trocaria
name1
e neste exemplo
name2
. O construtor não irá realmente trocar as referências de objetos armazenadas nas variáveis
name1
e
name2
. Isto sugere que os parâmetros do construtor são inicializados com cópias destas referências. Então o construtor troca as cópias. Quando o construtor conclui seu trabalho, as variáveis x e y são destruídas e as variáveis originais
name1
continuam
name2
a se referir aos objetos anteriores.
Alterando os parâmetros passados ao construtor.
O construtor não pode modificar os parâmetros passados de tipos básicos. Porém, o construtor pode modificar o estado do objeto passado como parâmetro. Por exemplo, considere o seguinte programa: Arquivo
Salary1.java
class Salary1
{
Salary1(int x)
{
x = x * 3;
System.out.println("x="+x);
}
public static void main(String args[])
{
int value = 1000;
Salary1 s1 = new Salary1(value);
System.out.println("value="+value);
}
}
A saída do programa é:
x=3000
value=1000
Obviamente, este método não alterará o parâmetro de tipo principal. Portanto, após chamar o construtor, o valor da variável
value
permanece igual a
1000
. Essencialmente três coisas acontecem:
- A variável
x
é inicializada com uma cópia do valor do parâmetro value
(ou seja, um número 1000
).
- O valor da variável
x
é triplicado – agora é igual a 3000
. No entanto, o valor da variável value
permanece igual a 1000
.
- O construtor termina e a variável
x
não é mais usada.
No exemplo a seguir, o salário do funcionário é triplicado com sucesso porque o valor de uma referência de objeto é passado como parâmetro para o método. Arquivo
Salary2.java
class Salary2
{
int value = 1000;
Salary2()
{
}
Salary2(Salary2 x)
{
x.value = x.value * 3;
}
public static void main(String args[])
{
Salary2 s1 = new Salary2();
Salary2 s2 = new Salary2(s1);
System.out.println("s1.value=" +s1.value);
System.out.println("s2.value="+s2.value);
}
}
A saída do programa é:
s1.value=3000
s2.value=1000
O valor da referência do objeto é usado como parâmetro. Ao executar a linha
Salary2 s2 = new Salary2(s1)
; o construtor
Salary2(Salary x)
receberá o valor de uma referência ao objeto variável
s1
, e o construtor efetivamente triplicará o salário de
s1.value
, já que até mesmo a cópia
(Salary x)
criada dentro do construtor aponta para o objeto variável
s1
.
Construtores parametrizados por primitivas.
Se os parâmetros de um construtor sobrecarregado usam um primitivo que pode ser reduzido (por exemplo
int <- double
), então é possível chamar um método com um valor restrito, apesar do fato de não haver nenhum método sobrecarregado com tal parâmetro. Por exemplo: Arquivo
Primitive.java
class Primitive
{
Primitive(double d)
{
d = d + 10;
System.out.println("d="+d);
}
public static void main(String args[])
{
int i = 20;
Primitive s1 = new Primitive(i);
}
}
A saída do programa é:
d=30.0
Apesar de a classe
Primitive
não possuir um construtor que possua um parâmetro de tipo
int
, um construtor com um parâmetro de entrada funcionará
double
. Antes de o construtor ser chamado, a variável
i
será expandida de tipo
int
para tipo
double
. A opção oposta, quando a variável
i
seria do tipo
double
, e o construtor teria apenas um parâmetro
int
, nesta situação levaria a um erro de compilação.
Chamada e operadora do construtornew
O construtor é sempre chamado pelo operador
new
. Quando um construtor é chamado com o operador
new
, o construtor sempre gera uma referência a um novo objeto. É impossível forçar o construtor a formar uma referência a um objeto já existente em vez de uma referência a um novo objeto, exceto substituindo o objeto que está sendo desserializado. E com o operador new, em vez de uma referência a um novo objeto, é impossível formar uma referência a um objeto já existente. Por exemplo: Arquivo
Salary3.java
class Salary3
{
int value = 1000;
Salary3()
{
}
Salary3(Salary3 x)
{
x.value = x.value * 3;
}
public static void main(String args[])
{
Salary3 s1 = new Salary3();
System.out.println("First object creation: "+s1.value);
Salary3 s2 = new Salary3(s1);
System.out.println("Second object creation: "+s2.value);
System.out.println("What's happend with first object?:"+s1.value);
Salary3 s3 = new Salary3(s1);
System.out.println("Third object creation: "+s3.value);
System.out.println("What's happend with first object?:"+s1.value);
}
}
A saída do programa é:
First object creation: 1000
Second object creation: 1000
What's happend with first object?: 3000
Third object creation: 1000
What's happend with first object?: 9000
Primeiro, usando a linha
Salary3 s1 = new Salary3()
; um novo objeto é criado. A seguir, se estiver usando a linha
Salary3 s2 = new Salary3(s1)
; ou cordas
Salary3 s3 = new Salary3(s1)
; seria possível criar um link para um objeto já existente, então
s1.value s2.value
eles
s3.value
armazenariam o mesmo valor
1000
. Na verdade na fila
Salary3 s2 = new Salary3(s1)
; um novo objeto para a variável será criado
s2
e o estado do objeto para a variável mudará
s1
passando seu valor de referência para o objeto no parâmetro do construtor. Isso pode ser verificado pelos resultados de saída. E ao executar a linha
Salary3 s3 = new Salary3(s1)
; um NOVO objeto para a variável será criado
s3
e o estado do objeto para a variável mudará novamente
s1
.
Construtores e blocos de inicialização, sequência de ações ao chamar um construtor
A seção
Criando um Objeto e Construtores lista as ações gerais que são executadas ao criar um objeto. Entre eles estão os processos de inicialização dos campos da classe e elaboração do construtor da classe, que por sua vez também possui uma ordem interna:
- Todos os campos de dados são inicializados com seus valores padrão (0, falso ou nulo).
- Todos os inicializadores de campo e blocos de inicialização são executados na ordem em que são listados na declaração de classe.
- Se outro construtor for chamado na primeira linha de um construtor, então o construtor chamado será executado.
- O corpo do construtor é executado.
O construtor está relacionado à inicialização porque em Java existem três maneiras de inicializar um campo em uma classe:
- atribuir um valor na declaração;
- atribuir valores no bloco de inicialização;
- defina seu valor no construtor.
Naturalmente, você precisa organizar o código de inicialização para que seja fácil de entender. A seguinte classe é dada como exemplo:
class Initialization
{
int i;
short z = 10;
static int x;
static float y;
static
{
x = 2000;
y = 3.141;
}
Initialization()
{
System.out.println("i="+i);
System.out.println("z="+z);
z = 20;
System.out.println("z="+z);
}
}
No exemplo acima, as variáveis são inicializadas na seguinte ordem: variáveis estáticas são inicializadas primeiro
x
com
y
valores padrão. A seguir, o bloco de inicialização estática é executado. Em seguida, a variável é inicializada
i
com o valor padrão e a variável é inicializada
z
. Em seguida, o designer começa a trabalhar. A chamada de construtores de classe não deve depender da ordem em que os campos são declarados. Isso pode levar a erros.
Construtores e herança
Construtores não são herdados. Por exemplo:
public class Example
{
Example()
{
}
public void sayHi()
{
system.out.println("Hi");
}
}
public class SubClass extends Example
{
}
A classe
SubClass
herda automaticamente o método
sayHi()
definido na classe pai. Ao mesmo tempo, o construtor
Example()
da classe pai não é herdado pelo seu descendente
SubClass
.
Palavra-chave this
em construtores
Construtores são usados
this
para se referir a outro construtor na mesma classe, mas com uma lista de parâmetros diferente. Se o construtor usar a palavra-chave
this
, ela deverá estar na primeira linha; ignorar esta regra resultará em um erro do compilador. Por exemplo: Arquivo
ThisDemo.java
public class ThisDemo
{
String name;
ThisDemo(String s)
{
name = s;
System.out.println(name);
}
ThisDemo()
{
this("John");
}
public static void main(String args[])
{
ThisDemo td1 = new ThisDemo("Mary");
ThisDemo td2 = new ThisDemo();
}
}
A saída do programa é:
Mary
John
Neste exemplo existem dois construtores. O primeiro recebe um argumento de string. O segundo não recebe nenhum argumento, simplesmente chama o primeiro construtor usando o nome padrão "John". Assim, você pode usar construtores para inicializar os valores dos campos de forma explícita e por padrão, o que geralmente é necessário em programas.
Palavra-chave super
em construtores
Construtores são usados
super
para chamar um construtor de superclasse. Se o construtor usar
super
, então esta chamada deverá estar na primeira linha, caso contrário o compilador gerará um erro. Abaixo está um exemplo: Arquivo
SuperClassDemo.java
public class SuperClassDemo
{
SuperClassDemo()
{
}
}
class Child extends SuperClassDemo
{
Child()
{
super();
}
}
Neste exemplo simples, o construtor
Child()
contém uma chamada
super()
que cria uma instância da classe
SuperClassDemo
, além da classe
Child
. Como
super
deve ser a primeira instrução executada em um construtor de subclasse, essa ordem é sempre a mesma e não depende se
super()
. Se não for usado, o construtor padrão (sem parâmetros) de cada superclasse, começando pela classe base, será executado primeiro. O programa a seguir demonstra quando os construtores são executados. Arquivo
Call.java
class A
{
A()
{
System.out.println("Inside A constructor.");
}
}
class B extends A
{
B()
{
System.out.println("Inside B constructor.");
}
}
class C extends B
{
C()
{
System.out.println("Inside C constructor.");
}
}
class Call
{
public static void main(String args[])
{
C c = new C();
}
}
Saída deste programa:
Inside A constructor.
Inside B constructor.
Inside C constructor.
Os construtores são chamados em ordem de subordinação de classe. Isso faz algum sentido. Como a superclasse não tem conhecimento de nenhuma subclasse, qualquer inicialização que ela precise realizar é separada. Se possível, deve preceder qualquer inicialização realizada pela subclasse. É por isso que deve ser feito primeiro.
Construtores personalizáveis
O mecanismo de identificação de tipo em tempo de execução é um dos princípios básicos poderosos da linguagem Java que implementa o polimorfismo. No entanto, tal mecanismo não protege o desenvolvedor da conversão de tipos incompatíveis em alguns casos. O caso mais comum é a manipulação de um grupo de objetos, cujos vários tipos são previamente desconhecidos e determinados em tempo de execução. Como os erros associados à incompatibilidade de tipo só podem aparecer no estágio de tempo de execução, isso os torna difíceis de encontrar e eliminar. A introdução de tipos customizados no Java 2 5.0 move alguns desses erros do tempo de execução para o tempo de compilação e fornece parte da segurança de tipo ausente. Não há necessidade de conversão de tipo explícita ao passar de um tipo
Object
para um tipo concreto. Deve-se ter em mente que as ferramentas de customização de tipos funcionam apenas com objetos e não se aplicam a tipos de dados primitivos que estão fora da árvore de herança de classes. Com tipos personalizados, todas as conversões são realizadas automaticamente e nos bastidores. Isso permite proteger contra incompatibilidades de tipo e reutilizar código com muito mais frequência. Tipos personalizados podem ser usados em construtores. Os construtores podem ser personalizados mesmo que sua classe não seja um tipo personalizado. Por exemplo:
class GenConstructor
{
private double val;
<T extends Number> GenConstructor(T arg)
{
val = arg.doubleValue();
}
void printValue()
{
System.out.println("val: "+val);
}
}
class GenConstructorDemo
{
public static void main(String args[])
{
GenConstructor gc1 = new GenConstructor(100);
GenConstructor gc2 = new GenConstructor(123.5F);
gc1.printValue();
gc2.printValue();
}
}
Como o construtor
GenConstructor
especifica um parâmetro de tipo personalizado que deve ser uma classe derivada de class
Number
, ele pode ser chamado de qualquer
GO TO FULL VERSION