简体   繁体   中英

Proper use of Java Generics

I have the following function:

/**
 * Finds all entities of a certain type
 * @param <T> The type of the entity
 * @param entityType The class of the entity
 * @return A list of all the entities found, null if the entity is not in the database
 * or on error
 */
public <T> List<T> findAll(Class entityType) 
{
    javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
    cq.select(cq.from(entityType));
    return getEntityManager().createQuery(cq).getResultList();
}

You will note it is rather repetitive. Is there anyway that I can refactor this function so that it does not need to take a Class as a parameter. Is there anyway that I can use the generic type being passed in?

No, you cannot — directly. In a couple paragraphs I'll show you another way around the problem.

Java generics are implemented via type erasure , meaning that all type information is stripped away at runtime. When your method is invoked, it knows that it is supposed to return a List , but at run time there is nothing to tell it that it is supposed to return List<Foo> . The ArrayList constructor doesn't need access to the class object to do its job; your code, however, does.

The way around this is to just pass the class which, since this is a generic method (as opposed to a generic class), you can do. If you change the declaration of your method to be

public <T> List<T> findAll(Class<T> entityType)

Then you can call it with the class:

findAll(String.class)

and the compiler will automatically detect that it is supposed to return a List<String> . It reduces the redundancy, but by inferring the type argument from the class rather than the other way around. This is the standard way to solve this kind of problem — it shows up a lot in libraries like Guava .

The standard practice to create a "Generic DAO" is to have an abstract class that is parametrizable and have subclasses with a specific parameter. This way, the method itself is already parametrized with the correct type.

Take a look at this for an example:

http://netbeans.org/projects/samples/sources/samples-source-code/content/samples/javaee/AffableBean/src/java/session/AbstractFacade.java

Not really, no. Generics are a compile time feature. At run time a caller of your API can supply any instance of Class<T> for entityType so you need it available at runtime to provide to hibernate. The compiler has no ability to basically build a separate version of the method for every possible T , which is what it would have to do in order to omit the class parameter.

Also, Class is a raw type, you are already using generics improperly ;)

Java uses Type erasure, which means that the generic type parameter is not available at runtime (used at compile time to ensure your code is correct wrt to types). This means that to use persistence you will need to explicitly pass the class so the runtime can figure out how to do the persistence (eg what class the persisted object belongs to)

The simple answer is "no". Due to something called type erasure, all parameterized types are treated as Object.class at runtime in the compiled bytecode. The generic types are only used at compile time to prevent you from using things wrong.

You can more or less do what you're asking in Scala. (I'm not sure if you can use scala, but just for reference, here it is).

object Main extends App{
    def findAll[T : Manifest]() : Array[T] = {
       var a = new Array[T](0)
       println(a.getClass)
       a
    }

    var result = findAll[String]()
    println(result.getClass)

    var result2 = findAll[Array[Int]]()
    println(result2.getClass)
}

By using the implicit Manfiest, the scala compiler keeps a record of what generics get erased on compile.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM