开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。我们一般通过“容器”来容纳和管理数据。
事实上,数组就是一种容器,可以在其中放置对象或基本类型数据。数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足对于“管理和组织数据的需求”,所以需要一种更强大、更灵活、容量随时可扩的容器来装载对象。 这就是容器,也叫集合(Collection)。以下是容器的接口层次结构图:
为了更好的理解容器,我们需要先了解一下泛型。
一、泛型(Generics)1.泛型简介1.1 泛型的基本概念:
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。 可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
参数化类型,通俗来讲就是:1. 把类型当作是参数一样传递。2. <数据类型> 只能是引用类型。
1.2泛型的好处
在不使用泛型的情况下,可以使用 Object 类型来实现任意的参数类型,但是在使用时需要强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:1.代码可读性更好【不用强制转换】。2.程序更加安全【只要编译时期没有警告,运行时期就不会出现 ClassCastException 异常】
1.3 类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。泛型主要用于编译阶段,编译后生成的字节码 class 文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。 类型参数在编译后会被替换成 Object,运行时虚拟机并不知道泛型。泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
2.泛型的使用2.1 泛型的定义
泛型字符可以是任何标识符,一般采用这几个标记:E、T、K、V、N、?。
泛型标记 | 对应单词 | 说明 |
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的Java类 |
K | Key | 表示键,例如Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的Java类型 |
2.2 泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如:<T>、<T,K,V>
语法结构:
public class 类名<泛型表示符号>{
}
示例:
package cn.pxy.Generics;
public class Generic<T> {
private T flag;
public void setFlag(T flag) {
this.flag=flag;
}
public T getFlag() {
return this.flag;
}
}
package cn.pxy.generics;
public class Test {
public static void main(String[] args) {
Generic<String> generic=new Generic<>();
generic.setFlag("admin");
String flag=generic.getFlag();
System.out.println(flag);
Generic<Integer> generic1=new Generic<>();
generic1.setFlag(100);
Integer flag1=generic1.getFlag();
System.out.println(flag1);
}
}
运行结果:
2.3泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
语法结构
public interface 接口名<泛型表示符号> {
}
示例:
package cn.pxy.generics;
//接口
public interface Igeneric<T> {
T getName(T name);
}
package cn.pxy.generics;
//接口实现类
public class IgenericImpl implements Igeneric<String>{
public String getName (String name) {
return name;
}
}
package cn.pxy.generics;
//测试
public class Test2 {
public static void main(String[] args) {
IgenericImpl igeneric=new IgenericImpl();
String name=igeneric.getName("liqi");
System.out.println(name);
Igeneric<String> igeneric1=new IgenericImpl();
String name1=igeneric1.getName("pxyxss");
System.out.println(name1);
}
}
运行结果:
2.4 泛型方法
泛型类中所定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接收不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>。定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
2.4.1非静态方法:
语法结构:
public<泛型表示符号> void getName(泛型表示符号 name){
}
public<泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
示例:
package cn.pxy.generics;
public class MethodGeneric {
public <T> void setName(T name) {
System.out.println(name);
}
public <T> T getName(T name) {
return name;
}
}
package cn.pxy.generics;
public class Test3 {
public static void main(String[] args) {
MethodGeneric methodGeneric=new MethodGeneric();
methodGeneric.setName("胖咸鱼");
methodGeneric.setName(123456);
MethodGeneric methodGeneric2=new MethodGeneric();
String name=methodGeneric2.getName("pxy");
Integer name1=methodGeneric2.getName(123);
System.out.println(name1);
System.out.println(name);
}
}
运行结果: