免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3223 | 回复: 6
打印 上一主题 下一主题

Java泛型 个人笔记 一,二,三 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-04-07 21:04 |只看该作者 |倒序浏览
看个工程源码用到了java泛型,我找了份官方的pdf,看了下,结合自己的理解,翻译了一点,加入了一点,抄了一点,形成了以下这个记录。 欢迎交流。。。

我特地把原版的英文版发出来,不管大家看不看,起码表示我是尊重原文的。让大家放心哈

这个是官方文档的前一部分,把java泛型大概介绍完了,后半部分是介绍泛型和传统code的联系,在下一次发贴在贴吧。。

本来jdk5之前有java.util.List这个Interface

是这么用的:
List myIntList = new LinkedList(); // LinkedList是实现了List Interface的类
myIntList.add(new Integer(0));
Integer x = (Integer)myIntList.iterator().next();

这里myIntList其实可以myIntList.add(new Object());因为声明语句就没有写出该List到底支持什么。
所以myIntList.iterator().next()返回的只能是Object,它并不知道这里面装的到底是什么具体的类。
因为这个List里既可能有Integer对象,也可能有Double对象~

为了不要(Integer)这个强制转换,我们有了这个:
List<Integer> myIntList = new LinkedList<Integer>();
这个时候,Integer x = (Integer)myIntList.iterator().next();就可以去掉强制转换写成
Integer x = myIntList.iterator.next();
因为编译器确实知道了myIntList里放的就是Integer,不会是其他。

这个地方要注意
List<Integer> myIntList = new LinkedList<Integer>();
这句话的真正含义
右边是创建一个LinkedList<Integer>的对象,然后把引用给左边一个叫myIntList的名字。
理解下面两个例子就知道了:
List myIntList = new LinkedList<Integer>(); //警告,不出错 myIntList可以add(Object)
List<Integer> myIntList = new LinkedList(); //警告,不出错 myIntList只能add(Integer)
List<Integer> myIntList = new LinkedList<Double>(); // 出错

事情到这里,看似所有问题都解决了,但是不尽然。慢慢道来:
class Shape {
 public void say();
};

class Circle extends {
 public void say() {
         System.out.println("i am a circle");
 }
};
通过上面,我们可以
Shape shape = new Circle();
shape.say(); // i am a circle.
这里表明,父类可以代表子类。
但是
List<Shape>却不是List<Circle>的父类

如果有个函数:
public void sayAll(List<Shape> list) {
 for(Shape s : list) {
         s.say();
 }
}
那么这个函数不能被这样调用:
List<Circle> circleList = new List<Circle>();
sayAll(circleList);
只能被这样调用:
List<Shape> shapeList = new List<Shape>();
sayAll(shapeList);

##这里详细解释为什么List<Shape>不能代表List<Circle>
看下面代码就知道:
List<Circle> circleList = new List<Circle>();
List<Shape> shapeList = circleList;
shapeList.add(new Shape());
circleList.iterator().next(); //出错。

那么怎么解决这个问题?可以这样解决:
public void sayAll(List<?> list) {
 for(Object s : list) {
 
 }
}
具体我们刚才讨论的Shape和Circle的问题可以这样写:
public void sayAll(List<? extends Shape> list) {
 for(Shape s : list) {
         s.say();
 }
}
其中public void sayAll(List<?> list) 就是public void sayAll(List<? extends Object> list) 的意思

这个例子说明,List<?>才是List<Circle>和List<Integer>的“父类”,可以传递引用。
也说明List<? extends Shape>是List<Circle>的“父类”,可以传递引用,而不是List<Integer>的“父类”,因为Integer extends Shape不成立

我们再来看一个例子来加深印象,结束这个话题。然后讨论新话题。
class Person {}
class Student extends Person{}
public class Census {
 public static void addRegistry(Map<String, ? extends Person> registry) { ... }
};

Map<String,Driver> allDrivers = ...
Census.add(allDrivers);

