Tipologia <T> como fazer isntanceof T

22 respostas
Lavieri

agora me deparei com uma duvida... como saber c um Object é do tipo de uma classe T...

por exemplo..

public class Teste<T> {

    public boolean teste(Object obj) [
        if (!(obj instanceof T))
           return false;
        else
           // ....
    }
}

este teste não funciona... não posso usar instanceof T ... tem q por uma classe la... so que eu não tenho essa classe no meu objeto Teste<T>....

como fazer ? esse teste ?? tem como ?

22 Respostas

renatosilva

Por que você iria querer fazer isso? A informação de genéricos é descartada após a compilação, portanto instanceof T teria que ser resolvido para alguma coisa concreta. Não sei o que você quer, mas que tal mudar para:

public class Teste<T> {
	
	public boolean teste(T obj) { 
		// obj sempre é do tipo T, do contrário nem compila...
		return true;
	}
}
Lavieri

é mais eu estou querendo fazer justamente o contrario… heheh

o argumento que eu vou receber precisa ser um Object…

e eu preciso testar c é instancia de T antes de prosseguir, e se não for abortar…

estou querendo fazer isso…

public <T> Filter toGenericType(final Flter<T> filter) { return new Filter() { public boolean match(Object candidate) { if (candidate instanceof T) return false; else return filter.match((T)candidate); } }; }

em resumo… quero criar um filtro generico, recebendo como argumento um filtro especifico… e como esse filtro generico pode aceitar para testes objetos que não são do tipo T, eu preciso antes de fazer o cast para testar, verificar c o candidato é do tipo T…

o Filter<T> é uma interface assim

public Filter<T> { public boolean match(T candidate); }

c eu não fizer o teste de isntanceof, antes de fazer o cast, da CastException (não lembro extamente o nome da exceção)

tnaires

Você precisa arrumar alguma forma de passar um objeto do tipo Class<T>, seja no construtor ou como parâmetro do método toGenericType() mesmo.

Lavieri

essa amarração é simplismente ridicula, ter q passar uma Class<T> para poder obter a classe do meu parametro T … totalmente sem sentido isso do java… tenho fé que exista uma forma menos burocrata de se fazer isso… não há sentido em ser obrigado a passar a classe junto, afinal de conta a definição do parametro T é justamente pra servir a esse proposito…

acho ridiculo ter q apelar pra conseguir a classe, ate sei como fazer, mais deveria ter 1 forma mais facil… a unica forma q encontrei segue abaixo… se alguem encontrar uma forma menos agreciva, eu agradeço

public <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { private T temp; public boolean match(Object candidate) { Class clazz = getClass().getFields()[0].getType(); return (clazz.isInstance(candidate)) ? filter.match((T)candidate) : false; } }; }

renatosilva

Lavieri, acho que você não está entendendo. O tal T quando compilado não existe mais, você quer usar os genéricos pra uma coisa que eles não foram feitos. Por que candidate tem que ser Object, se quando ele não é T o método aborta? Por que não abortar já em tempo de compilação?

Lavieri
renatosilva:
Lavieri, acho que você não está entendendo. O tal T quando compilado não existe mais, você quer usar os genéricos pra uma coisa que eles não foram feitos. Por que candidate tem que ser Object, se quando ele não é T o método aborta? Por que não abortar já em tempo de compilação?

vc não ta entendendo... eu simplismente kero criar um outro método... e esse outro método precisa saber qual é a classe do Tipo T, pra poder usa-lo corretamente...

preciso disso, pra montar um filtro generico... kero converter um filtro de um objeto especifico, que é tipado, em um filtro generico, ou seja... um filtro aplicavel a coisas não tipadas, pra isso preciso fazer um teste de instanceOf....

é um filtro dentro do outro...

Filter por exemplo, so pesquisa cidades, e so aceita cidades em seu parametro....

posso kerer criar um filtro que contenha esse filtro por exemplo..

