Java JDK1.5: 泛型 新特性的讲解说明

每博一文案

听到过这样一句话:“三观没有标准。在乌鸦的世界里,天鹅也有罪。”
环境、阅历的不同,造就了每个人独有的世界观、人生观、价值观。
三观并无对错高下,只有同与不同。恰如飞鸟不用和游鱼同行,高山不必同流水相逢。
总用自己的尺子去度量别人,无疑是一种狭隘。面对不同时,只有懂得尊重对方,才能跳出固有的认知,看得更高更远。
这个世界上没有标准答案,人不是只有一种活法。

@

1. 泛型概述

在任何不重要的软件项目中,错误都只是生活中的事实。 仔细的计划,编程和测试可以帮助减少他们的普遍性,但不知何故,在某个地方,他们总是会找到一种方法来进入你的代码。 随着新功能的推出以及您的代码库规模和复杂性的增加,这一点变得尤为明显。

幸运的是,一些错误比其他错误更容易被发现。例如,编译时错误可以在早期发现; 你可以使用编译器的错误信息来找出问题所在,然后修正它。运行时错误,然而,可能是更多的问题; 它们并不总是立即出现,而且当它们这样做时,它可能在程序中的某一点远离问题的实际原因。

泛型通过在编译时检测更多的错误来增加代码的稳定性。

  • 泛型的设计背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为 Object,JDK1.5 之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

  • 泛型的概述

    • 所谓的泛型,就是允许在定义类,接口时通过一个标识<T>类中某个属性的类型或者时某个方法的返回值以及参数类型。或者换句话说:就是限定类/接口/方法(参数/返回值)的类型。特别的就是限定集合中存储的数据类型。这个类型参数将在使用时(例如:继承或实现这个接口,用这个类型声明变量,创建对象时) 确定(即传入实际的类型参数,也称为 “类型实参”)。
    • JDK1.5 以后,java 引入了 “参数化类型 (Parameterized type)” 的概念,允许我们在创建集合时再指定集合元素的类型,正如: List<String> ,这表明该 List 集合只能存储 字符串String类型的对象
    • JDK1.5 改写了集合框架中全部接口和类,为这些接口,类增加了泛型支持,从而可以在声明集合变量,创建集合对象时传入 类型实参。

2. 为什么要使用泛型

那么为什么要有泛型呢,直接Object 不是也可以存储数据吗?

  1. 解决元素存储的安全性问题,好比商品,药品标签,不会弄错
  2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿药,药品都要辨别,是否拿错误。

如下举例:

没有使用泛型

我们创建一个 ArrayList 集合不使用泛型,默认存储的是 Object 类型,用来存储 学生的成绩 int 类型,添加成绩时不小心添加了

学生的姓名,因为该集合没有使用泛型,默认是Object 类型,什么都可以存储,所以也把这个输入错误的 学生姓名给存储进去了。

当我们把 ArrayList 集合当中的存储的数据取出 (强制转换为 int 类型的数据成绩时),报异常:java.lang.ClassCastException 类型转换异常。因为你其中集合当中存储了一个学生的姓名,String 是无法强制转换成 int 类型的。


import java.util.ArrayList; public class GenericTest {
// 没有使用泛型
public static void main(String[] args) {
// 定义了泛型没有使用的话,默认是 Object 类型存储
ArrayList arrayList = new ArrayList(); // 添加成绩
arrayList.add(99);
arrayList.add(89);
arrayList.add(79); // 问题一:存储的类型不安全
// 不小心添加了一个学生的姓名
arrayList.add("Tom"); for (Object o : arrayList) {
// 问题二: 强转时,可能出现ClassCastException 异常
int stuScore = (Integer)o; // 因为你存储的类型可能与强制转换的类型,没有继承关键,实例关系
// 导致转换失败.
System.out.println(stuScore);
}
}
}

使用了泛型

将 ArrayLsit 集合定义为 ArrayList<Integer> 使用上泛型,限定了该集合只能存储 Integer 类型的数据,其它类型的无法存入进到集合当中,编译的时候就会报错。

import java.util.ArrayList;

public class GenericTest {
// 使用上泛型
public static void main(String[] args) {
// 泛型限定了存储类型,泛型指定定义引用类型,基本数据类型不行
ArrayList<Integer> arrayList = new ArrayList<Integer>();
// 使用了泛型: 就会进行类型检查,保证数据的安全
arrayList.add(99); // 包装类,自动装箱
arrayList.add(78);
arrayList.add(76);
arrayList.add(89);
arrayList.add(88); // arrayList.add("Tom"); // 存储不符合泛型的数据,编译无法通过。
for (Integer integer : arrayList) {
int stuScore = integer; // 不需要强制转换自动拆箱 System.out.println(stuScore);
}
} }

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 java.lang.ClassCastException异常。同时代码更加简洁,健壮

简而言之,在定义类,接口和方法时,泛型使 类型(类和接口)成为参数。 就像方法声明中使用的更熟悉的 形式参数 一样,类型参数为您提供了一种方法, 让您在不同的输入中重用相同的代码。区别在于形式参数的输入是值,而类型参数的输入是类型。

使用泛型的代码比非泛型代码有许多优点:

  • 编译时更强大的类型检查。

    Java 编译器将强类型检查应用于通用代码,并在代码违反类型安全性时发出错误。修复编译时错误比修复运行时错误要容易得多。

  • 消除强制转换 。

3. 集合中使用泛型

在 Java SE 7 和更高版本中,只要编译器可以根据上下文确定或推断类型参数,就可以用一组空类型参数(<>)替换调用泛型类的构造函数所需的类型参数。 这一对尖括号,<>,非正式地称为钻石。例如,您可以使用以下语句创建 Box <Integer> 的实例:

List<String> list = new ArrayList<String>();

在 List集合中使用泛型,存取数据



import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class GenericTest2 {
// List 集合中使用泛型存取数据
public static void main(String[] args) {
// 使用泛型<String> 限定 List 集合存储的类型对象,
// 注意:泛型中只能存储引用类型的,基本数据类型不可以(int,double)
List<String> list = new ArrayList<>(); list.add("Tom");
list.add("李华");
list.add("张三"); Iterator<String> iterator = list.iterator(); while(iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
} }
}


在Set集合中使用泛型,存取数据

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; public class GenericTest2 {
public static void main(String[] args) {
// Set 集合中使用泛型存取数据
// 使用泛型<Integer> 限定Set 集合存储的类型对象
// 注意:泛型中只能存储引用类型的,基本数据类型不可以(int,double)
Set<Integer> set = new HashSet<>(); set.add(1);
set.add(2);
set.add(3); Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next);
}
}

在 Map 集合中使用泛型存取数据


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; public class GenericTest2 {
// Map 集合中使用泛型存取数据
public static void main(String[] args) {
// 使用泛型<String,Integer> 限定 Map 集合存储的类型对象
// 注意:泛型中只能存储引用类型的,基本数据类型不可以(int,double)
Map<String, Integer> map = new HashMap<>();
map.put("Tom", 99);
map.put("李华", 89);
map.put("张三", 79); Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue()); }
}
}

泛型是可以被嵌套的,多层嵌套泛型Set<Map.Entry<T>>

Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
// 这里泛型嵌套了两层: Set<Map.Entry<>> 这是一层
// 内部还有一层: Map.Entry<String,Integer> 这是第二层

4. 自定义泛型结构

4.1 输入参数命名约定

按照惯例,类型参数名称是单个大写字母。这与你已经知道的变量命名约定形成了鲜明的对比 ,并且有很好的理由:没有这个约定,很难区分类型变量和普通类或接口名称。

最常用的类型参数名称是:

  • E - 元素(由 Java 集合框架广泛使用)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

4.2 自定义泛型结构的接口

一个泛型接口的定义格式如下:

接口中的泛型可以定义一个,也可以定义多个,多个泛型 T 使用逗号, 分隔开来。

public interface MyGeneric<T1, T2, ..., Tn> {
}

接口中的泛型 T ,可以在抽象方法中应用起来:

在抽象方法中作为 方法值 T

public interface MyGeneric<T> {

    // 定义含有泛型的 T 抽象方法:泛型作为返回值;
public T fun();
}

在抽象方法中作为 参数 T

public interface MyGeneric<T> {
// 定义含有泛型的 T 抽象方法: 泛型作为参数
public void fun2(T t); }

