(一)泛型
1.泛型的本质是参数化类型,通俗的讲就是创建一个用类型作为参数的类:
规则:a、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
b、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
c、泛型的类型参数可以有多个。
d、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
e、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(java.lang.String);
- List<Integer> list = new ArrayList<Integer>(); //泛型
- list.add(new Integer(10));
- Integer a = list.get(0);
- Map<String,Integer> m = new HashMap<String,Integer>();
- m.put("a",1); //自动封装机制
- m.put("b",2);
- m.put("c",3);
- int b = m.get("b"); //自动解封机制
List<Integer> list = new ArrayList<Integer>(); //泛型
list.add(new Integer(10));
Integer a = list.get(0);
Map<String,Integer> m = new HashMap<String,Integer>();
m.put("a",1); //自动封装机制
m.put("b",2);
m.put("c",3);
int b = m.get("b"); //自动解封机制
2.自定义泛型类
- public class Gen<T> {
- private T ob; //定义泛型成员变量
- public Gen(T ob) {
- this.ob = ob;
- }
- public T getOb() {
- return ob;
- }
- public void setOb(T ob) {
- this.ob = ob;
- }
- public void showTyep() {
- System.out.println("T的实际类型是: " + ob.getClass().getName());
- }
- }
- public class GenDemo {
- public static void main(String[] args){
- //定义泛型类Gen的一个Integer版本
- Gen<Integer> intOb=new Gen<Integer>(88);
- intOb.showTyep();
- int i= intOb.getOb();
- System.out.println("value= " + i);
- System.out.println("----------------------------------");
- //定义泛型类Gen的一个String版本
- Gen<String> strOb=new Gen<String>("Hello Gen!");
- strOb.showTyep();
- String s=strOb.getOb();
- System.out.println("value= " + s);
- }
- }
public class Gen<T> {
private T ob; //定义泛型成员变量
public Gen(T ob) {
this.ob = ob;
}
public T getOb() {
return ob;
}
public void setOb(T ob) {
this.ob = ob;
}
public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo {
public static void main(String[] args){
//定义泛型类Gen的一个Integer版本
Gen<Integer> intOb=new Gen<Integer>(88);
intOb.showTyep();
int i= intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义泛型类Gen的一个String版本
Gen<String> strOb=new Gen<String>("Hello Gen!");
strOb.showTyep();
String s=strOb.getOb();
System.out.println("value= " + s);
}
}
在Java 5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。
强制类型转换很麻烦,我还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,比如将“Hello Generics!”字符串强制转换为Double,那么编译的时候不会报错,可是运行的时候就挂了。那有没有不强制转换的办法----有,改用 Java5泛型来实现。
3.用extends和super来控制泛型的类型范围
class A <B extends List>{ };
表示B只能是List的子类或子接口A<String> a= new A<String>();就是错误的。
如果用super的话就是表示父类或者父接口了。
4.通配符
- void print(List<Integer> a) {
- for (Integer i : a) { //表示从a中不断的取出对象定义成 i 然后进行循环
- System.out.println(i);
- }
- }
- void print(List<?> a) { // 类型通配符,用?表示不知道List里放的什么类型
- for (Integer i : a) {
- System.out.println(i);
- }
- }
void print(List<Integer> a) {
for (Integer i : a) { //表示从a中不断的取出对象定义成 i 然后进行循环
System.out.println(i);
}
}
void print(List<?> a) { // 类型通配符,用?表示不知道List里放的什么类型
for (Integer i : a) {
System.out.println(i);
}
}
看一个有趣的例子:
- public class CollectionGenFooDemo {
- public static void main(String args[]) {
- CollectionGenFoo<ArrayList> listFoo = null;
- listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
- //出错了,不让这么干。
- // CollectionGenFoo<Collection> listFoo = null;
- // listFoo=new CollectionGenFoo<ArrayList>(new ArrayList());
- System.out.println("实例化成功!");
- }
- }
public class CollectionGenFooDemo {
public static void main(String args[]) {
CollectionGenFoo<ArrayList> listFoo = null;
listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
//出错了,不让这么干。
// CollectionGenFoo<Collection> listFoo = null;
// listFoo=new CollectionGenFoo<ArrayList>(new ArrayList());
System.out.println("实例化成功!");
}
}
当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为<T extends Collection>这么定义类型的时候,就限定了构造此类实例的时候T是确定的一个类型,这个类型实现了Collection接口,但是实现 Collection接口的类很多很多,如果针对每一种都要写出具体的子类类型,那也太麻烦了,我干脆还不如用Object通用一下。别急,泛型针对这种情况还有更好的解决方案,那就是“通配符泛型”。
为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口。那么上面实现的方式可以写为:
- public class CollectionGenFooDemo {
- public static void main(String args[]) {
- CollectionGenFoo<ArrayList> listFoo = null;
- listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
- //现在不会出错了
- CollectionGenFoo<? extends Collection> listFoo1 = null;
- listFoo=new CollectionGenFoo<ArrayList>(new ArrayList());
- System.out.println("实例化成功!");
- }
- }
public class CollectionGenFooDemo {
public static void main(String args[]) {
CollectionGenFoo<ArrayList> listFoo = null;
listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
//现在不会出错了
CollectionGenFoo<? extends Collection> listFoo1 = null;
listFoo=new CollectionGenFoo<ArrayList>(new ArrayList());
System.out.println("实例化成功!");
}
}
注意:
1)、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。
2)、通配符泛型不单可以向下限制,如<? extends Collection>,还可以向上限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
3)、泛型类定义可以有多个泛型参数,中间用逗号隔开,还可以定义泛型接口,泛型方法。这些都泛型类中泛型的使用规则类似。
(二)增强的for循环
- void print(List<?> a) {
- for (Integer i : a) {
- System.out.println(i);
- }
- }
void print(List<?> a) {
for (Integer i : a) {
System.out.println(i);
}
}
(三) 可变参数(Vararg)
实现可变参数,传统上一般是采用“利用一个数组来包裹要传递的实参”的做法来应付.
J2SE 1.5中提供了Varargs机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
1.定义实参个数可变的方法
在一个形参的“类型”与“参数名”之间加上三个连续的“.”
- private static int sumUp(int... values) {
- }
private static int sumUp(int... values) {
}
注意,只有最后一个形参才能被定义成“能和不确定个实参相匹配”的。因此,一个方法里只能有一个这样的形参。另外,如果这个方法还有其它的形参,要把它们放到前面的位置上。
编译器会在背地里把这最后一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,表明这是个实参个数可变的方法。
实参个数可变的方法的秘密形态
- private static int sumUp(int[] values) {
- }
private static int sumUp(int[] values) {
}
由于存在着这样的转化,所以不能再为这个类定义一个和转化后的方法签名一致的方法。
会导致编译错误的组合
- private static int sumUp(int... values) {
- }
- private static int sumUp(int[] values) {
- }
private static int sumUp(int... values) {
}
private static int sumUp(int[] values) {
}
2.调用实参个数可变的方法
可以传递若干个实参
sumUp(1, 3, 5, 7);
在背地里,编译器会把这种调用过程转化为用“数组包裹实参”的形式:
偷偷出现的数组创建
sumUp(new int[]{1, 2, 3, 4});
另外,这里说的“不确定个”也包括零个,所以这样的调用也是合乎情理的:也可以传递零个实参
sumUp();
这种调用方法被编译器秘密转化之后的效果,则等同于这样:零实参对应空数组
sumUp(new int[]{});
注意这时传递过去的是一个空数组,而不是null。这样就可以采取统一的形式来处理,而不必检测到底属于哪种情况。
3.是数组?不是数组?
尽管在背地里,编译器会把能匹配不确定个实参的形参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示“能匹配不确定个实参的形参”和“数组形参”完全没有差异。
一个明显的差异是,如果按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会导致一个“cannot be applied to”的编译错误。
- private static void testOverloading(int[] i) {
- System.out.println("A");
- }
- public static void main(String[] args) {
- testOverloading(1, 2, 3);//编译出错
- }
private static void testOverloading(int[] i) {
System.out.println("A");
}
public static void main(String[] args) {
testOverloading(1, 2, 3);//编译出错
}
由于这一原因,不能在调用只支持用数组包裹实参的方法的时候(例如在不是专门为J2SE 1.5设计第三方类库中遗留的那些),直接采用这种简明的调用方式。
如果不能修改原来的类,为要调用的方法增加参数个数可变的版本,而又想采用这种简明的调用方式,那么可以借助“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法来近似的达到目的。
4.当个数可变的实参遇到泛型
J2SE 1.5中新增了“泛型”的机制,可以在一定条件下把一个类型参数化。例如,可以在编写一个类的时候,把一个方法的形参的类型用一个标识符(如T)来代表,至于这个标识符到底表示什么类型,则在生成这个类的实例的时候再行指定。这一机制可以用来提供更充分的代码重用和更严格的编译时类型检查。
不过泛型机制却不能和个数可变的形参配合使用。如果把一个能和不确定个实参相匹配的形参的类型,用一个标识符来代表,那么编译器会给出一个“generic array creation”的错误。
当Varargs遇上泛型:
- private static <T> void testVarargs(T... args) {//编译出错
- }
private static <T> void testVarargs(T... args) {//编译出错
}
造成这个现象的原因在于J2SE 1.5中的泛型机制的一个内在约束——不能拿用标识符来代表的类型来创建这一类型的实例。在出现支持没有了这个约束的Java版本之前,对于这个问题,基本没有太好的解决办法。
不过,传统的“用数组包裹”的做法,并不受这个约束的限制。
可以编译的变通做法
- private static <T> void testVarargs(T[] args) {
- for (int i = 0; i < args.length; i++) {
- System.out.println(args[i]);
- }
- }
private static <T> void testVarargs(T[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
5.重载中的选择问题
实参个数固定的版本优先
- public class OverloadingSampleA {
- public static void main(String[] args) {
- testOverloading(1);//打印出A
- testOverloading(1, 2);//打印出B
- testOverloading(1, 2, 3);//打印出C
- }
- private static void testOverloading(int i) {
- System.out.println("A");
- }
- private static void testOverloading(int i, int j) {
- System.out.println("B");
- }
- private static void testOverloading(int i, int... more) {
- System.out.println("C");
- }
- }
public class OverloadingSampleA {
public static void main(String[] args) {
testOverloading(1);//打印出A
testOverloading(1, 2);//打印出B
testOverloading(1, 2, 3);//打印出C
}
private static void testOverloading(int i) {
System.out.println("A");
}
private static void testOverloading(int i, int j) {
System.out.println("B");
}
private static void testOverloading(int i, int... more) {
System.out.println("C");
}
}
如果在编译器看来,同时有多个方法具有相同的优先权,它就会陷入无法就到底调用哪个方法作出一个选择的状态。在这样的时候,它就会产生一个 “reference to 被调用的方法名 is ambiguous”的编译错误,并耐心的等候作了一些修改,足以免除它的迷惑的新源代码的到来。
在引入了Varargs机制之后,这种可能导致迷惑的情况,又增加了一些。例如现在可能会有两个版本都能匹配,在其它方面也如出一辙,而且都是实参个数可变的冲突发生。
左右都不是,为难了编译器
- public class OverloadingSampleB {
- public static void main(String[] args) {
- testOverloading(1, 2, 3);//编译出错
- }
- private static void testOverloading(Object... args) {
- }
- private static void testOverloading(Object o, Object... args) {
- }
- }
public class OverloadingSampleB {
public static void main(String[] args) {
testOverloading(1, 2, 3);//编译出错
}
private static void testOverloading(Object... args) {
}
private static void testOverloading(Object o, Object... args) {
}
}
另外,因为J2SE 1.5中有“Autoboxing/Auto-Unboxing”机制的存在,所以还可能发生两个版本都能匹配,而且都是实参个数可变,其它方面也一模一样,只是一个能接受的实参是基本类型,而另一个能接受的实参是包裹类的冲突发生。
- //Autoboxing/Auto-Unboxing带来的新问题
- public class OverloadingSampleC {
- public static void main(String[] args) {
- /* 编译出错 */
- testOverloading(1, 2);
- /* 还是编译出错 */
- testOverloading(new Integer(1), new Integer(2));
- }
- private static void testOverloading(int... args) {
- }
- private static void testOverloading(Integer... args) {
- }
- }
//Autoboxing/Auto-Unboxing带来的新问题
public class OverloadingSampleC {
public static void main(String[] args) {
/* 编译出错 */
testOverloading(1, 2);
/* 还是编译出错 */
testOverloading(new Integer(1), new Integer(2));
}
private static void testOverloading(int... args) {
}
private static void testOverloading(Integer... args) {
}
}
(四) 枚举(Enum)
- public class program {
- enum Color {
- Red,
- White,
- Yellow
- }
- public static void main(String[] args) {
- Color[] cs = Color.values();
- for(Color c : cs){
- System.out.println(c);
- }
- }
- }
public class program {
enum Color {
Red,
White,
Yellow
}
public static void main(String[] args) {
Color[] cs = Color.values();
for(Color c : cs){
System.out.println(c);
}
}
}
(五) 静态导入(Static Import)
使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出他们的类名。
- import static java.lang.Math.*;
- public class program {
- public static void main(String[] args) {
- int i = (int)(random()*10);
- System.out.println(i);
- }
- }
import static java.lang.Math.*;
public class program {
public static void main(String[] args) {
int i = (int)(random()*10);
System.out.println(i);
}
}
(六) 格式化输入输出
- public class program {
- public static void main(String[] args) { System.out.printf("Integer=%d String=%s", 13, "abc");
- }
- }
public class program {
public static void main(String[] args) { System.out.printf("Integer=%d String=%s", 13, "abc");
}
}
格式化的格式是 "%[argument_index$][flags][width][.precision]conversion"。
(七) 标注(Annotation)
标注(Annotation)就是在程序中加上一些说明,容器在调用这些类,方法的时候就知道怎么处理了。
java.lang.annotation
接口 Annotation
所有已知实现类:
Deprecated, Documented, Inherited, Override, Retention, SuppressWarnings, Target
--------------------------------------------------------------------------------
public interface Annotation所有 annotation 类型都要扩展的公共接口。注意,手动扩展该公共接口的接口不 定义 annotation 类型。还要注意此接口本身不定义 annotation 类型。
例如下面是在seasar框架中使用的例子
- @StrutsActionForm(name = "ownerForm") //can replace the struts config file
- public class OwnerForm{}