可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am designing a simple Data Access Object for my Java application. I have a few classes (records) that represents a single row in tables like User
and Fruit
.
I would like to have a single method for getting all records of a specific type.
For the moment I have it like this:
public List<User> getAllUsers() {
...
}
public List<Fruit> getAllFruits() {
...
}
....
But I would like to have a single polymorphic method like this (wrong):
public List<T> getAllRecords(Class<T> type) {
if(type instanceof User) {
// Use JDBC and SQL SELECT * FROM user
} else if(type instanceof Fruit) {
// Use JDBC and SQL SELECT * FROM fruit
}
return collection;
}
Example for uses:
List<Fruit> fruits = myDataAccessObject.getAllRecrods(Fruit.class);
List<User> users = myDataAccessObject.getAllRecords(User.class);
How can I do this in Java?
回答1:
Since you say that you don't want you data access methods in different classes(in the comment to anish's answer),I thought why not try something like this.
public class Records {
public interface RecordFetcher<T>{
public List<T> getRecords();
}
static RecordFetcher<Fruit> Fruit=new RecordFetcher<Fruit>(){
public List<Fruit> getRecords() {
...
}
};
static RecordFetcher<User> User=new RecordFetcher<User>(){
public List<User> getRecords() {
...
}
};
public static void main(String[] args) {
List<Fruit> fruitRecords=Records.Fruit.getRecords();
List<User> userRecords=Records.User.getRecords();
}
}
EDIT:
I would like to add one more of my implementation.
public class Test
{
public static void main(String[] args)
{
Test dataAccess=new Test();
List<Fruit> FruitList=dataAccess.getAllRecords(Fruit.myType);
List<User> UserList=dataAccess.getAllRecords(User.myType);
}
<T> List<T> getAllRecords(T cl)
{
List<T> list=new ArrayList<T>();
if(cl instanceof Fruit)
{
// Use JDBC and SQL SELECT * FROM fruit
}
else if(cl instanceof User)
{
// Use JDBC and SQL SELECT * FROM user
}
return list;
}
}
class Fruit
{
static final Fruit myType;
static {myType=new Fruit();}
}
class User
{
static final User myType;
static {myType=new User();}
}
EDIT:
I think this implementation is just as you have asked
public class Test
{
public static void main(String[] args) throws InstantiationException, IllegalAccessException
{
Test dataAccess=new Test();
List<Fruit> FruitList=dataAccess.getAllRecords(Fruit.class);
List<User> UserList=dataAccess.getAllRecords(User.class);
}
<T> List<T> getAllRecords(Class<T> cl) throws InstantiationException, IllegalAccessException
{
T inst=cl.newInstance();
List<T> list=new ArrayList<T>();
if(inst instanceof Fruit)
{
// Use JDBC and SQL SELECT * FROM user
}
else if(inst instanceof User)
{
// Use JDBC and SQL SELECT * FROM fruit
}
return list;
}
}
回答2:
It looks like you want to adapt what Josh Bloch calls a Typesafe Heterogenous Container pattern: you are passing a type token Class<T>
, and you want back a List<T>
.
Plain old THC can map a Class<T>
to a T
in a typesafe manner, but since you actually want a List<T>
instead, then you want to use what Neal Gafter calls the super type tokens.
The following snippet is adapted from Crazy Bob Lee's code posted in Neal Gafter's blog:
public abstract class TypeReference<T> {
private final Type type;
protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class<?>) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
public Type getType() {
return this.type;
}
}
Now you can create a super type token like these:
TypeReference<String> stringTypeRef =
new TypeReference<String>(){};
TypeReference<Integer> integerTypeRef =
new TypeReference<Integer>(){};
TypeReference<List<Boolean>> listBoolTypeRef =
new TypeReference<List<Boolean>>(){};
Essentially you pass a TypeReference<T>
instead of a Class<T>
. The difference is that there is no List<String>.class
, but you can make a TypeReference<List<String>>
.
So now we can make our container as follows (the following is adapted from Josh Bloch's original code):
public class Favorites {
private Map<Type, Object> favorites =
new HashMap<Type, Object>();
public <T> void setFavorite(TypeReference<T> ref, T thing) {
favorites.put(ref.getType(), thing);
}
public <T> T getFavorite(TypeReference<T> ref) {
@SuppressWarnings("unchecked")
T ret = (T) favorites.get(ref.getType());
return ret;
}
}
Now we can put the two together:
Favorites f = new Favorites();
f.setFavorite(stringTypeRef, "Java");
f.setFavorite(integerTypeRef, 42);
f.setFavorite(listBoolTypeRef, Arrays.asList(true, true));
String s = f.getFavorite(stringTypeRef);
int i = f.getFavorite(integerTypeRef);
List<Boolean> list = f.getFavorite(listBoolTypeRef);
System.out.println(s); // "Java"
System.out.println(i); // "42"
System.out.println(list); // "[true, true]"
Neal Gafter argued in his blog that with some more bells and whistles, TypeReference
for super type tokens will make a worthy inclusion in the JDK.
Attachments
- Complete source code on ideone.com
References
- Neal Gafter's Blog - Super Type Tokens
回答3:
You are pretty close.
public <T> LinkedList<T> getAllRecords(List<T> list) {
...
}
This is called a Generic Method.
You will want to specify a parameter like List<T>
. Then, based upon the type of the list you pass in, Java will infer the generic type to return.
Edit:
Poly's answer is very good. It should be easy enough for you to do the following and not have to create a TypeReference
class.
List<Fruit> fruit = myDataAccessObject.getAllRecrods(new LinkedList<Fruit>());
List<User> users = myDataAccessObject.getAllRecords(new LinkedList<User>());
回答4:
Depending on how you actually retrieve your data, you can do something like this:
private static <T> List<T> getAll(Class<T> cls){
List<T> fromSql = (List<T>) sql.query("SELECT * FROM objects WHERE type="+cls.getName());
return fromSql;
}
This requires your sql
object to return the correct type of list, which O/R mappers like iBatis do.
If you need to differentiate between the passed types, you can still do a switch/case on cls
.
回答5:
Well, I really don't know if you need it this way.
But here is a polymorphic approach. It might help somewhere somehow.
Create different objects for different tables all implementing a common interface. This means you represent each table as an object.
import java.util.LinkedList;
public class DataAccessTest
{
/**
* @param args
*/
public static void main(String[] args)
{
DataAccess myDataAccessObject = new DataAccess();
Type type1 = new Fruit();
Type type2 = new User();
LinkedList<Type> list1 = myDataAccessObject.getAllRecords(type1);
LinkedList<Type> list2 = myDataAccessObject.getAllRecords(type2);
LinkedList<Type> list3 = myDataAccessObject.getAllRecords(new Fruit());
LinkedList<Type> list4 = myDataAccessObject.getAllRecords(new User());
}
}
class DataAccess
{
public LinkedList<Type> getAllRecords(Type type)
{
return type.getAllRecords();
}
}
interface Type
{
public LinkedList<Type> getAllRecords();
}
class Fruit implements Type
{
public LinkedList<Type> getAllRecords()
{
LinkedList<Type> list = new LinkedList<Type>();
list.add(new Fruit());
return list;
}
}
class User implements Type
{
public LinkedList<Type> getAllRecords()
{
LinkedList<Type> list = new LinkedList<Type>();
list.add(new User());
return list;
}
}
回答6:
I believe what you are trying to do is possible with a bit of generics magic. I had to solve the same problem just now and this is what I did:
public class ListArrayUtils{
@SuppressWarnings("unchecked") // It is checked.
public static <T,E> List<T> filterByType(List<E> aList, Class<T> aClass){
List<T> ans = new ArrayList<>();
for(E e: aList){
if(aClass.isAssignableFrom(e.getClass())){
ans.add((T)e);
}
}
return ans;
}
}
And unit tests:
public class ListArrayUtilsTest{
interface IfA{/*nothing*/}
interface IfB{/*nothing*/}
class A implements IfA{/*nothing*/}
class B implements IfB{/*nothing*/}
class C extends A implements IfB{/*nothing*/}
@Test
public void testFilterByType(){
List<Object> data = new ArrayList<>();
A a = new A();
B b = new B();
C c = new C();
data.add(a);
data.add(b);
data.add(c);
List<IfB> ans = ListArrayUtils.filterByType(data, IfB.class);
assertEquals(2, ans.size());
assertSame(b, ans.get(0));
assertSame(c, ans.get(1));
}
}
回答7:
I've actually done this in a generic data access library. See Norm. Full source code on Github.