既作为抽象方法中的返回值 T ,又作为抽象方法中的 参数 T

public interface MyGeneric<T> {

    // 定义含有泛型的 T 抽象方法: T 泛型作为返回值,参数
public T fun3(T t); }
public interface MyGeneric<T> {

    // 定义含有泛型的 T 抽象方法:泛型作为返回值;
public T fun(); // 定义含有泛型的 T 抽象方法: 泛型作为参数
public void fun2(T t); // 定义含有泛型的 T 抽象方法: T 泛型作为返回值,参数
public T fun3(T t); }

4.3 自定义泛型结构的类

一个泛型类的定义格式如下:

class name<T1, T2, ..., Tn> { /* ... */ }

由尖括号(<>)分隔的类型参数部分在类名后面。它指定了类型参数(也称为类型变量)T1,T2,...,和Tn。

要更新 Box 类以使用泛型,可以通过将代码 public class Box 更改为 public class Box <T> 来创建泛型类型声明。 这引入了类型变量 T,可以在类中的任何地方(非静态方法,属性,参数,返回值)使用

把一个集合中的内容限制为一个特定的数据类型,这就是泛型背后的核心思想

注意:含有泛型的类的构造器的创建,和没有使用泛型一样创建构造器,就可以了,不要附加你的奇思妙想

如下:

public class Box<T> {

    // 泛型<T> 应用类属性当中
T t; // 无参构造器
public Box() { } // 带泛型参数构造器
public Box(T t) {
this.t = t;
}
}

具体如下代码


public class Box<T> { // 泛型<T> 应用类属性当中
T t; // 无参构造器
public Box() { } // 带泛型参数构造器
public Box(T t) {
this.t = t;
} // 泛型<T> 应用到方法返回值中
public T fun() {
return null;
} // 泛型<T> 应用到参数当中
public void fun2(T t) { } // 泛型<T> 应用到返回值,参数当中
public T set(T t) {
return null;
} }

注意异常类中不可以使用泛型<T> 编译无法通过

不可以使用泛型创建数组,编译无法通过

但是我们可以用,特殊方法实现如下:通过创建一个 new Object[] 的数组,再强制转换为 T[] 泛型数组,因为泛型默认没有使用的话,是 Object 类型。

泛型不可以作为实例化对象出现,因为泛型是在实例化的时候才确定该泛型具体的类型是什么的,如果直接对泛型实例化,你都不知道实例化成什么类型的对象的。 所以直接编译无法通过。

如下:

4.3.1 含有泛型的类实例化对象

带有泛型的实例化:一定要在类名/接口后面指定类型参数的值(类型)。如下:

List<String> list = new ArrayList<String>();

**JDK 7 ** 版本以后可以省略 = 等号右边的 <>泛型声明了,只要声明左边的就可以了。就算你右边附加上了<>泛型声明, 默认也是会被省略的。

List<String> list = new ArrayList<>();

注意泛型和集合一样,只能存储引用类型的数据,泛型不能用基本数据类型填充,必须使用引用类型填充,这里包装类就起到了非常重要的作用了。

4.4 自定义泛型结构的方法

泛型方法 是引入自己的类型参数的方法。这与声明泛型类型相似,但是类型参数的作用域仅限于声明它的方法。允许使用静态和非静态泛型方法,以及泛型类构造函数。

泛型方法的语法包括一个类型参数列表,里面的尖括号出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。

泛型方法,与该类是不是含有泛型类无关 ,换句话说:泛型方法所属的类是不是泛型类都没有关系,同样可以定义泛型方法。

定义非静态泛型方法格式如下:

访问权限修饰符 <泛型>(表示泛型方法不可省略) 返回类型 方法名(参数类型 参数名) 抛出的异常 

public <E> E fun3(E e) {
return e;
}

定义静态泛型方法格式如下:在附加上一个 static 静态修饰,

访问权限修饰符 static  <泛型>(表示泛型方法不可省略) 返回类型 方法名(参数类型 参数名) 抛出的异常
public static <E> E fun4(E e) {
System.out.println("静态:泛型方法,泛型作为返回值,参数"+e);
return e;
}

