面试收集:java泛型相关

泛型

泛型就是参数化类型

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定
  • 泛型归根到底就是“模版”

优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。

泛型主要使用在集合中

import java.util.ArrayList;
import java.util.List;
 
public class Demo01 {
  
  // 不使用泛型,存取数据麻烦
  public static void test1(){
    List  list = new ArrayList();
    list.add(100);
    list.add("zhang");
    /*
     * 从集合中获取的数据是Object类型,Object类型是所有类型的根类,但是在具体使用的时候需要
     * 类型检查,类型转化,处理类型转化异常
     * 使用麻烦
     */
    Object o = list.get(1);
    if (o instanceof String) {
     String s = (String)o;
    }
    System.out.println(o);
  }
  
  // 使用泛型
  public static void test2(){
    List<String> list = new ArrayList<String>();
    //list.add(100); 放数据时安全检查,100不是String类型,不能存放
    list.add("存数据安全,取数据省心");
    String s = list.get(0); //取出来的数据直接就是泛型规定的类型
    System.out.println(s);
    
  }
  
  public static void main(String[] args) {
    test1();
    test2();
  }
 
}

自定义泛型

泛型字母

  • 形式类型参数(formal type parameters)即泛型字母
  • 命名泛型字母可以随意指定,尽量使用单个的大写字母(有时候多个泛型类型时会加上数字,比如T1,T2)
    常见字母(见名知意)

    • T Type
    • K V Key Value
    • E Element
  • 当类被使用时,会使用具体的实际类型参数(actual type argument)代替

泛型类

  • 只能用在成员变量上,只能使用引用类型
//泛型类定义
//在泛型类、泛型接口的方法中,把泛型中声明的类型形参当成普通类型使用
public class GenericsClass<K, V> {
    private K key;
    private V value;

    public GenericsClass() {
    }

    public GenericsClass(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }
}

//泛型类使用
class UseTest {
    public static void main(String[] args) {
        GenericsClass<String, String> genericsClass = new GenericsClass<>("hhehe", "21321");
        GenericsClass<Integer, String> genericsClass1 = new GenericsClass<>(1213, "21321");
        GenericsClass<String, Double> genericsClass2 = new GenericsClass<>("hhehe", 2.333);

        System.out.println(genericsClass.getKey() + genericsClass.getValue());
        System.out.println(genericsClass1.getKey() + genericsClass1.getValue());
        System.out.println(genericsClass2.getKey() + genericsClass2.getValue());
    }
}

泛型接口

  • 只能用在抽象方法上

泛型方法

  • 返回值前面加上 <>
  • 泛型方法的用法格式如下:

    修饰符<T, S> 返回值类型 方法名(形参列表){
       方法体
    }
import java.util.ArrayList;
import java.util.List;

class Fruit {
    private int age;

    public Fruit() {
    }

