(!!!为了能够更好的学习容器,我们首先要先来学习一个概念:泛型!!!)
一 泛型简介
(泛型的本质——>数据类型的参数化)
(把“泛型”理解为数据类型的一个占位符(类似:形式参数))
(泛型的<数据类型> 只能是引用类型——>不能使用基本数据类型了,只能使用对象/引用类型)
(泛型主要是方便了程序员的代码编写,以及更好的安全性检测)
1 泛型基本概念
泛型是JDK5.0以后增加的新特性。
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型
参数化类型,白话说就是:
- 把类型当作是参数一样传递。
- <数据类型> 只能是引用类型。
2 泛型的好处——>(在编译期就会报错)
在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:
- 代码可读性更好【不用强制转换】
- 程序更加安全【只要编译时期没有警告,运行时期就不会出现ClassCastException异常】
3 类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
二 泛型类
(在类上定义泛型)
(public class 类名<泛型表示符号> {} , 或public class 类名<泛型表示符号,泛型表示符号>{})
(用了泛型如果类型不匹配编译期间会报错,规范了)
1 泛型标记
定义泛型时,一般采用几个标记:E、T、K、V、N、?。他们约定俗称的含义如下:
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的JAVA类 |
K | Key | 表示键,例如:Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的JAVA类型 |
2 泛型类的使用
语法结构
public class 类名<泛型标识符号> {
}public class 类名<泛型标识符号,泛型标识符号> {
}
示例
public class Generic<T> {private T flag;public void setFlag(T flag){this.flag = flag;}public T getFlag(){return this.flag;}
}
实例化时需要给定具体类型的,如果没有默认的object类型
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);}
}
用了泛型如果类型不匹配编译器会报错,规范了
三 泛型接口
(在接口上定义泛型)
(public interface 接口名<泛型表示符号>{} , public interface 接口名<泛型表示符号,泛型表示符号>{})
(实现时传递,使用时传递)
泛型接口的使用
语法结构
public interface 接口名<泛型标识符号> {
}public interface 接口名<泛型标识符号,泛型标识符号> {
}
示例
public interface IGeneric<T> {T getName(T name);
}
//1 在实现接口时传递具体数据类型
public class IgenericImpl implements Igeneric<String> {@Overridepublic String getName(String name) {return name;}
}//2 在实现接口时仍然使用泛型作为数据类型
public class IGenericImpl2<T> implements IGeneric<T>{@Overridepublic T getName(T name) {return name;}
}
public class Test {public static void main(String[] args) {IGeneric<String> igeneric= new IGenericImpl();String name = igeneric.getName("xiao");System.out.println(name);IGeneric<String> igeneric1 = new IGenericImpl2<>();String name1 = igeneric1.getName("jia");System.out.println(name1);}
}
四 泛型方法
(在方法上定义泛型)
(无返回值方法public<泛型表示符号>void getName(泛型标识符号name){})
(有返回值方法public<泛型表示符号>泛型表示符号getName(泛型标识符号namme){})
(类上定义的泛型,在方法中也可以使用。但有时需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法)
(调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型)
(静态方法只能在方法上定义泛型)
泛型方法的使用
1 非静态方法
非静态方法可以使用泛型类中所定义的泛型,也可以将泛型定义在方法上。
语法结构
//无返回值方法
public <泛型标识符号> void getName(泛型标识符号 name){
}//有返回值方法
public <泛型标识符号> 泛型标识符号 getName(泛型标识符号 name){
}
示例
public class MethodGeneric {public <T> void setName(T name){System.out.println(name);}public <T> T getAge(T age){//定义泛型和使用泛型return age;}
}
public class Test2 {public static void main(String[] args) {MethodGeneric methodGeneric = new MethodGeneric();methodGeneric.setName("oldlu");Integer age = methodGeneric.getAge(123);System.out.println(age);}
2 静态方法
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型,所以必须要将泛型定义在方法上。
语法结构
//无返回值静态方法
public static <泛型标识符号> void setName(泛型标识符号 name){
}//有返回值静态方法
public static <泛型标识符号> 泛型表示符号 getName(泛型标识符号 name){
}
示例
public class MethodGeneric {public static <T> void setFlag(T flag){System.out.println(flag);}public static <T> T getFlag(T flag){return flag;}
}
public class Test4 {public static void main(String[] args) {MethodGeneric.setFlag("oldlu");Integer flag1 = MethodGeneric.getFlag(123123);System.out.println(flag1);}
}
五 泛型方法与可变参数
(public<泛型表示符号> void showMsg(泛型标识符号… agrs){})
(在泛型方法中,泛型也可以定义可变参数类型)
(泛型标识符号… agrs)——>可变数组
语法结构
public <泛型标识符号> void showMsg(泛型标识符号... agrs){
}
示例
public class MethodGeneric {public <T> void method(T...args){for(T t:args){System.out.println(t);}}
}
//用for each遍历
MethodGeneric methodGeneric = new MethodGeneric();
String [] arr = new String[]{"a","b","c"};
Integer [] arr2 = new Integer[]{1,2,3,4,5};
methodGeneric.method(arr);
methodGeneric.method(arr2);
//两种数据都能操作
六 泛型中的通配符
(使用泛型的时候要用<?>,T之类的是定义泛型时使用的标识符)
(当类型不确定时可以用<?>)
无界通配符
“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。
语法结构
public void showFlag(Generic<?> generic){
}
示例
public class Generic<T> {private T flag;public void setFlag(T flag){this.flag = flag;}public T getFlag(){return this.flag;}
}
public class ShowMsg {public void showFlag(Generic<?> generic){System.out.println(generic.getFlag());}
}//如果<?>换成了Integer类型,那么下面的代码只能使用Integer类型
public class Test3 {public static void main(String[] args) {ShowMsg showMsg = new ShowMsg();Generic<Integer> generic = new Generic<>();generic.setFlag(20);showMsg.showFlag(generic);Generic<Number> generic1 = new Generic<>();generic1.setFlag(50);showMsg.showFlag(generic1);Generic<String> generic2 = new Generic<>();generic2.setFlag("jjj");showMsg.showFlag(generic2);}
}
七 统配符的上下限定
(!!!约束机制!!!——>比如继承了number就不能使用String类型了)
(上线限定——>实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型)
(下线限定——>实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型)
1 统配符的上限限定
对通配符的上限的限定:<? extends 类型>
?实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型;
语法结构
public void showFlag(Generic<? extends Number> generic){
}
示例
public class ShowMsg {public void showFlag(Generic<? extends Number> generic){System.out.println(generic.getFlag());}
}
//规定了只能使用继承的类或者它的子类
public class Test4 {public static void main(String[] args) {ShowMsg showMsg = new ShowMsg();Generic<Integer> generic = new Generic<>();generic.setFlag(20);showMsg.showFlag(generic);Generic<Number> generic1 = new Generic<>();generic1.setFlag(50);showMsg.showFlag(generic1);}
}
2 通配符的下限限定
对通配符的下限的限定:<? super 类型>
?实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型;
语法结构
public void showFlag(Generic<? super Integer> generic){
}
示例
public class ShowMsg {public void showFlag(Generic<? super Integer> generic){System.out.println(generic.getFlag());}
}
//只能使用Integer类型或者它的父类,不能使用子类型
public class Test6 {public static void main(String[] args) {ShowMsg showMsg = new ShowMsg();Generic<Integer> generic = new Generic<>();generic.setFlag(20);showMsg.showFlag(generic);Generic<Number> generic1 = new Generic<>();generic1.setFlag(50);showMsg.showFlag(generic1);}
}
八 泛型总结
!!!!!泛型的作用!!!!!
实现数据类型参数化的——>为了解决(没有泛型的时候,在使用一些类型时,需要做一些强制类型转化,有可能出现类型不匹配的异常,这种异常是在代码运行时出现的,编译器不会出现异常)
泛型通过占位符的思想,对数据类型做了一个参数的定义,什么时候确切的使用泛型了,再将确切的类型传递进来,这样代码当中就不需要做强制类型转化了,而且一旦类型自己所给定的和它定义的不匹配,在编译器就会有异常提示,避免了在运行期间出现问题,相当于对数据处理的安全措施
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。 类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:
-
基本类型不能用于泛型
Test<int> t;
这样写法是错误,我们可以使用对应的包装类Test<Integer> t ;
-
不能通过类型参数创建对象
T elm = new T();
运行时类型参数T
会被替换成Object
,无法创建T类型的对象,容易引起误解,java干脆禁止这种写法。