千锋教育Java入门全套视频教程(java核心技术,适合java零基础,Java

泛型
1、泛型简述
JDK1.4以前:
1、 装入集合的数据都会被当作Object对象来存放,从而失去了自己的实际类型。
2、 从集合中取出元素时,需要进行强制类型转换。效率低,容易产生错误。
从JDK1.5开始:
sun公司推出了泛型来解决上述问题
1、在定义一个集合时就指定集合存储的对象的数据类型
Collection<String> coll = new ArrayList<String>();
3、 存入数据时只能存入泛型指定的数据类型,如果存入其他数据类型,则会编译错误,避数据存入时的问题。
coll.add(new Integer(1)); //**编译错误!**
3、从集合中取出元素时,无需转型了。
如:String str1 = it.next();
优点
1、简化集合的使用
2、增强代码的可读性和稳定性
2、泛型的使用
使用场合
1.非静态的成员属性类型 2.非静态方法的形参类型(包括非静态成员方法和构造器) 3.非静态的成员方法的返回值类型
泛型标识
一般来说泛型标识是可以任意设置的
T :代表一般的任何类。 E :代表 Element 元素的意思,或者 Exception 异常的意思。 K :代表 Key 的意思。 V :代表 Value 的意思,通常与 K 一起配合使用。 S :代表 Subtype 的意思,文章后面部分会讲解示意。
泛型接口
public interface IFan<T> { // t 这个参数的数据类型为 T, T 的类型由外部传入 default void fun(T t){ System.out.println(); } }
定义一个接口 IA 继承了 泛型接口 IFan,在 接口 IA 定义时必须确定泛型接口 IFan中的类型参数。
定义一个类 Sub 实现了 泛型接口 IUsb,在 类 Sub 定义时需要确定泛型接口 IFan中的类型参数。
public interface IFan<T> {} //1、确定接口的实际类型 class Sub implements IFan<String>{} interface Test extends IFan<String>{} //2、在子类中也添加泛型,并且和父类的泛型相同 class Sub<T> implements IFan<T>{} interface Test<T> extends IFan<T>{}
因为子类会继承父类的方法和属性,调用子类时如果没有确定类型或者子类没有添加泛型,则无法将参数类型传给IFan接口,就找不到类型来替代T
泛型类
public class Fan<T> { // t 这个成员变量的数据类型为 T, T 的类型由外部传入 private T t; // 泛型构造方法形参 t 的类型也为 T,T 的类型由外部传入 public Fan(T t) { this.t = t; } // 泛型方法 getT 的返回值类型为 T,T 的类型由外部指定 public T getT(){ return t; } }
泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数
- 泛型类中的类型参数的确定是在创建泛型类对象的时候(例如 ArrayList< Integer >)。
- 而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。
泛型类不只接受一个类型参数,它还可以接受多个类型参数。
public class Fan <E,T>{}
泛型限定方法
当在一个方法签名中的返回值前面声明了一个 < T > 时,该方法就被声明为一个泛型方法。< T >表明该方法声明了一个类型参数 T,并且这个类型参数 T 只能在该方法中使用。当然,泛型方法中也可以使用泛型类中定义的泛型参数。
public <类型参数> 返回类型 方法名(类型参数 变量名) { ... }
只有在方法签名中声明了< T >的方法才是泛型方法,仅使用了泛型类定义的类型参数的方法并不是泛型方法。
public class Test<U> { // 该方法只是使用了泛型类定义的类型参数,不是泛型方法 public void testMethod(U u){ System.out.println(u); } // <T> 真正声明了下面的方法是一个泛型方法 public <T> T testMethod1(T t){ return t; } }
泛型方法中可以同时声明多个类型参数。
public class TestMethod<U> { public <T, S> T testMethod(T t, S s) { return null; } }
泛型方法中也可以使用泛型类中定义的泛型参数。
public class TestMethod<U> { public <T> U testMethod(T t, U u) { return u; } }
特别注意的是:泛型类中定义的类型参数和泛型方法中定义的类型参数是相互独立的,它们一点关系都没有。
public class Test<T> { public void testMethod(T t) { System.out.println(t); } public <T> T testMethod1(T t) { return t; } }
上面代码中,Test< T > 是泛型类,testMethod() 是泛型类中的普通方法,其使用的类型参数是与泛型类中定义的类型参数。
而 testMethod1() 是一个泛型方法,他使用的类型参数是方法签名中声明的类型参数。
虽然泛型类中定义的类型参数标识和泛型方法中定义的类型参数标识都为< T >,但它们彼此之间是相互独立的。也就是说,泛型方法始终以自己声明的类型参数为准。
- < T >表明该方法声明了一个类型参数 T,并且这个类型参数 T 只能在该方法中使用。
- 为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。
- 与泛型类的类型参数定义一样,此处泛型方法中的 T 可以写为
任意标识
,常见的如 T、E、K、V 等形式的参数常用于表示泛型。
泛型方法签名中声明的类型参数只能在该方法里使用,而泛型接口、泛型类中声明的类型参数则可以在整个接口、类中使用。当调用泛型方法时,根据外部传入的实际对象的数据类型,编译器就可以判断出类型参数 T所代表的具体数据类型。
3、泛型可擦除性
java文件源码
List<Integer> list = new ArrayList<>();
反编译后的java代码
java.util.List list = new ArrayList();
明显的把泛型<Integet>给擦除了
既然java的泛型仅存在于源码,编译后就被擦除,那泛型是怎么起作用的呢
装箱
编译前:
list.add(1); list.add(2); list.add(3); list.add(4);
编译后:
List list = new ArrayList(); list.add(Integer.valueOf(1)); list.add(Integer.valueOf(2)); list.add(Integer.valueOf(3)); list.add(Integer.valueOf(4));
明显的用了valueOf方法装箱
泛型不安全
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); fun(list,"str"); System.out.println(list); } //定义一个方法,用来实现add方法 //但是接收参数时不加泛型,因为在编译后泛型会被擦除,所以这种操作是允许的 public static void fun(List list,Object o){ list.add(o); }
结果:
[1, 2, 3, 4, str]
混进去一个String类型的数据