    public Fruit(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class Banana extends Fruit {
    private int age;

    public Banana() {
    }

    public Banana(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getBananaMethodMethod() {
        return "bananaMethod";
    }
}

class Apple extends Fruit {
    private int age;

    public Apple() {
    }

    public Apple(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAppleMethod() {
        return "appleMethod";
    }

    public void addToList(List<? super Apple> list) {
        list.add(this);
    }
}

/*
泛型相关:
https://www.wanandroid.com/wenda/show/8821
https://lrh1993.gitbooks.io/android_interview_guide/content/java/basis/genericity.html
https://juejin.cn/post/6844903901552967688
 */

class Test {
    public static void main(String[] args) {
        /**
         * <? extends Fruit>
         * 作用:可以获取某个基类的子类的返回值,但是无法添加进去
         */
        List<? extends Fruit> list = new ArrayList<>();//前面是实参,后面是形参
//        list.add(new Banana());//Error,不能添加的原因:
        //我们知道实参list是Fruit,假设这个list的形参是苹果,但是list.add(香蕉/橘子)就肯定是错误的,
        //无法添加的原因:list内添加的对象必须是Fruit的子类,那么,list可以添加苹果、香蕉、橘子等,肯定错误
        //因此无法添加子类对象
        Fruit fruit = list.get(0);//可以获取元素

        /**
         * <T extends Fruit>,用于形参,限定返回值
         * 只能获取,无法添加
         */
        System.out.println("tag_apple " + ((Apple) getApple()).getAge());
        System.out.println("tag_apple " + ((Apple) getApple()).getAppleMethod());
        System.out.println("tag_banana " + ((Banana) getBanana()).getAge());
        System.out.println("tag_banana " + ((Banana) getBanana()).getBananaMethodMethod());

        /**
         * <? extends Fruit>,用于实参,限定传入参数
         * 只能获取,无法添加
         */
        List<Apple> listApple = new ArrayList<>();
        listApple.add(new Apple(1));
        List<Banana> listBanana = new ArrayList<>();
        listBanana.add(new Banana(2));
        int totalAge = getTotalWeight(listApple) + getTotalWeight(listBanana);//编译报错
        System.out.println("tag_totalAge " + totalAge);

        /**
         * <? super Fruit>
         * 作用:添加子类,但是无法获取出来
         * 只能用于实参,即作为参数传递,只能添加,无法获取
         */
        List<Fruit> fruits = new ArrayList<Fruit>();
        Apple apple = new Apple();
        List<? super Apple> lists = new ArrayList<>();
        lists.add(apple);
        fruits.add(apple);
        apple.addToList(fruits);
        apple.addToList(lists);

        List<? super Fruit> list2 = new ArrayList<Fruit>();
        list2.add(new Apple());//不报错
//        Apple apple = list2.get(0);//ERROR
        //错误原因:get到基类是Fruit及其父类,无法判断获取到的类型具体是苹果/香蕉/橘子的具体,
        //而只能知道是Fruit
        Object object = list2.get(0);//这里不是具体类型,而是Object,然后根据类型判断
        if (object instanceof Apple) {
            System.out.println("list2 Apple: " + ((Apple)object).getAppleMethod());
        } else if (object instanceof Fruit) {
            System.out.println("list2 Fruit:" + ((Fruit)object).getAge());
        }

    }


//*******************************************<T extends Fruit> 用于方法定义****************************************
//*******************************************<? extends Fruit> 用于方法调用传参************************************

    /**
     * https://juejin.cn/post/6844903901552967688
     * <T extends E> 和 <? extends E> 有什么区别
     * 它们用的地方不一样:
     * <T extends E>只能用于形参(也就是泛型定义的时候),限定返回值
     * <? extends E>只能用于实参(也就是传入具体泛型类型的时候),限定传入参数,不能添加元素
     * <p>
     * 用于方法的返回值类型时,约束了返回值类型
     */
    private static <T extends Fruit> T getApple() {
        Apple apple = new Apple();
        apple.setAge(12);
        return (T) apple;
    }

    private static <V extends Fruit> V getBanana() {
        Banana banana = new Banana(34);
        return (V) banana;
    }

    /**
     * <T extends E> 和 <? extends E> 有什么区别
     * 它们用的地方不一样:
     * <T extends E>只能用于形参(也就是泛型定义的时候),限定返回值
     * <? extends E>只能用于实参(也就是传入具体泛型类型的时候),限定传入参数,不能添加元素
     */
    public static int getTotalWeight(List<? extends Fruit> list) {
        int totalWeight = 0;
        for (int i = 0; i < list.size(); i++) {
            Fruit fruit = list.get(i);
            totalWeight += fruit.getAge();
        }

        // 不能把元素加入到其中。因为程序无法确定list集合中元素的类型,所以不能向其添加对象。
        // list.add(new Apple(12));//会编译报错,因为传入的是Fruit父类list,不确定是苹果、香蕉还是橘子,
        //假设list是苹果,但是我们list.add(香蕉/橘子),这就是错误的,因此无法添加子类对象

        Fruit fruit = list.get(0);
        if (fruit instanceof Apple) {
            System.out.println("pingguo:" + ((Apple) fruit).getAppleMethod());
        } else if (fruit instanceof Banana) {
            System.out.println("xiangjiao:"  + ((Banana) fruit).getBananaMethodMethod());
        }

        return totalWeight;
    }
//*******************************************************************************************************************

}

类型通配符

顾名思义就是匹配任意类型的类型实参。

类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被成为通配符,它的元素类型可以匹配任何类型。

public void test(List<?> c){
  for(int i =0;i<c.size();i++){
    System.out.println(c.get(i));
  }
}

现在可以传入任何类型的List来调用test()方法,程序依然可以访问集合c中的元素,其类型是Object。

List<?> c = new ArrayList<String>();
//编译器报错
c.add(new Object());

但是并不能把元素加入到其中。因为程序无法确定c集合中元素的类型,所以不能向其添加对象。
下面就该引入带限通配符,来确定集合元素中的类型。

带限通配符

简单来讲,使用通配符的目的是来限制泛型的类型参数的类型,使其满足某种条件,固定为某些类。

主要分为两类即:上限通配符下限通配符

1.上限通配符

如果想限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口,也可以是这个类或接口本身。

它表示集合中的所有元素都是Shape类型或者其子类
List<? extends Shape>

这就是所谓的上限通配符,使用关键字extends来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。
例如:

//Circle是其子类
List<? extends Shape> list = new ArrayList<Circle>();

这样就确定集合中元素的类型,虽然不确定具体的类型,但最起码知道其父类。然后进行其他操作。

2.下限通配符

如果想限制使用泛型类别时,只能用某个特定类型或者是其父类型才能实例化该类型时,可以在定义类型时,使用super关键字指定这个类型必须是是某个类的父类,或者是某个接口的父接口,也可以是这个类或接口本身。

它表示集合中的所有元素都是Circle类型或者其父类
List <? super Circle>

这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。
例如:

//Shape是其父类
List<? super Circle> list = new ArrayList<Shape>();

extends/super

上限(extends)

指定的类必须是继承某个类,或者实现了某个接口(不是implements),即<=

  • ? extends List

下限(super)

即父类或本身

  • ? super List
import java.util.ArrayList;
import java.util.List;
/**
 * extends:泛型的上限 <= 一般用于限制操作 不能使用在添加数据上,一般都是用于数据的读取
 *
 * supper:泛型的上限 >= 即父类或自身。一般用于下限操作
 *
 * @author Administrator
 * @param <T>
 */
 
public class Test<T extends Fruit> {
 
  private static void test01() {
    Test<Fruit> t1 = new Test<Fruit>();
    Test<Apple> t2 = new Test<Apple>();
    Test<Pear> t3 = new Test<Pear>();
  }
 
  private static void test02(List<? extends Fruit> list) {
 
  }
 
  private static void test03(List<? super Apple> list) {
 
  }
 
  public static void main(String[] args) {
 
    // 调用test02(),测试 extends  <=
    test02(new ArrayList<Fruit>());
    test02(new ArrayList<Apple>());
    test02(new ArrayList<ReadApple>());
    // test02(new ArrayList<Object>()); Object 不是 Fruit 的子类 ,编译不通过
    
    
    // 调用test03() ,测试super >=
    test03(new ArrayList<Apple>());
    test03(new ArrayList<Fruit>());
    //test03(new ArrayList<ReadApple>());  ReadApple < apple,所以不能放入
  }
 
}
 
class Fruit {
 
}
 
class Apple extends Fruit {
 
}
 
class Pear extends Fruit {
 
}
 
class ReadApple extends Apple {
 
}

泛型的继承

//泛型类定义
//在泛型类、泛型接口的方法中,把泛型中声明的类型形参当成普通类型使用
public class GenericsClass<K, V> {
}


//泛型类派生子类
//当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,
// 或者从该父类派生子类,需要注意:使用这些接口、父类派生子类时不能再包含类型形参,
// 需要传入具体的类型。
// 保留父类泛型 ----》泛型子类
// 1)全部保留
class A<K, V> extends GenericsClass<K, V> {

}

// 2) 部分保留
class B<K> extends GenericsClass<K, String> {

}

// 不保留父类泛型 -----》子类按需实现
// 1)具体类型
class C extends GenericsClass<String, Integer> {

}

// 2)没有具体类型
// 泛型擦除:实现或继承父类的子类,没有指定类型,类似于Object
class D extends GenericsClass {

}

Java为什么不能创建泛型数组

如果创建泛型数组,将能任何类的对象存放在数组中,并且能够通过编译,在编译阶段由于泛型擦除该数组被看作一个Object[ ];

但是在使用数组中的元素时,如果对元素进行强制类型转换可能会发生一些不可预知的错误。所以,Java不允许创建泛型数组。

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/113740648