Filter<Cidade> filterCidade;
Filter<Pessoa> filterPessoa;
Filter<?> pessoaOuCidade  = FilterUtils.or(filterCidade,filterPessoa);

agora eu tenho uma lsita assim

List<?> listaContendoDiversosObjetos;

//aki eu recebo uma lsita que contem as pessoas do primeiro filtro, ou as cidades do segundo filtro
List<?> list = CollectionUtils.findAll(listaContendoDiversosObjetos, pessoaOuCidade);

isso é apenas 1 exemplo...

eu simplismente kero conseguir transformar um Filtro tipado, em um não tipado..., e para isso preciso conseguir testar isntanceof

renatosilva

Não dá pra descobrir se um objeto é do tipo T porque quando o código rodar, T terá sido removido pelo compilador no bytecode gerado.

http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html

Lavieri:
public <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { private T temp; public boolean match(Object candidate) { Class clazz = getClass().getDeclaredFields()[0].getType(); return (clazz.isInstance(candidate)) ? filter.match((T)candidate) : false; } }; }

Esse tipo de código não funciona. Ele roda porque candidate é instância de clazz, mas clazz é Object e não T! O compilador converte “T temp” em “Object temp” no bytecode gerado. Como uma instância candidate é sempre um Object, seu código mesmo rodando não faz o que você quer.

Lavieri

eu sei… eu vi que não funciona… o que funciona mesmo é um assim

public static <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { public boolean match(Object candidate) { Class clazz = filter.getClass().getMethods()[0].getParameterTypes()[0]; System.out.println(clazz); return (clazz.isInstance(candidate)) ? filter.match((T)candidate) : false; } }; }

mesmo o T, daquele filter T sumindo… ele não vira um Object, ele se transforma na mesma classe do T passado…

eu ja testei e assim fucniona… porem depende do numero de métodos que o filter tem…

to vendo que vou acabar fazendo a versão mais feia =/ …

public static <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { public boolean match(Object candidate) { try { return filter.match((T)candidate); } catch(ClassCastException ex) { return false; } } }; }

victorwss

Lavieri:
public static <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { public boolean match(Object candidate) { try { return filter.match((T)candidate); } catch(ClassCastException ex) { return false; } } }; }
Isso é o mesmo que isso: public static <T> Filter toGenericType(final Filter<T> filter) { return new Filter() { public boolean match(Object candidate) { return filter.match((T)candidate); } }; }Que provavelmente vai ser equivalente a isso: public static <T> Filter toGenericType(final Filter<T> filter) { return filter; }E isso com certeza não é o que você quer.
Fazer um cast para T não funciona, o compilador vai apagar o T e vai virar um cast para Object. Um cast para Object nunca lança ClassCastException.
Tenta assim: public static <T> Filter toGenericType(Class<T> classe, final Filter<T> filter) { return new Filter() { public boolean match(Object candidate) { try { return filter.match(classe.cast(candidate)); } catch(ClassCastException ex) { return false; } } }; }Edit: Pergunta: Porque que o Filter retornado não é genérico e o recebido por parâmetro é? Será que você não deveria tentar usar Filter<?> ou Filter<Object>?

victorwss
Lavieri:
eu sei... eu vi que não funciona... o que funciona mesmo é um assim
public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                Class clazz = filter.getClass().getMethods()[0].getParameterTypes()[0];
                System.out.println(clazz);
                return (clazz.isInstance(candidate)) ?
                    filter.match((T)candidate) : false;
            }
        };
    }

mesmo o T, daquele filter T sumindo... ele não vira um Object, ele se transforma na mesma classe do T passado...