我们下一个话题,先由这个引起:
我们说过,如果
List<Shape>是List<Circle>的“父类”
那么会出现一个矛盾:
List<Circle> circleList = new List<Circle>();
List<Shape> shapeList = circleList; //错误
shapeList.add(new Shape());
circleList.iterator().next(); //出错。

那么List<?>确实是List<Circle>的“父类”,会有这个矛盾不?同样有,如下:
List<Circle> circleList = new List<Circle>();
List<?> list= circleList; //正确
list.add(new Shape()); //错误
为什么list不能加新元素?
因为list里的元素类型已经定义好了,是?,不是任何类型,所以不能加入任何类型的元素。
所以如下方法是不能使用的:
static void fromArrayToCollection(Object[] a, Collection<?> c) {
 for (Object o : a) {
         c.add(o); // compile time error 因为c不能加入元素
 }
}

但是我们想要在List<?>里加入元素如何弄呢?一个例子概括如下:
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
 for (T o : a) {
         c.add(o); // correct
 }
}
那么这样,c这个Collection<T>就能加入东西了,加入的东西必须是T类,看例子:
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(oa, co);// T inferred to be Object

String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
fromArrayToCollection(sa, cs);// T inferred to be String
fromArrayToCollection(sa, co);// T inferred to be Object

Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);// T inferred to be Number
fromArrayToCollection(fa, cn);// T inferred to be Number
fromArrayToCollection(na, cn);// T inferred to be Number
fromArrayToCollection(na, co);// T inferred to be Object
fromArrayToCollection(na, cs);// compile-time error


到现在为止,上面一共讲了两个jdk1.5中新的事物
List<? extends Parent> list = new LinkedList<Child>(); // wildcards
static <T> void fromArrayToCollection(T[] a, Collection<T> c) 

那么看下面两个例子,他们做的事情都是一样的,但是分别用了这两个方法:
interface Collection<E> {
 public boolean containsAll(Collection<?> c);
 public boolean addAll(Collection<? extends E) c); 
}

interface Collection<E> {
 public <T> boolean containsAll(Collection<T> c);
 public <T extends E> boolean addAll(Collection<T> c);
}

他们都做两件事:判断任意类的容器是不是在本身这容器里。加入E子类的容器到本身的容器里。

也可以同时都用上两个新东西:
class Collection {
 public static <T> void copy(List<T> dest, List<? extends T> src){}
}

或者写成
class Collection {
 public static <T,S extends T> void copy(List<T> dest, List<S> src){}
}

作为总结这两个新的java特性,举出一个复杂例子:

static List<List<? extends Shape>> history = new ArrayList<List<? extends Shape>>();

public void drawAll(List<? extends Shape> shapes) {
 history.addLast(shapes);
 for(Shape s : shape) {
         s.draw(this);
 }
}

[ 本帖最后由 hdc1112 于 2009-4-10 17:33 编辑 ]

java-generic.pdf

69.99 KB, 下载次数: 81

论坛徽章:
0
2 [报告]
发表于 2009-04-08 09:33 |只看该作者

回复 #1 hdc1112 的帖子

没人跟贴我正好不另开新贴了,这是第二个笔记,估计要搞三个。那个pdf太长了 :em17:

接着上次笔记的讲。

根据上次笔记,java泛型已经介绍完了,但是事情还没有结束。
在很长一段时间内,并不是每个程序员都会使用最新版本的jdk,说不定他就在使用没有引入
泛型的jdk1.4。而且他在jdk1.4基础上已经开发了很多代码,难以一下子改成泛型的。
所以我们面临的新问题就是如何利用以前的代码。
还是像上次笔记一样,提出一个问题,解释一个问题。

在没有泛型的时候,某个程序员写了如下代码:
package com.Fooblibar.widgets;

public interface Part { ...}

public class Inventory {
        /**
        * Adds a new Assembly to the inventory database.
        * The assembly is given the name name, and consists of a set
        * parts specified by parts. All elements of the collection parts
        * must support the Part interface.
        **/
        public static void addAssembly(String name, Collection parts) {...}
        public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
        Collection getParts(); // Returns a collection of Parts
}

