Java泛型学习笔记
2015-09-17 19:31
519 查看
1、什么是泛型
我们知道一般的类和方法在定义的时候,其成员变量、参数、返回值等都必须指明具体的类型,要么是基本的数据类型,要么是自定义的类。如果需要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。而泛型正是为了解决这个问题而产生的,其含义即“可以应用于许多许多类型”,它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用于更多的类型上。2、泛型类与泛型方法
泛型类
public class Container<T> { private T a; public Container(T a) { this.a = a;} public void set(T a) { this.a = a;} public T get() { return a;} public static void main(String[] args) { Container<String> c = new Container<String>("Hello World!"); System.out.println(c.get()); // c.set(250); 无法编译 } }
在Container的定义中,对象a的类型是T,即a可以被申明为任何类型(T),但是系统在编译Container的时候,实际把a当成的是一个Object对象。在我们运行程序实例化Container后,T就被限定为String类型,当我们想传入一个整型的数据时编译器就报错。
泛型方法
public class MultiArgs { /**泛型方法**/ public static <T> List<T> f(T...args) { List<T> result = new ArrayList<T>(); for(T item:args) { result.add(item); } return result; } public static void main(String[] args) { System.out.println(f("a","b","d")); System.out.println(f(1,3,5,100,23)); } }
定义泛型方法,只需要将泛型参数列表置于返回值之前,这里MultiArgs并不是泛型的,只是包含一个泛型方法。泛型方法f()传入不定数量、不定类型的参数,返回一个ArrayList。这里有一个需要注意的地方,在使用泛型类时,必须在创建对象的时候指定类型参数的值,如上面Container实例化时指定参数类型为String类型;而使用泛型方法的时候,通常不必指定参数类型,如f(“a”,”b”,”d”),编译会为我们找出具体的类型,这称为类型参数推断。
3、擦除与边界
泛型是自Java SE5引入的,其引入的最主要目的是为了创造容器类,来确保我们存入容器的对象都是同一种类型,因此Java的泛型相对于C++的泛型来说有很大的局限性,我们通过下面的代码来说明:/***C++中的泛型***/ class People { void name() { cout << "Tom" << endl; } } template<class T> class Test { T obj; public: Test(T t) { obj = t;} void test() { obj.f(); } }; int main() { People p; Test<People> temp(p); //实例化 temp.test(); } /***Java中的泛型***/ class People { void name() { System.out.println("Kimi"); } } public class Test<T> { private T obj; public Test(T t) { obj = t;} void test() { //obj.f() 编译无法通过,因为obj是Object对象,并没有f()方法 } public static void main(String[] args) { People p = new People(); Test<People> temp = new Test<People>(p); p.test(); } }
可以看到在Java代码中,由于在代码在编译期进行类型检查,编译器无法获知obj是否含有test()方法,因此编译无法通过;而在C++代码中,C++在实例化这个模版时进行检查,Test被实例化的一刻,它看到People拥有一个方法test(),因而程序可以正常运行。
Java泛型是使用“擦除”来实现的,这意味着你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。由于有了擦除,Java编译器无法将test()必须能够在obj上调用f()这一需求映射到People必须有f()这一事实上。也正是因为擦除,像下面这些在运行时需要知道确切类型信息的操作都将无法工作:
public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) { if(arg instanceof T) {} //Error T var = new T(); //Error T[] array = new T[SIZE]; //Error T[] array = (T)new Object[SIZE]; //Unchecked warning } }
一种折衷的办法是给泛型参数T指定边界,如
public class Test<T extends People> { private T obj; public Test(T t) { obj = t;} void test() { obj.f() } public static void main(String[] args) { People p = new People; Test<People> temp = new Test<People>(p); p.test(); } }
在指定参数T继承至People后,代码中的obj对象将不仅仅是Object对象,而是People对象,而People对象必定包含f()方法,因此编译就可以通过了。指定边界其实变相的降低了代码的通用性,这实际就不能算是真正的泛型了,只是一种多态的运用,相当于在凡是需要说明类型的地方,我们都使用基类进行说明。
如果既想保留代码的泛化能力,又能够实现f()的调用,利用Java反射机制也是可以实现的
public class Test<T > { private T obj; public Test(T t) { obj = t;} void test() { Class c = obj.getClass(); try{ Method m = c.getMethod("f"); m.invoke(obj); }catch(Exception e) { System.out.println(c.getSimpleName + " doesn't have f()") } } public static void main(String[] args) { People p = new People; Test<People> temp = new Test<People>(p); p.test(); } }
相关文章推荐
- NetBeans IDE中实现页面跳转的 MainFrame类
- 把别的项目导入myeclipse中出现的错误
- javaweb9
- javaweb7
- javaweb6
- NetBeans IDE中实现页面跳转的 LoginFrame类
- javaweb3
- Spring xml注入实例
- Java多线程基础(一)
- javaweb2
- 使用commons-beanutils把javabean转换成Map日期date不能自定义格式
- javaweb
- Java中Properties类的操作
- java计算文件的MD5值
- javase2
- javase
- 大龄屌丝自学笔记--Java零基础到菜鸟--019
- leetcode:151Reverse Words in a String java实现
- 如何利用 JConsole观察分析Java程序的运行,进行排错调优
- java学习之Map集合