??xml version="1.0" encoding="utf-8" standalone="yes"?>欧美不卡一区二区,欧美久久久久久,中文字幕第一区 http://www.aygfsteel.com/ammayjxf/zh-cn Wed, 18 Jun 2025 22:19:29 GMT Wed, 18 Jun 2025 22:19:29 GMT 60 java 初始?/title> http://www.aygfsteel.com/ammayjxf/archive/2009/12/16/306229.htmlammay ammay Wed, 16 Dec 2009 14:48:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/16/306229.html http://www.aygfsteel.com/ammayjxf/comments/306229.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/16/306229.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/306229.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/306229.html
解析 Java cd对象的初始化q程
׃个单态模式引出的问题谈v
U别Q?初
?国徏 (guojian.zhang@gmail.com ), 软g工程? 北京高伟达西南分?br />
2006 q?8 ?31 ?/p>
cȝ初始化和对象初始化是 JVM 理的类型生命周期中非常重要的两个环节,Google 了一遍网l,有关c装载机制的文章倒是不少Q然而类初始化和对象初始化的文章q不多,特别是从字节码和 JVM 层次来分析的文章更是鲜有所见?/p>
本文主要对类和对象初始化全过E进行分析,通过一个实际问题引入,源代码转换?JVM 字节码后Q对 JVM 执行q程的关键点q行全面解析Qƈ在文中穿插入了相?JVM 规范?JVM 的部分内部理论知识,以理Z实际l合的方式介l对象初始化和类初始化之间的协作以及可能存在的冲H问题?/p>
问题引入
q日我在调试一个枚丄型的解析器程序,该解析器是将数据库内一万多条枚举代码装载到~存中,Z实现快速定位枚举代码和具体枚Dcd的所有枚丑օ素,该类在装载枚举代码的同时对其采取两种{略建立内存索引。由于该cL一个公共服务类Q在E序各个层面都会使用到它Q因此我它实现Z个单例类。这个类在我调整cd例化语句位置之前q行正常Q但当我把该cd例化语句调整到静态初始化语句之前Ӟ我的E序不再为我工作了?/p>
下面是经q我化后的示例代码:
QL单一Q?/strong>
package com.ccb.framework.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class CachingEnumResolver {
//单态实例 一切问题皆由此行引?
private static final CachingEnumResolver SINGLE_ENUM_RESOLVER = new
CachingEnumResolver();
/*MSGCODE->Category内存索引*/
private static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//Z说明问题,我在q里初始化一条数?
CODE_MAP_CACHE.put("0","北京?);
}
//private, for single instance
private CachingEnumResolver() {
//初始化加载数? 引v问题Q该Ҏ也要负点责Q
initEnums();
}
/**
* 初始化所有的枚Dcd
*/
public static void initEnums() {
// ~~~~~~~~~问题从这里开始暴?~~~~~~~~~~~//
if (null == CODE_MAP_CACHE) {
System.out.println("CODE_MAP_CACHE为空,问题在这里开始暴?");
CODE_MAP_CACHE = new HashMap();
}
CODE_MAP_CACHE.put("1", "北京?);
CODE_MAP_CACHE.put("2", "云南?);
//..... other code...
}
public Map getCache() {
return Collections.unmodifiableMap(CODE_MAP_CACHE);
}
/**
* 获取单态实?
*
* @return
*/
public static CachingEnumResolver getInstance() {
return SINGLE_ENUM_RESOLVER;
}
public static void main(String[] args) {
System.out.println(CachingEnumResolver.getInstance().getCache());
}
}
惛_大家看了上面的代码后会感觉有些茫Ӟq个cȝh没有问题啊,q的属于典型的饿汉式单态模式啊Q怎么会有问题呢?
是的Q他看v来的没有问题,可是如果他 run hӞ其结果是他不会ؓ你正?work。运行该c,它的执行l果是:
QL单二Q?/strong>
CODE_MAP_CACHE为空,问题在这里开始暴?
{0=北京市}
我的E序怎么会这PZ么在 initEnum() Ҏ?CODE_MAP_CACHE 为空Qؓ什么我输出?CODE_MAP_CACHE 内容只有一个元素,其它两个元素呢?Q?Q!Q?/p>
看到q里Q如果是你在调试该程序,你此M定觉得很奇怪,N是我?Jvm 有问题吗Q非也!如果不是Q那我的E序是怎么了?q绝对不是我惌的结果。可事实上无论怎么修改 initEnum() Ҏ都无于事,L我最初是一定不会怀疑到问题可能出在创徏 CachingEnumResolver 实例q一环节上。正是因为我太相信我创徏 CachingEnumResolver 实例的方法,加之?Java cd始化与对象实例化底层原理理解有所偏差Q我ؓ此付Z三、四个小?-U半个工作日的大好青春?/p>
那么问题I竟出在哪里呢?Z么会出现q样的怪事呢?在解册个问题之前,先让我们来了解一下JVM的类和对象初始化的底层机制?/p>
cȝ生命周期
上图展示的是cȝ命周期流向;在本文里Q我只打谈谈类?初始?以及"对象实例?两个阶段?/p>
cd始化
c?初始?阶段Q它是一个类或接口被首次使用的前阶段中的最后一工作,本阶D负责ؓcd量赋予正的初始倹{?/p>
Java ~译器把所有的cd量初始化语句和类型的静态初始化器通通收集到 <clinit> Ҏ内,该方法只能被 Jvm 调用Q专门承担初始化工作?/p>
除接口以外,初始化一个类之前必须保证其直接超cd被初始化Qƈ且该初始化过E是?Jvm 保证U程安全的。另外,q所有的c都会拥有一?<clinit>() ҎQ在以下条g中该cM会拥?<clinit>() ҎQ?/p>
该类既没有声明Q何类变量Q也没有静态初始化语句Q?
该类声明了类变量Q但没有明确使用cd量初始化语句或静态初始化语句初始化;
该类仅包含静?final 变量的类变量初始化语句,q且cd量初始化语句是编译时帔R表达式?
对象初始?/span>
在类被装载、连接和初始化,q个cd随时都可能用了。对象实例化和初始化是就是对象生命的起始阶段的活动,在这里我们主要讨论对象的初始化工作的相关特点?/p>
Java ~译器在~译每个cL都会c至生成一个实例初始化Ҏ--?"<init>()" Ҏ。此Ҏ与源代码中的每个构造方法相对应Q如果类没有明确地声明Q何构造方法,~译器则cȝ成一个默认的无参构造方法,q个默认的构造器仅仅调用父类的无参构造器Q与此同时也会生成一个与默认构造方法对应的 "<init>()" Ҏ.
通常来说Q?lt;init>() Ҏ内包括的代码内容大概为:调用另一?<init>() ҎQ对实例变量初始化;与其对应的构造方法内的代码?/p>
如果构造方法是明确C调用同一个类中的另一个构造方法开始,那它对应?<init>() Ҏ体内包括的内容ؓQ一个对本类?<init>() Ҏ的调用;对应用构造方法内的所有字节码?/p>
如果构造方法不是通过调用自ncȝ其它构造方法开始,q且该对象不?Object 对象Q那 <init>() 法内则包括的内容为:一个对父类 <init>() Ҏ的调用;对实例变量初始化Ҏ的字节码Q最后是对应构造子的方法体字节码?/p>
如果q个cL ObjectQ那么它?<init>() Ҏ则不包括对父c?<init>() Ҏ的调用?/p>
cȝ初始化时?/span>
本文到目前ؓ止,我们已经大概有了解到了类生命周期中都l历了哪些阶D,但这个类的生命周期的开始阶D?-c装载又是在什么时候被触发呢?cd是何时被初始化的呢?让我们带着q三个疑问l去L{案?/p>
Java 虚拟范ؓcȝ初始化时机做了严格定义:"initialize on first active use"--" 在首ơ主动用时初始?。这个规则直接媄响着c装载、连接和初始化类的机?-因ؓ在类型被初始化之前它必须已经被连接,然而在q接之前又必M证它已经被装载了?/p>
在与初始化时机相关的c装载时机问题上QJava 虚拟范ƈ没有对其做严格的定义Q这׃?JVM 在实C可以Ҏ自己的特Ҏ供采用不同的装蝲{略。我们可以思考一?Jboss AOP 框架的实现原理,它就是在对你?class 文g装蝲环节做了手脚--插入?AOP 的相x截字节码Q这使得它可以对E序员做到完全透明化,哪怕你?new 操作W创建出的对象实例也一栯?AOP 框架拦截--与之相对应的 Spring AOPQ你必须通过他的 BeanFactory 获得?AOP 代理q的受管对象Q当?Jboss AOP 的缺点也很明?-他是?JBOSS 服务器绑定很紧密的,你不能很L的移植到其它服务器上。嗯~……Q说到这里有些跑题了Q要知道 AOP 实现{略_以写一本厚厚的书了Q嘿嘿,此打住?/p>
说了q么多,cȝ初始化时机就是在"在首ơ主动用时"Q那么,哪些情Ş下才W合首次d使用的要求呢Q?/p>
首次d使用的情形:
创徏某个cȝ新实例时--new、反、克隆或反序列化Q?
调用某个cȝ静态方法时Q?
使用某个cL接口的静态字D|对该字段赋值时Qfinal字段除外Q;
调用Java的某些反方法时
初始化某个类的子cL
在虚拟机启动时某个含有main()Ҏ的那个启动类?
除了以上几种情Ş以外Q所有其它用JAVAcd的方式都是被动用的Q他们不会导致类的初始化?/p>
我的问题I竟出在哪里
好了Q了解了JVM的类初始化与对象初始化机制后Q我们就有了理论基础Q也可以理性的d析问题了?/p>
下面让我们来看看前面[清单一]的JAVA源代码反l译出的字节码:
QL单三Q?/strong>
public class com.ccb.framework.enums.CachingEnumResolver extends
java.lang.Object{
static {};
Code:
0: new #2; //class CachingEnumResolver
3: dup
4: invokespecial #14; //Method "<init>":()V ?
7: putstatic #16; //Field
SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver;
10: new #18; //class HashMap ?
13: dup
14: invokespecial #19; //Method java/util/HashMap."<init>":()V
17: putstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
20: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
23: ldc #23; //String 0
25: ldc #25; //String 北京?
27: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ?
32: pop
33: return
private com.ccb.framework.enums.CachingEnumResolver();
Code:
0: aload_0
1: invokespecial #34; //Method java/lang/Object."<init>":()V
4: invokestatic #37; //Method initEnums:()V ?
7: return
public static void initEnums();
Code:
0: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map; ?
3: ifnonnull 24
6: getstatic #44; //Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #46; //String CODE_MAP_CACHE为空,问题在这里开始暴?
11: invokevirtual #52; //Method
java/io/PrintStream.println:(Ljava/lang/String;)V
14: new #18; //class HashMap
17: dup
18: invokespecial #19; //Method java/util/HashMap."<init>":()V ?
21: putstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
24: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
27: ldc #54; //String 1
29: ldc #25; //String 北京?
31: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ?
36: pop
37: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
40: ldc #56; //String 2
42: ldc #58; //String 云南?
44: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ?
49: pop
50: return
public java.util.Map getCache();
Code:
0: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
3: invokestatic #66; //Method
java/util/Collections.unmodifiableMap:(Ljava/util/Map;)Ljava/util/Map;
6: areturn
public static com.ccb.framework.enums.CachingEnumResolver getInstance();
Code:
0: getstatic #16;
//Field SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver; ?
3: areturn
}
如果上面QL单一Q显C,清单内容是在 JDK1.4 环境下的字节码内容,可能q䆾清单对于很大部分兄弟来说实没有多少吸引力,因ؓq些 JVM 指o实不像源代码那h亮易懂。但它的的确是查找和定位问题最直接的办法,我们惌的答案就在这?JVM 指o清单里?/p>
现在Q让我们对该cMcd始化到对象实例初始化全过E分析[清单一]中的代码执行轨迹?/p>
如前面所qͼcd始化是在cȝ正可用时的最后一前阶工作,该阶D负责对所有类正确的初始化|此项工作是线E安全的QJVM会保证多U程同步?/p>
W1步:调用cd始化Ҏ CachingEnumResolver.<clinit>()Q该Ҏ对外界是不可见的Q换句话说是 JVM 内部专用ҎQ?lt;clinit>() 内包括了 CachingEnumResolver 内所有的h指定初始值的cd量的初始化语句。要注意的是q每个c都h该方法,具体的内容在前面已有叙述?/p>
W2步:q入 <clinit>() Ҏ内,让我们看字节码中?"? 行,该行与其上面两行l合h代表 new 一?CachingEnumResolver 对象实例Q而该代码行本w是指调?CachingEnumResolver cȝ <init>Q)Ҏ。每一?Java c都h一?<init>() ҎQ该Ҏ?Java ~译器在~译时生成的Q对外界不可见,<init>() Ҏ内包括了所有具有指定初始化值的实例变量初始化语句和javacȝ构造方法内的所有语句。对象在实例化时Q均通过该方法进行初始化。然而到此步Q一个潜在的问题已经在此埋伏好,q着你来犯了?/p>
W3步:让我们顺着执行序向下看,"? 行,该行所在方法就是该cȝ构造器Q该Ҏ先调用父cȝ构造器 <init>() 对父对象q行初始化,然后调用 CachingEnumResolver.initEnum() Ҏ加蝲数据?/p>
W4步:"? 行,该行获取 "CODE_MAP_CACHE" 字段|其运行时该字Dgؓ null。注意,问题已经开始显C。(作ؓE序员的你一定是希望该字D已l被初始化过了,而事实上它还没有被初始化Q。通过判断Q由于该字段?NULLQ因此程序将l箋执行?"? 行,该字段实例化ؓ HashMap()?/p>
W5步:?"??? 行,其功能就是ؓ "CODE_MAP_CACHE" 字段填入两条数据?/p>
W6步:退出对象初始化Ҏ <init>()Q将生成的对象实例初始化l类字段 "SINGLE_ENUM_RESOLVER"。(注意Q此刻该对象实例内的cd量还未初始化完全Q刚才由 <init>() 调用 initEnum() Ҏ赋值的cd?"CODE_MAP_CACHE" ?<clinit>() Ҏq未初始化字D,它还在后面的类初始化过E再ơ被覆盖Q?/p>
W7步:l箋执行 <clinit>Q)Ҏ内的后代码Q?? 行,该行?"CODE_MAP_CACHE" 字段实例化ؓ HashMap 实例Q注意:在对象实例化时已l对该字D赋D了,现在又重新赋gؓ另一个实例,此刻Q?CODE_MAP_CACHE"变量所引用的实例的cd量D覆盖Q到此我们的疑问已经有了{案Q?/p>
W?步:cd始化完毕Q同时该单态类的实例化工作也完成?/p>
通过对上面的字节码执行过E分析,或许你已l清楚了解到D错误的深层原因了Q也或许你可能早已被上面的分析过E给弄得晕头转向了,不过也没折,虽然我也可以从源代码的角度来阐述问题Q但q样不够深度Q同时也会有仅ؓ个h观点、不_信之嫌?/p>
如何解决
要解决上面代码所存在的问题很单,那就是将 "SINGLE_ENUM_RESOLVER" 变量的初始化赋D句{Ud getInstance() Ҏ中去卛_。换句话说就是要避免在类q未初始化完成时从内部实例化该类或在初始化过E中引用q未初始化的字段?/p>
写在最?/span>
静下燥之心Q仔l思量自己是否真的掌握了本文主题所引出的知识,如果您觉得您已经完全或基本掌握了Q那么很好,在最后,我将前面的代码稍做下修改Q请思考下面两l程序是否同样会存在问题呢?
E序一
public class CachingEnumResolver {
public static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//Z说明问题,我在q里初始化一条数?
CODE_MAP_CACHE.put("0","北京?);
initEnums();
}
E序?/strong>
public class CachingEnumResolver {
private static final CachingEnumResolver SINGLE_ENUM_RESOLVER;
public static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//Z说明问题,我在q里初始化一条数?
CODE_MAP_CACHE.put("0","北京?);
SINGLE_ENUM_RESOLVER = new CachingEnumResolver();
initEnums();
}
最后,一点关?JAVA 体的感aQ时下正是各U开源框架盛行时期,Spring 更是大行光Q吸引着一大批 JEE 开发者的眼球Q我也是 fans 中的一员)。然而,让我们仔l观察一?-?Spring 体ZQ在那么多的 Spring fans 当中Q有多少人去研究q?Spring 源代码?又有多少人对 Spring 设计思想有真正深入了解呢Q当Ӟ我是没有资格以这L口吻来说事的Q我只是惌明一个观?-学东西一定要"正本清源"?/p>
献上此文Q}以共勉?/p>
关于作?/span>
北京高伟达西南分?Java EE 软g工程师,三年 Java EE 目l验Q行业方向ؓ银行 OCRM pȝ。对 JAVA 有着厚的兴,业余研究 AOP/ESB 方向?/p>
]]> 泛型 http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305972.htmlammay ammay Mon, 14 Dec 2009 14:55:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305972.html http://www.aygfsteel.com/ammayjxf/comments/305972.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305972.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/305972.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/305972.html W一部分QDavid Flanagan描述了如何用泛型。这部分QDavid Flanagan具体告诉你如何创徏自己的泛型和泛型ҎQƈ且以Java核心API很多重要的泛型作为结束ȝ?br />
创徏泛型和泛型方?/strong>
创徏一个简单的泛型是非常容易的。首先,在一对尖括号(< >)中声明类型变量,以逗号间隔变量名列表。在cȝ实例变量和方法中Q可以在Mcd的地方用那些类型变量。切讎ͼcd变量仅在~译时存在,所以不能用instanceof和newq类q行时操作符来操作类型变量?br />
让我们以一个简单的例子来开始这部分的学习,而后精q个例子。这D代码定义了一个树形数据结构,使用cd变量V代表存储在各个树l点中的倹{?br />
import java.util.*;
/**
* A tree is a data structure that holds values of type V.
* Each tree has a single value of type V and can have any number of
* branches, each of which is itself a Tree.
*/
public class Tree {
// The value of the tree is of type V.
V value;
// A Tree can have branches, each of which is also a Tree
List<tree branches = new ArrayList<tree();
// Here's the constructor. Note the use of the type variable V.
public Tree(V value) { this.value = value; }
// These are instance methods for manipulating the node value and branches.
// Note the use of the type variable V in the arguments or return types.
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree getBranch(int n) { return branches.get(n); }
void addBranch(Tree branch) { branches.add(branch); }
}
正如你所看到的,命名一个类型变量习惯于一个大写字母。用一个字母可以同现实中那些具有描q性的Q长的实际变量名有所区别。用大写字母要同变量命名规则一_q且要区别于局部变量,Ҏ参数Q成员变量,而这些变量常怋用一个小写字母。集合类中,比如java.util中常怋用类型变量E代表“Element type”。T和S常常用来表示范型变量名(好像使用i和j作ؓ循环变量一P?br />
注意刎ͼ当一个变量被声明为泛型时Q只能被实例变量和方法调用(q有内嵌cdQ而不能被静态变量和Ҏ调用。原因很单,参数化的泛型是一些实例。静态成员是被类的实例和参数化的cL׃n的,所以静态成员不应该有类型参数和他们兌。方法,包括静态方法,可以声明和用他们自qcd参数Q但是,调用q样一个方法,可以被不同地参数化。这些内容将在本章后面谈到?br />
cd变量l定
上面例子中的Tree中的cd变量V是不受约束的QTree可以被参数化ZQ何类型。以前我们常怼讄一些约束条件在需要用的cd上:也许我们需要强制一个类型参数实C个或多个接口Q或是一个特定类的子cR这可以通过指明cdl定来完成。我们已l看Cl配W的上界Q而且使用单的语法可以指定一般类型变量的上界。后面的代码Q还是用Treeq个例子Qƈ且通过实现Serializable和Comparable来重写。ؓ了做到这点,例子中用类型变量绑定来保值类型的Serializable和Comparable?br />
import java.io.Serializable;
import java.util.*;
public class Tree>
implements Serializable, Comparable<tree
{
V value;
List<tree branches = new ArrayList<tree();
public Tree(V value) { this.value = value; }
// Instance methods
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree getBranch(int n) { return branches.get(n); }
void addBranch(Tree branch) { branches.add(branch); }
// This method is a nonrecursive implementation of Comparable<tree
// It only compares the value of this node and ignores branches.
public int compareTo(Tree that) {
if (this.value == null && that.value == null) return 0;
if (this.value == null) return -1;
if (that.value == null) return 1;
return this.value.compareTo(that.value);
}
// javac -Xlint warns us if we omit this field in a Serializable class
private static final long serialVersionUID = 833546143621133467L;
}
一个类型变量的l定是通过extends后的名字和一个类型列表(q可以是参数化的Q就像Comparable一P表达的。注意当有不止一个绑定时Q就像上面例子中的,l定的类型要?amp;作ؓ分隔W,而不是用逗号。都后用来分隔类型变量,如果用来分隔cd变量l定Q就会模׃可。一个类型变量可以有M数量的绑定,包括M数量的借口和至多一个类?br />
范型中的通配W?/strong>
上一章的例子中我们看C通配W和控制参数化类型的通配W绑定。这些在范型中同样非常有用。当前设计的Tree要求每个节点有相同类型的|V。也许这样太严格了,也许我们应该让Tree的branches能够存放V的子c而不全是V。这个版本的TreeQ删除了Comparable和Serializable接口的实玎ͼq样做会更灵zR?br />
public class Tree {
// These fields hold the value and the branches
V value;
List<tree<? extends V>> branches = new ArrayList<tree<? extends V>>();
// Here's a constructor
public Tree(V value) { this.value = value; }
// These are instance methods for manipulating value and branches
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree<? extends V> getBranch(int n) { return branches.get(n); }
void addBranch(Tree<? extends V> branch) { branches.add(branch); }
}
通配W绑定允许我们在枝节点上增加一个TreeQ比如,一个树枝TreeQ?br />
Tree t = new Tree(0); // Note autoboxing
t.addBranch(new Tree(1)); // int 1 autoboxed to Integer
通过getBranch()查询树枝Q而树枝的q回cd不知道,所以必M用统配符来表达。接下来的两个是合法的,但第三个不是Q?br />
Tree<? extends Number> b = t.getBranch(0);
Tree<?> b2 = t.getBranch(0);
Tree b3 = t.getBranch(0); // compilation error
当我们这h查询一个树枝时Q不能精确定它的返回类型,但是存在cd的上限,所以,我们可以q样做:
Tree<? extends Number> b = t.getBranch(0);
Number value = b.getValue();
那我们不能做什么呢Q设定树枝的|或者在原有的树枝上d新的树枝。早前章节解释的Q上界的存在不会改变q回值的cd不可知,~译器没有够的信息让我们安全的lsetValue()或者一个树枝(包括值类型)的addBranch()传递一个倹{下面的两行代码都是非法的:
b.setValue(3.0); // Illegal, value type is unknown
b.addBranch(new Tree(Math.PI));
q个例子在设计时扑ֈ了一个^衡点Q用绑定通配W得数据结构更加灵z,但是减少了安全用其中方法的可能。这个设计是好是坏就要根据上下文联系了。通常Q好的范型设计是非常困难的。幸q的是,大多我们要用的已经在java.util包中设计好了Q而不用我们自己再去设计?br />
范型Ҏ
正如前面说的Q范型只能被实例成员调用Q而不是静态成员。同实例Ҏ一P静态方法也可以使用通配W。尽静态方法不能用包含他们的cM的类型变量,但是他们可以声明自己的类型变量。当一个方法声明了自己的类型变量,叫做范型方法?br />
q里有一个要d到Tree中的静态方法。他不是一个范型方法,但是使用了绑定的通配W,好像先前我们看到的sumList()一P
/** Recursively compute the sum of the values of all nodes on the tree */
public static double sum(Tree<? extends Number> t) {
double total = t.value.doubleValue();
for(Tree<? extends Number> b : t.branches) total += sum(b);
return total;
}
通过通配W的上界l定Q声明自qcd变量来重写这个方法:
public static double sum(Tree t) {
N value = t.value;
double total = value.doubleValue();
for(Tree<? extends N> b : t.branches) total += sum(b);
return total;
}
范型的sum()不比通配W版本的单,而且声明变量q没有让我们获得什么。这U情况下Q通配W方案要比范型方法更有效Q当一个类型变量用来表达两个参C间或者参数和q回g间的关系Ӟ范型Ҏ才是需要的。请看下面的例子Q?br />
// This method returns the largest of two trees, where tree size
// is computed by the sum() method. The type variable ensures that
// both trees have the same value type and that both can be passed to sum().
public static Tree max(Tree t, Tree u) {
double ts = sum(t);
double us = sum(u);
if (ts > us) return t;
else return u;
}
q个Ҏ使用cd变量N来约束参数和q回值有相同cdQƈ且参数是Number或者他的子cR?br />
使得参数h相同cd也许是有争议的,应该让我们能调用max()不论是Tree或者Tree。一U方法是使用两个不相q的cd变量来表CZ个不相干的值类型。注意,我们不能在方法的q回时用变量而必M用通配W:
public static
Tree<? extends Number> max(Tree t, Tree u) {...}
既然两个cd变量N和M没有M联系Q而且每个仅在{的时候用,他们没有提供比通配W更多的好处Q这U方法最好这样写Q?br />
public static Tree<? extends Number> max(Tree<? extends Number> t,
Tree<? extends Number> u) {...}
所有在q里的范型方法都是静态的Q这q不是必ȝQ实例方法也可以声明自己的类型变量?br />
调用范型Ҏ
当你使用范型Ӟ必须指定实际cd参数来代替相应的cd变量。但q些对范型方法有些不同:~译器L能计出Z你所传递的参数的相应范型方法参数。考虑一下上面定义的max()Q作Z子:
public static Tree max(Tree t, Tree u) {...}
当你调用q个ҎӞ不需要指明NQ因为N是隐含地由t和u指明。在后面的代码中Q编译器军_N为IntegerQ?br />
Tree x = new Tree(1);
Tree y = new Tree(2);
Tree z = Tree.max(x, y);
~译器判断范型方法的参数cdUCؓcd推断。类型推断是相对于知觉推断的。而实际编译器的实现方法是一U非常复杂的q程Q超q了q本书的讨论范围。更多的l节在The Java Language Specification, Third Edition的第十五章?br />
让我们看一个更加复杂的cd推断Q考虑一下这个方法:
public class Util {
/** Set all elements of a to the value v; return a. */
public static T[] fill(T[] a, T v) {
for(int i = 0; i < a.length; i++) a[i] = v;
return a;
}
}
q里有两个该Ҏ的调用:
Boolean[] booleans = Util.fill(new Boolean[100], Boolean.TRUE);
Object o = Util.fill(new Number[5], new Integer(42));
在第一个例子中Q编译器可以L的推断出T是BooleancdQ第二个例子中,~译器判断T是Number?br />
在非常罕见的情况下,你可能会昄的指明范型方法的参数cd。有时候这是必要的Q比如,当范型方法不需要参数时。考虑一下java.util.Collections.emptySet()Q返回一个空集合Q但是不同于Collections.singleton()Q可以在参考部分察看)Q他不带M参数Q但需要指明返回类型。通过在方法名前的<>中,可以昄的指明参数类型:
Set empty = Collections.emptySet();
cd参数不能同没有限制的Ҏ名结合用:他们必须跟随在一?后或者在关键字new后,或者在关键字this前,或者构造函数的super前?br />
可以证明Q如果如果你Collections.emptySet()的返回Dl一个变量,像我们上边通过cd推断机制推断Z变量cd的参数类型。尽显C的cd说明可以更加清楚Q但q不是必要的Q可以像下面一样重写:
Set empty = Collections.emptySet();
在方法调用表辑ּ中,昄的说明emptySet()的返回值类型是必要的。比如,假设你要调用一个名为printWords()的方法,该方法仅需一个Set的参敎ͼ如果你想传递一个空的集合给该方法,p像下面一样写Q?br />
printWords(Collections.emptySet());
q种情况下,昄的类型说明是必要的?br />
范型Ҏ和数l?/strong>
早先我们看到Q编译器不允许创Z个类型参数化的数l。但是对于范型的使用会是不同的。考虑一下前面定义的Util.fill()Q它得以W一个参数和q回值类型都是T[]。而方法体内不必创ZQ何参CؓT的数l,所以这个方法是合法的?br />
如果你创Z个方法用varargsQ参见第二章?.6.4Q和cd变量Q记住调用varargs隐含创徏一个数l,L下面的例子:
/** Return the largest of the specified values or null if there are none */
public static > T max(T... values) { ... }
你可以用一个Integercd来调用这个方法,因ؓ~译器会在调用的时候插入必要的数组创徏代码。但是你不能参数{换ؓComparable来调用这个方法,因ؓ创徏一个Comparable[]是不合法的?br />
参数化异?/strong>
异常是在q行时抛出和捕获的。没有办法让~译器完成类型检查,来保证在catch块中抛出的未知的cd匚w异常。由于这个原因,catch块很可能不包含类型变量和通配W。既然不可能保证在运行时捕获一个编译器时类型参数完整性异常,所以不允许创徏MThrowablecd的子cR参数化异常是不允许的?br />
但是你可以用类型变量在throw块里的方法签名中。看看下面的例子Q?br />
public interface Command {
public void doit(String arg) throws X;
}
q个接口描述了一?#8220;command”Q一块代码只有一个Stringcd的参敎ͼ没有q回倹{代码可能抛Z个类型ؓX的异常。这里有一个例子用这个接口:
Command save = new Command() {
public void doit(String filename) throws IOException {
PrintWriter out = new PrintWriter(new FileWriter(filename));
out.println("hello world");
out.close();
}
};
try { save.doit("/tmp/foo"); }
catch(IOException e) { System.out.println(e); }
范型个案研究Q比较和枚D
Java1.5引入的范型新Ҏ,?.5的API中有使用Q特别多的是在java.util包中Q但是在java.langQjava.lang.reflect和java.util.concurrent中也有。这些API都是l过仔细的斟酌创建的Q通过学习q些API我们可以学到很多好的设计Ҏ?br />
java.util中的范Ş是比较简单的Q因为大多都是集合类Q类型变量也是代表集合中的元素。java.lang中的几个重要范型是比较难以理解的Q他们不是集合,而且W一眼很不容易理解ؓ什么设计成范型。学习这些范型可以让我们更深层次的理解范形的工作机制Qƈ且介l一些我们没有提到的概念。特别的Q我们要查Comparable接口和Enumc(枚Dcd的超c,后面一张讲解)q且学习一些重要但是很用的范型Ҏ,比如通配W下界?br />
在java1.5中,Comparable接口被修改ؓ范型的。大多数的类都实Cq个接口Q考虑一下IntegerQ?br />
public final class Integer extends Number implements Comparable
原先的Comparable接口在类型安全方面是有问题的。两个承了Comparable接口的对象可能不能相互比较。JDK5.0前,非范形的Comparable接口是非常有用但是不安全的,而现在的接口Q捕获了我们需要的信息Q他告诉我们一个对象是可比较的Qƈ且可以同什么比较?br />
现在Q考虑一下comparablecȝ子类。Integer是final的,所以不能有子类Q那么让我们看看java.math.BigIntegerQ?br />
public class BigInteger extends Number implements Comparable
如果我们实现一个BiggerIntegercLBigInteger的子c,他从父类那里l承了Comparable接口Q但是注意承的是Comparable而不是Comparable。这意味着BigInteger和BiggerInteger是可以相互比较的Q这是非常好的。BiggerInteger可以重蝲compareTo()Q但是不允许实现一个不同的参数化的Comparable。这是说BiggerInteger不能同时l承BigInteger和实现Comparable?br />
当你使用可比较的对象Ӟ当写排序法的时候)C两点。首先,使用原生cd是不够充分的Q考虑到类型安全,必须指明同什么比较。接下来Q类型是不允许同自己比较的:有时候他会同他的先比较。ؓ了具体说明,考虑java.util.Collections.max()Q?br />
q是一个冗长而且复杂的方法标{,我们来一步步考虑Q?br />
Ҏ中包含一个类型变量TQƈ且有复杂的绑定,E后我们q回来讨论?br />
Ҏ的返回值类型是T?br />
Ҏ名是max()?br />
Ҏ的参数是一个集合。元素的cd指定为绑定的通配W。我们ƈ不知道元素的切cdQ但直到有一个上限T。所以我们知道元素的cd要么为TQ要么是T的子cR集合的M元素都可以作回g用?br />
q些是比较简单的Q本章我们已l看C通配W上界,我们再来看看max()中的cd变量声明Q?br />
>
要说明的W一点,T必须实现了Comparable接口。(范型的语法用关键字extends来代表类型绑定,不论是类或接口)q是期望的,因ؓq个Ҏ是找到集合中最大的元素。但是观察这个参数化的Comparable接口Q这是一个通配W,但是q个通过关键字super来绑定,而不是extends。这是下界绑定? extends T是我们熟悉的上界l定Q这意味着T或者其子类? super T比较用Q这意味着T或者他的超cR?br />
ȝ一下,cd变量声明表明Q?#8220;T是一个实CComparable接口或者他的父cdC该接口的cd?#8221;Collections.min()和Collections.binarySearch()有着相同的声明?br />
对其他的下界通配W(对于Comparable接口没有作用Q的例子QCollections中的addAll()Qcopy()Q和fill()。观察addAll()的声明:
public static boolean addAll(Collection<? super T> c, T... a)
q是一个varargsҎQ接受Q意数量的参数Qƈ且传递给他们一个T[]Q命名ؓa。他a中的所有元素都赋给集合c。集合的元素cd虽然不知道,但是有一个下界:元素均ؓT或者T的超cR不论类型是什么,我们可以定数组的元素都是类型的实例Q所以将数组的元素添加到集合中是合法的?br />
q回到我们先前讨论的上界通配W,如果有一个集合的元素是上界通配W,那么都是只读的。考虑List<? extends Serializable>。我们知道,所有的元素都是SerializableQ所以像get()q样的方法返回一个Serializablecd的返回倹{编译器不允许我们调用add()q样的方法,因ؓ实际的元素类型是不可知的。不能够dl对的Serializable对象到list中,因ؓ实现他们的类可能不是正确的类型?br />
既然上界l配W的l果是只ȝQ所以你可能会期望下界通配W来实现只写的集合。实际ƈ不是q样Q假设这里有一个List<? extends Integer>。元素的实际cd是不知道的,但是可能性是Integer或者他的祖先类Number和Object。无论实际类型是什么,IntegercdQ而不是Number和Object对象Q的元素d到list中是安全的。无论实际类型是什么,list中所有元素都是Object对象的实例,所以list中像get()一LҎq回Object?br />
最后,让我们把注意力放到java.lang.EnumcREnum是所有枚丄型的父类Q它实现了Comparable接口Q但是有一个让惑的范型声明ҎQ?br />
public class Enum> implements Comparable, Serializable
W一|cd变量E的声明在一个@环中。再仔细的看一看:声明真正说明了,Enum必须是一个本w就是Enumcd的类型。这U表面上的@环是很显然的Q如果我们看Cimplements子句。正如我们看到的QComparablec通常被定义ؓ可以同自己比较的。而且他们的子cM可以同他们的父类比较。从另一个方面将QEnum实现了Comparable接口不是Z他本w,而是Z他的子类E?br />
资源:
·Onjava.com:Onjava.com
·Matrix-Java开发者社?http://www.matrix.org.cn/
Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd
Java中的泛型 W二部分
]]> 泛型cd http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305971.htmlammay ammay Mon, 14 Dec 2009 14:53:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305971.html http://www.aygfsteel.com/ammayjxf/comments/305971.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305971.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/305971.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/305971.html 泛型cdQ第一部分
作? David Flanagan
译Q?a target="_new">cat
版权声明 Q可以Q意{载,转蝲时请务必以超链接形式标明文章原始出处和作者信息及本声?br />
作?
David Flanagan;cat
原文地址:
http://www.onjava.com/pub/a/onjava/excerpt/javaian5_chap04/index.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43864_Generic_Types.html
关键词: Generic Types
~辑按:《Java in a Nutshell, 5th Edition》覆盖了jdk5.0中很多变化和新特征,其中最重要的就是泛型。在本文的第一部分Q作者David Flanagan介绍了如何用泛型;而在W二部分Q作者描qC如何写你自己的泛型和泛型Ҏ?br />
Java5.0的新Ҏ之一是引入了泛型cd和泛型方法。一个泛型类型通过使用一个或多个cd变量来定义,q拥有一个或多个使用一个类型变量作Z个参数或者返回值的占位W。例如,cdjava.util.List<E>是一个泛型类型:一个listQ其元素的类型被占位WE描述。这个类型有一个名为add()的方法,被声明ؓ有一个类型ؓE的参敎ͼ同时Q有一个get()ҎQ返回D声明为Ecd?br />
Z使用泛型cdQ你应该为类型变量详l指明实际的cdQŞ成一个就像List<String>cM的参数化cd。[1]指明q些额外的类型信息的原因是编译器据此能够在编译期为您提供很强的类型检查,增强您的E序的类型安全性。D个例子来_您有一个只能保持String对象的ListQ那么这U类型检查就能够L您往里面加入String[]对象。同LQ增加的cd信息使编译器能够为您做一些类型{换的事情。比如,~译器知道了一个List<String>有个get()ҎQ其q回值是一个String对象Q因此您不再需要去返回值由一个Object强制转换为String?br />
Java.util包中的集合类在java5.0中已l被做成了泛型,也许您将会在您的E序中频J的使用C们。类型安全的集合cd是一个泛型类型的典型案例。即便您从没有定义过您自q泛型cd甚至从未用过除了java.util中的集合cM外的泛型cdQ类型安全的集合cȝ好处也是极有意义的一个标志——他们证明了q个主要的新语言Ҏ的复杂性?br />
我们从探索类型安全的集合cM的基本的泛型用法开始,q而研I更多用泛型类型的复杂l节。然后我们讨论类型参数通配W和有界通配W。描l了如何使用泛型以后Q我们阐明如何编写自q泛型cd和泛型方法。我们对于泛型的讨论结束于一对于JavaAPI的核心中重要的泛型类型的旅行。这旅E将探烦q些cd以及他们的用法,旅程的目的是Z让您Ҏ型如何工作这个问题有个深入的理解?br />
cd安全集合c?/span>
Java.utilcd包含了Java集合框架QJava Collections FrameworkQ,q是一批包含对象的set、对象的list以及Zkey-value的map。第五章谈到集合类。这里,我们讨论的是在java5.0中集合类使用cd参数来界定集合中的对象的cd。这个讨论ƈ不适合java1.4或更早期版本。如果没有泛型,对于集合cȝ使用需要程序员C每个集合中元素的cd。当您在java1.4U创Z一个集合,您知道您攑օ到集合中的对象的cdQ但是编译器不知道。您必须心地往其中加入一个合适类型的元素Q当需要从集合中获取元素时Q您必须昑ּ的写强制cd转换以将他们从Object转换Z们真是的cd。考察下边的java1.4的代码?br />
public static void main(String[] args) {
// This list is intended to hold only strings.
// The compiler doesn't know that so we have to remember ourselves.
List wordlist = new ArrayList();
// Oops! We added a String[] instead of a String.
// The compiler doesn't know that this is an error.
wordlist.add(args);
// Since List can hold arbitrary objects, the get() method returns
// Object. Since the list is intended to hold strings, we cast the
// return value to String but get a ClassCastException because of
// the error above.
String word = (String)wordlist.get(0);
}
泛型cd解决了这D代码中的显C的cd安全问题。Java.util中的List或是其他集合cdl用泛型重写过了。就像前面提到的Q?List被重新定义ؓ一个listQ它中间的元素类型被一个类型可变的名称为E的占位符描述。Add()Ҏ被重新定义ؓ期望一个类型ؓE的参敎ͼ用于替换以前的ObjectQget()Ҏ被重新定义ؓq回一个EQ替换了以前的Object?br />
在java5.0中,当我们申明一个List或者创Z个ArrayList的实例的时候,我们需要在泛型cd的名字后面紧跟一?#8220;<>”Q尖括号中写入我们需要的实际的类型。比如,一个保持String的List应该写成“List<String>”。需要注意的是,q非常象l一个方法传一个参敎ͼ区别是我们用类型而不是|同时使用括可不是圆括号
Java.util的集合类中的元素必须是对象化的,他们不能是基本类型。泛型的引入q没有改变这炏V泛型不能用基本类型:我们不能q样来申明——Set<char>或者List<int>。记住,无论如何Qjava5.0中的自动打包和自动解包特性得用Set<Character>或者List<Integer>和直接用char和intghѝ(查看W二章以了解更多关于自动打包和自动解包的l节Q?br />
在Java5.0中,上面的例子将被重写ؓ如下方式Q?br />
public static void main(String[] args) {
// This list can only hold String objects
List<String> wordlist = new ArrayList<String>();
// args is a String[], not String, so the compiler won't let us do this
wordlist.add(args); // Compilation error!
// We can do this, though.
// Notice the use of the new for/in looping statement
for(String arg : args) wordlist.add(arg);
// No cast is required. List<String>.get() returns a String.
String word = wordlist.get(0);
}
值得注意的是代码量其实ƈ没有比原来那个没有泛型的例子多。?#8220;(String)”q样的类型{换被替换成了cd参数“<String>”?不同的是cd参数需要且仅需要声明一ơ,而list能够被用Q何多ơ,不需要类型{换。在更长点的例子代码中,q一点将更加明显。即使在那些看上L型语法比非泛型语法要冗长的例子里Q用泛型依然是非常有h值的——额外的cd信息允许~译器在您的代码里执行更强的错误查。以前只能在q行h能发现的错误现在能够在编译时p发现。此外,以前Z处理cd转换的异常,我们需要添加额外的代码行。如果没有泛型,那么当发生类型{换异常的时候,一个ClassCastException异常׃被从实际代码中抛出?br />
像一个方法可以用Q意数量的参数一Pcd怋用多个类型变量。接口Java.util.Map是一个例子。一个Map体现了从一个key的对象到一个value的对象的映射关系。接口Mapx了一个类型变量来描述key的类型而另一个类型变量来描述value的类型。D个例子来_假设您希望做一个String对象到Integer对象的映关p:
public static void main(String[] args) {
// A map from strings to their position in the args[] array
Map<String,Integer> map = new HashMap<String,Integer>();
// Note that we use autoboxing to wrap i in an Integer object.
for(int i=0; i < args.length; i++) map.put(args[i], i);
// Find the array index of a word. Note no cast is required!
Integer position = map.get("hello");
// We can also rely on autounboxing to convert directly to an int,
// but this throws a NullPointerException if the key does not exist
// in the map
int pos = map.get("world");
}
象List<String>q个一个参数类型其本n也是也一个类型,也能够被用于当作其他cd的一个类型变量倹{您可能会看到这L代码Q?br />
// Look at all those nested angle brackets!
Map<String, List<List<int[]>>> map = getWeirdMap();
// The compiler knows all the types and we can write expressions
// like this without casting. We might still get NullPointerException
// or ArrayIndexOutOfBounds at runtime, of course.
int value = map.get(key).get(0).get(0)[0];
// Here's how we break that expression down step by step.
List<List<int[]>> listOfLists = map.get(key);
List<int[]> listOfIntArrays = listOfLists.get(0);
int[] array = listOfIntArrays.get(0);
int element = array[0];
在上面的代码里,java.util.List<E>和java.util.Map<K,V>的get()Ҏq回一个类型ؓE的list元素或者一个类型ؓV的map元素。注意,无论如何Q泛型类型能够更_֯的用他们的变量。在本书中的参考章节查看List<E>Q您会看到它的iterator( )Ҏ被声明ؓq回一个Iterator<E>。这意味着Q这个方法返回一个跟list的实际的参数cd一L一个参数类型的实例。ؓ了具体的说明q点Q下面的例子提供了不使用get(0)Ҏ来获取一个List<String>的第一个元素的Ҏ?br />
List<String> words = // ...initialized elsewhere...
Iterator<String> iterator = words.iterator();
String firstword = iterator.next();
理解泛型cd
本段对泛型cd的用细节做q一步的探讨Q以试说明下列问题Q?br />
不带cd参数的用泛型的后果
参数化类型的体系
一个关于编译期泛型cd的类型安全的漏洞和一个用于确保运行期cd安全的补?br />
Z么参数化cd的数l不是类型安全的
未经处理的类型和不被查的警告
即被重写的Java集合cd来了泛型的好处,在用他们的时候您也不被要求说明类型变量。一个不带类型变量的泛型cd被认为是一个未l处理的cdQraw typeQ。这P5.0版本以前的java代码仍然能够q行Q您昑ּ的编写所有类型{换就像您已经q样写的一P您可能会被一些来自编译器的麻烦所困扰。查看下列存储不同类型的对象C个未l处理的ListQ?br />
List l = new ArrayList();
l.add("hello");
l.add(new Integer(123));
Object o = l.get(0);
q段代码在java1.4下运行得很好。如果您用java5.0来编译它Qjavac~译了,但是会打印出q样?#8220;抱?#8221;Q?br />
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
如果我们加入-Xlint参数后重新编译,我们会看到这些警告:
Test.java:6: warning: [unchecked]
unchecked call to add(E) as a member of the raw type java.util.List
l.add("hello");
^
Test.java:7: warning: [unchecked]
unchecked call to add(E) as a member of the raw type java.util.List
l.add(new Integer(123));
^
~译在add()Ҏ的调用上l出了警告,因ؓ它不能够信加入到list中的值具有正的cd。它告诉我们说我们用了一个未l处理的cdQ它不能验证我们的代码是cd安全的。注意,get()Ҏ的调用是没有问题的,因ؓ能够被获得的元素已经安全的存在于list中了?br />
如果您不想用Q何的java5.0的新Ҏ,您可以简单的通过?source1.4标记来编译他们,q样~译器就不会?#8220;抱?#8221;了。如果您不能q样做,您可以忽略这些警告,通过使用一?#8220;@SuppressWarnings("unchecked")”注解Q查看本章的4.3节)隐瞒q些警告信息或者升U您的代码,加入cd变量描述。[2]下列CZ代码Q编译的时候不再会有警告但仍然允许您往list中放入不同的cd的对象?br />
List<Object> l = new ArrayList<Object>();
l.add("hello");
l.add(123); // autoboxing
Object o = l.get(0);
参数化类型的体系
参数化类型有cd体系Q就像一般的cd一栗这个体pd于对象的cdQ而不是变量的cd。这里有些例子您可以试Q?br />
ArrayList<Integer> l = new ArrayList<Integer>();
List<Integer> m = l; // okay
Collection<Integer> n = l; // okay
ArrayList<Number> o = l; // error
Collection<Object> p = (Collection<Object>)l; // error, even with cast
一个List<Integer>是一个Collection<Integer>Q但不是一个List<Object>。这句话不容易理解,如果您想理解Z么泛型这样做Q这D值得看一下。考察q段代码Q?br />
List<Integer> li = new ArrayList<Integer>();
li.add(123);
// The line below will not compile. But for the purposes of this
// thought-experiment, assume that it does compile and see how much
// trouble we get ourselves into.
List<Object> lo = li;
// Now we can retrieve elements of the list as Object instead of Integer
Object number = lo.get(0);
// But what about this?
lo.add("hello world");
// If the line above is allowed then the line below throws ClassCastException
Integer i = li.get(1); // Can't cast a String to Integer!
q就是ؓ什么List<Integer>不是一个List<Object>的原因,虽然List<Integer>中所有的元素事实上是一个Object的实例。如果允许{换成List<Object>Q那么{换后Q理Z非整型的对象也将被允许添加到list中?br />
q行时类型安?/span>
像我们所见到的,一个List<X>不允许被转换Z个List<Y>Q即使这个X能够被{换ؓY。然而,一个List<X>能够被{换ؓ一个ListQ这h可以通过l承的方法来做这L事情?br />
q种参数化cd转换为非参数化类型的能力对于向下兼容是必要的Q但是它会在泛型所带来的类型安全体pM凿个漏洞Q?br />
// Here's a basic parameterized list.
List<Integer> li = new ArrayList<Integer>();
// It is legal to assign a parameterized type to a nonparameterized variable
List l = li;
// This line is a bug, but it compiles and runs.
// The Java 5.0 compiler will issue an unchecked warning about it.
// If it appeared as part of a legacy class compiled with Java 1.4, however,
// then we'd never even get the warning.
l.add("hello");
// This line compiles without warning but throws ClassCastException at runtime.
// Note that the failure can occur far away from the actual bug.
Integer i = li.get(0);
泛型仅提供了~译期的cd安全。如果您使用java5.0的编译器来编译您的代码ƈ且没有得CQ何警告,q些~译器的查能够确保您的代码在q行期也是类型安全的。如果您获得了警告或者用了像未l处理的cd那样修改您的集合的代码,那么您需要增加一些步骤来保q行期的cd安全。您可以通过使用java.util.Collections中的checkedList()和checkedMap( )Ҏ来做到这一步。这些方法将把您的集合打包成一个wrapper集合Q从而在q行时检查确认只有正类型的D够被|入集合众。下面是一个能够补上类型安全漏z的一个例子:
// Here's a basic parameterized list.
List<Integer> li = new ArrayList<Integer>();
// Wrap it for runtime type safety
List<Integer> cli = Collections.checkedList(li, Integer.class);
// Now widen the checked list to the raw type
List l = cli;
// This line compiles but fails at runtime with a ClassCastException.
// The exception occurs exactly where the bug is, rather than far away
l.add("hello");
参数化类型的数组
在用泛型类型的时候,数组需要特别的考虑。回忆一下,如果T是S的父c(或者接口)Q那么类型ؓS的数lS[]Q同时又是类型ؓT的数lT[]。正因ؓ如此Q每ơ您存放一个对象到数组中时QJava解释器都必须q行查以保您放入的对象cd与要存放的数l所允许的类型是匹对的。例如,下列代码在运行期会检查失败,抛出一个ArrayStoreException异常Q?br />
String[] words = new String[10];
Object[] objs = words;
objs[0] = 1; // 1 autoboxed to an Integer, throws ArrayStoreException
虽然~译时obj是一个Object[]Q但是在q行时它是一个String[]Q它不允许被用于存放一个Integer.
当我们用泛型类型的时候,仅仅依靠q行时的数组存放异常查是不够的,因ؓ一个运行时q行的检查ƈ不能够获取编译时的类型参C息。查看下列代码:
List<String>[] wordlists = new ArrayList<String>[10];
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali; // No ArrayStoreException
String s = wordlists[0].get(0); // ClassCastException!
如果上面的代码被允许Q那么运行时的数l存储检查将会成功:没有~译时的cd参数Q代码简单地存储一个ArrayListC个ArrayList[]数组Q非常正。既然编译器不能L您通过q个Ҏ来战胜类型安全,那么它{而阻止您创徏一个参数化cd的数l。所以上q情节永q不会发生,~译器在W一行就开始拒l编译了?br />
注意qƈ不是一个在使用数组时用泛型的全部的约束,q仅仅是一个创Z个参数化cd数组的约束。我们将在学习如何写泛型Ҏ时再来讨个话题?br />
cd参数通配W?/span>
假设我们需要写一个方法来昄一个List中的元素。[3]在以前,我们只需要象q样写段代码Q?br />
public static void printList(PrintWriter out, List list) {
for(int i=0, n=list.size(); i < n; i++) {
if (i > 0) out.print(", ");
out.print(list.get(i).toString());
}
}
在Java5.0中,List是一个泛型类型,如果我们试图~译q个ҎQ我们将会得到unchecked警告。ؓ了解册些警告,您可能需要这h修改q个ҎQ?br />
public static void printList(PrintWriter out, List<Object> list) {
for(int i=0, n=list.size(); i < n; i++) {
if (i > 0) out.print(", ");
out.print(list.get(i).toString());
}
}
q段代码能够~译通过同时不会有警告,但是它ƈ不是非常地有效,因ؓ只有那些被声明ؓList<Object>的list才会被允怋用这个方法。还记得么,cM于List<String>和List<Integer>q样的Listq不能被转型为List<Object>。事实上我们需要一个类型安全的printList()ҎQ它能够接受我们传入的Q何ListQ而不兛_它被参数化ؓ什么。解军_法是使用cd参数通配W。方法可以被修改成这P
public static void printList(PrintWriter out, List<?> list) {
for(int i=0, n=list.size(); i < n; i++) {
if (i > 0) out.print(", ");
Object o = list.get(i);
out.print(o.toString());
}
}
q个版本的方法能够被~译q,没有警告Q而且能够在Q何我们希望用的地方使用。通配W?#8220;Q?#8221;表示一个未知类型,cdList<?>被读?#8220;List of unknown”
作ؓ一般原则,如果cd是泛型的Q同时您q不知道或者ƈ不关心值的cdQ您应该使用“?”通配W来代替一个未l处理的cd。未l处理的cd被允总是ؓ了向下兼容,而且应该只能够被允许出现在老的代码中。注意,无论如何Q您不能在调用构造器时用通配W。下面的代码是非法的Q?br />
List<?> l = new ArrayList<?>();
创徏一个不知道cd的List是毫无道理的。如果您创徏了它Q那么您必须知道它将保持的元素是什么类型的。您可以在随后的Ҏ中不兛_元素cd而去遍历q里listQ但是您需要在您创建它的时候描q元素的cd。如果你实需要一个List来保持Q何类型,那么您只能这么写Q?br />
List<Object> l = new ArrayList<Object>();
从上面的printList()例子中,必须要搞清楚List<Q?gt;既不是List<Object>也不是一个未l处理的List。一个用通配W的List<?>有两个重要的Ҏ。第一Q考察cM于get()的方法,他们被声明返回一个|q个值的cd是类型参C指定的。在q个例子中,cd?#8220;unknown”Q所以这些方法返回一个Object。既然我们期望的是调用这个object的toString()ҎQ程序能够很好的满我们的意ѝ?br />
W二Q考察List的类似add()的方法,他们被声明ؓ接受一个参敎ͼq个参数被类型参数所定义。出人意料的是,当类型参数是未确定的Q编译器不允许您调用M有不定参数cd的方法——因为它不能认您传入了一个恰当的倹{一个List(Q?实际上是只读的——既然编译器不允许我们调用类gadd(),set(),addAll()q类的方法?br />
界定通配W?/span>
让我们在我们原来的例子上作些小的稍微复杂一点的改动。假设我们希望写一个sumList()Ҏ来计list中Numbercd的值的合计。在以前Q我们用未l处理的ListQ但是我们不x弃类型安全,同时不得不处理来自编译器的unchecked警告。或者我们可以用List<Number>Q那L话我们就不能调用List<Integer>、List<Double>中的Ҏ了,而事实上我们需要调用。如果我们用通配W,那么我们实际上不能得到我们期望的cd安全Q我们不能确定我们的Ҏ被什么样的List所调用QNumberQ还是Number的子c?甚至QString?q样的一个方法也怼被写成这P
public static double sumList(List<?> list) {
double total = 0.0;
for(Object o : list) {
Number n = (Number) o; // A cast is required and may fail
total += n.doubleValue();
}
return total;
}
要修改这个方法让它变得真正的cd安全Q我们需要用界定通配W(bounded wildcardQ,能够保List的类型参数是未知的,但又是Number或者Number的子cR下面的代码才是我们惌的:
public static double sumList(List<? extends Number> list) {
double total = 0.0;
for(Number n : list) total += n.doubleValue();
return total;
}
cdList<? extends Number>可以被理解ؓ“Number未知子类的List”。理解这炚w帔R要,在这D|字中QNumber被认为是其自w的子类?br />
注意Q这L话,那些cd转换已经不再需要了。我们ƈ不知道list中元素的具体cdQ但是我们知道他们能够向上{型ؓNumberQ因此我们可以把他们从list中把他们当作一个Number对象取出。用一个for/in循环能够E微装一下从list中取出元素的q程。普遍性的原则是当您用一个界定通配W时Q类gList中的get()Ҏ的那些方法将q回一个类型ؓ上界的倹{因此如果我们在for/in循环中调用list.get()Q我们将得到一个Number。在前一节说C用通配W时cM于list.add()q种Ҏ中的限制依然有效QD个例子来_如果~译器允许我们调用这cL法,我们可以将一个Integer攑ֈ一个声明ؓ仅保持Short值的list中去?br />
同样可行的是使用下界通配W,不同的是用super替换extends。这个技巧在被调用的Ҏ上有一点不同的作用。在实际应用中,下界通配W要比上界通配W用得少。我们将在后面的章节里讨个问题?br />
脚注
[1] 在本章中Q我会坚持用术语”泛型cd”来指一个声明一个或多个cd变量的类型,?#8221;参数化的cd”来指由实际类型参数来替换其类型变量的泛型cd。然而,在一般情况下Q这U区别ƈ不明显,q且q些术语有时通用?br />
[2] 在撰写本文时候,javacq不支持@SuppressWarnings 的注解。期望在Java 5.1中得到支持?
[3] 本节所C的3个printList()Ҏ忽略了这样一个事实,即java.util 中List的所有实现类都有一个可用的toString()Ҏ。还要注意这些方法假定List实现RandomAccessq在LinkedList实例中只提供了很差的q行效率?br />
David Flanagan是众多O'Reilly书籍的作者。这些书包括《Java in a Nutshell》,《Java Examples in a Nutshell》,《Java Foundation Classes in a Nutshell》,《JavaScript: The Definitive Guide》,《JavaScript Pocket Reference》?br />
在Java in a Nutshell, 5th Edition中查看目录信息?br />
]]>泛型 http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305969.htmlammay ammay Mon, 14 Dec 2009 14:52:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305969.html http://www.aygfsteel.com/ammayjxf/comments/305969.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305969.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/305969.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/305969.html
泛型?Sun 公司发布?JDK 5.0 中的一个重要特性,它的最大优Ҏ提供了程序的cd安全同可以向后兼宏Vؓ了帮助读者更好地理解和用泛型,本文通过一些示例从基本原理Q重要概念,关键技术,以及怼技术比较等多个角度?Java 语言中的泛型技术进行了介绍Q重点强调了泛型中的一些基本但又不是很好理解的概念?/p>
Z避免?C++ 中的模板hQ本文简要介l了 Java 中的泛型?C++ 中的模板的主要区别,希望q种比较能够帮助读者加深对泛型的理解?/p>
引言
很多 Java E序员都使用q集合(CollectionQ,集合中元素的cd是多U多LQ例如,有些集合中的元素?Byte cd的,而有些则可能?String cd的,{等。Java 语言之所以支持这么多U类的集合,是因为它允许E序员构Z个元素类型ؓ Object ?CollectionQ所以其中的元素可以是Q何类型?/p>
当?Collection Ӟ我们l常要做的一件事情就是要q行cd转换Q当转换成所需的类型以后,再对它们q行处理。很明显Q这U设计给~程人员带来了极大的不便Q同时也Ҏ引入错误?/p>
在很?Java 应用中,上述情况非常普遍Qؓ了解册个问题,?Java 语言变得更加安全好用Q近些年的一些编译器?Java 语言q行了扩充,?Java 语言支持?泛型"Q特别是 Sun 公司发布?JDK 5.0 更是泛型作为其中一个重要的Ҏ加以推qѝ?/p>
本文首先Ҏ型的基本概念和特点进行简单介l,然后通过引入几个实例来讨论带有泛型的c,泛型中的子类型,以及范化Ҏ和受限类型参数等重要概念。ؓ了帮助读者更加深ȝ理解q用泛型,本文q介l了泛型的{化,卻I如何带有泛型的 Java E序转化成一般的没有泛型?Java E序。这P读者对泛型的理解就不会仅仅局限在表面上了。考虑到多数读者仅仅是使用泛型Q因此本文ƈ未介l泛型在~译器中的具体实现。Java 中的泛型?C++ 中的模板表面上非常相|但实际上二者还是有很大区别的,本文最后简单介l了 Java 中的泛型?C++ 模板的主要区别?/p>
泛型概览
泛型本质上是提供cd?cd参数"Q它们也被称为参数化cdQparameterized typeQ或参量多态(parametric polymorphismQ。其实泛型思想q不?Java 最先引入的QC++ 中的模板是一个运用泛型的例子?/p>
GJQGeneric JavaQ是?Java 语言的一U扩展,是一U带有参数化cd?Java 语言。用 GJ ~写的程序看h和普通的 Java E序基本相同Q只不过多了一些参数化的类型同时少了一些类型{换。实际上Q这?GJ E序也是首先被{化成一般的不带泛型?Java E序后再q行处理的,~译器自动完成了?Generic Java 到普?Java 的翻译。具体的转化q程大致分ؓ以下几个部分Q?/p>
参数化cd中的cd参数"擦除"QerasureQ掉Q?
类型变量用"上限Qupper boundQ?取代Q通常情况下这些上限是 Object。这里的cd变量是指实例域,本地Ҏ域,Ҏ参数以及Ҏq回g用来标记cd信息?变量"Q例如:实例域中的变量声?A elem;
Q方法声?Node (A elem){};
Q其中,A 用来标记 elem 的类型,它就是类型变量?
dcd转换q插?桥方?Qbridge methodQ,以便覆盖QoverriddenQ可以正常的工作?
转化后的E序和没有引入泛型时E序员不得不手工完成转换的程序是非常一致的Q具体的转化q程会在后面介绍。GJ 保持了和 Java 语言以及 Java 虚拟机很好的兼容性,下面?GJ 的特点做一个简要的ȝ?/p>
cd安全?泛型的一个主要目标就是提?Java E序的类型安全。用泛型可以ɾ~译器知道变量的cd限制Q进而可以在更高E度上验证类型假设。如果没有泛型,那么cd的安全性主要由E序员来把握Q这昄不如带有泛型的程序安全性高?
消除强制cd转换。泛型可以消除源代码中的许多强制cd转换Q这样可以代码更加可读Qƈ减少出错的机会?
向后兼容。支持泛型的 Java ~译器(例如 JDK5.0 中的 JavacQ可以用来编译经q泛型扩充的 Java E序QGJ E序Q,但是现有的没有用泛型扩充的 Java E序仍然可以用这些编译器来编译?
层次清晰Q恪守规范。无~译的源E序是否使用泛型扩充Q编译生成的字节码均可被虚拟机接受ƈ执行。也是说不编译器的输入是 GJ E序Q还是一般的 Java E序Q经q编译后的字节码都严格遵循《Java 虚拟范》中对字节码的要求。可见,泛型主要是在~译器层面实现的Q它对于 Java 虚拟机是透明的?
性能收益。目前来Ԍ?GJ ~写的代码和一般的 Java 代码在效率上是非常接q的?但是׃泛型会给 Java ~译器和虚拟机带来更多的cd信息Q因此利用这些信息对 Java E序做进一步优化将成ؓ可能?
以上是泛型的一些主要特点,下面通过几个相关的例子来?Java 语言中的泛型q行说明?/p>
带有泛型的类
Z帮助大家更好地理?Java 语言中的泛型Q我们在q里先来Ҏ两段实现相同功能?GJ 代码?Java 代码。通过观察它们的不同点来对 Java 中的泛型有个M的把握,首先来分析一下不带泛型的 Java 代码Q程序如下:
1 interface Collection {
2 public void add (Object x);
3 public Iterator iterator ();
4 }
5
6 interface Iterator {
7 public Object next ();
8 public boolean hasNext ();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList implements Collection {
14
15 protected class Node {
16 Object elt;
17 Node next = null;
18 Node (Object elt) { this.elt = elt; }
19 }
20
21 protected Node head = null, tail = null;
22
23 public LinkedList () {}
24
25 public void add (Object elt) {
26 if (head == null) { head = new Node(elt); tail = head; }
27 else { tail.next = new Node(elt); tail = tail.next; }
28 }
29
30 public Iterator iterator () {
31
32 return new Iterator () {
33 protected Node ptr = head;
34 public boolean hasNext () { return ptr != null; }
35 public Object next () {
36 if (ptr != null) {
37 Object elt = ptr.elt; ptr = ptr.next; return elt;
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
38 } else throw new NoSuchElementException ();
39 }
40 };
41 }
42 }
接口 Collection
提供了两个方法,x加元素的Ҏ add(Object x)
Q见W?2 行,以及q回?Collection
?Iterator
实例的方?iterator()
Q见W?3 行?code>Iterator 接口也提供了两个ҎQ其一是判断是否有下一个元素的Ҏ hasNext()
Q见W?8 行,另外是q回下一个元素的Ҏ next()
Q见W?7 行?code>LinkedList cLҎ?Collection
的实玎ͼ它是一个含有一pd节点的链表,节点中的数据cd?ObjectQ这样就可以创徏Lcd的节点了Q比?ByteQ?String {等。上面这D늨序就是用没有泛型的传l的 Java 语言~写的代码。接下来我们分析一下传l的 Java 语言是如何用这个类的?/p>
代码如下Q?/p>
1 class Test {
2 public static void main (String[] args) {
3 // byte list
4 LinkedList xs = new LinkedList();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = (Byte)xs.iterator().next();
7 // string list
8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = (String)ys.iterator().next();
11 // string list list
12 LinkedList zss = new LinkedList();
13 zss.add(ys);
14 String z = (String)((LinkedList)zss.iterator().next()).iterator().next();
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
15 // string list treated as byte list
16 Byte w = (Byte)ys.iterator().next(); // run-time exception
17 }
18 }
从上面的E序我们可以看出Q当从一个链表中提取元素旉要进行类型{换,q些都要q序员昑ּ地完成。如果我们不心?String cd的链表中试图提取一?Byte 型的元素Q见W?15 到第 16 行的代码Q那么这会抛出一个运行时的异常。请注意Q上面这D늨序可以顺利地l过~译Q不会生Q何编译时的错误,因ؓ~译器ƈ不做cd查,q种查是在运行时q行的。不隑֏玎ͼ传统 Java 语言的这一~陷推迟了发现程序中错误的时_从Y件工E的角度来看Q这对Y件的开发是非常不利的。接下来Q我们讨Z下如何用 GJ 来实现同样功能的E序。源E序如下Q?/p>
1 interface Collection<A> {
2 public void add(A x);
3 public Iterator<A> iterator();
4 }
5
6 interface Iterator<A> {
7 public A next();
8 public boolean hasNext();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList<A> implements Collection<A> {
14 protected class Node {
15 A elt;
16 Node next = null;
17 Node (A elt) { this.elt = elt; }
18 }
19
20 protected Node head = null, tail = null;
21
22 public LinkedList () {}
23
24 public void add (A elt) {
25 if (head == null) { head = new Node(elt); tail = head; }
26 else { tail.next = new Node(elt); tail = tail.next; }
27 }
28
29 public Iterator<A> iterator () {
30 return new Iterator<A> () {
31 protected Node ptr = head;
32 public boolean hasNext () { return ptr != null; }
33 public A next () {
34 if (ptr != null) {
35 A elt = ptr.elt; ptr = ptr.next; return elt;
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
36 } else throw new NoSuchElementException ();
37 }
38 };
39 }
40 }
E序的功能ƈ没有M改变Q只是在实现方式上用了泛型技术。我们注意到上面E序的接口和cd带有一个类型参?AQ它被包含在一对尖括号Q?lt; >Q中Q见W?1Q? ?13 行,q种表示法遵循了 C++ 中模板的表示习惯。这部分E序和上面程序的主要区别是?Collection
, Iterator
, ?LinkedList
出现的地方均?Collection<A>
, Iterator<A>
, ?LinkedList<A>
来代替,当然Q第 22 行对构造函数的声明除外?/p>
下面再来分析一下在 GJ 中是如何对这个类q行操作的,E序如下Q?/p>
1 class Test {
2 public static void main (String [] args) {
3 // byte list
4 LinkedList<Byte> xs = new LinkedList<Byte>();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = xs.iterator().next();
7 // string list
8 LinkedList<String> ys = new LinkedList<String>();
9 ys.add("zero"); ys.add("one");
10 String y = ys.iterator().next();
11 // string list list
12 LinkedList<LinkedList<String>>zss=
newLinkedList<LinkedList<String>>();
13 zss.add(ys);
14 String z = zss.iterator().next().iterator().next();
15 // string list treated as byte list
16 Byte w = ys.iterator().next(); // compile-time error
17 }
18 }
在这里我们可以看刎ͼ有了泛型以后Q程序员q不需要进行显式的cd转换Q只要赋予一个参数化的类型即可,见第 4Q? ?12 行,q是非常方便的,同时也不会因为忘记进行类型{换而生错误。另外需要注意的是当试图从一个字W串cd的链表里提取Z个元素,然后它赋值给一?Byte 型的变量Ӟ见第 16 行,~译器将会在~译时报出错误,而不是由虚拟机在q行时报错,q是因ؓ~译器会在编译时d GJ 代码q行cd查,此种机制有利于尽早地发现q改正错误?/p>
cd参数的作用域是定义这个类型参数的整个c,但是不包括静态成员函数。这是因为当讉K同一个静态成员函数时Q同一个类的不同实例可能有不同的类型参敎ͼ所以上q提到的那个作用域不应该包括q些静态函敎ͼ否则׃引v混ؕ?/p>
泛型中的子类?/span>
?Java 语言中,我们可以某U类型的变量赋值给其父cd所对应的变量,例如QString ?Object 的子cdQ因此,我们可以?String cd的变量赋值给 Object cd的变量,甚至可以?String [ ] cd的变量(数组Q赋值给 Object [ ] cd的变量,?String [ ] ?Object [ ] 的子cd?/p>
上述情Ş恐怕已l深深地印在了广大读者的脑中Q对于泛型来Ԍ上述情Ş有所变化Q因此请q大读者务必引h意。ؓ了说明这U不同,我们q是先来分析一个小例子Q代码如下所C:
1 List<String> ls = new ArrayList<String>();
2 List<Object> lo = ls;
3 lo.add(new Integer());
4 String s = ls.get(0);
上述代码的第二行?List<String>
赋值给?List<Object>
Q按照以往的经验,q种赋值好像是正确的,因ؓ List<String>
应该?List<Object>
的子cd。这里需要特别注意的是,q种赋值在泛型当中是不允许的!List<String>
也不?List<Object>
的子cd?/p>
如果上述赋值是合理的,那么上面代码的第三行的操作将是可行的Q因?lo
?List<Object>
Q所以向其添?Integer cd的元素应该是完全合法的。读到此处,我们已经看到了第二行的这U赋值所潜在的危险,它破坏了泛型所带来的类型安全性?/p>
一般情况下Q如?A ?B 的子cdQC 是某个泛型的声明Q那?C<A>
q不?C<B>
的子cdQ我们也不能?C<A>
cd的变量赋值给 C<B>
cd的变量。这一点和我们以前接触的父子类型关pL很大的出入,因此误者务必引h意?/p>
泛化Ҏ和受限类型参?/span>
在这一部分我们讨论有x化方法(generic method Q和受限cd参数Qbounded type parameterQ的内容Q这是泛型中的两个重要概念,q是先来分析一下与此相关的代码?/p>
1 interface Comparable<A> {
2 public int compareTo(A that);
3 }
4
5 class Byte implements Comparable<Byte> {
6 private byte value;
7 public Byte(byte value) {this.value = value;}
8 public byte byteValue() {return value;}
9 public int compareTo(Byte that) {
10 return this.value - that.value;
11 }
12 }
13
14 class Collections {
15 public static <A implements Comparable<A>>
16 A max (Collection<A> xs) {
17 Iterator<A> xi = xs.iterator();
18 A w = xi.next();
19 while (xi.hasNext()) {
20 A x = xi.next();
21 if (w.compareTo(x) < 0) w = x;
22 }
23 return w;
24 }
25 }
q里定义了一个接?Comparable<A>
Q用来和 A cd的对象进行比较。类 Byte 实现了这个接口,q以它自׃为类型参敎ͼ因此Q它们自己就可以和自p行比较了?/p>
W?14 行到W?25 行的代码定义了一个类 Collections
Q这个类包含一个静态方?max(Collection<A> xs)
Q它用来在一个非I的 Collection
中寻找最大的元素q返回这个元素。这个方法的两个特点是它是一个泛化方法ƈ且有一个受限类型参数?/p>
之所以说它是泛化了的ҎQ是因ؓq个Ҏ可以应用到很多种cd上。当要将一个方法声明ؓ泛化ҎӞ我们只需要在q个Ҏ的返回类型(AQ之前加上一个类型参敎ͼAQ,q用括P< >Q将它括h。这里的cd参数QAQ是在方法被调用时自动实例化的。例如,假设对象 m 的类型是 Collection<Byte>
Q那么当使用下面的语句:
Byte x = Collections.max(m);
调用Ҏ max Ӟ该方法的参数 A 被推测?Byte?/p>
Ҏ上面讨论的内容,泛化Ҏ max 的完整声明应该是下面的Ş式:
< A > A max (Collection<A> xs) {
max 的方法体
}
但是Q我们见到的 max ?< A > 中还多了 "implements Comparable<A>" 一,q是什么呢Q这是我们下面要谈到?受限的类型参?。在上面的例子中Q类型参?A 是一个受限的的类型参敎ͼ因ؓ它不是泛指Q何类型,而是指那些自己和自己作比较的cd。例如参数可以被实例化ؓ ByteQ因为程序中?Byte implements Comparable<Byte>
的语句,参见W?5 行。这U限Ӟ或者说是范_通过如下的方式表C,"cd参数 implements 接口"Q或?"cd参数 extend c?Q上面程序中?Byte implements Comparable<Byte>"是一例?/p>
泛型的{?/span>
在前面的几部分内容当中,我们介绍了有x型的基础知识Q到此读者对 Java 中的泛型技术应该有了一定的了解Q接下来的这部分内容讨论有x型的转化Q即如何带有泛型的 Java 代码转化成一般的没有泛型 Java 代码。其实在前面的部分里Q我们或多或地也提C一些相关的内容Q下面再来详l地介绍一下?/p>
首先需要明的一Ҏ上面所讲的q种转化q程是由~译器(例如QJavacQ完成的Q虚拟机q不负责完成q一d。当~译器对带有泛型?Java 代码q行~译Ӟ它会L行类型检查和cd推断Q然后生成普通的不带泛型的字节码Q这U字节码可以被一般的Java虚拟机接收ƈ执行Q这U技术被UCؓ擦除QerasureQ?/p>
可见Q编译器可以在对源程序(带有泛型?Java 代码Q进行编译时使用泛型cd信息保证cd安全Q对大量如果没有泛型׃会去验证的类型安全约束进行验证,同时在生成的字节码当中,这些类型信息清除掉?/p>
对于不同的情况,擦除技术所执行?擦除"动作是不同的Q主要分Z下几U情况:
对于参数化类型,需要删除其中的cd参数Q例如,LinkedList<A>
被"擦除"?LinkedListQ?/code>
对于非参数化cdQ不作擦除,或者说用它自己来擦除自己,例如 String 被"擦除"?StringQ?
对于cd变量Q有关类型变量的说明请参?泛型概览"相关内容Q,要用它们的上限来对它们进行替换。多数情况下q些上限?ObjectQ但是也有例外,后面的部分将会对此进行介l?
除此之外Q还需要注意的一ҎQ在某些情况下,擦除技术需要引入类型{换(castQ,q些情况主要包括Q?/p>
情况 1. Ҏ的返回类型是cd参数Q?/p>
情况 2. 在访问数据域Ӟ域的cd是一个类型参数?/p>
例如在本?带有泛型的类"一节的最后,我们l出了一D|试程序,一?Test cR这个类包含以下几行代码Q?/p>
8 LinkedList<String> ys = new LinkedList<String>();
9 ys.add("zero"); ys.add("one");
10 String y = ys.iterator().next();
q部分代码{换后变成了如下的代码:
8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = (String)ys.iterator().next();
W?10 行的代码q行了类型{换,q是因ؓ在调?next()
ҎӞ~译器发现该Ҏ的返回值类型是cd参数 AQ请参见Ҏ?next()
的定义)Q因此根据上面提到的情况 1Q需要进行类型{换?/p>
上面介绍了泛型{化中的擦除技术,接下来,我们讨论一下泛型{化中的另外一个重要问题-Q桥ҎQbridge methodQ?/p>
Java 是一U面向对象的语言Q因此覆盖(overriddenQ是其中的一w要技术。覆盖能够正?工作"的前提是Ҏ名和Ҏ的参数类型及个数完全匚wQ参数的序也应一_Qؓ了满要求,~译器在泛型转化中引入了桥方法(bridge methodQ。接下来Q我们通过一个例子来分析一下桥Ҏ在泛型{化中所L作用。在本文"泛化Ҏ和受限类型参?一节所l出的代码中Q第 9 行到W?11 行的E序如下所C:
9 public int compareTo(Byte that) {
10 return this.value - that.value;
11 }
q部分代码经q{化,变成了下面的样子:
9 public int compareTo(Byte that) {
10 return this.value - that.value;
11 }
12 public int compareTo(Object that){
13 return this.compareTo((Byte)that);
14 }
W?12 行的Ҏ compareTo(Object that)
是一个桥ҎQ在q里引入q个Ҏ是ؓ了保证覆盖能够正常的发生。我们在前面提到q,覆盖必须保证Ҏ名和参数的类型及数目完全匚wQ在q里通过引入q个"?卛_辑ֈq一目的Q由q个"?q行cd转换Qƈ调用W?9 行参数类型ؓ Byte 的方?compareTo(Byte that)Q需要注意的一Ҏq里?"Object" 也ƈ不一定是完全匚w的类型,但由于它?Java 语言中类层次l构的根Q所以这里用 "Object" 可以接受其他Mcd的参数?/p>
Ҏ面向对象的基本概念,我们知道Q重载(overloadingQ允许桥Ҏ和原来的Ҏ׃n同一个方法名Q正如上面例子所昄的那P因此桥方法的引入是完全合法的。一般情况下Q当一个类实现了一个参数化的接口或是承了一个参数化的类Ӟ需要引入桥Ҏ?/p>
到此Q我们对泛型中的子类型,带有泛型的类Q泛化方法,受限cd参数以及泛型的{化进行了要的介绍Q下面部分将l合q些技术对前面提到的例子进行一下ȝQ以便能够帮助读者更深刻更全面地理解泛型?/p>
首先来分析一下本文提到的那个 Collection
的例子。这里先是定义了两个接口 Collection
?Iterator
Q然后又定义了一个对接口 Collection
的一个实?LinkedList
。根据上面所介绍的对泛型的{化过E,q段代码转化后的 Java E序为:
1 interface Collection {
2 public void add (Object x);
3 public Iterator iterator ();
4 }
5
6 interface Iterator {
7 public Object next ();
8 public boolean hasNext ();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList implements Collection {
14
15 protected class Node {
16 Object elt;
17 Node next = null;
18 Node (Object elt) { this.elt = elt; }
19 }
20
21 protected Node head = null, tail = null;
22
23 public LinkedList () {}
24
25 public void add (Object elt) {
26 if (head == null) {
27 head = new Node(elt); tail = head;
28 } else {
29 tail.next = new Node(elt); tail = tail.next;
30 }
31 }
32
33 public Iterator iterator () {
34 return new Iterator () {
35 protected Node ptr = head;
36 public boolean hasNext () { return ptr != null; }
37 public Object next () {
38 if (ptr != null) {
39 Object elt = ptr.elt; ptr = ptr.next; return elt;
40 } else {
41 throw new NoSuchElementException ();
42 }
43 }
44 };
45 }
46 }
通过分析上述代码Q我们不隑֏玎ͼ所有参数化cd Collection, Iterator ?LinkedList 中的cd参数 "A" 全都被擦除了。另外,剩下的类型变?"A" 都用其上限进行了替换Q这里的上限?ObjectQ见黑体字标出的部分Q这是{化的关键部分?/p>
下面我们分析一下在介绍有关泛化ҎQgeneric methodQ和受限cd参数Qbounded type parameterQ时丄那个例子Q该D?GJ 代码l过转换后的{h Java E序如下所C:
1 interface Comparable {
2 public int compareTo(Object that);
3 }
4
5 class Byte implements Comparable {
6 private byte value;
7 public Byte(byte value) {this.value = value;}
8 public byte byteValue(){return value;}
9 public int compareTo(Byte that) {
10 return this.value - that.value;
11 }
12 public int compareTo(Object that){
13 return this.compareTo((Byte)that);
14 }
15 }
16
17 class Collections {
18 public static Comparable max(Collection xs){
19 Iterator xi = xs.iterator();
20 Comparable w = (Comparable)xi.next();
21 while (xi.hasNext()) {
22 Comparable x = (Comparable)xi.next();
23 if (w.compareTo(x) < 0) w = x;
23 }
24 return w;
25 }
26 }
同样误者注意黑体字标出的部分,q些关键Ҏ们在前面已经介绍q了Q故不赘q。唯一需要注意的一点就是第 18Q?0Q?2 行出现的Comparable 。在泛型转化中,cd变量应该用其上限来替换,一般情况下q些上限?"Object"Q但是当遇到受限的类型参数时Q这个上限就不再?"Object" 了,~译器会用限制这些类型参数的cd来替换它Q上qC码就用了?A q行限制的类?"Comparable" 来替?A?/p>
桥方法的引入Qؓ解决覆盖问题带来了方便,但是q种Ҏq存在一些问题,例如下面q段代码Q?/p>
1 interface Iterator<A> {
2 public boolean hasNext ();
3 public A next ();
4 }
5 class Interval implements Iterator<Integer> {
6 private int i;
7 private int n;
8 public Interval (int l, int u) { i = l; n = u; }
9 public boolean hasNext () { return (i <= n); }
10 public Integer next () { return new Integer(i++); }
11 }
Ҏ以上所讲的内容Q这部分代码转换后的 Java E序应该是如下这个样子:
1 interface Iterator {
2
3 public boolean hasNext ();
4 public Object next ();
5
6 }
7
8 class Interval implements Iterator {
9
10 private int i;
11 private int n;
12 public Interval (int l, int u) { i = l; n = u; }
13 public boolean hasNext () { return (i <= n); }
14 public Integer next%1% () { return new Integer(i++); }
15 // bridge
16 public Object next%2%() { return next%1%(); }
17
18 }
怿有些读者已l发Cq里的问题,q不是一D合法的 Java 源程序,因ؓW?14 行和W?16 行的两个 next() 有相同的参数Q无法加以区分。代码中?%1% ?%2% 是ؓ了区分而h为加入的Qƈ?GJ 转化的结果?/p>
不过Q这q不是什么太大的问题Q因?Java 虚拟机可以区分这两个 next() ҎQ也是_?Java 源程序的角度来看Q上q程序是不正的Q但是当~译成字节码ӞJVM 可以对两?next() Ҏq行识别。这是因为,?JVM 中,Ҏ定义时所使用的方法签名包括方法的q回cdQ这样一来,只要 GJ ~译出的字节码符合Java字节码的规范卛_Q这也正好说明了 GJ ?JVM 中字节码规范要求的一致性!
最后,值得一提的是,JDK 5.0 除了在编译器层面?Java 中的泛型q行了支持,Java 的类库ؓ支持泛型也做了相应地调整Q例如,集合框架中所有的标准集合接口都进行了泛型化,同时Q集合接口的实现也都q行了相应地泛型化?/p>
Java 中的泛型?C++ 模板的比?/span>
GJ E序的语法在表面上与 C++ 中的模板非常cMQ但是二者之间有着本质的区别?/p>
首先QJava 语言中的泛型不能接受基本cd作ؓcd参数――它只能接受引用cd。这意味着可以定义 List<Integer>Q但是不可以定义 List<int>?
其次Q在 C++ 模板中,~译器用提供的cd参数来扩充模板,因此Qؓ List<A> 生成?C++ 代码不同于ؓ List<B> 生成的代码,List<A> ?List<B> 实际上是两个不同的类。?Java 中的泛型则以不同的方式实玎ͼ~译器仅仅对q些cd参数q行擦除和替换。类?ArrayList<Integer> ?ArrayList<String> 的对象共享相同的c,q且只存在一?ArrayList cR?/p>
ȝ
本文通过一些示例从基本原理Q重要概念,关键技术,以及怼技术比较等多个角度?Java 语言中的泛型技术进行了介绍Q希望这U介l方法能够帮助读者更好地理解和用泛型。本文主要针对广大的 Java 语言使用者,在介l了泛型的基本概念后Q重点介l了比较底层的泛型{化技术,旨在帮助读者更加深d掌握泛型Q笔者相信这部分内容可以使读者避免对泛型理解的表面化Q也所谓知其然更知其所以然?/p>
参考资?
关于作?/span>
]]> 泛型 http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305968.htmlammay ammay Mon, 14 Dec 2009 14:51:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305968.html http://www.aygfsteel.com/ammayjxf/comments/305968.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/14/305968.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/305968.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/305968.html Jdk1.5中的新特?--泛型 (详细?
本来只{载了个链接,和一个简单的使用E序Q但昨天不小心看到有人批判jdk1.5Q先说java要强制{型不好的问题没解冻I
容器不能攑֟cd不好Q接着说泛型没用。而恰恰Jdk1.5中解决了q些问题Q所以感叹之余,把这文章改一下,详细的说说泛型?/p>
一QJava中的泛型Q?br />
在Java中能使用到泛型的多是容器c?如各Ulist map setQ因为Java是单根承,所以容器里边可以放?br />
内容是Q何ObjectQ所以从意义上讲原本的设计才是泛型。但用过Java的h是否感觉每次转型很麻烦呢Q?br />
而且会有些错误,比如一个容器内攑օ了异质对象,强制转型的时候会出现cast异常。而这中错误在~译器是
无从发现的。所以jdk1.5中提供了泛型Q这个泛型其实是向c++靠拢?好,我们先看几个实例再细说原理?/p>
二,泛型的用?(多个实例)
1 实例A
2 ArrayList < String > strList = new ArrayList < String > ();
3 strList.add( " 1 " );
4 strList.add( " 2 " );
5 strList.add( " 3 " );
6 // 关键点(1Q?nbsp;注意下边q行Q没有强制{?/span>
7 String str = strList.get( 1 );
8 // 关键点(2Q然後我们加入,q个时候你会发现编译器报错Q错误在~译器被发现Q错误当然是发现的越早越?/span>
9 strList.add( new Object());
1 实例B
2 ArrayList < Integer > iList = new ArrayList < Integer > ();
3 // 关键点(3Q?nbsp;注意直接把整数放入了集合中,而没有用Integer包裹
4 iList.add( 1 );
5 iList.add( 2 );
6 iList.add( 3 );
7 // 关键点(4Q同L接取出就是int
8 int num = iList.get( 1 );
1 实例C
2 // 关键点(5Q展CZ下key-value的时候要怎么写,同时key和value也可以是基本cd了?/span>
3 HashMap < Integer,Integer > map = new HashMap < Integer,Integer > ();
4 map.put( 1 , 11 );
5 map.put( 2 , 22 );
6 map.put( 3 , 33 );
7 int inum = map.get( 1 );
8
三,看完了实例了Q详l来说说Z么吧
首先jdk1.5中的泛型Q第一个解决的问题Q就是Java中很多不必要的强制{型了Q具体的实现,我们以ArrayList
Z,下边是ArrayList中的片断代码:
1 ArrayListcȝ定义Q这里加入了 < E >
2 public class ArrayList < E > extends AbstractList < E >
3 implements List < E > , RandomAccess, Cloneable, java.io.Serializable
4
5 // getҎ,q回不再是Object 而是E
6 public E get( int index) {
7 RangeCheck(index);
8 return elementData[index];
9 }
10 // addҎ,参数不再是Object 而是E
11 public boolean add(E o) {
12 ensureCapacity(size + 1 ); // Increments modCount!!
13 elementData[size ++ ] = o;
14 return true ;
15 }
16
四,Boxing 和UnBoxing
看到上边的关键点Q?Q和Q?Q是否感觉惊奇呢Q因为Java中烦人的除了强制转型Q另一个就是基cd?br />
攑օ容器的时候要包装Q取Zq要转回。Jdk1.5中解决了q个问题.如上边的使用Ҏ
五,泛型的生命周期(使用注意事项Q?br />
如果我们试着把ArrayList<String> list的内容序列化Q然後再d出来Q在使用的过E中会发现出错,
Z么呢Q用Streamd一下回来的数据Q你会发?lt;String>不见了,list变成了普通的ArrayListQ而不?br />
参数化型别的ArrayList了,Z么会q样?Q见下边的比?/p>
六,C++的泛型和Java的泛?br />
在泛型的实现上,C++和Java有着很大的不同,
Java是擦拭法实现?br />
C++是膨胀法实现的
因ؓJava原本实现是泛型的,现在加入型别Q其实是"H化",所以采用擦拭法Q在实现上,其实是封装了原本?br />
ArrayList,q样的话Q对于下边这些情况,Java的实现类只有一个?br />
1 ArrayList < Integer > .; public class ArrayList
2 ArrayList< String > ..; -- 同上 --
3 ArrayList< Double > ..; -- 同上 --
4 而C++ 采用的是膨胀?对于上边的三U情况实际是每一U型别都对应一个实玎ͼ实现cL多个
5 list< int > li; class list; // int 版本
6 list < string > ls; class list; // string 版本
7 list < double > ld; class list; // double 版本
q就造成了,在序列化后,Java不能分清楚原来的ArrayList?br />
ArrayList<Integer>q是ArrayList
七,题外话,在很多东西的实现上C++和Java有很多不?br />
例如q算W的问题i=i++问题Q?a href="http://www.aygfsteel.com/dreamstone/archive/2006/11/04/79058.html">详细看这?/a>
例如在C++中能很好实现的double-checked locking单态模式,在Java中几乎很隑֮?详细看这?/a>
q有是上边提到的泛型实C?br />
八,Jdk 1.5加入了不新东西Q有些能很大的提高开发质量,例如Jdk1.4 QJdk.15中StringBuffer的不?br />
因ؓ??转入1?不久Q所以慢慢会发一些在1?的用过E中发现的东ѝ?br />
最后,我们q可以自己写cMArrayListq样的泛型类Q至于如何自定义泛型c,泛型Ҏ请参见候捷先生的文?br />
]]> xquery http://www.aygfsteel.com/ammayjxf/archive/2009/12/03/304707.htmlammay ammay Thu, 03 Dec 2009 14:43:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/12/03/304707.html http://www.aygfsteel.com/ammayjxf/comments/304707.html http://www.aygfsteel.com/ammayjxf/archive/2009/12/03/304707.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/304707.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/304707.html ]]>mysql triger http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303824.htmlammay ammay Thu, 26 Nov 2009 14:56:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303824.html http://www.aygfsteel.com/ammayjxf/comments/303824.html http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303824.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/303824.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/303824.html /Files/ammayjxf/MySQL.pdf
]]> mysql 触发?/title> http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303818.htmlammay ammay Thu, 26 Nov 2009 14:31:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303818.html http://www.aygfsteel.com/ammayjxf/comments/303818.html http://www.aygfsteel.com/ammayjxf/archive/2009/11/26/303818.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/303818.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/303818.html 我们在MySQL 5.0中包含对触发器的支持是由于以下原?
MySQL早期版本的用户长期有需要触发器的要求?br />
我们曄许诺支持所有ANSI标准的特性?br />
您可以用它来检查或预防坏的数据q入数据库?br />
您可以改变或者取消INSERT, UPDATE以及DELETE语句?br />
您可以在一个会话中监视数据改变的动作。在q里我假定大安读过"MySQL新特?丛书的第一?-"MySQL存储q程"Q那么大安应该知道MySQLx存储q程和函敎ͼ那是很重要的知识Q因为在触发器中你可以用在函数中用的语句。特别D个例子:
复合语句(BEGIN / END)是合法的.
控ӞFlow-of-controlQ语?IF, CASE, WHILE, LOOP, WHILE, REPEAT, LEAVE,ITERATE)也是合法?
变量声明(DECLARE)以及指派(SET)是合法的.
允许条g声明.
异常处理声明也是允许?
但是在这里要C函数有受限条?不能在函C讉K?
因此在函C使用以下语句是非法的?br />
ALTER 'CACHE INDEX' CALL COMMIT CREATE DELETE
DROP 'FLUSH PRIVILEGES' GRANT INSERT KILL
LOCK OPTIMIZE REPAIR REPLACE REVOKE
ROLLBACK SAVEPOINT 'SELECT FROM table'
'SET system variable' 'SET TRANSACTION'
SHOW 'START TRANSACTION' TRUNCATE UPDATE
在触发器中也有完全一L限制.触发器相对而言比较斎ͼ因此会有QbugsQ缺?所以我在这里给大家警告Q就像我在存储过E书中所说那?不要在含有重要数据的数据库中使用q个触发器,如果需要的话在一些以试为目的的数据库上使用Q同时在你对表创发器时确认这些数据库是默认的?/p>
语法
1. 语法Q命名规?/p>
CREATE TRIGGER <触发器名U?gt; <--
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE }
ON <表名U?gt;
FOR EACH ROW
<触发器SQL语句>
触发器必L名字Q最?4个字W,可能后面会附有分隔符.它和MySQL中其他对象的命名方式基本相象.
q里我有个习惯:是用表的名字+'_'Q触发器cd的羃?因此如果是表t26Q触发器是在事gUPDATEQ参考下面的点(2Q和Q?Q)之前QBEFOREQ的Q那么它的名字就是t26_bu?
2. 语法Q触发时?/p>
CREATE TRIGGER <触发器名U?gt;
{ BEFORE | AFTER } <--
{ INSERT | UPDATE | DELETE }
ON <表名U?gt;
FOR EACH ROW
<触发的SQL语句>
触发器有执行的时间设|:可以讄Z件发生前或后?/p>
3. 语法Q事?/p>
CREATE TRIGGER <触发器名U?gt;
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE } <--
ON <表名U?gt;
FOR EACH ROW
<触发的SQL语句>
同样也能讑֮触发的事Ӟ它们可以在执行insert、update或delete的过E中触发?br />
4. 语法Q表
CREATE TRIGGER <触发器名U?gt;
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE }
ON <表名U?gt; <--
FOR EACH ROW
<触发的SQL语句>
触发器是属于某一个表?当在q个表上执行插入?br />
更新或删除操作的时候就D触发器的Ȁz?
我们不能l同一张表的同一个事件安排两个触发器?/p>
5. 语法Q( 步长Q触发间?/p>
CREATE TRIGGER <触发器名U?gt;
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE }
ON <表名U?gt;
FOR EACH ROW <--
<触发的SQL语句>
触发器的执行间隔QFOR EACH ROW子句通知触发?br />
每隔一行执行一ơ动作,而不是对整个表执行一ơ?/p>
6. 语法Q语?/p>
CREATE TRIGGER <触发器名U?gt;
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE }
ON <表名U?gt;
FOR EACH ROW
<触发的SQL语句> <--
触发器包含所要触发的SQL语句Q这里的语句可以是Q何合法的语句Q?br />
包括复合语句Q但是这里的语句受的限制和函数的一栗?br />
Privileges权限
你必L有相当大的权限才能创发器QCREATE TRIGGERQ?br />
如果你已l是Root用户Q那么就_了。这跟SQL的标准有所不同?/p>
因此在下一个版本的MySQL中,
你完全有可能看到有一U叫做CREATE TRIGGER的新权限?br />
然后通过q样的方法赋予:
GRANT CREATE TRIGGER ON <表名U?gt; TO <用户或用户列?gt;;
也可以通过q样收回权限Q?br />
REVOKE CREATE TRIGGER ON <表名U?gt; FROM <用户或用户列?gt;;
关于旧的和新创徏的列的标?/p>
在触发器的SQL语句中,你可以关联表中的L列。但你不能仅使用列的名称L识,那会使系l淆,因ؓ那里可能会有列的新名Q这可能正是你要修改的,你的动作可能正是要修改列名)Q还有列的旧名存在。因此你必须用这L语法来标识: "NEW . column_name"或?OLD . column_name".q样在技术上处理QNEW | OLD . column_nameQ新和旧的列名属于创Zq渡变量Q?transition variables"Q?
对于INSERT语句,只有NEW是合法的Q对于DELETE语句Q只有OLD才合法;而UPDATE语句可以在和NEW以及OLD同时使用。下面是一个UPDATE中同时用NEW和OLD的例子?
CREATE TRIGGER t21_au
BEFORE UPDATE ON t22
FOR EACH ROW
BEGIN
SET @old = OLD . s1;
SET @new = NEW.s1;
END;//
现在如果t21表中的s1列的值是55Q那么执行了
"UPDATE t21 SET s1 = s1 + 1"之后@old的g变成55Q?br />
而@new的值将会变?6。Example of CREATE and INSERT CREATE和INSERT的例?br />
创徏有触发器的表
q里所有的例程中我都假定大家的分隔W已l设|成//QDELIMITER //Q?br />
CREATE TABLE t22 (s1 INTEGER)//
CREATE TRIGGER t22_bi
BEFORE INSERT ON t22
FOR EACH ROW
BEGIN
SET @x = 'Trigger was activated!';
SET NEW.s1 = 55;
END;//
在最开始我创徏了一个名字ؓt22的表Q然后在表t22上创Z一个触发器t22_biQ当我们要向表中的行插入Ӟ触发器就会被Ȁz,执行s1列的值改?5的动作?/p>
使用触发器执行插入动?
mysql> INSERT INTO t22 VALUES (1)//
让我们看如果向表t2中插入一行数据触发器对应的表会怎么P q里的插入的动作是很常见的,我们不需要触发器的权限来执行它。甚至不需要知道是否有触发器关联?
mysql> SELECT @x, t22.* FROM t22//
+------------------------+------+
| @x | s1 |
+------------------------+------+
| Trigger was activated! | 55 |
+------------------------+------+
1 row in set (0.00 sec)
大家可以看到INSERT动作之后的结果,和我们预期的一Px标记被改动了Q同时这里插入的数据不是我们开始输入的插入数据Q而是触发器自q数据?
"check"完整性约束例?br />
什么是"check"U束
在标准的SQL语言中,我们可以在(CREATE TABLEQ创的过E中使用"CHECK (condition)"Q?br />
例如Q?br />
CREATE TABLE t25
(s1 INT, s2 CHAR(5), PRIMARY KEY (s1),
CHECK (LEFT(s2,1)='A'))
ENGINE=INNODB;
q里CHECK的意思是"当s2列的最左边的字W不?A'Ӟinsert和update语句都会非法"QMySQL的视图不支持CHECKQ我个h是很希望它能支持的。但如果你很需要在表中使用q样的功能,我徏议大家用触发器来实现?
CREATE TABLE t25
(s1 INT, s2 CHAR(5),
PRIMARY KEY (s1))
ENGINE=INNODB//
CREATE TRIGGER t25_bi
BEFORE INSERT ON t25
FOR EACH ROW
IF LEFT(NEW.s2,1)<>'A' THEN SET NEW.s1=0; END IF;//
CREATE TRIGGER t25_bu
BEFORE UPDATE ON t25
FOR EACH ROW
IF LEFT(NEW.s2,1)<>'A' THEN SET NEW.s1=0; END IF;//
我只需要用BEFORE INSERT和BEFORE UPDATE语句p了,删除了触发器不会对表有媄响,同时AFTER的触发器也不能修改NEW的过E变量(transition variablesQ。ؓ了激z触发器Q我执行了向表中的行插入s1Q?的数据,之后只要执行W合LEFT(s2,1) <> 'A'条g的动作都会失败:
INSERT INTO t25 VALUES (0,'a') /* priming the pump */ //
INSERT INTO t25 VALUES (5,'b') /* gets error '23000' */ //
Don't Believe The Old MySQL Manual
本文来自CSDN博客Q{载请标明出处Qhttp://blog.csdn.net/crazy_rain/archive/2007/07/05/1680128.aspx
]]>ant工程配置 http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302978.htmlammay ammay Thu, 19 Nov 2009 13:16:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302978.html http://www.aygfsteel.com/ammayjxf/comments/302978.html http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302978.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/302978.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/302978.html
2008 - 09 - 25
l于把ANT搞定了,现在发布一个通用的ANT的build.xml文gQ以备后用?
]]> ant 的用说?/title> http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302977.htmlammay ammay Thu, 19 Nov 2009 13:09:00 GMT http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302977.html http://www.aygfsteel.com/ammayjxf/comments/302977.html http://www.aygfsteel.com/ammayjxf/archive/2009/11/19/302977.html#Feedback 0 http://www.aygfsteel.com/ammayjxf/comments/commentRss/302977.html http://www.aygfsteel.com/ammayjxf/services/trackbacks/302977.html
2009 - 04 - 10
关键? ant 的用说?/strong>
>>>>>>>>>>>声明
1.目的是学习,备忘Q共享。个达能力有限,看不懂,表达错误的,见谅
>>>>>>>>>>>ant是什?
1.专业一点说是构建工P是协助开发h员管理工E的工具
2.通俗一点说是懒人工P帮助我们做一些重复的力_
3.举个例子Q发布一个web工程Q你会如何做
a.web工程打包
b.停止web工程
c.上传web工程
d.重启web工程
通常Q你可能会用IDE打包Q然后登陆到服务器,把WEB工程shutdownQ再用上传工具传war包,然后再重?
有了antQ你不用q么累了Q运行一个build.xmlOK?
>>>>>>>>>>ant的特?
1.跨^収ͼ因ؓ用JAVA写的
2.功能强大(q是一个口?Q扩展性比较好(q倒是实话Q但代h是你得找支持的jar?
3.上手ҎQ因法简?
>>>>>>>>>>ant的应?eclipse?
1.传说牛h都不用IDEQ我用eclipseQ这玩意儿还是免费的Q真?
2.exlipse3.0以后应该都内嵌antQ找扄Q?eclipse\plugins\org.apache.ant_1.6.5
3.新徏工程Q工E根目录下新建build.xml
4.试试好不用,q行一?
<?xml version= "1.0" encoding= "UTF-8" ?>
<project name= "WebTest" basedir= "E:\work_ccats2\WebTest" default = "main" >
<target name= "main" depends= "test" description= "Main target" >
</target>
<target name= "test" description= "test" >
<echo> task start </echo>
</target>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project name="WebTest" basedir="E:\work_ccats2\WebTest" default="main">
<target name="main" depends="test" description="Main target">
</target>
<target name="test" description="test">
<echo> task start </echo>
</target>
</project>
5.ftp功能Q?
a.下蝲commons-net-1.4.0.jar jakarta-oro-2.0.8.jar NetComponents.jar optional.jar
b.上面q些jarQ如果你的ant或jdk版本高,有的没有Q但如果不是Q劝你都?
c.eclipse? Window->Preferance->Ant->Runtime->Ant Home Entries->自己加上jar
6.ssh:
a.下蝲jsch-0.1.41.jar
b.后面同[5]
7.l一个build.xml样例
<?xml version= "1.0" encoding= "UTF-8" ?>
<project name= "WebTest" basedir= "E:\work_ccats2\WebTest" default = "main" >
<target name= "main" depends= "taskover" description= "Main target" >
</target>
<!-- ***************************变量定义开?************************ -->
<!--工程名称-->
<property name= "project.name" value= "WebTest" />
<!--打包war文g存放的位|?->
<property name= "war.dir" location= "E:\war" />
<!--服务器IP-->
<property name= "hostIP" value= "10.4.116.212" />
<!--服务器OS帐户-->
<property name= "userID" value= "esb" />
<!--服务器OS密码-->
<property name= "password" value= "esb" />
<!--服务器tomcat路径-->
<property name= "hostHome" value= "/home/esb" />
<!-- *************************变量定义l束************************* -->
<!-- 试 -->
<target name= "test" description= "test" >
<echo> task start </echo>
</target>
<!-- 打包war文g -->
<target name= "pgWar" depends= "test" description= "Package application as a war" >
<mkdir dir= "${war.dir}" />
<war destfile= "${war.dir}/${project.name}.war" webxml= "web.xml" >
<fileset dir= "${basedir}" >
</fileset>
</war>
</target>
<target name= "remote-tomcat-stop" depends= "pgWar" >
<sshexec host= "${hostIP}" username= "${userID}" password= "${password}"
command= "ps -ef|grep tomcat55|grep -v grep |awk '{print $2}'|xargs -n1 kill -9;ls;rm -rf /home/esb/tomcat55/webapps/WebTest*"
trust= "true" />
<sleep seconds= "5" />
</target>
<!-- 上传文g-->
<target name= "uploadFile" depends= "remote-tomcat-stop" >
<ftp server= "${hostIP}" remotedir= "${hostHome}/tomcat55/webapps" userid= "${userID}"
password= "${password}" depends= "yes" >
<fileset dir= "${war.dir}" >
<include name= "**/*.war" />
</fileset>
</ftp>
</target>
<!-- q程重启tomcat-->
<target name= "remote-tomcat-start" depends= "uploadFile" >
<sshexec host= "${hostIP}" username= "${userID}" password= "${password}"
command= "cd /home/esb/tomcat55/bin;JAVA_HOME=/home/esb/jdk1.5/jdk1.5.0_06 export JAVA_HOME;nohup sh startup.sh;ls"
trust= "true" />
<sleep seconds= "5" />
</target>
<target name= "taskover" depends= "remote-tomcat-start" >
<echo> task over </echo>
</target>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project name="WebTest" basedir="E:\work_ccats2\WebTest" default="main">
<target name="main" depends="taskover" description="Main target">
</target>
<!-- ***************************变量定义开?************************ -->
<!--工程名称-->
<property name="project.name" value="WebTest"/>
<!--打包war文g存放的位|?->
<property name="war.dir" location="E:\war"/>
<!--服务器IP-->
<property name="hostIP" value="10.4.116.212" />
<!--服务器OS帐户-->
<property name="userID" value="esb" />
<!--服务器OS密码-->
<property name="password" value="esb" />
<!--服务器tomcat路径-->
<property name="hostHome" value="/home/esb" />
<!-- *************************变量定义l束************************* -->
<!-- 试 -->
<target name="test" description="test">
<echo> task start </echo>
</target>
<!-- 打包war文g -->
<target name="pgWar" depends="test" description="Package application as a war">
<mkdir dir="${war.dir}" />
<war destfile="${war.dir}/${project.name}.war" webxml="web.xml">
<fileset dir="${basedir}">
</fileset>
</war>
</target>
<target name="remote-tomcat-stop" depends="pgWar" >
<sshexec host="${hostIP}" username="${userID}" password="${password}"
command="ps -ef|grep tomcat55|grep -v grep |awk '{print $2}'|xargs -n1 kill -9;ls;rm -rf /home/esb/tomcat55/webapps/WebTest*"
trust="true" />
<sleep seconds="5"/>
</target>
<!-- 上传文g-->
<target name="uploadFile" depends="remote-tomcat-stop" >
<ftp server="${hostIP}" remotedir="${hostHome}/tomcat55/webapps" userid="${userID}"
password="${password}" depends="yes" >
<fileset dir="${war.dir}">
<include name="**/*.war"/>
</fileset>
</ftp>
</target>
<!-- q程重启tomcat-->
<target name="remote-tomcat-start" depends="uploadFile" >
<sshexec host="${hostIP}" username="${userID}" password="${password}"
command="cd /home/esb/tomcat55/bin;JAVA_HOME=/home/esb/jdk1.5/jdk1.5.0_06 export JAVA_HOME;nohup sh startup.sh;ls"
trust="true" />
<sleep seconds="5"/>
</target>
<target name="taskover" depends="remote-tomcat-start" >
<echo> task over </echo>
</target>
</project>
>>>>>>>>>>>>>命o行下的ant应用
1.环境变量设一?
ANT_HOME= $ant路径
PATH = %ANT_HOME%;%ANT_HOME%\bin
2.试
输入antQ看有反应没
]]>
վ֩ģ壺
|
ɽ |
˷ |
ˮ |
Ƕ |
|
Ӫɽ |
ԭ |
Դ |
² |
㺺 |
ʡ |
ƽ |
ɽ |
ɳ |
˾ |
|
¡ |
ݳ |
|
|
|
|
ع |
|
|
|
|
ʯ |
|
|
|
|
¸ |
|
|
|
â |
г |
|
² |