如果第一眼没看出什么,那么我稍微改点便于快速理解,反正估计没人有耐心来研究我的笔记。

package com.Fooblibar.widgets;

public interface 组件 { ... }

public class 仓库 {
        public static void add一类组件(String 这类组件的名字, Collection parts) { ... }
        public static void Assembly get一类组件(String name) { ... }
}

public interface Assembly {
        Collectioin getParts();
}

为了便于介绍,我自己加了段这段代码的应用场景,一下子就知道了。这个场景也是在没有泛型的时候,程序员写的。

class 小刀 implements Part { ... }
class 菜刀 implements Part { ... }

public class Main {
        public staic void main(String[] args) {
                Collection c = new ArrayList();
                c.add(new 小刀());
                c.add(new 菜刀());
                仓库.add一类组件("刀",c);
                Collection k = 仓库.get一类组件(刀).getParts();
        }
}

但是有个程序员用上了泛型的新特性,于是他这样写:
class 小刀 implements Part { ... }
class 菜刀 implements Part { ... }

public class Main {
        public static void main(String[] args) }
                Collection<Part> c = new ArrayList<Part>();
                c.add(new 小刀());
                c.add(new 菜刀());
                仓库.add一类组件("刀",c);
                Collection<Part> k = 仓库.get一类组件(刀).getParts();
        }
}
两段代码的差别只在于<Part>加了与否。

好了,到现在为止我们已经模拟出了一个泛型代码来使用非泛型代码的例子了。
会出现一个问题。
就是Collection<Part>的参数会传入Collection的形参
具体发生在
仓库.add一类组件("刀",c); //这里c是 Collection<Part>类型
调用
public static void add一类组件(String 这类组件的名字, Collection parts) { ... }
的时候

那么这里就会出现未检查错误。相当于
Collection parts = new Collection<Part>(); //这个问题上个笔记讲过,这个只是警告,没有错误,parts可以add(new Object());或者add(new Double(1.0));
这里会出现危险操作,因为parts可能加入非Part实现类进入容器。

还有一个问题。
就是Collection<Part> k = 仓库.get一类组件(刀).getParts();
这句话相当于,
Collection<Part> k = new Collection(); //这个问题上个笔记也讲过,这个也只是警告,没有错误,parts只能add(new Part实现类());

以上两个错误只是会引发潜在的代码危险。
当然,如果不允许这样赋值就一切解决了,只是java设计人员是故意这样保持前后兼容才这样搞的。
这句话我说了没用,英文原文:(没现英语水平,多谢配合)

So raw types are very much like wildcard types, but they are not typechecked as
stringently. This is a deliberate design decision, to allow generics to interoperate with
pre-existing legacy code.

我的注解:raw types就是 Collection ,wildcard就是Collection<Part>

ok,现在又讨论一个新问题
就是
List<String> ys = new Linked<String>();
List xs = ys;
xs.add(new Integer(0)); //编译会警告不会出错,但是运行时出现ClassCastException
到现在为止,应该可以理解这个。
编译只警告是因为想兼容,运行出错是因为类型不对。
这里有人会问
Collection parts = new Collection<part>();
这个为什么parts能add(new Object());运行时不出错

Collection<String> parts = new Collection<String>();
Collection parts2 = parts;
parts2.add(new Object());
会出错。

我也不知道~
但是我测试过确实是这样的。下面是两段测试代码,可以试试。

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class JavaGeneric62 {
        public String loophole(Object x) {
                Collection<String> ys = new LinkedList<String>();
                Collection xs = ys;
                xs.add(x);
                return ys.iterator().next().toString();
        }

        public static void main(String[] args) {
                new JavaGeneric62().loophole(new Object());
        }
}
上面代码运行时出错,出错如下
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
        at JavaGeneric62.loophole(JavaGeneric62.java:10)
        at JavaGeneric62.main(JavaGeneric62.java:14)


import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class JavaGeneric62 {
        public String loophole(Object x) {
                Collection ys = new LinkedList<String>();
                ys.add(x);
                return ys.iterator().next().toString();
        }

        public static void main(String[] args) {
                new JavaGeneric62().loophole(new Object());
        }
}
上面代码无警告运行时无异常

