`
regular
  • 浏览: 75945 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Object数组到泛型数组转换的伪解决方案

    博客分类:
  • Java
阅读更多

闲来无事,想要用目前的Java技术模拟一个对象数据库。最初只是停留在很简单的构想,单用户的,本地的,仅仅是为了实现一些纯对象化的数据库操作:

 

public interface DataBase
{
    public <T> T[] select(Filter<T> filter);
    public <T> void insert(T t);
    public int update(Filter<T> filter, Updater<T> handler);
    public <T> int delete(T t);
}

 

 

其中,FilterUpdater分别是两个接口,处理查询子句和更新子句。

 

public interface Filter<T>
{
	public boolean where(T obj);
}

public interface Updater<T>
{
	public void set(T obj);
}

 

 

这么设计的目的是以一种对象化的方式处理数据,把数据库的操作和业务流程无缝的结合在一起。我说过,这只是我的一个练习,因此请仅把它看成是练习就好。数据库的对象容器用了一个泛型的List,里面可以包含任何对象。

 

然而在select方法的时候,碰到了一些问题。因为它要返回的是T[]类型结果,而由于T是泛型,在ListtoArray时,仅能生成Object[]类型的数组,与需求有差异。

 

//...

@SuppressWarnings("unchecked")
public <T> T[] select(Filter<T> filter)
{
    Collection<T> c= new ArrayList<T>();
    for (Object obj : pool) {
        try {
            T t = (T) obj;
            if (filter.where(t)) c.add(t);
        } catch (Exception ex) {
            continue;
        }
    }
    // 到现在都OK
    return c.toArray(); // 不OK,返回类型为Object[],与结果不符
    return (T[]) c.toArray(); // 不OK,仍返回 Object[],ClassCastException
    return c.toArray(new T[0]); // 不OK,编译错误。
}

//...

 

首先要解释一下为什么return c.toArray(new T[0]); 不能通过编译。因为据Java官方所说,为了向旧版本兼容,Java的泛型只是存在于编译期,其目的是编译期的源代码正确性校验,变成了.class之后所有泛型内容全部被抛弃。所以,new T[0]这种写法在运行期是不可能有意义的,因为没有T的具体定义。

 

@SuppressWarnings("unchecked")的原因是T t = (T) obj;语句。没有想到更好的办法做泛型的类型比较,用这种方法最直接。不过可能会产生ClassCastException,所以一旦发生转换错误,只是简单地继续处理下一个元素。若有其它解决方案也请能不吝赐教,这里谢过。

 

现在,回到问题上来:怎么实现这个泛型的数组转换呢?

 

我这里有一个伪解决方案。就是从结果集合中取一个值出来,然后创建这个值类型的数组,见下:

 

@SuppressWarnings("unchecked")
public <T> T[] select(Filter<T> filter)
{
    Collection<T> c;
//...
    Iterator<T> it = c.iterator();
    if (it.hasNext()) {
        T[] tt = (T[]) Array.newInstance(it.next().getClass(), c.size());
        return c.toArray(tt);
    } else return null;
}

 

 

说这是个伪解决方案的原因是集合中的值并不就等于是T,有可能是T的子类。若集合中所有元素都是一个类别倒还好说,怕只怕有些元素又是子类的子类(孙类)。那么假设碰巧集合中第一个拿到的就是孙类,那么tt实际是孙类的数组,这样对于包含子类对象的集合执行toArray操作的时候就会出现ArrayStoreException

 

好了,在暂时不考虑这个“伪”字的情况下,这里举个使用的例子:

 

public static void main(String[] args)
{
    DataBase db = new DataBaseImpl();
    db.insert("Hello, ");
    db.insert(" this ");
    db.insert(new Integer(3));
    db.insert("world!");

    String[] ss = (db.select(new Filter<String>() {

        @Override
        public boolean where(String obj)
        {
            return (obj.trim().length() > 4);
        }

    }));

    for (String s : ss)
        System.out.println(s);
}

 

======== 补充========

春节休假在家,偶尔看了一下论坛。结合下面的评论并思考了一下,目前采用通过传入的Filter获得泛型类的方法:

 

import java.lang.reflect.*;
import java.util.*;

public class Container {
    private Collection<Object> container = new ArrayList<Object>();

    public void add(Object obj) {
        container.add(obj);
    }

    @SuppressWarnings("unchecked")
    public <T> T[] select(Filter<T> filter)
    {
        Class clazz;
        Type[] types = filter.getClass().getGenericInterfaces();
        if (types != null && types.length > 0) {
            clazz = ((Class) ((ParameterizedType) types[0])
                    .getActualTypeArguments()[0]);
        } else return null;
        Collection<T> c = new ArrayList<T>();
        for (Object o : container) {
            if (clazz.isInstance(o) && filter.where((T) o))
                c.add((T) o);
        }
        T[] tt = (T[]) Array.newInstance(clazz, c.size());
        return c.toArray(tt);
    }

    public static void main(String[] args) {
        Container c = new Container();
        c.add("Hello");
        c.add("world");
        c.add("I");
        c.add("love");
        c.add("you");
        c.add(new Integer(4));
        c.add("ever");

        String[] list = c.select(new Filter<String>() {
            public boolean where(String s) {
                return (s.length() > 3);
            }
        });
        for (String s : list)
            System.out.println(s);
    }
}

interface Filter<T> {
	boolean where(T t);
}

 

分享到:
评论
27 楼 rmn190 2009-03-19  
rmn190 写道
c.toArray((T[])new Object[0] ); 就可以了.


发现我前些天用的方法有问题, 报异常了:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object;
at serializable.TryGeneric.main(TryGeneric.java:16)

实验用代码如下:


import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TryGeneric {
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		Date now = new Date();
		List list = new ArrayList();
		list.add(now);
		
		Date[] results = (Date[]) convertToArray(list); 
	}
	
	@SuppressWarnings("unchecked")
	private static <T> T[] convertToArray(List<T> list) {
		return list.toArray((T[])new Object[0] );
	}
}

26 楼 rmn190 2009-03-16  
c.toArray((T[])new Object[0] ); 就可以了.
25 楼 xingqiliudehuanghun 2009-02-16  
留个标记回家看,公司用普元的EOS做开发,JDK是1.4的,泛型用不上,郁闷
24 楼 AlwenS 2009-02-12  
   
23 楼 melode11 2009-02-06  
c.toArray(new T[0]); 返回是传入参数本身,你拿个0长度数组来return 肯定不行吧。
22 楼 sjtde 2009-02-04  
泛型可以使用数组呀,网上怎么有好多人说java泛型不支持数组??
21 楼 jjkelvin2046 2009-02-02  
数组并不适合泛型...集合忧郁数组....
20 楼 kimmking 2009-02-02  
是我理解错了,java1.5的泛型跟xxx语言的泛型不一样
只是一个syntax sugar,只存在于编译期,强制转型其实还是存在的“伪泛型”。
19 楼 RednaxelaFX 2009-02-01  
kimmking 写道
同意wendong007的看法。

泛型的出现是为了改进使用object类型时转换类型的效率。
这么的写法不如直接用实际的类型或是Object[]和List来的好一些。

Java里泛型的出现不是为了源码“更安全”么?执行效率应该没有多少区别才对。你看对这样的一个测试代码:
import java.util.ArrayList;

public class TestGeneric {
    public static void main(String[] args) {
        foo1();
        foo2();
    }

    private static void foo1() {
        ArrayList list = new ArrayList();
        MyObject obj = new MyObject();
        list.add(obj);
        MyObject objref = (MyObject)list.get(0);
    }

    private static void foo2() {
        ArrayList<MyObject> list = new ArrayList<MyObject>();
        MyObject obj = new MyObject();
        list.add(obj);
        MyObject objref = list.get(0);
    }
}

class MyObject { }

其中的foo1()和foo2()编译出来是完全一样的,都是这样:
   0:	new	#4; //class java/util/ArrayList
   3:	dup
   4:	invokespecial	#5; //Method java/util/ArrayList."<init>":()V
   7:	astore_0
   8:	new	#6; //class MyObject
   11:	dup
   12:	invokespecial	#7; //Method MyObject."<init>":()V
   15:	astore_1
   16:	aload_0
   17:	aload_1
   18:	invokevirtual	#8; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
   21:	pop
   22:	aload_0
   23:	iconst_0
   24:	invokevirtual	#9; //Method java/util/ArrayList.get:(I)Ljava/lang/Object;
   27:	checkcast	#6; //class MyObject
   30:	astore_2
   31:	return

注意在标号为27的那条指令,那个就对应着foo1()的强制类型转换,而foo2()使用了泛型之后隐式的包含了这个操作。

我也觉得干脆不返回数组比较好。其实很多时候数组用起来都很尴尬,与Iterable<E>也不兼容。
18 楼 galaxystar 2009-02-01  
Filter<T>接口的实例, 如果实现了多个接口且无法保证顺序时, 会造成ClassCastException.

即 FilterImpl implements A<String>, Filter<T>, B<String>
楼主代码里写死 types[0], 会给以后重构或者其他因素的代码改动, 带来风险, 欠妥.

建议, ((Class<?>)types[0].getRawType() ==  Filter.class) + 遍历types

17 楼 kimmking 2009-02-01  
同意wendong007的看法。

泛型的出现是为了改进使用object类型时转换类型的效率。
这么的写法不如直接用实际的类型或是Object[]和List来的好一些。
16 楼 marlonyao 2009-02-01  
<div class="quote_title">wendong007 写道</div>
<div class="quote_div">其实直接返回List类型就可以了,没必要搞得那么复杂,你这里的类型检查用 instanceof 就好,异常不是那么用的<br />不过我觉得你的想法还是不错</div>
<p><br /><br />同意wendong007的看法。<br /><br />这里的类型不安全在于设计上的缺陷,有两种解决方法:</p>
<ul>
<li>将DataBase本身设计成泛型,这时DataBase只持有某种类型的对象,就像Collection类型</li>
<li>如果想使DataBase能够容纳任何对象,原来的方法最好加上一个Class&lt;T&gt;类型的参数代表要过滤的对象类型。<br /></li>
</ul>
<p>public interface DataBase  <br />{  <br />    public &lt;T&gt; T[] select(Filter&lt;? super T&gt; filter, Class&lt;T&gt; clazz); // 可以将Filter&lt;T&gt;改成Filter&lt;? super T&gt;<br />    public void insert(Object obj);  // 这里没有必要使用泛型,因为T只在一个地方(参数或返回值)被使用<br />    public int update(Filter&lt;T&gt; filter,  Class&lt;T&gt; clazz, Updater&lt;T&gt; handler);  // 按照这种设计,我总觉得Updater没有必要设计成泛型<br />    public int delete(Object obj);  // 同样,这里也没有必要使用泛型<br />}</p>
<p> </p>
<p>下面这种捕获异常的方式实在不敢恭维,异常不是这么用的。一个副作用是它捕获了filter的异常。</p>
<p>         try {  <br />             T t = (T) obj;  <br />             if (filter.where(t)) c.add(t);  <br />         } catch (Exception ex) {  <br />             continue;  <br />         }</p>
15 楼 regular 2009-02-01  
wendong007 写道

javatar 写道
把Filter的check()改成where()看起来会更符合SQL标准,呵呵
&nbsp;
我是尽量参照楼主原来的命名

吼嘞吼嘞,已经啊补充过嘞……

的确where更符合品鉴的口味。呵呵,采纳了。
14 楼 wendong007 2009-01-31  
<div class="quote_title">javatar 写道</div>
<div class="quote_div">把Filter的check()改成where()看起来会更符合SQL标准,呵呵</div>
<p> </p>
<p>我是尽量参照楼主原来的命名</p>
13 楼 yizhuo 2009-01-31  
一个我通常用的方法是把Class这个对象传进去,就是说需要加一个方法为
Class<T> getDataType()


然后通过

Array.newInstance(getDataType(), 0);


来创建一个数组
12 楼 cloud21 2009-01-31  
public static void main(){
}
11 楼 javatar 2009-01-31  
把Filter的check()改成where()看起来会更符合SQL标准,呵呵
10 楼 wendong007 2009-01-31  
<p>我只是提供一个思路,并没有让你照搬,你用这种方法需要改一下设计</p>
<p> </p>
<p>
</p><pre name="code" class="java">public abstract class Filter&lt;T&gt; {

protected Class clazz = (Class)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

public boolean check(Object obj){
boolean flag = false;
if(clazz.isInstance(obj) &amp;&amp; filter(obj))
flag = true;
return flag;
}

public abstract boolean filter(Object obj);
}</pre>
 <p></p>
9 楼 regular 2009-01-31  
wendong007 写道


...

那你可以试下下面这种方法



Java代码

Class&nbsp;clazz&nbsp;=&nbsp;(Class)((ParameterizedType)&nbsp;getClass().getGenericSuperclass()).getActualTypeArguments()[0]; &nbsp;&nbsp;
clazz.isInstance(obj);&nbsp;&nbsp;Class clazz = (Class)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
clazz.isInstance(obj);


这种做法是不合适的,这么做是把当前对象的泛型取出来,但当前对象是容器,不是容器内的对象,更不是T。可以注意到,这段代码里面一丝一毫都没提到过T。因此,肯定是不能解决我提到的问题的。
8 楼 wendong007 2009-01-24  
<div class="quote_title">regular 写道</div>
<div class="quote_div">
<div class="quote_title">wendong007 写道</div>
<div class="quote_div"><br />其实直接返回List类型就可以了,没必要搞得那么复杂,你这里的类型检查用 instanceof 就好,异常不是那么用的不过我觉得你的想法还是不错<br /></div>
<br />呵呵,试试看吧。<br />
<pre name="code" class="java">if (obj instanceof T) ... // 不行,因为T在运行时被erasure为Object
</pre>
</div>
<p> </p>
<p>那你可以试下下面这种方法</p>
<p>
</p><pre name="code" class="java">Class clazz = (Class)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
clazz.isInstance(obj);</pre>
<p></p>

相关推荐

    Visual C#2010 从入门到精通(Visual.C#.2010.Step.By.Step).完整去密码锁定版 I部分

    18.2 泛型解决方案 323 18.2.1 对比泛型类与泛化类 325 18.2.2 泛型和约束 325 18.3 创建泛型类 325 18.3.1 二叉树理论 325 18.3.2 使用泛型构造二叉树类 328 18.4 创建泛型方法 337 18.5 可变性和泛型接口 ...

    ASP.NET编程之道.part1.rar

    这是第一部分,需要两部分一起才能解压 目录: 第1篇 编程经验与感悟篇 第1章 编程感悟6则 感悟01 程序员的成长之路 感悟02 程序员应具备的素质 感悟03 程序员的必备技能 ...第13章 项目中经常用到的20个解决方案

    Visual C# 2010程序设计教程(教程PPT+源代码)

    1.2.4 “属性”及“解决方案资源管理器”面板 1.2.5 其他面板 1.2.6 Visual Studio 2010的新特性 1.3 创建控制台应用程序 1.4 本章小结 1.5 习题 第2章 Visual C# 2010语法基础 2.1 C#语言概述 2.2 C#基础元素 ...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    · 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案 · 借助于异常处理来进行状态管理 · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态...

    CLR.via.C#.(中文第3版)(自制详细书签)Part1

    · 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案 · 借助于异常处理来进行状态管理 · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态...

    CLR.via.C#.(中文第3版)(自制详细书签)

    · 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案 · 借助于异常处理来进行状态管理 · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态...

    CLR.via.C#.(中文第3版)(自制详细书签)Part3

    · 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案 · 借助于异常处理来进行状态管理 · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    每个实例都是经过笔者精心筛选的,具有很强的实用性,其中一些实例是开发人员难于寻觅的解决方案。 本书两卷共计1200个例子,包括了开发中各个方面最常用的实例,是目前市场上实例最全面的开发类图书;本书实例来源...

Global site tag (gtag.js) - Google Analytics