泛型
泛型就是参数化类型
- 适用于多种数据类型执行相同的代码
- 泛型中的类型在使用时指定
- 泛型归根到底就是“模版”
优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。
泛型主要使用在集合中
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不允许创建泛型数组。