package blogs.blog8; public class GenericTest4 {
public static void main(String[] args) {
// 泛型方法的调用: } } class MyClass { // 泛型方法,无返回值的
public <E> void fun() {
System.out.println("泛型方法,无返回值,无参数的");
} // 泛型方法: 泛型作为参数传入
public <E> void fun2(E e) {
System.out.println("泛型方法,无返回值,有泛型参数"+e);
} // 泛型方法: 泛型作为返回值,和参数
public <E> E fun3(E e) {
System.out.println("泛型方法,泛型作为返回值,参数"+e);
return e;
} public static <E> E fun4(E e) {
System.out.println("静态:泛型方法,泛型作为返回值,参数"+e);
return e;
}
}

泛型方法的调用和没有普通的方法一样的方式调用,没有什么区别,区别是在 JVM 运行编译的时候的不同。调用是一样的方法如下

package blogs.blog8;

public class GenericTest4 {
public static void main(String[] args) {
// 泛型方法的调用:
MyClass myClass = new MyClass();
myClass.fun();
myClass.fun2(new String("Hello"));
String s = myClass.fun3("你好世界");
System.out.println(s); System.out.println("**********");
String s2 = MyClass.fun4("Hello Wrold");
System.out.println(s2); } } class MyClass { // 泛型方法,无返回值的
public <E> void fun() {
System.out.println("泛型方法,无返回值,无参数的");
} // 泛型方法: 泛型作为参数传入
public <E> void fun2(E e) {
System.out.println("泛型方法,无返回值,有泛型参数"+e);
} // 泛型方法: 泛型作为返回值,和参数
public <E> E fun3(E e) {
System.out.println("泛型方法,泛型作为返回值,参数"+e);
return e;
} public static <E> E fun4(E e) {
System.out.println("静态:泛型方法,泛型作为返回值,参数"+e);
return e;
}
}

泛型方法在你调用的时候,就会推断出你要 <E> 泛型的具体的类型了。 如下:

5. 泛型在继承上的体现

关于父类中含有泛型<> 对应的子类的对父类泛型的处理情况:如下

  • 父类有泛型,子类继承父类:不保留父类中的泛型,擦除了父类中的泛型(默认是 Object)
// 父类
class Father<T1,T2> { } // 子类没有保留父类的泛型,擦除了: 等价于class Son extends Father<Object,Object>{}
class Son1 extends Father{ }
  • 父类有泛型,子类继承父类:并指明了父类的泛型(具体类型)

注意: 由于子类在继承泛型的父类/实现的接口时,指明了泛型具体是什么类型,所以实例化子类对象时,不再需要指明泛型了。

但是单独实例化父类还是要指明其泛型的具体类型的。

// 父类
class Father<T1,T2> { } // 子类保留了父类的泛型,并指明了父类中泛型的具体类型
class Son2 extends Father<String,Integer> { }
  • 父类有泛型,子类继承父类:并保留了父类的泛型(并没有指明具体类型)

注意: 因为子类并没有指明父类泛型的具体类型,所以子类要沿用上父类的泛型<>从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> { } // 子类保留了父类的泛型,并没有指明父类的具体类型
// 注意:因为没有指明父类泛型的具体类型,所以子类要沿用上父类的泛型<>从而对父类上的泛型(赋予具体类型)
class Son3<T1,T2> extends Father<T1,T2> { }
  • 父类有泛型,子类继承父类:并保留了父类的泛型(并没有指明具体类型),外加子类定义自己独有的泛型

注意: 因为子类并没有指明父类泛型的具体类型,所以子类要沿用上父类的泛型<>从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> { } // 子类继承父类:并保留了父类的泛型(并没有指明具体类型),外加子类定义自己独有的泛型
class Son4<T1,T2,E,E2> extends Father<T1,T2> { }
  • 父类有泛型,子类继承父类:并保留了父类的部分泛型(部分指明了父类的泛型具体类型,部分没有指明父类的泛型具体类型),外加子类定义自己独有的泛型

注意: 因为子类并没有指明父类泛型的具体类型,所以子类要沿用上父类的泛型<>从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> { } // 父类有泛型,子类继承父类:并保留了父类的`部分`泛型(部分指明了父类的泛型具体类型,部分没有指明父类的泛型具体类型),外加子类定义自己独有的泛型
class Son4K<T2,E,E2> extends Father<String,T2> { }

6. <泛型> 中的 通配符

泛型的多态性上的使用

  • 注意: ArrayList<String> 和 ArrqayList<Intger> 是两种不同的类型,虽然它们整体上是都是 ArrayList 集合类,但是所指定的泛型(指明的类型不同,导致存储的类型不同) ,编译时被鉴定为了两种不同的类型,无法相互引用赋值。但是,在运行时只有一个 ArrayList 被加载到 JVM 中,因为类一样的,所存储的类型不同而已,类仅仅只会加载一次到i内存当中。
  • 简单的说:就是泛型不同的不可以相互引用赋值 ,编译无法通过。

如下代码:

两个泛型相同的类型可以引用赋值如下

根据上述情况,我们对不同的泛型(具体指明的类型) 需要定义不同的方法了。注意: 如果是的泛型<指明的具体类型>不同是无法重载方法的。

import java.util.ArrayList;
import java.util.List; public class GenericTest6 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>(); fun1(arrayList); // <String>
fun2(arrayList2); // <Integer>
} public static void fun1(List<String> list) { } public static void fun2(List<Integer> list) { }
}