eu ja testei e assim fucniona... porem depende do numero de métodos que o filter tem...
Tenta assim:
public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                Method m = achaMetodo(filter.getClass());
                Class clazz = m.getParameterTypes()[0];

                return (clazz.isInstance(candidate)) ?
                    filter.match((T) candidate) : false;
            }
        };
    }

    private static Method achaMetodo(Class<?> clazz) {
        for (Method m : clazz.getMethods()) {
            if (m.getName().equals("match")
                    && !m.isSynthetic()
                    && !m.isBridge()
                    && m.getParameterTypes().length == 1
                    && m.getReturnType() == boolean.class) {
                return m;
            }
        }
        throw new IllegalArgumentException(clazz.getName());
    }
Lavieri
victorwss:
Lavieri:
public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                try {
                    return filter.match((T)candidate);
                } catch(ClassCastException ex) {
                    return false;
                }
            }
        };
    }
Isso é o mesmo que isso:
public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                return filter.match((T)candidate);
            }
        };
    }
Que provavelmente vai ser equivalente a isso:
public static <T> Filter toGenericType(final Filter<T> filter) {
        return filter;
    }
E isso com certeza não é o que você quer. Fazer um cast para T não funciona, o compilador vai apagar o T e vai virar um cast para Object. Um cast para Object nunca lança ClassCastException. Tenta assim:
public static <T> Filter toGenericType(Class<T> classe, final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                try {
                    return filter.match(classe.cast(candidate));
                } catch(ClassCastException ex) {
                    return false;
                }
            }
        };
    }
Edit: Pergunta: Porque que o Filter retornado não é genérico e o recebido por parâmetro é? Será que você não deveria tentar usar Filter<?> ou Filter<Object>?

não é verdade Victorwsss .... isso que eu fiz funciona, e resolve o problema, porem eu acho feio... e o cast para (T) gera sim uma ClassCastException

não há sentido em enviar a classe como parametro, ja que na tipologia do Filter<T> ja contem a classe, não vejo logica em ter q enviar juntamente a classe T ....

so para demostrar o que falei
public static void main(String[] args) {
        Filter<Cidade> cidadeFilter = new Filter<Cidade>() {
            public boolean match(Cidade candidate) {
                return candidate.getNome().equals("João Pessoa");
            }
        };
        //converte o cidadeFilter em um filtro generico
        Filter genericFilter = FilterUtils.toGenericType(cidadeFilter);
        String s = "teste q gera exceção";
        System.out.println(genericFilter.match(s)); //posso testar o match com qualquer objeto

        //isso abaixo obviamente não compilaria.
        //cidadeFilter.match(s);
    }

    public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                try {
                    return filter.match((T)candidate);
                } catch(ClassCastException ex) {
                    System.out.println("o cast (T) gerou exceção");
                    return false;
                }
            }
        };
    }

a resposta do console é

o cast (T) gerou exceção
false

ou seja... quando não é possivel realizar o cast, ele retorna false imediatamente, como é impresso na linha abaixo

victorwss
Lavieri:
não é verdade Victorwsss .... isso que eu fiz funciona, e resolve o problema, porem eu acho feio... e o cast para (T) gera sim uma ClassCastException

não há sentido em enviar a classe como parametro, ja que na tipologia do Filter<T> ja contem a classe, não vejo logica em ter q enviar juntamente a classe T ....

so para demostrar o que falei
public static void main(String[] args) {
        Filter<Cidade> cidadeFilter = new Filter<Cidade>() {
            public boolean match(Cidade candidate) {
                return candidate.getNome().equals("João Pessoa");
            }
        };
        //converte o cidadeFilter em um filtro generico
        Filter genericFilter = FilterUtils.toGenericType(cidadeFilter);
        String s = "teste q gera exceção";
        System.out.println(genericFilter.match(s)); //posso testar o match com qualquer objeto

        //isso abaixo obviamente não compilaria.
        //cidadeFilter.match(s);
    }

    public static <T> Filter toGenericType(final Filter<T> filter) {
        return new Filter() {
            public boolean match(Object candidate) {
                try {
                    return filter.match((T)candidate);
                } catch(ClassCastException ex) {
                    System.out.println("o cast (T) gerou exceção");
                    return false;
                }
            }
        };
    }

