泛型是jdk1.5中一个非常的重要的新特性,它涉及的内容很多,其中有高级算法之类,需要很高的数学技巧才能掌握。不过我们用到的基本都是常用的地方,不需要掌握那么高级的内容,只需要知道泛型在一般的程序中有哪些常见的应用即可。
最常见的泛型就是用在集合上了,如果使用集合的时候,我们不加泛型,编译器会给出安全提示。虽然仍然可以通过编译,但是用泛型更方便,之后就不需要强制转化了。下面就用ArrayList集合来做说明:
ArrayList<E>类定义和ArrayList<Integer>类引用中的术语:
ArrayList<E> -----> 泛型类型
E --------> 类型变量或类型参数
ArrayList<Integer> -----> 参数化的类型
Integer --------> 类型参数的实例或实际类型参数
<> -------------> typeof
ArrayList -------->原始类型
泛型限定:
<>中写入的类型即已经限定了可以使用的类型,它不仅仅用于集合上,还可以用在方法上、类上等,如:
public <T> void show(T t){……}
即可对接收的参数进行类型限定
public <A extends Collecton>A getCollection(Class<T> collection){……}
这里返回值是A类型,那么A是什么呢?用泛型来说明A就是Collection的子类,这样就方便使用许多了。
当泛型定义类上的时候,就是在类中所有的方法上使用了该泛型,如:
class Demo<T>{……}
定义后所有方法均视为对T使用,如:Demo<T> d = new Demo<T>();
泛型的作用很广,这了结合反射写出一个例子:
package cn.itcast.day2;
import java.util.ArrayList;
public class GenericDemo {
public static void main(String[] args) throws Exception {
//定义一个用Integer泛型限定的集合
ArrayList<Integer> al1 = new ArrayList<Integer>();
al1.add(123);
// al1.add("abc"); 不能加入String类型,因为对al做了泛型限定,只能加入Integer类型
//定义一个用String泛型限定的集合
ArrayList<String> al2 = new ArrayList<String>();
al2.add("sss");
// al2.add(111); 不能加入Integer类型,因为对al做了泛型限定,只能加入String类型
//比较al1与al2的Class类是否相同,结果发现是相同的,那么说明泛型只是作用在编译时
System.out.println(al1.getClass()==al2.getClass());
//既然泛型只作用于编译时,那么用反射穿过编译器,直接往ArrayList类中加入泛型限定外的类型会何如呢
//通过反射往al1中加入String类型 “gg”
al1.getClass().getMethod("add", Object.class).invoke(al1, "gg");
System.out.println(al1.get(1));
//通过编译,并打印出gg
}
}
这个例子很好的说明了泛型的作用,可以限制不是所限定的类型加入集合,但是只在编译时作用。当用到反射的方式调用add方法时就能通过编译,这更能帮助我们理解泛型的作用时机。
类的加载器:
当class文件从硬盘写入到内存是,需要将硬盘上的二进制数据转成字节码文件的对象,在加载到内存中,这就需要用到类加载器了。类加载器的作用就是将class文件加载到内存中,以实现对其的调用。这是Java中一种特有的机制,所有的类都是通过加载器加载进内存中的,这种方式保证了安全性。
类加载器同时也是一种类,那么它也需要被类加载器加载,这么一直推算下来,总一个有所加载器的父加载器,那么它是如何加载进内存中的呢?这个类就是BootStrap类,它是用C++写的一段程序,不需要使用Java中的类加载器机制,存在于jvm中,当jvm启动的时候,就会自动加载。
用代码验证类加载器机制:
package cn.itcast.day2;
public class LoadClassDemo1 {
public static void main(String[] args) {
//得到这个类的类加载器
ClassLoader loader = LoadClassDemo1.class.getClassLoader();
//如果父类不为空,就打印父类加载器的名字
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
//打印最父类的加载器
System.out.println(loader);
}
}
打印结果为:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
可以看出来,加载器一共有三个,分别是:AppClassLoader,ExtClassLoader,BootStrap(用C++写的,打印为null)
当我们使用任何一个已有类的时候,都是使用这3个加载器加载的,那么这3个加载器到底分别负责哪些类的加载呢?它们的加载顺序是怎样的呢?
先想一个问题,如果我们写一个String类,然后调用这个类,到底是使用jdk中的String类还是我们自己写的String类呢,这就涉及到了类加载器的问题。很显然,会使用jdk中的String类,这便是加载器的委托机制。
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
//接收class文件的字节数组,返回类加载器
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// 获取需要加载的class文件的byte数组
…
}
}
这就是一个最简单的类加载器,如果需要,我们可以给类加载器加上密钥,只要当使用我们自己的类加载器才能将此类加载进内存。