为了解决上述,因为泛型(指明的具体类型)的不同,而导致的繁琐操作。Java为程序员提供了 通配符?

在泛型代码中,被称为通配符的是 一个问号(?) 表示未知类型。 通配符可用于多种情况:作为参数的类型、字段或局部变量;

有时作为返回类型(尽管更好的编程实践更具体)。比如:List ,MapList<?> 可以理解为是Lis<String>t、List<Object>等各种泛型List的父类。

通配符永远不会用作泛型方法调用,泛型类实例创建或超类型的类型参数。

举例:

import java.util.ArrayList;
import java.util.List; public class GenericTest6 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>();
ArrayList<?> arrayList3 = new ArrayList<>();
arrayList3 = arrayList; // 尽管 <String> 不同,都可以赋值给 <?> 通配符
arrayList3 = arrayList2; // 尽管 <Integer> 不同,都可以赋值给 <?> 通配符
}
}

举例:

import java.util.ArrayList;
import java.util.List; public class GenericTest6 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>(); fun(arrayList); // <String>
fun(arrayList2); // <Integer>
} public static void fun(List<?> list) { }
}

对于 List<?>Map<?,?>Set<?> 等等对象读取(添加)数据元素时,永远时可以添加成功的,因为不管 list<泛型> 中的泛型具体指明的是什么类型都,它们都是包含了 Object ,都可以被 ? 接受住。

我们可以调用 get() 方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object

如下代码:

import java.util.ArrayList;
import java.util.List; public class GenericTest6 {
public static void main(String[] args) {
List<?> list3 = null;
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World"); // 将 List<String> 引用赋值给 List<?>
list3 = list;
// 获取值
Object o = list3.get(0);
System.out.println(o);
Object o2 = list3.get(1);
System.out.println(o2); System.out.println("*******************************"); List<Integer> list2 = new ArrayList<>();
list2.add(99);
list2.add(999); // 将 List<Integer> 引用赋值给 List<?>
list3 = list2;
// 获取值
Object o3 = list3.get(0);
System.out.println(o3);
Object o4 = list3.get(1);
System.out.println(o4); }
}

对于 List<?>Map<?,?>Set<?> 等等对象读取(添加)数据元素时,报编译无法通过。因为我们不知道 的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。

将任意元素加入到其中不是类型安全的

Collection c = new ArrayList();

c.add(new Object()); // 编译时错误

因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。

举例:

6.1 通配符的使用:注意点

  • 注意点1:编译错误:通配符不能用在泛型方法声明上,返回值类型前面也<>不能使用 。

  • 注意点2:编译错误:通配符不能用在泛型类的声明上。

  • 注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象。

6.2 有限制的通配符

6.2.1 无界通配符

<?> 允许所以泛型的引用调用。称为 无界通配符 。上面介绍了,就这里就不多介绍了。

6.2.2 上界通配符

< ? extends XXX> 上限 extends :使用时指定的类型必须是继承某个类(XXX),或者实现了某个接口(X

xX)。 即 <=

比如:

< ? extends Person>;  // (无穷小, Person] 只允许泛型为 Person 以及 Person 子类的引用调用
< ? extends Comparable>; // 只允许泛型为实现 Comparable 接口的实现类的引用调用

举例:

package blogs.blog8;

import java.util.ArrayList;
import java.util.List; public class GenericTest7 {
public static void main(String[] args) {
List< ? extends Person> list = null; // <=
List<Person> list2 = new ArrayList<>();
List<Student> list3 = new ArrayList<>();
List<Object> list4 = new ArrayList<>(); // list 可以存取 <= Person 的类型
list = list2;
list = list3;
list = list4; // 这就Object > Person 不行 }
} class Person { } class Student extends Person { }

注意: 同样 <? extends XXX> 下界通配符,的引用不可以添加数据(因为是未知的类型),但是可以获取起其中的数据,返回 XXX 最大的。

“写入” 添加数据,无法写入

"读":获取数据: 返回对应最大的 XXX

package blogs.blog8;

import java.util.ArrayList;
import java.util.List; public class GenericTest7 {
public static void main(String[] args) {
List< ? extends Person> list = null; // <=
List<Person> list2 = new ArrayList<>();
List<Student> list3 = new ArrayList<>();
List<Object> list4 = new ArrayList<>();
list3.add(new Student() ); // list 可以存取 <= Person 的类型
list = list2;
list = list3; // 获取数据 “读”
Person person = list.get(0);
System.out.println(person); }
} class Person { } class Student extends Person { }

6.2.3 下界通配符

< ? super XXX> 下限 super:使用时指定的类型不能小于操作的类 XXX,即 >=

比如:

< ? super Person>;  // [Person, 无穷大] 只允许泛型为 Person 及  Person父类的引用调用

举例:

注意: 同样 <? superXXX> 上界通配符,的引用不可以添加数据(因为是未知的类型),但是可以获取起其中的数据,返回 XXX 最大的。

“写入” 添加数据,无法写入

"读":获取数据: 返回对应最大的 Object

package blogs.blog8;

import java.util.ArrayList;
import java.util.List; public class GenericTest7 {
public static void main(String[] args) {
List< ? super Person> list = null; // >= Person
List<Person> list2 = new ArrayList<>();
List<Student> list3 = new ArrayList<>();
List<Object> list4 = new ArrayList<>();
list3.add(new Student() ); // list 可以存取 >= Person 的类型
list = list2;
list = list4;
list.add(new Person()); // 小于的可以添加上
list.add(new Student()); Object object = list.get(0);
System.out.println(object); } } class Person { } class Student extends Person { }

7. 对泛型的限制(泛型的使用上的注意事项)

要有效地使用 Java 泛型,您必须考虑以下限制:

  • 注意:泛型不能只能填充引用类型,不可填充基本数据类型。使用包装类

  • 注意:泛型不可以无法创建类型参数的实例 E new () 不可以 编译无法通过

  • 注意:不能声明类型是类型参数的静态字段/静态方法中(编译无法通过),但是可以创建静态泛型方法。 因为泛型是实例化对象的时候才确定其指明具体类型,而 静态是在实例化之前的操作。静态泛型方法是:在调用静态泛型方法的时候泛型才确定指明具体类型的。所以没问题。

  • 注意:不能使用带有参数化类型的 cast 或 instanceof

  • 注意:泛型不能创建参数化类型的数组

但是我们可以用,特殊方法实现如下:通过创建一个 new Object[] 的数组,再强制转换为 T[] 泛型数组,因为泛型默认没有使用的话,是 Object 类型。

  • 注意:泛型可以用于创建,捕捉或抛出参数化类型的对象 自定义异常类中不可以用泛型类

  • 不能重载每个过载的形式参数类型擦除到相同的原始类型的方法,简单的说:就是不能通过指明的泛型的不同实现重载的,不满足重载的要求的

8. 泛型应用举例

  • 定义个泛型类 DAO,在其中定义一个 Map 成员变量,Map 的键 为 String 类型,值为 T 类型。

分别创建以下方法:

public void save(String id,T entity): 保存 T 类型的对象到 Map 成员 变量中 。

public T get(String id):从 map 中获取 id 对应的对象。

public void update(String id,T entity):替换 map 中 key 为 id 的内容,改为 entity 对象 。

public List list():返回 map 中存放的所有 T 对象 。

public void delete(String id):删除指定 id 对象 。

定义一个 User 类:

该类包含:private 成员变量(int 类型) id,age;(String 类型)name。

定义一个测试类:

创建 DAO 类的对象, 分别调用其 save、get、update、list、delete 方 法来操作 User 对象,

使用 Junit 单元测试类进行测试。

DAO类,和 User类

package blogs.blog8;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects; public class DAO<T> {
private Map<String, T> map = null; public DAO() {
// 构造器为其Map集合初始化
this.map = new HashMap<>();
} // 保存 T 类型的对象到Map成员变量中
public void save(String id, T entity) {
map.put(id, entity);
} // 从map中获取id对应的对象
public T get(String id) {
return map.get(id);
} // 替换Map中的 key 为 id 的内容,改为 entity对象
public void update(String id, T entity) {
if (map.containsKey(id)) { // 首先判断该修改的 key 是否存在,
// 存在通过 put()添加覆盖
map.put(id, entity);
}
} // 返回map中存放的所以 T 对象
public List<T> list() {
Collection<T> values = map.values();
List<T> list = new ArrayList<T>();
// 注意了: Collection 是 List 的父类接口,如果List 对象不是 Collection 的实例
// 是无法将一个父类强制(向下)为子类的,(这里两个都是接口,不可能有实例的)
// 通过取出所以的values 值赋值到一个新创建的 List 对象当中再返回。
for (T t : values) {
list.add(t);
}
return list; } // 删除指定id对象
public void delete(String id) {
map.remove(id);
}
} class User {
private int id;
private int age;
private String name; public User() { } public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} // 因为存储的是在Map当中所以,Map当中的Key 存储对象需要重写 equals() 和 hashCode() 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return getId() == user.getId() &&
getAge() == user.getAge() &&
Objects.equals(getName(), user.getName());
} @Override
public int hashCode() {
return Objects.hash(getId(), getAge(), getName());
} @Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}

DAOTest

package blogs.blog8;

import day25.DAOS;
import org.junit.Test; import java.util.List; public class DAOTest {
@Test
public void test() {
DAOS<User> dao = new DAOS<User>(); dao.save("1001",new User(1001,34,"周杰伦"));
dao.save("1002",new User(1002,20,"昆菱"));
dao.save("1003",new User(1003,25,"蔡依林")); dao.update("1003",new User(1003,30,"万文山")); dao.delete("1002"); List<User> list = dao.list();
list.forEach(System.out::println); }
}

9. 总结:

  1. 泛型是 JDK5.0 的新特性。
  2. Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
  3. 把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想
  4. 泛型只能填充引用类型,基本数据类型不可填充泛型,使用包装类。
  5. 使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
  6. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验: 泛型要使用一路都用。要不用,一路都不要用。
  7. 自定义泛型类,泛型接口,泛型方法。
  8. 泛型类在父类上的继承变化上的使用。
  9. 泛型中的通配符上的使用:无界通配符<?>,上界通配符< ? extends XXX> (<=),下界通配符 <? super XXX> (>= )
  10. 泛型在使用上的限制以及注意事项。

10. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善。谢谢大家,江湖再见,后会有期!!!

Java JDK1.5: 泛型 新特性的讲解说明的更多相关文章

  1. JAVA JDK1.5-1.9新特性

    1.51.自动装箱与拆箱:2.枚举(常用来设计单例模式)3.静态导入4.可变参数5.内省 1.61.Web服务元数据2.脚本语言支持3.JTable的排序和过滤4.更简单,更强大的JAX-WS5.轻量 ...

  2. Java高新技术 JDK1.5之新特性

      Java高新技术  JDK1.5的新特性 知识概要:                 (1)静态导入 (2)可变参数 (3)增强for循环 (4)基本数据类型的自动拆箱和装箱 静态导入     ...

  3. 各版本JDK1.5-1.8新特性

    概述 一jdk15新特性 泛型 foreach 自动拆箱装箱 枚举 静态导入Static import 元数据Metadata 线程池 Java Generics 二jdk16新特性 Desktop类 ...

  4. Java 8 正式发布,新特性全搜罗

    经过2年半的努力.屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本. Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码: ...

  5. Java引入的一些新特性

    Java引入的一些新特性 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程, ...

  6. 多线程(JDK1.5的新特性互斥锁)

    多线程(JDK1.5的新特性互斥锁)(掌握)1.同步·使用ReentrantLock类的lock()和unlock()方法进行同步2.通信·使用ReentrantLock类的newCondition( ...

  7. 使用示例带你提前了解 Java 9 中的新特性

    使用示例带你提前了解 Java 9 中的新特性 转载来源:https://juejin.im/post/58c5e402128fe100603cc194 英文出处:https://www.journa ...

  8. Java学习之==>Java8 新特性详解

    一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...

  9. JAVA笔记 之 JDK新特性

    JDK1.5新特性1.泛型(Generics) 为集合(collections)提供编译时类型安全,无需每刻从Collections取得一个对象就进行强制转换(cast) 2.增强的for循环(for ...

  10. java--加强之 jdk1.5简单新特性,枚举,注解

    转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9944041 Jdk1.51新特性(静态导入,可变参数,加强for循环,自动拆装箱) 08.ja ...

随机推荐

  1. Python基础之模块:7、项目开发流程和项目需求分析及软件开发目录

    一.项目开发流程 1.项目需求分析 明确项目具体功能: 明确到底要写什么东西,实现什么功能,在这个阶段的具体要询问项目经理和客户的需求 参与人员: 产品经理.架构师.开发经理 技术人员主要职责: 引导 ...

  2. CheckBox 单选实现及取值

    <input name="ck" type="checkbox" value="1"/><span>按计划进行< ...

  3. day41 6-1 安装配置maven & 6-2 创建maven项目 & 6-3 搭建springMVC框架 & 6-4 springMVC常用注解和封装工作单元

    day41 调度器 定义 web.xml配置 控制器Controller 配置自动扫描控制器 在spring-mv.xml中加入 <!-- 启用spring mvc 的注解 --> < ...

  4. 【kafka】JDBC source&sink connect实现数据从Oracle实时同步插入更新到PostgreSQL(PG)

    〇.所需资料 1.JDBC connect的plugins下载地址(confluent) 一.Oracle建表 1.表规划 表名:Test_TimeFormat_Order.Test_Stress_O ...

  5. 【数据库】在公司开发过程中总结的SQL编写规范,参考开发手册

    〇.概述 1.常用资料链接 (1)阿里巴巴开发手册 链接:https://pan.baidu.com/s/1OtOFuItDIP7nchfODGIZwg?pwd=htx0 提取码:htx0 2.包含内 ...

  6. 【重难点】函数式接口、函数式编程、匿名内部类、Lambda表达式、语法糖

    一.函数式接口 1.概念 仅有一个抽象方法的接口 适用于函数式编程(Lambda表达式) 常见:Runnable.Comparator<>.生产型接口Producer的get方法.消费型接 ...

  7. CORS与CSRF在Spring Security中的使用

    背景 在项目使用了Spring Security之后,很多接口无法访问了,从浏览器的网络调试窗看到的是CORS的报错和403的报错 分析 我们先来看一下CORS是什么,和它很相似的CSRF是什么,在S ...

  8. Doris安装部署

    下载安装 Doris运行在Linux环境中,推荐 CentOS 7.x 或者 Ubuntu 16.04 以上版本,同时你需要安装 Java 运行环境(JDK最低版本要求是8) 1.下载安装包 下载地址 ...

  9. CSS中和颜色及渐变

    CSS可以设置的颜色 颜色名称 transparent(全透明黑色) pink yellowgreen 等指定的颜色名称 16进制 #ABCDEF 参数 含义 范围 AB 红色渠道值 00-FF CD ...

  10. webShell攻击及防御

    最近公司项目也是经常被同行攻击,经过排查,基本定位都是挂马脚本导致,所以针对webShell攻击做一下记录. 首先简单说下 什么是webShell? 利用文件上传,上传了非法可以执行代码到服务器,然后 ...