泛型

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);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值