1、为什么要用泛型?
如果没有用泛型,像用集合这样的类型时, 就会遇到:
(1)需要向下转型:麻烦
(2)向下转型过程中,如果没有加instanceof判断,容易发生ClassCastException:安全隐患问题
(3)无法阻止不符合类型的元素添加等:无法提前阻止非法值,灵活度小
有了泛型,类型处理更灵活,安全,避免了类型转换。
2、泛型是什么?
JRE/JDK开发团队在设计集合(例如:ArrayList)这个类的时候,他是不知道我们会用它来装什么类型的对象的。
相当于集合的元素类型是未知的。
只有当程序员用这个具体的集合(例如:ArrayList)类型的容器对象来装元素对象时,才能确定元素的类型。
相当于集合的元素类型要延后到在使用时才能确定。
在JDK1.5时,就新增了一种语法,可以让我们在编译时(设计类时,例如:ArrayList)用<E>来代表未知的元素类型,
然后在使用集合时(例如:ArrayList),然后在ArrayList的后面用<String>来表示元素的类型是String类型,
那么像<类型>这样的形式就是泛型。
3、举例:设计一个负责的学生类(不同于我们平时用的学生类,不是说我们以后所有的类都要像这样做,这里只是演示一个例子)
语文老师说,学生类对象中,有一个成绩,成绩的类型是String类型,成绩用“优秀、良好等表示”
数学老师说,学生类对象中,有一个成绩,成绩的类型是double类型,成绩用“85.5,90.0”等表示
英语老师说,学生类对象中,有一个成绩,成绩的类型是char类型,成绩用“A,B,C”等表示
如果我想要设计这样的一个通用的学生类,怎么表示?
4、总结:泛型代表一种未知的类型
这个未知的类型通常用<E>、<T>,<K,V>等表示
E:element,用E代表元素的类型,看到E,容易想到元素
K:key ,用K代表map的key的类型,看到K,容易想到key
V:value,用V代表map的value的类型,看到value,容易想到value
T:type,用T代表类型的意思,当你不清楚用什么字母代表好,一般就T来表示。
当使用时,把这些未知类型用具体的类型代替,例如:<String>,<Integer>等。
class Student<T>{ //<T>相当于声明泛型
private String name;
private T score;//使用泛型字母T,代表一种类型,用它来声明score变量
public Student(String name, T score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class TestGeneric {
@Test
public void test04() {
//语文老师用这个学生类
Student<String> student = new Student<String>("张三","优秀");
//得到学生的成绩
String score = student.getScore();
System.out.println("score = " + score);
}
@Test
public void test03() {
//现在要往一个ArrayList集合中放对象
//添加到ArrayList中的元素只能是String类型
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
// list.add(15);//错误,编译不通过,因为15是Integer类型
// list.add(new Date());//错误,编译不通过,因为对象是Date类型
for (String s : list) {
System.out.println(s.length());//求字符串长度
}
}
@Test
public void test02(){
//现在要往一个ArrayList集合中放对象
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//遍历数组,判断元素字符串的长度
for(Object obj : list){
String str = (String) obj;
System.out.println(str.length());
}
}
@Test
public void test01(){
//现在要往一个ArrayList集合中放对象
ArrayList list = new ArrayList();
list.add("hello");
list.add(15);
list.add(new Date());
//遍历数组,判断元素字符串的长度
for(Object obj : list){
if(obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
}
}
}
5、泛型的声明可以在哪些位置声明?
(1)在声明类或声明接口时,我们把这样的类或接口称为泛型类或泛型接口
例如:
public interface Collection<E>
public interface List<E>
public class ArrayList<E>
public interface Set<E>
public class HashSet<E>
public interface Map<K,V>
public class HashMap<K,V>
....
public interface Comparator<T>
public interface Comparable<T>
(2)在声明方法时,在返回值类型的前面也可以声明泛型,我们把这样的方法称为泛型方法。
例如:
java.util.Arrays数组工具类
public static <T> int binarySearch(T[] a,T key, Comparator<? super T> c)
public static <T> void sort(T[] a, Comparator<? super T> c)
public static <T> T[] copyOf(T[] original, int newLength)
6、泛型类和泛型接口
(1)声明泛型类或泛型接口的语法:
【修饰符】 class 类名<泛型字母>{
}
【修饰符】 interface 类名<泛型字母>{
}
说明:
<泛型字母>,通常是用单个大写字母来表示,<E>、<T>,<K,V>等表示,不建议用单词表示。
<泛型字母>,在声明它的这个类或接口中,可以用它来表示变量的类型,参数的类型,返回值的类型,相当于某个具体类型的替身。
<泛型字母>,可以是多个,例如:<K,V>,多个之间使用逗号分隔
<泛型字母>,在声明它的类或接口中,是不能用在“静态成员”上。
(2)如何使用泛型类或泛型接口
强烈建议,如果某个类或接口声明了泛型,使用时,请正确指定泛型,否则会发生泛型擦除情况。(泛型擦除如何处理一会说明)。
A:在使用类或泛型接口声明变量,并创建对象时,具体化类型
B:在子类继承泛型父类,或实现泛型接口时,也可以指定具体类型
C:在子类继承泛型父类,或者实现泛型接口时,子类/实现类也可以延续泛型
D:泛型只能指定为“引用数据类型”,不能基本数据类型
public class TestGenericClassInterface {
@Test
public void test05() {
// ArrayList<double> list = new ArrayList<double>();//指定泛型,不能是基本数据类型
ArrayList<Double> list = new ArrayList<Double>();
// list.add(1);//1只能自动装箱为Integer,不会自动装箱为Double
list.add(1.0);
int num = 2;
list.add((double)num);//先把num转型为double,然后自动装箱
}
@Test
public void test04() {
// ArrayList<int> list = new ArrayList<int>();//指定泛型,不能是基本数据类型
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);//自动装箱为Integer
}
@Test
public void test03() {
//HashMap<K,V>
//key的类型是Integer
//value的类型是String
HashMap<Integer,String> list = new HashMap<Integer,String>();//指定泛型
}
@Test
public void test02() {
ArrayList<String> list = new ArrayList<String>();//指定泛型
}
@Test
public void test01(){
ArrayList list = new ArrayList();
//此时ArrayList的泛型被擦除了,在这个地方,擦除后按照Object处理。
list.add("hello");
Object obj = list.get(0);
}
}
//未知的类型Type
//确定的类型Type
//这里是代表未知的类型,但是容易让人误以为是java.lang.reflect.Type类型。
class MyClass<Type>{
}
//实现java.lang.Comparable接口
class Circle implements Comparable<Circle>{
private double radius;
@Override
public int compareTo(Circle o) {
//this对象是Circle类型
//o对象也是Circle类型,所以Comparable接口后面写<Circle>
return Double.compare(this.radius,o.radius);
}
}
class Father<T>{
// private static T num;//错误的
private T a;//可以
}
class Sub extends Father<String>{ //如果子类不延续,那么必须把<T>具体化,Father后面必须写<String>
}
class Son<T> extends Father<T>{//子类Son延续父类的<T>
}
class Daughter<E> extends Father<E>{//子类Daughter延续父类的<T>,只是在子类中换了一个字母
}
(3)什么可以确定泛型类或泛型接口的<泛型字母>的具体类型?
「1」new对象
ArrayList<String> list = new ArrayList<String>();
「2」继承泛型父类或实现泛型接口时
public class Circle implements Comparable<Circle>{
public int compareTo(Circle o){
//....
}
}
class Father<T>{
}
class Son extends Father<String>{
}
(4)延续泛型父类或父接口的泛型
【修饰符】 class 子类<泛型字母> extends 父类<泛型字母>{
}
class Father<T>{
private T a;
public T method(){
//...
}
}
class Sub1<T> extends Father<T>{ //只需要子类名和父类名后面<泛型字母>一致即可
@Override
public T method(){
//....
}
}
class Sub2<E> extends Father<E>{
@Override
public E method(){
//....
}
}
(5)在泛型类或泛型接口上声明的<泛型字母>,不能用在静态成员上。
二、泛型方法
1、如果某个方法所在的类或接口,不是泛型类或泛型接口,但是在声明这个方法时,也有未知的类型(例如:某个形参的类型未知),
这个时候,可以在方法中单独声明泛型。
需求:
在MyCollections工具类中,声明一个方法,可以遍历一个ArrayList的集合,每个元素打印一行。
注意:Object是String的父类,是Integer的父类
<Object> 的泛型 不能接收<String><Integer>
例如:ArrayList<Object> 不能接收 ArrayList<String>
例如:ArrayList<Object> 不能接收 ArrayList<Integer>
声明ArrayList<E>这个E是代表“一种”未知的类型,那么如果用ArrayLis时指定E为<Object>,那同一个集合,不能说<E>又代表<String>2、泛型方法声明的泛型和类或接口上面声明的泛型有什么区别?
(1)如果是在类或接口上面声明的泛型,它在整个类、接口中对于所有方法来说,代表的是同一种类型
class Father<T>{
public void method(T t){
}
public void test(T t){ //这两个方法的T是同一个类型
}
}
class Father{
public <T> void method(T t){
}
public <T> void test(T t){ //这两个方法的T是独立的类型
}
}
(2)如果是在类或接口上面声明的泛型,不能用在静态成员上,也就不能用在静态方法上
class Father<T>{
public static void method(T t){ //错误
}
}
class Father{
public static <T> void method(T t){ //可以
}
}
3、泛型方法的声明格式
【修饰符】 <泛型字母列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
}
在方法上面声明的<泛型字母列表>,只能在当前方法使用,可以用于声明形参类型,或返回值类型。
public class TestGenericMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
MyCollections my = new MyCollections();
my.iterate(list);
// my.iterate2(list);//ArrayList<Object> 类型的变量/形参是不能接收 ArrayList<String>类型的集合
my.iterate3(list);
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
// my.iterate(list2);//ArrayList<String> 类型的变量/形参是不能接收 ArrayList<Integer>类型的集合
//my.iterate2(list2);//ArrayList<Object> 类型的变量/形参是不能接收 ArrayList<Integer>类型的集合
my.iterate3(list2);
// ArrayList<Object> list3 = new ArrayList<String>();//<E>只能代表一种类型
}
}
class MyCollections{
/*
ArrayList的元素类型未知
*/
public void iterate(ArrayList<String> list){
for (String s : list) {//foreach循环
System.out.println(s);
}
}
public void iterate2(ArrayList<Object> list){
for (Object s : list) {//foreach循环
System.out.println(s);
}
}
/*
<T>这个未知的类型,在调用iterate3方法时确定,根据传入的实参来确定
*/
public <T> void iterate3(ArrayList<T> list){
for (T s : list) {//foreach循环
System.out.println(s);
}
}
public <T> void iterate4(ArrayList<T> list){
for (T s : list) {//foreach循环
System.out.println(s);
}
}
}
三、泛型声明时,指定上限
1、当某个泛型不能代表所有的引用数据类型时,需要限定上限时,可以这么做:
<泛型字母 extends 上限>
2、说明
(1)对于某个<泛型字母>如果没有指定上限,它的上限就是默认为Object;
(2)对于某个<泛型字母>如果指定上限,那么在确定该<泛型字母>的具体类型时,只能确定为该上限或上限的子类类型。
例如:<T extends Number> ,这个<T>的类型只能指定为<Integer>,<Double>,<BigInteger>等
(3)上限可以有多个限制,如果有多个限制,表示这个泛型要“同时”继承这个类和实现这些接口。
但是多个限制中,类只能有一个,接口可以有多个。并且如果有类,类必须在最左边。
例如:<T extends Number & Serializable & Comparable >
例如:<T extends Number & Object & Serializable & Comparable > 错误,因为类不能有两个
例如:<T extends Serializable & Comparable & Number> 错误,因为类必须在最左边
例如:<T extends Serializable & Comparable> 可以
例如:<T implements Serializable & Comparable> 错误,表示泛型的上限的关键字就是extends,没有别的
3、泛型的擦除
当我们使用某个泛型类,或者泛型接口时,没有为<泛型字母>指定具体的类型时,就你会发生泛型擦除。
当泛型擦除后,<泛型字母>的未知类型按照什么类型处理呢?
答:按照<泛型字母>第一个上限类型处理,如果没有指定上限,默认就是Object。
需求:声明一个坐标类Coordinate<T>,它有两个属性:x,y,都为T类型。
这个<T>不能代表任意的引用数据类型,代表数字类型。
数字类型有,例如:Integer,Double,BigInteger等
String不是数字类型。
public class TestGenericLimit {
public static void main(String[] args) {
// Coordinate<String> c1 = new Coordinate<>("北纬38.6", "东经36.8");
Coordinate<Integer> c2 = new Coordinate<>(38, 36);
Coordinate<Double> c3 = new Coordinate<>(38.6, 36.8);
Coordinate c4 = new Coordinate(38.6, 36.8);//泛型擦除
Number x = c4.getX();
ArrayList list = new ArrayList();//泛型擦除
}
}
class Circle implements Comparable{//泛型擦除
private double radius;
@Override
public int compareTo(Object o) {
return 0;
}
}
//java.lang.Number类型,它是数字类型的父类
class Coordinate<T extends Number & Serializable & Comparable >{
private T x;
private T y;
public Coordinate(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
@Override
public String toString() {
return "Coordinate{" +
"x=" + x +
", y=" + y +
'}';
}
}
四、泛型通配符
1、什么情况下,可以使用泛型通配符?
答案:当我们"使用某个泛型类或泛型接口"来声明形参(也可以是其他变量,只是形参比较多见)时,
此时依然不能确定这个泛型类或泛型接口的<泛型字母>的具体类型,那么可以考虑使用泛型通配符。
2、泛型通配符是一个?,它代表任意引用数据类型。
3、这个?的使用有三种形式:
<?>:?代表任意引用数据类型
<? extends 上限>:?代表上限或它的子类类型 [上限, 无底线(子类即可)]
<? super 下限>:?代表下限或它的父类类型 [下限,Object]
需求:要声明一个方法,可以用来比较两个Collection系列的集合的元素是否一致。
public class TestWildCard {
/*
此时的问题:
(1)不知道两个Collection的元素是什么?
(2)两个Collection的元素的类型不一定一样
方案一:声明两个泛型<T,E>分别代表两个Collection的元素类型
方案二:使用通配符
*/
public static <T,E> boolean same1(Collection<T> c1, Collection<E> c2){
//省略过程
return false;
}
public static boolean same2(Collection<?> c1, Collection<?> c2){
//省略过程
return false;
}
/*
声明一个方法,可以从一个Collection (例如:src)复制元素到另一个Collection(例如:target)
target这个Collection的泛型类型 >= src这个Collection的泛型类型
因为target这个集合得需要能够接收src这个集合的所有元素对象,所以它的类型要够大。
*/
public static <T> void copy1(Collection<T> target, Collection<? extends T> src){
}
public static <T> void copy2(Collection<? super T> target, Collection<T> src){
}
public static <T> void copy3(Collection<? super T> target, Collection<? extends T> src){
}
public static void main(String[] args) {
Collection<String> c1 = new ArrayList<>();
c1.add("hello");
c1.add("world");
Collection<Integer> c2 = new ArrayList<>();
c2.add(1);
c2.add(2);
// copy(c1, c2);
Collection<Number> c3 = new ArrayList<>();
c3.add(3);
c3.add(5.6);
c3.add(new BigInteger("125544555"));
Collection<Integer> c4 = new ArrayList<>();
c4.add(1);
c4.add(2);
copy1(c3,c4);//copy1(Collection<T> target, Collection<? extends T> src){
//T是Number
copy2(c3,c4);//copy2(Collection<? super T> target, Collection<T> src)
//T是Integer
copy3(c3,c4);//copy3(Collection<? super T> target, Collection<? extends T> src)
//T是Integer
}
}
/*
class Student implements Comparable<?>{
@Override
public int compareTo(? o) {
return 0;
}
}*/
五、集合工具类:java.util.Collections
1、集合工具类中有很多静态方法,为各种集合服务。
public static <T> boolean addAll(Collection<? super T> c, T... elements):把后面的elements的各个元素对象,添加到c这个Collection集合中。
这里这个Collection系列的集合的<泛型>必须 >= T,所以它写<? super T>
public static <T> void copy(List<? super T> dest, List<? extends T> src):从src的集合中复制元素到dest集合中,并且依次替换dest中的元素
注意:(1)<? super T> >=T >= <? extends T>
(2)dest的长度(size)要大于src的长度(size)
public static boolean disjoint(Collection<?> c1,Collection<?> c2):如果两个指定 collection 中没有相同的元素,则返回 true。
即判断c1和c2是否完全没有交集。
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll):在Collection集合coll中找最大值
这个方法声明了一个泛型<T extends Object & Comparable<? super T>>,这个T代表未知的类型,要求T必须继承Object类,同时实现Comparable接口。
T是Collection集合的元素类型,它如果没有实现Comparable接口,是无法“比较大小”,就不能找最大值
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp):如果Collection集合的元素没有实现Comparable接口,
可以Comparator接口的实现类对象
public static void reverse(List<?> list):反转list集合的元素
class Rectangle implements Comparable<Rectangle>{
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public String toString() {
return "Rectangle{" +
"length=" + length +
", width=" + width +
'}';
}
@Override
public int compareTo(Rectangle o) {
int result = Double.compare(length, o.length);//先比较两个矩形对象的长
return result != 0 ? result : Double.compare(width,o.width); //如果长不相等,就直接返回比较长的结果
//如果长相同,就返回比较宽的结果
}
}
public class TestCollections {
@Test
public void test07() {
Collection<Rectangle> coll = new ArrayList<>();
coll.add(new Rectangle(4,1));
coll.add(new Rectangle(6,2));
coll.add(new Rectangle(6,1));
System.out.println(Collections.max(coll));
}
@Test
public void test06() {
Collection<Integer> coll = new ArrayList<>();
coll.add(4);
coll.add(1);
coll.add(5);
coll.add(2);
coll.add(8);
System.out.println(Collections.max(coll));
}
@Test
public void test05() {
List<String> list1 = new ArrayList<>();
List<Character> list2 = new ArrayList<>();
list1.add("a");
list2.add('a');
//两个集合的元素的数据类型完全不同
System.out.println(Collections.disjoint(list1, list2));//true
}
@Test
public void test04() {
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
list1.add("hello");
list2.add(1);
System.out.println(Collections.disjoint(list1, list2));
}
@Test
public void test03(){
List<Object> list1 = new ArrayList<>();
list1.add("java");
list1.add("bigdata");
list1.add("h5");
list1.add("ui");
list1.add("python");
List<String> list2 = new ArrayList<>();
list2.add("hello");
list2.add("world");
Collections.copy(list1, list2);
System.out.println(list1);
}
@Test
public void test02(){
//演示:public static <T> boolean addAll(Collection<? super T> c, T... elements)
Collection<Object> c1 = new ArrayList<>();
Collections.addAll(c1,"hello","world","java");
System.out.println(c1);
}
@Test
public void test01(){
//演示:public static <T> boolean addAll(Collection<? super T> c, T... elements)
Collection<String> c1 = new ArrayList<>();
Collections.addAll(c1,"hello","world","java");
System.out.println(c1);
}
}
/*
问题
*/
public class TestProblem {
/*
为什么会报错?
因此此时对于method方法来说,没调用之前ArrayList的元素类型是不确定的,可能是任意的引用数据类型
*/
public void method1(ArrayList<?> list){
//想要往list集合中添加元素
// list.add("hello"); //有风险
// list.add(1); //有风险
// list.add(1.0); //有风险
list.add(null); //无论你的元素类型是什么,null都是可以的,即任意引用数据类型都得赋值为null
}
/*
为什么会报错?
因此此时对于method方法来说,没调用之前ArrayList的元素类型是不确定的,可能是Number或Number的任何一个一种子类
即这个?可能代表的是Integer,或Double,或....
*/
public void method2(ArrayList<? extends Number> list){
// list.add(1);//有风险
// list.add(1.0);//有风险
list.add(null);
}
/*
为什么这里可以添加 Integer或Double的对象等?
因为这?代表的类型是 >=Number。
这个类型肯定可以接收Number或它子类的对象。
*/
public void method3(ArrayList<? super Number> list){
list.add(1);//
list.add(1.0);//
// list.add("hello");//绝对有问题,String不是Number系列的
list.add(null);
}
}