a resposta do console é

o cast (T) gerou exceção
false

ou seja... quando não é possivel realizar o cast, ele retorna false imediatamente, como é impresso na linha abaixo

Na verdade esse ClassCastException não vem do cast para T. Vem dos métodos bridge sintéticos que o compilador cria para resolver os generics. Seu compilador criou na classe anônima de cidade filter um método assim:
public boolean match(Object candidate) {
                return match((Cidade) candidate);
            }
Repetindo, o compilador cria esses métodos por conta. Se você lançar uma exceção dentro do método match(Cidade), verá que no stacktrace aparecerá duas vezes o método match.

De qualquer forma, veja a minha segunda resposta que eu dei.

Lavieri

victorwss:

Na verdade esse ClassCastException não vem do cast para T. Vem dos métodos bridge sintéticos que o compilador cria para resolver os generics.
Seu compilador criou na classe anônima de cidade filter um método assim: public boolean match(Object candidate) { return match((Cidade) candidate); }Repetindo, o compilador cria esses métodos por conta. Se você lançar uma exceção dentro do método match(Cidade), verá que no stacktrace aparecerá duas vezes o método match.

De qualquer forma, veja a minha segunda resposta que eu dei.

ora… e o que vc disse é exatamente o que eu disse… a intenção era essa… que T, se tranforme na classe tipada, no caso especifico Cidade…

ou seja… vc falar

public boolean match(Object candidate) { return match((Cidade) candidate); }

e falar

public boolean match(Object candidate) { return match((T) candidate); //onde T é Cidade }

da no mesmo ora… como disse… o cast para o tipo T, que no caso que exemplifiquei é Cidade, gera sim um ClassCastException… e a intenção era essa…

e o método criado pela classe anonima que o compilador gera é na verdade assim

public boolean match(Object candidate) { try { return filter.match((Cidade) candidate); } catch (ClassCastException ex) { return false; } }

a minha vontade era que fosse com isntanceof, mais por algum motivo bizarro o compilador não deixa fazer, não há sentido em não deixar, mais não deixa =/

era pra ser assim: public boolean match(Object candidate) { if (candidate instanceof Cidade)) return false; return filter.match((Cidade) candidate); }

So não entendo qual o motivo do compilador não deixar fazer instanceof com tipologia…

victorwss
Lavieri:
...
Você não entendeu o que eu disse. Esse é o seu código:
public static void main(String[] args) {  
    Filter<Cidade> cidadeFilter = new Filter<Cidade>() {  
        public boolean match(Cidade candidate) {  
            return candidate.getNome().equals("João Pessoa");  
        }  
    };  
    //converte o cidadeFilter em um filtro generico  
    Filter genericFilter = FilterUtils.toGenericType(cidadeFilter);  
    String s = "teste q gera exceção";  
    System.out.println(genericFilter.match(s)); //posso testar o match com qualquer objeto  
  
    //isso abaixo obviamente não compilaria.  
    //cidadeFilter.match(s);  
}  
  
public static <T> Filter toGenericType(final Filter<T> filter) {  
    return new Filter() {  
        public boolean match(Object candidate) {  
            try {  
                return filter.match((T)candidate);  
            } catch(ClassCastException ex) {  
                System.out.println("o cast (T) gerou exceção");  
                return false;  
            }  
        }  
    };  
}
O compilador apaga os generics na compilação. Eles somem, desaparecem. O compilador transforma o seu código nisso:
public static void main(String[] args) {  
    Filter cidadeFilter = new Filter() {
        // Esse método é colocado pelo compilador!
        public boolean match(Object candidate) {  
            return match((Cidade) candidate); // É esse o cast que te dá o ClassCastException!
        }

        public boolean match(Cidade candidate) {  
            return candidate.getNome().equals("João Pessoa");  
        }
    };  
    //converte o cidadeFilter em um filtro generico  
    Filter genericFilter = FilterUtils.toGenericType(cidadeFilter);  
    String s = "teste q gera exceção";  
    System.out.println(genericFilter.match(s)); //posso testar o match com qualquer objeto  
  
    //isso abaixo obviamente não compilaria.  
    //cidadeFilter.match(s);  
}  
  