总结:java virtual machine是无敌的~

好了,上面讨论的是使用泛型的程序员在调用不使用泛型的代码的时候发生的故事,
那么下面讨论一下不使用泛型的程序员在调用泛型代码的时候发生什么故事:
看了上面的例子大家已经进入状态,那么直接贴出代码,而且其中故事已经非常分明了

package com.Fooblibar.widgets;

public interface Part { ...}

public class Inventory {
        /**
        * Adds a new Assembly to the inventory database.
        * The assembly is given the name name, and consists of a set
        * parts specified by parts. All elements of the collection parts
        * must support the Part interface.
        **/
        public static void addAssembly(String name, Collection<Part> parts) {...}
        public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
        Collection<Part> getParts(); // Returns a collection of Parts
}

package com.mycompany.inventory;

import com.Fooblibar.widgets.*;

public class Blade implements Part {
}

public class Guillotine implements Part {
}

public class Main {
        public static void main(String[] args) {
                Collection c = new ArrayList();
                c.add(new Guillotine()) ;
                c.add(new Blade());
                Inventory.addAssembly(”thingee”, c); // 1: unchecked warning
                Collection k = Inventory.getAssembly(”thingee”).getParts();
        }
}
其中会有警告,原因跟上面一样。

那么下面开始新的问题
首先我们认识一下java.lang.Object这个对象的一个方法:
Class<? extends Object> getClass();
返回一个对象的运行时类。
看个我弄的例子和结果就知道,然后开始讨论新问题。

class ParentClass {
}

class ChildClass extends ParentClass {
}

public class JavaGeneric71 {
        public static void main(String[] args) {
                System.out.println(new Integer(1).getClass() == new Integer(3)
                                .getClass());
                System.out.println(new Integer(1).getClass() == new Double(3)
                                .getClass());
                System.out.println(new ParentClass().getClass() == new ChildClass()
                                .getClass());

        }
}
结果是
true
false
false

新问题是
List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
的结果是什么。
正确结果是true
每个泛型类都是一个运行时类
比如
List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
Collection<Double> c3 = new ArrayList<Double>();
System.out.println(l1.getClass() == l2.getClass());
System.out.println(l1.getClass() == c3.getClass());
的结果是两个true
true
true

论坛徽章:
0
3 [报告]
发表于 2009-04-08 13:13 |只看该作者

原创帖没人顶

原创帖没人顶  :em16:

继续发我的第三个笔记吧。还有后面几个。实在太长了。我是按自己的理解很细的写的。

写完两个笔记,才发现才只写到官方文档的第7节~ 一共11节~
我非常吐血。

Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ...} // illegal
这个是错误的,因为上个笔记讲过所有泛型类的getClass都是一样的。
所以通常问它的instanceof是没有意义的。原文如下:
Another implication of the fact that a generic class is shared among all its instances,
is that it usually makes no sense to ask an instance if it is an instance of a particular
invocation of a generic type

而且如果要强制转换:
Collection<String> cstr = (Collection<String>) cs
会出现警告,其实这个道理上两个笔记讲了n遍了。。

有个例子
<T> T badCast(T t, Object o) {return (T) o; // unchecked warning
}
这个有警告,一个道理。


到现在为止,Java泛型都是用在容器里的,比如
Collection<? extends Shape> shapechildren;
或者void drawAll(Collection<? extends Shap> shapchilren) { ... }
但是java泛型不是只能用在容器里。
java泛型用在容器里比较让人容易理解,因为Collection<T>就表示
这个容器里的每个元素都是T类型,T可以替换。
List<? extends Shape> listshape;就表示Shape类的子类的集合。
在另外一个特殊的类中,泛型也有广泛的高级应用。
那就是java.lang.Class<T>类。
这个类在jdk1.5之前没有<T>
例子场景:
从数据库里读取n个对象,然后放入一个容器。
比如读取一个厂里的工作人员的信息:
第一个版本:可以这样写:

interface Factory<T> { T make(); }

public <T> Collection<T> select(Factory<T> factory, String statement) {
        Collection<T> result = new ArrayList<T>();
        //运行sql语句
        for(//遍历ResultSet) {
                T item = factory.make();
                //利用反射填入对象T
                result.add(item);
        }
        return result;
}

那么第一个版本有两个缺点:
1.使用不方便。
2.代码不重用。

为什么使用不方便?
我们使用这段代码只有两种方式:
select(new Factory<EmplInfo>(){
                public EmplInfo make() {
                        return new EmplInfo();
                }
        }, "sql语句");

还一种方式:
class EmplInfoFactory implements Factory<EmplInfo> {
        ...
        public EmplInfo make() {
                return new EmplInfo();
        }
}
然后
EmplInfoFactory emplfactory = new EmplInfoFactory();
select(emplfactory, "sql语句");

其实大家都知道,上面两个方法的本质是一样的,都是要首次建立一个实现类。
从这一点上说,使用不方便。

为什么不重用?
上个例子中我是需要EmplInfo这个对象的容器,但是如果我需要这个厂里的
Manager的容器的时候,就需要重写代码,如下:
class ManaInfoFactory implements Factory<ManaInfo> {
        ...
        public ManaInfomake() {
                return new ManaInfo();
        }
}
然后
ManaInfoFactory manafactory = new ManaInfoFactory();
select(manafactory, "sql语句");
从这里可以看出代码不重用。因为我需要另外一个实现类。

以上方法发生在没有使用泛型,也没有使用java.lang.Class的时代
那么使用上了java.lang.Class会是什么样呢?
如果我们使用了jdk1.5之前的java.lang.Class,那么以上两个缺点就解决了,
当然会引入新的缺点,最终由jdk1.5中的java.lang.Class<T>完全解决。

Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”); //这是调用句

public static Collection select(Class c, String sqlStatement) {
        Collection result = new ArrayList();
        /* run sql query using jdbc */
        for ( /* iterate over jdbc results */ ) {
                Object item = c.newInstance();
                /* use reflection and set all of item’s fields from sql results */
                result.add(item);
        }
        return result;
}
这段代码解决了
1.使用不方便,因为不需要建立实现类
2.代码不重用,因为如果要ManaInfo的话,可以这样使用,Collection mana = sqlUtility.select(ManaInfo.class, ”select * from mana”)
这段代码引入了一个问题:
返回值是Collection并且最终赋值给了Collection.
这个做法前面几个笔记讲了n遍了,会引起警告,因为Collection是可以加入任何元素的。不仅仅是EmplInfo

所以最终解决方案,使用jdk1.5中引入的java.lang.Class<T>
Collection<EmpInfo> emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);

public static <T> Collection<T> select(Class<T> c, String sqlStatement) {
        Collection<T> result = new ArrayList<T>();
        /* run sql query using jdbc */
        for ( /* iterate over jdbc results */ ) {
                T item = c.newInstance();
                /* use reflection and set all of item’s fields from sql results */
                result.add(item);
        }
        return result;
}
到此,Class<T>介绍结束。Class主要用于让代码重用。因为他可以代表任何类,传入一个EmplInfo.class,那么它就代表EmplInfo

论坛徽章:
0
4 [报告]
发表于 2009-04-08 13:18 |只看该作者
官方pdf后面讲到了
? Super T
以后再看了~

论坛徽章:
0
5 [报告]
发表于 2009-04-10 15:44 |只看该作者
不错,
Mark下 慢慢看。:wink:

论坛徽章:
0
6 [报告]
发表于 2009-04-12 16:21 |只看该作者
感觉还可以,不过我还是没看懂,觉得写的不够精辟,比较繁杂………………
谢谢………………

论坛徽章:
0
7 [报告]
发表于 2009-04-12 19:47 |只看该作者
恩,谢谢意见。就是一边看pdf,一边做笔记的。我的本意是这个东西就写给自己看。结果发现越写越多。我搞完也只大概略知一二。记住了一些基本概念
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP