泛型
概述
概念:
- 泛型,是JDK5引入的特性,提供了编译时类型安全监测机制,该机制允许在编译时检测到非法的类型
- 本质 = 参数化类型,就是所操作的数据类型被指定为一个参数
- 提到参数 就会想到 定义方法是的形参,然后调用此方法时传递的是实参
- 参数化类型 = 将类型由原来具体的类型参数化,然后在使用/调用时传入具体的类型
- 参数化类型的参数 可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口。
泛型定义格式:
<类型> :
指定一种类型的格式,这里的类型可以看成是形参
<类型1,类型2....>:
指定多种类型的格式,多种类型之间用逗号隔开
将来具体调用时给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
泛型好处:
①把运行时期的问题提前到了编译期间
比如:当list集合原本存储的是String类型,之后存储integer类型的时候,不使用泛型编译阶段不会报错,而运行时会报错;使用泛型之后,报错会提前出现在编译阶段。
②避免了强制类型转换
比如:使用迭代器遍历集合,集合不使用泛型,则迭代器获得对象类型是Object对象,假设原本的对象是String类型,可能就需要进行强制类型转换,将Object类型转换为String类型;集合使用泛型之后,迭代器对象自动返回泛型类型,不需要再进行强制转换。
案例:
// 217-FDemo
public class FDemo {
public static void main(String[] args) {
// 创建Collection集合,不适用泛型
Collection c = new ArrayList();
// 在创建集合的时候不使用泛型< E >,这样在使用集合.add方法的时候,添加元素默认为Object类型,因为泛型只能是引用数据类型,所有引用数据类型都是Object类型
c.add("hello"); // 属于String - Object 向上转型
c.add("java");
c.add("world");
// 问题1:没有使用泛型,当集合之前存储String类型,之后又存储Integer类型的时候,编译不会报错,但是运行会报错
// c.add(100);//ClassCastException
// 使用迭代器 遍历集合
Iterator it = c.iterator();
while(it.hasNext()){
// 按照默认的Object类型 可以执行
// Object o = it.next();
// 但就是想使用String类型的时候,需要强制转化 可以执行
String s = (String)it.next();
System.out.println(s);
}
System.out.println("-------------");
// 问题2:如果集合使用泛型,那么迭代器的部分也会使用泛型,这样就避免使用强制类型转换。
Collection<String> cs = new ArrayList<String>();
cs.add("just do it");
cs.add("i can do it");
Iterator<String> it2 = cs.iterator();
while(it2.hasNext()){
String s = it2.next();
System.out.println(s);
}
}
}
泛型类
泛型类的定义格式:
修饰符 class 类名< 类型T >{
// 成员变量
private T t;
// 成员变量的get/set方法
public T getT(){}
public void setT(T t){
this.t = t;
}
// 成员方法使用 泛型方法
public <T> 返回值类型 方法名(T t){
System.out.println(t);
}
}
例子: public class Generic< T >{}
注意:此处的T是任意标识,常见的有T/E/K/V等形式的参数用于表示泛型
案例:
//217-test4
测试类:
public class GenericDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("汪苏泷");
System.out.println(s.getName());
Teacher t = new Teacher();
t.setAge(45);
// t.setAge("100"); // 此时接收字符串类型参数会报错。
System.out.println(t.getAge());
// 问题:使用setXXX方法只能接收一种类型的参数,不能接收多种类型的参数
// 上述问题通过定义泛型类解决
System.out.println("-----------------");
GenericClass<String> g = new GenericClass<String>();
g.setT("汪苏泷");
System.out.println(g.getT());
GenericClass<Integer> g2 = new GenericClass<Integer>();
g2.setT(100);
System.out.println(g2.getT());
// 实现两个不同对象不同类型参数的输入,采用一个泛型类即可。
}
}
泛型类:
public class GenericClass < T > {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
泛型方法
泛型方法定义格式:
修饰符 < 类型 > 返回值类型 方法名(类型 变量名){}
例子:public < T > void show(T t){}
解决问题
当一个类中的方法需要输出多种参数类型变量的时候,
解决方式有两种:
①泛型类: 也就是说通过泛型类 去设置方法的参数类型,当方法需要多个参数的时候,就对应需要创建多个对象,造成内存资源的浪费;
②泛型方法:在定义方法的时候再指定泛型,不需要创建多个对象。
案例:
// 217-test5-GenericDemo
测试类:
public class GenericDemo {
public static void main(String[] args) {
GenericClass g = new GenericClass();
g.show("string");
g.show(10);
g.show(true);
// 问题1:如果在类中 创建多个show方法,实现不同类型参数的 show方法重载,这样会造成对象调用方法受限制,只能使用类中定义的方法。
// 解决方式:使用泛型类
/* System.out.println("---------------");
GenericClass<String> g2 = new GenericClass<String>();
g2.show("string");
GenericClass<Integer> g3 = new GenericClass<Integer>();
g3.show(100);*/
// 问题2:采用泛型类的方式,会在定义对象的时候明确泛型,这样也重复定义了对象。
System.out.println("------------");
GenericClass g2 = new GenericClass();
g2.show("string");
g2.show(125);
g2.show(true);
}
}
类:
/*public class GenericClass {
// 定义show方法,实现三种类型数据的输出
public void show(String s){
System.out.println(s);
}
public void show(boolean b){
System.out.println(b);
}
public void show(int i){
System.out.println(i);
}
}*/
// 问题1 : 使用泛型类
//public class GenericClass<T>{
// public void show(T t){
// System.out.println(t);
// }
//}
// 问题2 使用泛型方法
public class GenericClass{
public <T> void show(T t){
System.out.println(t);
}
}
泛型接口
定义格式:
修饰符 interface 接口名< 类型 >{}
例子:public interface Generic<T>{}
接口中的方法:
修饰符 返回值类型 方法名(T t);
泛型接口不能直接实例化,需要借助接口的实现类
泛型接口实现类定义:
修饰符 class 类名<T> implements 接口名<T>{}
还需要重写接口中的方法。
@Override
修饰符 返回值类型 方法名(T t) {}
案例:
// 218-test1
// 定义泛型接口,
// 实例化对象不同直接通过接口进行,所以要定义接口的实现类。根据List接口相关定义
public interface Generic<T> {
void show(T t);
}
public class GenericImpl<T> implements Generic<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
public class GenericDemo {
public static void main(String[] args) {
Generic<String> g = new GenericImpl<String>();
g.show("汪苏泷");
Generic<Integer> g2 = new GenericImpl<Integer>();
g2.show(33);
}
}
类型通配符
- 为了表示各种泛型List的父类,可以使用类型通配符
类型通配符:<?>
List<?>:表示元素类型未知的List,
它的元素可以匹配任何的类型
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
- 类型通配符的上限
想将List<?>从表示任何泛型List的父类,变成,只表示某一类泛型List的父类,可以使用类型通配符的上限
类型统配符上限:List<? extends 类型>
例子:
List<? extends Number> 表示的类型是Number或者其子类型
理解:?继承自Number,说明?是Number或者是Number的子类型
- 类型通配符的下限
类型通配符下限:List< ? super 类型>
例子:
List< ? super Number> 表示的类型是Number或者其父类型
理解:? super Number,说明?是Number或者是Number的父类型
案例:
// 218
public class GenericDemo1 {
public static void main(String[] args) {
// object ==> Number ==> Integer
// 类型通配符 List<?>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
// 类型统配符上限:List<? extends Number>
// List<? extends Number> list4 = new ArrayList<Object>();
// 报错:因为上限是Number,object比Number更高一级
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
// 类型通配符下限:List<? super Number>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
// List<? super Number> list9 = new ArrayList<Integer>();
//报错:因为下限是Number,Integer比Number低一级
}
}
可变参数
可变参数:又称参数个数可变,用作方法的形式参数出现,那么方法参数个数就是可变的。
格式:
修饰符 返回值类型 方法名(数据类型...变量名){}
例子:pubilc static int sum(int...a){}
注意:
1 这里的变量其实是一个数组
2 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
案例:
// 218
public class SumDemo {
public static void main(String[] args) {
System.out.println(sum(10, 20));
System.out.println(sum(10,20,30));
System.out.println(sum(10,20,30,40));
System.out.println(sum(10,20,30,40,50));
System.out.println(sum(10,20,30,40,50,60));
}
// 问题:要想是求5个参数的和,那么就需要再定义对应的方法,那么当需要更多参数的时候,同样也需要再定义。这样比较麻烦
// 解决方式:可变参数
public static int sum(int...a){
// System.out.println(a);//输出内容是:I@1b6d3586 表示int类型数组
int sum = 0;
for(int i:a){
sum += i;
}
return sum;
}
// public static int sum(int a,int b){
// return a + b;
// }
// public static int sum(int a,int b ,int c){
// return a + b + c;
// }
// public static int sum(int a,int b,int c,int d){
// return a + b + c + d;
// }
}
可变参数方法
所属类 | 方法名 | 说明 | 方法使用 |
---|---|---|---|
Array | public static < T> List < T > asList(T…a) | 返回由指定数组支持的固定大小的列表 返回值类型是List< T > | add、remove不可以使用 set可以使用 |
List | public static < T > List < T > of(E…elements) | 返回包含任意数量元素的不可变列表 返回值类型是List< T > | add、remove、set都不可以使用 |
Set | public static < T > Set < E > of(E…elements) | 返回包含任意数量元素的不可变集合 返回值类型是Set< T> | add、remove不可以使用 没有索引,没有set方法 设置集合元素的时候,不能设置重复元素 |
案例Array、List、Set
// 218
public class ArrDemo {
public static void main(String[] args) {
List<String> lis = Arrays.asList("hello", "java", "world");
// lis.add("javaee");//UnsupportedOperationException
// 报错,说明Arrays.asList创建对象不支持 add方法
// lis.remove("world");//UnsupportedOperationException
// 报错,说明Arrays.asList创建对象不支持 remove方法
lis.set(1, "javaee");
// 修改成功,因为 add和remove方法会改变list集合长度
System.out.println(lis);
// 输出信息是:[hello, java, world]
// 本机装的jdk8 没有安装 jdk9 所以会报错
// List<String> lis2 = List.of("hello","java","world","java");
// 这里报错的原因是 jdk低于9版本,因为这个方法是jdk9引入的。
// Set.of();// 同理也是jdk9添加的
}
}