目录
往 泛型指定为Double类型的List集合中添加非 Double 的实例:
what's Generic?
- 泛型(广泛的类型):
- 泛型就相当于是一个标签。形象一些就是:在生活中随处可见的类似于泛型的参考。比如:男卫生间门上贴的标签 --》 男 or boy ,女卫生间门上的标签 --》 女 or girl,能表示性别的标签 理解在Java中就叫做泛型。再 比如:垃圾分类的垃圾桶上面贴的标签,比如:厨余辣鸡、有害辣鸡、可回收辣鸡、其他辣鸡,……等等。
- 泛型的声明格式:<泛型标识符[,泛型标识符2,……]> 这个 <> 因为像,也叫做钻石运算符。
- Java中 泛型的支持:泛型只支持引用数据类型,不支持基本数据类型!!!!
-
泛型的概念:
- 集合(有的叫容器)类在设计阶段/声明阶段,并不能够确定这个容器 实际应该存入什么样类型的对象。所以在 JDK1.5 之前都只能把元素的类型设计为Object。这样在使用集合的时候。如果想使用添加的元素的类型的特有成员时。就难免会使用到 类 的向下转型!所以很有可能引发 ClassCastException 异常!
- JDK1.5 开始 就使用泛型来解决。因为这个时候除了集合中的元素类型不确定。其他的部分都是确定的。比如关于这个元素应该怎么存储。怎么进行管理这些逻辑是 集合中的API 已经实现了逻辑了的。所以这时把元素的类型 设计成为一个参数(泛型标识符)的话。这个类型 就叫做泛型!在实例化集合类的时候。当开发者指定为 什么 数据类型,那么集合中所有使用到 泛型标识符 的地方都会在 编译时成为 开发者指定的数据类型!从而就可以直接在实例化的地方调用 该类型特有和继承、实现下来的成员!
- ·Java中泛型(Generic)是在 JDK1.5 引入的新特性。泛型的主要作用就是提供了 编译时类型 的安全监测机制。该机制允许开发者在编译时 检测到非法的类型数据结构,即指定了什么数据类型,就只能操作什么数据类型的成员!!
- 泛型的本质就是参数化类型。换言之,就是所操作的数据类型被指定为一个参数!当使用泛型类的时候,开发者给定了什么类型,那么泛型类 的 泛型标识符所表示的数据类型就是 该类型!从而就可以直接使用、 该类型特有和继承、实现下来的成员!
-
泛型的好处:
- Java泛型可以在编译阶段 保证 数据的安全性。比如在集合类中的体现。 当泛型指定为 List<String> list = new ArrayList<>(); 当调用 list.add() API 的时候,就只能往集合中添加String类型的 实例。
- 消除了 类 的向下转型 。消除了 有可能引发的 ClassCastException 异常的产生!同时,代码更加简洁、健壮。
- 在Java的源码中、各种设计模式、框架中 泛型得到广泛的应用!
泛型类、泛型接口、泛型方法中常用的泛型标识符的意义释义:
- <E> : E ---> Element(元素) 的首字母缩写。表示为类中的元素的泛型(比如表示为:实例变量、常量 的泛型,在Java中的List集合、Set集合中经常会看到这样的泛型)。
- <T>:T ---> Type(类型)的首字母缩写。表示为类中的泛型 "任意引用数据类型"。(比如表示为:类中的实例成员方法、实例变量、常量的泛型)。
- <K>:K ---> Key(键)的首字母缩写。表示为类中的 键的数据类型。(在Java中的Map集合中 经常会看到这样的泛型)
- <V>:V ---> Value(值)的首字母缩写。表示为类中的 值的数据类型。(在Java中的Map集合中 经常会看到这样的泛型)
- <S>:S --->等同于 T 的意义。可以理解成 Species (种类、物种) 的首字母缩写。表示为类中的泛型 "任意引用数据类型。 表示为类中的泛型 "任意引用数据类型"
- <U>:U ---> 等同于 T 的意义。不是什么单词首字母的缩写。如果非要解释那就是 U的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
- <R>:R ---> 等同于 T 的意义。不是什么单词首字母的缩写。如果非要解释那就是 R的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
- <Q>:Q ---> 等同于 T 的意义。不是什么单词首字母的缩写。如果非要解释那就是 Q的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
- <A>: A ---> 等同于 T 的意义。不是什么单词首字母的缩写。可以理解成 a (一、任一、每个、每一) 的冠词的大写。表示为类中的泛型 "任意引用数据类型"
- <C>:C ---> 等同于 T 的意义。可以理解成为Class(类),Category(种类、分类)的首字母缩写。表示为类中的泛型 "任意引用数据类型"
- <G>:G ---> 等同于 T 的意义。可以理解成 Genre (类型) 的首字母缩写。表示为类中的泛型 "任意引用数据类型"
-
常用的泛型标识符说明:
- 其实泛型的标识符不仅仅局限于上面 我所举例的常用的泛型标识符。泛型标识符可以是 任意的英文字母 大写。甚至是 英文单词 都可以的!
- 只是为了避免歧义。所以才会有这种约定俗成的 命名规则。
- 基本上不会使用英文单词来作为泛型标识符。
- 如果真的拿英文单词来作为泛型表示符的话。如果按照 小驼峰命名法 。和变量名、方法名 就产生了歧义。
- 如果是 按照 大驼峰命名法。和类名 就产生了歧义。
- 如果是 全部大写。和常量名 就产生了歧义。
- 应该避免 产生歧义。而不是制造歧义!
自定义泛型类(常用):
例如:
class Son<T> {
private T t;
private T[] tArr;
public final List<T> list = new ArrayList<>();
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
public List<T> getList(){
return list;
}
}
test:
- ,此时会看到在编译阶段(也就是写代码时),set方法的数据类型就行在 写泛型类的时候 开发者指定的数据类型。达到了数据类型的安全性!
- ,此时会发现,在getT返回设置的属性的时候,就已经是开发者给定的数据类型。从而就可以直接调用String类中的API,就不用再进行类型的强制转换!
- ,而get到 List集合的时候,此时的 add方法中的入参类型已经变成了 在一开始实例化对象的时候。就已经给定了的 数据类型!
- 另外在集合中的表现是一回事,比如:
自定义泛型接口(常用):
例如:
interface Person<T,S,R,U,Q>{
S s();
Map<T,R> setMap(U u,Q q);
default void print(T t,S s){
System.out.println(t.getClass());
System.out.println(s.getClass());
}
}
test:和自定义泛型类是一样的使用,只是接口的泛型是 它的实现类在初始化时、定义时 或者是 子接口定义时、或者是 子抽象类定义时指定的!
自定义泛型类、泛型接口的继承与实现:
形式1(稍微复杂些的)(不常用):
interface Person<Q> {
public abstract void personMethod(Q q);
}
// 不给定父接口泛型
interface SubPerson<Q, C> extends Person<Q> {
default void subMethod(C c) {
System.out.println(c.getClass());
}
}
// 给定父接口泛型,同时声明自己类中的泛型
abstract class AbstractPerson<T, U, S> implements SubPerson<List<String>, HashMap<String, String>> {
@Override
public void personMethod(List<String> list) {
list.add("抽象类向集合中添加元素");
}
public abstract ArrayList<Object> personMethod(T t, U u);
protected void method2(S s) {
System.out.println(s.getClass());
}
}
// 给定父接口的第三个泛型为 Scanner 类型
// 其他两个泛型在实例化 Coder的时候再指定。
// Coder再声明一个自己的泛型。
class Coder<R, T, U> extends AbstractPerson<T, U, Scanner> {
private T t;
private U u;
public Coder(T t,U u){
this.t = t;
this.u = u;
}
public T getT(){
return t;
}
public U getU(){
return u;
}
@Override
public ArrayList<Object> personMethod(T t, U u) {
ArrayList<Object> list = new ArrayList<>();
Collections.addAll(list, t, u);
return list;
}
public Double useR(R r) {
if (r instanceof Integer) {
return Double.valueOf(calculate((Integer) r, 100));
} else {
System.out.println(r.getClass());
}
return Math.PI;
}
private int calculate(int start, int target) {
return (start + target) * (target / 2);
}
}
test:
public static void testGenericExtends() {
Coder<Integer, String, Double> coder =
new Coder("第二个泛型为String类型",Math.E);
coder.method2(new Scanner(System.in));
// 直接以实例化时给定的泛型为准
String t = coder.getT();
System.out.println(t);
Double u = coder.getU();
System.out.println(u);
coder.personMethod(new ArrayList<>());
ArrayList<Object> objects = coder.personMethod("圆周率:", Math.PI);
System.out.println(objects);
Double result = coder.useR(1);
System.out.println(result);
}
形式2(什么泛型都不在定义泛型类、泛型接口的时候 给定)(常用):
- 说明:
- 如果子类、子接口、实现类什么类型都不在定义指定的话。那该类、该接口 就是成为泛型类、泛型接口。并且泛型标识符要和父类、父接口的一模一样!
- 其实子类、子接口、实现类 可以更改父类、父接口的泛型标识符的,但是更改了之后!在定义自己类、接口的时候,也要是这个标识符!(一般不会这样做!)
- 如果子类自己有拓展的标识符的话,那么和父类泛型标识符一样的那个标识符 在子类自己定义类 声明泛型标识符的时候,和父类泛型标识符一样的那个标识符可以放在泛型声明的 钻石运算符中的任意位置 。只是这可能会产生不好理解的情况。
-
interface People<T> { public abstract Integer peopleMethod(T t); } interface Man<T, S> extends People<T> { @Override Integer peopleMethod(T t); default String m1(S s, T t) { return s.getClass().toString() + "和" + t.getClass().toString(); } } abstract class Dad<T, S> implements Man<T, S> { protected T t; protected Dad(T t) { this.t = t; } @Override public Integer peopleMethod(T t) { return (Integer) t + 999999; } } class Child<T, S, L> extends Dad<T, S> { private S s; public Child(T t, S s) { super(t); this.s = s; } public S getS() { return s; } public List<? extends L> getList() { return new LinkedList<>(); } }
test:
public static void main(String[] args) { Child<Integer, String, Serializable> child = new Child<>(9090, "Created at 2021-06-14 08:59"); String s = child.getS(); System.out.println(s); Integer integer = child.peopleMethod(789); System.out.println(integer); Integer t = child.getT(); System.out.println(t); List<? super Serializable> list = child.getList(); list.add(new Date()); list.add(new java.sql.Date(System.currentTimeMillis())); list.add(new ArrayList<Timestamp>().add(null)); ArrayList<Long> currentTimeMillis = new ArrayList<>(); currentTimeMillis.add(new GregorianCalendar().getTime().getTime()); System.out.println(list); System.out.println(currentTimeMillis); System.out.println(child.m1(new String(), Integer.MAX_VALUE)); }
test:
自定义泛型方法(常用):
- 泛型方法声明格式:方法修饰符 <泛型标识符> 返回值类型 方法名(泛型 形参名,[其他数据类型 形参名]){return 返回值;}
-
result:,泛型在编译时的数据类型:public class GenericTest3 { public static void main(String[] args) { Generic<Generic> generic = new Generic<>(); Generic generic1 = generic.isNotGenericMethod(generic); System.out.println(generic); System.out.println(generic1); String s = generic.isGenericMethod("是String类型"); System.out.println(s); ArrayList<Date> dates = new ArrayList<>(); List<ArrayList<Date>> listInstance = Generic.getListInstance(dates); listInstance.add(dates); System.out.println(listInstance.getClass()); } } class Generic<T> { private T t; public Generic(){} // 此方法并不是泛型方法 public T isNotGenericMethod(T t) { return isNotGenericMethod2(t); } // 此方法也不是泛型方法 private T isNotGenericMethod2(T t) { T tt = null; try { // 通过这样的反射方式 创建本类对象,构造器必须显示初始化才可以! tt = (T) t.getClass().getConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); } return tt; } // 是泛型实例方法 public <S> S isGenericMethod(S s){ if ("是String类型".equals(s)){ return s; } return null; } // 是泛型静态方法 public static <U> List<U> getListInstance(U u){ if (u instanceof List){ return new LinkedList<>(); } return null; } }