public static Filter toGenericType(final Filter filter) {  
    return new Filter() {  
        public boolean match(Object candidate) {  
            try {  
                return filter.match((Object)candidate);  // Não vai ser esse cast que vai dar ClassCastException!
            } catch(ClassCastException ex) {  
                System.out.println("o cast (T) gerou exceção");  
                return false;  
            }  
        }  
    };  
}
Lavieri:
So não entendo qual o motivo do compilador não deixar fazer instanceof com tipologia...
Os generics são apagados pelo compilador. Eles não estão no bytecode, logo, a máquina virtual não os vê e nem sabe que eles existem. Logo, não tem como ela fazer um instanceof durante a execução. Apenas algumas poucas informações de assinatura de métodos e de classes ficam no bytecode para serem lidas via reflection apenas, de resto, é como se não existissem.

Observe que o compilador, ao criar os métodos sintéticos, para resolver o reflection, vai sobrecarregar alguns dos teus métodos. Por isso que getMethods[0] nem sempre trás o método correto. A solução para isso é procurar o método correto olhando para outros detalhes da sua assinatura, tal como no segundo exemplo que eu te mostrei.

Lavieri

Que o compilador some com o T, eu sei... so que da mesma forma que ele subistitui os T por Cidade e cria os métodos correspondentes...

ele poderia subistituir

candidate instanceof T

por

candidate instanceof Cidade

é tão simples substituir isso quando subistituir no resto, não entendo a dificuldade real de fazer isso... mais enfim... paciencia...

e de toda forma... eu vi que realmente o erro de cast é dentro do filter... fiz o seguinte e comprovei...

Filter<Cidade> cidadeFilter = new Filter<Cidade>() {    
        public boolean match(Cidade candidate) {    
            return candidate.getNome().equals("João Pessoa");    
        }
        
        //criando esse método abaixo, não da ClassCastException
       public boolean match(Object o) {
             System.out.println("Não da classCastException");
             return false;
       }
    };
renatosilva

Lavieri:
candidate instanceof Cidade

é tão simples substituir isso quando subistituir no resto, não entendo a dificuldade real de fazer isso… mais enfim… paciencia…

É simples mas não tem lógica alguma. Raciocina…

renatosilva

Que tal isso aqui:

public class FilterUtils_Sugestao {
	
	static class Cidade {
		public String getNome() {
			return "João Pessoa";
		}
	}

	interface Filter<T> {
		public boolean match(T o);
		public Class<? extends T> targetClass(); // Implementações são obrigadas a dizer que tipo de coisa elas filtram 
	}


	public static <T> Filter<Object> toGenericType(final Filter<T> filter) {

		return new Filter<Object>() {
			public boolean match(Object candidate) {
				// A implementação me dirá a classe com a qual o filtro trabalha 
				return filter.targetClass().isInstance(candidate)? 
					filter.match((T) candidate) : false;
			}
			public Class<? extends Object> targetClass() { 
				return Object.class;
			}
		};
	}

	public static void main(String[] args) {
		
		Filter<Cidade> cidadeFilter = new Filter<Cidade>() {
			public boolean match(Cidade candidate) {
				return candidate.getNome().equals("João Pessoa");
			}
			public Class<? extends Cidade> targetClass() {
				return Cidade.class; // Sou um filtro de Cidade
			}
		};
		
		Filter<Object> genericFilter = FilterUtils_Sugestao.toGenericType(cidadeFilter);
		System.out.println(genericFilter.match("teste")); 
		System.out.println(genericFilter.match(new Cidade()));	
	}

}
Lavieri

eu acho triste, ter que informar a classe quando há tipologia…

hora, a tipologia devia vim para facilitar esse trabalho, ter q informar a classe em 2 lugares acho algo triste =/

mas talvez seja obrigado a fazer isso =/

renatosilva

Eu acho triste você querer coisas sem sentido.

Realmente cara, dá uma lida sobre Generics, aí tu vai entender como instanceof T não tem lógica. Depois você pode até continuar insatisfeito, mas pelo motivo certo (não vou te dizer, vou deixar pra você concluir).

victorwss

Lavieri:
Que o compilador some com o T, eu sei… so que da mesma forma que ele subistitui os T por Cidade e cria os métodos correspondentes…

ele poderia subistituir

candidate instanceof T

por

candidate instanceof Cidade

é tão simples substituir isso quando subistituir no resto, não entendo a dificuldade real de fazer isso… mais enfim… paciencia…

Aí é que está o porém. O COMPILADOR NÃO FAZ ISSO. Ele substitui T por Object e ponto-final. Daí ele cria alguns casts e alguns métodos ocultos para lidar com a incompatibilidade.

Na verdade, a forma como generics foi implementado é uma gambiarra. E como toda boa gambiarra está cheia de problemas estranhos e regras obscuras. Eles implementaram assim para que não houvessem problemas de compatibilidade com java 1.4 e inferiores.

C++, por exemplo, faz essa substituição mais ou menos da mesma forma que você falou. Mas isso gera dois problemas:

  1. Você tem centenas de classes sintéticas. Em C++, coisas como List<Cachorro>, List<Gato> e List<Integer> são diferentes. Além disso, trabalhar com coringas (aquele ?) seria muito mais complexo no java (C++ não tem coringas). No java, como o generic é apagado, você tem List simplesmente.
  2. Problemas de compatibilidade com o java 1.4. Se o generic não fosse apagado do bytecode, seria necessário adicionar alguns atributos ocultos em diversas classes.

Além disso, há casos onde substituir não seria possível. Por exemplo:

public class Foo<T> { public T foo(Object o) { return (T) o; // *** } }public class Moo { public static void main(String[] args) { Object x = 42; Foo<Integer> a = new Foo<Integer>(); a.foo(x); x = "batatinha"; Foo<String> b = new Foo<String>(); b.foo(x); } }Aqui, essa substituição não funcionaria. Na linha com o ***, ele deveria colocar o que? Integer? String? A única coisa que dá é Object mesmo. Se fosse substituir por Integer ou por String, teria que fazer isso para cada objeto, e isso iria requerer uma classe para cada tipo de objeto. Ainda assim ia ter alguns probleminhas quando uma superclasse tivesse um generic em um método final. Outra alternativa seria simplesmente mandar a compatibilidade com o java 1.4 e inferiores para o espaço, mas uma das bandeiras do java é justamente a retrocompatibilidade.

aeciovc

sem sentido??

eu vi sentido no que o Lavieri falou e concordo.

não entendi na falta de sentido ai.

uma justificativa pra que ele não faça isso pode ser:

não um modelo da Linguagem Java.

A

pois é, também acho que faz todo sentido…

quando se começar a usar generics cedo ou tarde você acaba tendo necessidade (ou desejo) similar…

os artigos abaixo explicam bem os motivos que fizeram generics ser implementado assim:

http://www.ibm.com/developerworks/java/library/j-jtp01255.html
http://blog.caelum.com.br/2008/04/28/nao-posso-descobrir-nem-instanciar-tipos-genericos-porque/

e esse aqui uma forma NÃO recomendada de (talvez) contornar esse problema:
http://blog.caelum.com.br/2006/10/29/brincando-com-generics-o-bizarregenericdao/

apesar de entender os motivos dessa técnica de erasure eu realmente gostaria que generics tivesse mais poder…

Criado 5 de abril de 2009
Ultima resposta 6 de set. de 2010
Respostas 22
Participantes 6