Discussão: Class.forName vs Nova instância vs Singleton Pattern

Boa tarde a todos.
Sou novo no GUJ e estava ledo sobre java reflections aqui: https://www.google.com.br/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=java%20reflection%20tutorial

Dai surgiu uma dúvida que não minha opinião merece ser discutida mais a fundo:

Qual a diferença entre:
Class c = Class.forName(“example.packge.Myclass”).getInstance()

ou Myclass c = new Myclass();

ou ainda Myclass c = Myclass.getInstance (implementando em Myclass o pattern singleton é claro.)

Para mim basicamente a primeira e a terceira forma são formas próximas de recuperar uma instancia de uma determinada classe em memória.
Acho eu que a primeira simplesmente recuperaria uma instancia de uma determinada classe que estivesse em memória, a segunda criaria uma nova instancia de uma determinada classe e a terceira permitiria apenas uma instancia. Mas e ai? Isto está meio confuso para mim.

Tem ainda a Class.forName(“example.packge.Myclass”).newInstance() que faria analogia com a segunda forma citada acima assim como a terceira faz analogia com a primeira. Também não é tão visível a diferença e utilidade de ambas as formas exceto que sei que Class.forName(“example.packge.Myclass”).newInstance() sempre irá localizar o construtor sem argumentos via java reflection.

Sempre use o new.

Essa instrução:

Class c = Class.forName("example.packge.Myclass").getInstance();

Não funciona pois o método getInstance() não existe. Com esse método, todo mundo assumiria que existe apenas 1 instância desse objeto na memória e seria necessário capturá-lo de qualquer ponto do projeto. Mas isso pode não ser verdade, e como deve fazer?

O problema do Singleton, com a terceira opção, é que ele acabou virando um anti-pattern quando escrito dessa forma que você mostrou. Basicamente: Singleton não é um anti-pattern, mas você saber que você está usando um Singleton é um anti-pattern.

Eu percebo que as pessoas costumam achar que fazer new MyClass() pode ser um mal sinal. Eu não sei direito o por que. Mas Java é orientado a objetos, portando, prefira sempre o uso do new.

Como a questão é o reflection, você precisa saber como a sua classe está implementada para poder usá-lo. Logo, você, programador, tem que saber se tem ou não um construtor default (sem argumentos) aonde você poderia simplesmente chamar o newInstance().

Mas existe uma forma de você invocar um construtor via reflection, desde que você saiba como ele é:

		try {
			String abc = "ABC";
			Integer count = 123;
			long size = 9876543L;

			Class<?> c = Class.forName("com.package.MyClass");
			Object instance = c.getConstructor(String.class, Integer.class, long.class).newInstance("ABC", count, size);
			
			MyClass obj = (MyClass) instance;
		} catch (InstantiationException | IllegalAccessException |
				IllegalArgumentException | InvocationTargetException |
				NoSuchMethodException | SecurityException | ClassNotFoundException e) {
			throw new RuntimeException("Algo errado aconteceu.", e);
		}

[quote=Rafael Guerreiro]Sempre use o new.

Essa instrução:

Class c = Class.forName("example.packge.Myclass").getInstance();

Não funciona pois o método getInstance() não existe. Com esse método, todo mundo assumiria que existe apenas 1 instância desse objeto na memória e seria necessário capturá-lo de qualquer ponto do projeto. Mas isso pode não ser verdade, e como deve fazer?

O problema do Singleton, com a terceira opção, é que ele acabou virando um anti-pattern quando escrito dessa forma que você mostrou. Basicamente: Singleton não é um anti-pattern, mas você saber que você está usando um Singleton é um anti-pattern.

Eu percebo que as pessoas costumam achar que fazer new MyClass() pode ser um mal sinal. Eu não sei direito o por que. Mas Java é orientado a objetos, portando, prefira sempre o uso do new.

Como a questão é o reflection, você precisa saber como a sua classe está implementada para poder usá-lo. Logo, você, programador, tem que saber se tem ou não um construtor default (sem argumentos) aonde você poderia simplesmente chamar o newInstance().

Mas existe uma forma de você invocar um construtor via reflection, desde que você saiba como ele é:

[code]
try {
String abc = "ABC";
Integer count = 123;
long size = 9876543L;

		Class<?> c = Class.forName("com.package.MyClass");
		Object instance = c.getConstructor(String.class, Integer.class, long.class).newInstance("ABC", count, size);
		
		MyClass obj = (MyClass) instance;
	} catch (InstantiationException | IllegalAccessException |
			IllegalArgumentException | InvocationTargetException |
			NoSuchMethodException | SecurityException | ClassNotFoundException e) {
		throw new RuntimeException("Algo errado aconteceu.", e);
	}

[/code][/quote]

Curiosamente porque quando estamos aprendendo sobre JDBC normalmente pedem para usar um Class.forName() para capturar o driver de conexão com o banco?

Sim, mas é apenas para saber se essa classe existe no seu Classpath.

