简体   繁体   中英

Java generic child instance from abstract class

I have a abstract class, some childs and a generic DAO to the abstract class.

public abstract class Person { ... }
public class User extends Person { ... }
public class Client extends Person { ... }
public PersonDao <T extends Person> { ... }

I want to make a select method in PersonDao to return a child object of class Person.

public T select(int id) throws SQLException {
    String sql = "SELECT * FROM person WHERE per_id=?";
    PreparedStatement ps = con.preparedStatement(sql);
    ps.setInt(1, id);
    ResultSet rs = ps.executeQuery();
    T t = null;

    if(rs.next()) {
        t = new T(); // Error line

        t.setId(rs.getInt("per_id"));
        t.setName(rs.getString("per_name"));
    }

    return t;
}

How can I instantiate the generic child class and return a child object?

You cannot do this in Java because of Type Erasure . You can see this post to solve your problem. Basically, you have to provide the class that you want to obtain.

new T() is not allowed due to type erasure in Java. Anyway, the whole purpose of generics is to ensure type safety in your code at compile time, and your data access class does not ensure that, because that entry in the database could correspond to either a User or a Client . In other words, there is no mapping between the SQL statement and the subclass (note that this is normally handled by ORM frameworks such as Hibernate). The solution is to provide additional parameters in the code that specifies exactly the result type. Typically, each child subclass of Person should have its own DAO implementation (ie a UserDao and a ClientDao ), while the PersonDao method should be abstract, eg:

public abstract class PersonDao<T extends Person> { 

     abstract T select(int id);

     ...
}

public class UserDao extends PersonDao<User> {

    public User select(int id) throws SQLException {
        // Assuming there is a type column to differentiate between types of Person
        String sql = "SELECT * FROM person WHERE per_id=? and type=?";
        PreparedStatement ps = con.preparedStatement(sql);
        ps.setInt(1, id);
        ps.setString(2, User.class.getName());
        ResultSet rs = ps.executeQuery();
        User user = null;

        if(rs.next()) {
            user = new User();

            user.setId(rs.getInt("per_id"));
            user.setName(rs.getString("per_name"));
        }

        return user;
    }

    ...
}

If the code can be shared in the parent PersonDao because it is the same in both implementation (in which case I don't see the point of having the PersonDao generic in the first place, you could just make it return a Person instead of T ), then you can pass the class in the constructor:

public class PersonDao<T extends Person> { 

     private Class<T> type;

     public PersonDao(Class<T> type) {
         this.type = type;
     }

     public T select(int id) {
        String sql = "SELECT * FROM person WHERE per_id=? and type=?";
        PreparedStatement ps = con.preparedStatement(sql);
        ps.setInt(1, id);
        ps.setString(2, type.getName());
        ResultSet rs = ps.executeQuery();
        T t = null;

        if(rs.next()) {
            t = type.newInstance();

            t.setId(rs.getInt("per_id"));
            t.setName(rs.getString("per_name"));
        }

        return t;
     }

     ...
}

Side note: Your code snippet does not show how JDBC resources are being closed. Make sure to do that in your real code.

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