Repare que fazemos apenas Class.forName() e não damos newInstance().

[quote=Rafael Guerreiro]Sim, mas é apenas para saber se essa classe existe no seu Classpath.

Repare que fazemos apenas Class.forName() e não damos newInstance().[/quote]

Na verdade tem um truque meio louco nisso tudo. O Class.forName irá ocasionar o carregamento da classe e, com isso, quaisquer blocos estáticos serão executados. O driver JDBC irá se registrar no DriverManager exatamente em um desses blocos estáticos.

Exemplo: trecho de código do driver do PostgreSQL

public class Driver implements java.sql.Driver
{
// ...
    static
    {
        try
        {
            // moved the registerDriver from the constructor to here
            // because some clients call the driver themselves (I know, as
            // my early jdbc work did - and that was based on other examples).
            // Placing it here, means that the driver is registered once only.
            java.sql.DriverManager.registerDriver(new Driver());
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
    }
// ...

Caramba Marcelo! Muito legal! Eu não sabia disso!

Agora o Class.forName() faz muito mais sentido.

Hehe…eu descobri isso uma vez que tava puto por causa desse Class.forName só pra usar o driver.

O JDBC 4 já está usando o Service Providers, então não é mais necessário utilizar o Class.forName pra registrar o driver pois essa informação já fica dentro do META-INF do jar do driver e é reconhecida pelo DriverManager (mais especificamente no arquivo META-INF/services/java.sql.Driver).

http://docs.oracle.com/javase/7/docs/api/java/sql/DriverManager.html

Ainda está meio confuso pra mim a diferença entre carregar uma classe e instanciar ela. Ou eu sou burro ou não estou pesquisando direto.

Não seja tão duro consigo. Vamos lá!

Pense em uma receita de bolo como se fosse uma classe, dito isso:

Carregar uma classe

Esse processo seria algo como entender a receita. A JVM, ao carregar uma classe, identifica todas as informações sobre ela (os metadados) e as carrega. Dessa forma, ela sabe tudo o que é preciso para instanciar um objeto desta classe.

Instanciar uma classe

Agora seria análogo a fazer o bolo que a receita demonstra. A JVM irá criar uma instância da classe carregada, o objeto, obedecendo os metadados que foram criados no carregamento da classe. Com isso temos que: não podemos instanciar uma classe sem antes tê-la carregado.

Essa analogia tá meio tosca, mas acho que é um bom ponto de partida.


O ato de carregar a classe é bem abstraído do programador e talvez seja por isso que você tenha tido dificuldades pra entender a diferença.

Não seja tão duro consigo. Vamos lá!

Pense em uma receita de bolo como se fosse uma classe, dito isso:

Carregar uma classe

Esse processo seria algo como entender a receita. A JVM, ao carregar uma classe, identifica todas as informações sobre ela (os metadados) e as carrega. Dessa forma, ela sabe tudo o que é preciso para instanciar um objeto desta classe.

Instanciar uma classe

Agora seria análogo a fazer o bolo que a receita demonstra. A JVM irá criar uma instância da classe carregada, o objeto, obedecendo os metadados que foram criados no carregamento da classe. Com isso temos que: não podemos instanciar uma classe sem antes tê-la carregado.

Essa analogia tá meio tosca, mas acho que é um bom ponto de partida.


O ato de carregar a classe é bem abstraído do programador e talvez seja por isso que você tenha tido dificuldades pra entender a diferença.[/quote]

Isto que você disse foi o ponto chave para aliado com tudo que eu pesquisei eu entender um pouco sobre.

Então quer dizer que os blocos estáticos são carregados antes do construtor exatamente por serem carregados ao carregar a classe (bem, isto ficou estranho, mas vamos lá) e o construtor ao instanciar?

Exatamente!! O construtor só vai ser utilizado quando você usar a palavra-chave new.

Exato, recursos static pertencem à classe. Somente 1 por classe, se mudar uma variável static em uma classe, isso reflete na classe. Todo e qualquer objeto desse tipo tem acesso à esse recurso, mas ele é único e compartilhado entre todos os Objetos.

Cara, repito o que o Ataxexe lhe falou, “não seja duro consigo”, cada um tem seu tempo e você entendeu até que bem rápido. Isso eu demorei pra entender da forma correta. Quando eu pensava que tinha entendido static, aparecia uma coisa nova… Rsrsrsrs :smiley:

Abraços e sucesso nos estudos :wink:

Apenas uma curiosidade pra você ver como coisas “static” não dependem de instância:

java.sql.DriverManager.registerDriver(new Driver());

Essa linha funciona também desta forma:

java.sql.DriverManager dm = null;
dm.registerDriver(new Driver());

Isso é possível porque o método registerDriver é estático e, por isso, não importa a instância. O compilador, inclusive, irá gerar um alerta dizendo que o método estático não é utilizado de forma estática.

Espero que isso não tenha te confundido, mas é uma pegadinha legal!