例如:生产瓶子的厂家,一开始并不知道我们将来会用瓶子装什么,我们什么都可以装,但是有的时候,我们在使用时,想要限定某个瓶子只能用来装什么,这样我们不会装错,而用的时候也可以放心的使用,无需再三思量。我们生活中是在使用这个瓶子时在瓶子上“贴标签”,这样就轻松解决了问题。
还有,在Java中我们在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参表示。那么在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入值就可以了。
受以上两点启发,JDK1.5设计了泛型的概念。泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型。例如:
java.lang.Comparable接口和java.util.Comparator接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0。但是并不确定是什么类型的对象比较大小,之前的时候只能用Object类型表示,使用时既麻烦又不安全,因此JDK1.5就给它们增加了泛型。
public interface Comparable<T>{
int compareTo(T o) ;
}
public interface Comparator<T>{
int compare(T o1, T o2) ;
}
其中就是类型参数,即泛型。
示例代码:
JavaBean:圆类型
package com.atguigu.generic;
public class Circle{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
}
比较器
package com.atguigu.generic;
import java.util.Comparator;
public class CircleRadiusComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
//强制类型转换
Circle c1 = (Circle) o1;
Circle c2 = (Circle) o2;
return Double.compare(c1.getRadius(), c2.getRadius());
}
}
测试类
package com.atguigu.generic;
public class TestNoGeneric {
public static void main(String[] args) {
CircleRadiusComparator com = new CircleRadiusComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
}
}
那么我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患。
因为在设计(编译)Comparator接口时,不知道它会用于哪种类型的对象比较,因此只能将compare方法的形参设计为Object类型,而实际在compare方法中需要向下转型为Circle,才能调用Circle类的getRadius()获取半径值进行比较。
使用泛型:
比较器:
package com.atguigu.generic;
import java.util.Comparator;
public class CircleComparator implements Comparator<Circle> {
@Override
public int compare(Circle o1, Circle o2) {
//不再需要强制类型转换,代码更简洁
return Double.compare(o1.getRadius(), o2.getRadius());
}
}
测试类
package com.atguigu.generic;
public class TestHasGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
// System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
}
}
如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。
因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
<类型>这种语法形式就叫泛型。
其中:
是类型变量(Type Variables),而是代表未知的数据类型,我们可以指定为,, 等,那么<类型>的形式我们成为类型参数;
Comparator这种就称为参数化类型(Parameterized Types)。
自从有了泛型之后,Java的数据类型就更丰富了:
Class:Class
类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class
对象。基本的 Java 类型(boolean
、byte
、char
、short
、int
、long
、float
和 double
)和关键字 void
也表示为 Class
对象。
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{
}
例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
自从JDK1.5引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:集合框架集中的相关接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等等。
下面以Collection、ArrayList集合以及Iterator迭代器为例演示,泛型类与泛型接口的使用。
(1)创建一个Collection集合(暂时创建ArrayList集合对象),并指定泛型为
(2)添加5个[0,100)以内的整数到集合中,
(3)使用foreach遍历输出5个整数,
(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型
(5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型。
package com.atguigu.genericclass.use;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Predicate;
public class TestNumber {
public static void main(String[] args) {
Collection<Integer> coll = new ArrayList<Integer>();
Random random = new Random();
for (int i = 1; i <= 5 ; i++) {
coll.add(random.nextInt(100));
}
System.out.println("coll中5个随机数是:");
for (Integer integer : coll) {
System.out.println(integer);
}
coll.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
});
System.out.println("coll中删除偶数后:");
Iterator<Integer> iterator = coll.iterator();
while(iterator.hasNext()){
Integer number = iterator.next();
System.out.println(number);
}
}
}
(1)声明矩形类Rectangle,包含属性长和宽,属性私有化,提供有参构造、get/set方法、重写toString方法,提供求面积和周长的方法。
(2)矩形类Rectangle实现java.lang.Comparable接口,并指定泛型为,重写int compareTo(T t)方法,按照矩形面积比较大小,面积相等的,按照周长比较大小。
(3)在测试类中,创建Rectangle数组,并创建5个矩形对象
(4)调用Arrays的sort方法,给矩形数组排序,并显示排序前后的结果。
package com.atguigu.genericclass.use;
public class Rectangle implements Comparable<Rectangle>{
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double area(){
return length * width;
}
public double perimeter(){
return 2 * (length + width);
}
@Override
public String toString() {
return "Rectangle{" +
"length=" + length +
", width=" + width +
",area =" + area() +
",perimeter = " + perimeter() +
'}';
}
@Override
public int compareTo(Rectangle o) {
int compare = Double.compare(area(), o.area());
return compare != 0 ? compare : Double.compare(perimeter(),o.perimeter());
}
}
package com.atguigu.genericclass.use;
import java.util.Arrays;
public class TestRectangle {
public static void main(String[] args) {
Rectangle[] arr = new Rectangle[4];
arr[0] = new Rectangle(6,2);
arr[1] = new Rectangle(4,3);
arr[2] = new Rectangle(12,1);
arr[3] = new Rectangle(5,4);
System.out.println("排序之前:");
for (Rectangle rectangle : arr) {
System.out.println(rectangle);
}
Arrays.sort(arr);
System.out.println("排序之后:");
for (Rectangle rectangle : arr) {
System.out.println(rectangle);
}
}
}
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。
语法格式:
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【extends 父接口们】{
}
注意:
示例代码:
例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是’A’,‘B’,‘C’,‘D’,‘E’。那么我们在设计这个学生类时,就可以使用泛型。
package com.atguigu.genericclass.define;
public class Student<T>{
private String name;
private T score;
public Student() {
super();
}
public Student(String name, T score) {
super();
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 "姓名:" + name + ", 成绩:" + score;
}
}
在使用这种参数化的类与接口时,我们需要指定泛型变量的实际类型参数:
(1)实际类型参数必须是引用数据类型,不能是基本数据类型
(2)子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时,
package com.atguigu.genericclass.define;
//ChineseStudent不再是泛型类
public class ChineseStudent extends Student<String>{
public ChineseStudent() {
super();
}
public ChineseStudent(String name, String score) {
super(name, score);
}
}
public class Rectangle implements Comparable<Rectangle>
public interface Iterable<T>
public interface Collection<E>extends Iterable<E>
public interface List<E>extends Collection<E>
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable
(3)在创建泛型类的对象时指定类型变量对应的实际类型参数
package com.atguigu.genericclass.define;
public class TestStudent {
public static void main(String[] args) {
//语文老师使用时:
Student<String> stu1 = new Student<String>("张三", "良好");
ChineseStudent chineseStudent = new ChineseStudent("张三", "良好");
//数学老师使用时:
//Student stu2 = new Student("张三", 90.5);//错误,必须是引用数据类型
Student<Double> stu2 = new Student<Double>("张三", 90.5);
//英语老师使用时:
Student<Character> stu3 = new Student<Character>("张三", 'C');
//错误的指定
//Student
}
}
JDK1.7支持简写形式:Student stu1 = new Student<>(“张三”, “良好”);
指定泛型实参时,必须左右两边一致,不存在多态现象
在java.util.Arrays数组工具类中,有很多泛型方法,例如:
package com.atguigu.genericmethod;
import java.util.Arrays;
import java.util.List;
public class TestArrays {
public static void main(String[] args) {
List<String> list = Arrays.asList("java", "world", "hello", "atguigu");
System.out.println(list);
String[] arr = {"java", "world", "hello"};
String[] strings = Arrays.copyOf(arr, arr.length * 2);
System.out.println(Arrays.toString(strings));
}
}
泛型方法在调用时,由实参的类型确定泛型方法类型变量的具体类型。
前面介绍了在定义类、接口时可以声明<类型变量>,在该类的方法和属性定义、接口的方法定义中,这些<类型变量>可被当成普通类型来用。但是,在另外一些情况下,
(1)如果我们定义类、接口时没有使用<类型变量>,但是某个方法形参类型不确定时,这个方法可以单独定义<类型变量>;
(2)另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法的形参类型不确定时,静态方法可以单独定义<类型变量>。
语法格式:
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
示例代码:
我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。
package com.atguigu.genericmethod;
public class MyArrays {
public static <T> void sort(T[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-i; j++) {
if(((Comparable<T>)arr[j]).compareTo(arr[j+1])>0){
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
package com.atguigu.genericmethod;
import com.atguigu.generic.Circle;
import java.util.Arrays;
public class MyArraysTest {
public static void main(String[] args) {
int[] arr = {3,2,5,1,4};
// MyArrays.sort(arr);//错误的,因为int[]不是对象数组
String[] strings = {"hello","java","chai"};
MyArrays.sort(strings);
System.out.println(Arrays.toString(strings));
Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
MyArrays.sort(circles); //编译通过,运行报错,Circle没有实现Comparable接口
}
}
当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。
语法格式:
<类型变量 extends 上限>
如果有多个上限
<类型变量 extends 上限1 & 上限2>
如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。
如果在声明<类型变量>时没有指定任何上限,默认上限是java.lang.Object。
例如:我们要声明一个两个数算术运算的工具类,要求两个数必须是Number数字类型,并且实现Comparable接口。
package com.atguigu.limmit;
import java.math.BigDecimal;
import java.math.BigInteger;
public class NumberTools<T extends Number & Comparable<T>>{
private T a;
private T b;
public NumberTools(T a, T b) {
super();
this.a = a;
this.b = b;
}
public T getSum(){
if(a instanceof BigInteger){
return (T) ((BigInteger) a).add((BigInteger)b);
}else if(a instanceof BigDecimal){
return (T) ((BigDecimal) a).add((BigDecimal)b);
}else if(a instanceof Byte){
return (T)(Byte.valueOf((byte)((Byte)a+(Byte)b)));
}else if(a instanceof Short){
return (T)(Short.valueOf((short)((Short)a+(Short)b)));
}else if(a instanceof Integer){
return (T)(Integer.valueOf((Integer)a+(Integer)b));
}else if(a instanceof Long){
return (T)(Long.valueOf((Long)a+(Long)b));
}else if(a instanceof Float){
return (T)(Float.valueOf((Float)a+(Float)b));
}else if(a instanceof Double){
return (T)(Double.valueOf((Double)a+(Double)b));
}
throw new UnsupportedOperationException("不支持该操作");
}
public T getSubtract(){
if(a instanceof BigInteger){
return (T) ((BigInteger) a).subtract((BigInteger)b);
}else if(a instanceof BigDecimal){
return (T) ((BigDecimal) a).subtract((BigDecimal)b);
}else if(a instanceof Byte){
return (T)(Byte.valueOf((byte)((Byte)a-(Byte)b)));
}else if(a instanceof Short){
return (T)(Short.valueOf((short)((Short)a-(Short)b)));
}else if(a instanceof Integer){
return (T)(Integer.valueOf((Integer)a-(Integer)b));
}else if(a instanceof Long){
return (T)(Long.valueOf((Long)a-(Long)b));
}else if(a instanceof Float){
return (T)(Float.valueOf((Float)a-(Float)b));
}else if(a instanceof Double){
return (T)(Double.valueOf((Double)a-(Double)b));
}
throw new UnsupportedOperationException("不支持该操作");
}
}
测试类
package com.atguigu.limmit;
public class NumberToolsTest {
public static void main(String[] args) {
NumberTools<Integer> tools = new NumberTools<Integer>(8,5);
Integer sum = tools.getSum();
System.out.println("sum = " + sum);
Integer subtract = tools.getSubtract();
System.out.println("subtract = " + subtract);
}
}
我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable接口类型。
package com.atguigu.limmit;
public class MyArrays {
public static <T extends Comparable<T>> void sort(T[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-i; j++) {
if(arr[j].compareTo(arr[j+1])>0){
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
测试类
package com.atguigu.limmit;
import com.atguigu.generic.Circle;
import java.util.Arrays;
public class MyArraysTest {
public static void main(String[] args) {
int[] arr = {3,2,5,1,4};
// MyArrays.sort(arr);//错误的,因为int[]不是对象数组
String[] strings = {"hello","java","chai"};
MyArrays.sort(strings);
System.out.println(Arrays.toString(strings));
Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
// MyArrays.sort(circles); //编译报错
}
}
当使用参数化类型的类或接口时,如果没有指定泛型,那么会怎么样呢?
会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。
package com.atguigu.limmit;
import java.util.ArrayList;
import java.util.Collection;
public class TestErase {
public static void main(String[] args) {
NumberTools tools = new NumberTools(8,5);
Number sum = tools.getSum();//自动按照Number处理
System.out.println("sum = " + sum);
Number subtract = tools.getSubtract();
System.out.println("subtract = " + subtract);
Collection coll = new ArrayList();
coll.add("hello");
coll.add(1);
for (Object o : coll) {//自动按照Object处理
System.out.println(o);
}
}
}
声明一个方法,形参是Collection,但是元素类型不确定,怎么办?
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class TestProblem {
public static void m1(Collection<Object> coll){
for (Object o : coll) {
System.out.println(o);
}
}
public static void m2(Collection coll){
for (Object o : coll) {
System.out.println(o);
}
}
public static <T> void m3(Collection<T> coll){
for (T o : coll) {
System.out.println(o);
}
}
public static void main(String[] args) {
m1(new ArrayList<Object>());//Collection
m1(new ArrayList<>());//Collection coll = new ArrayList<>();//与上面完全等价
m1(new ArrayList());//Collection coll = new ArrayList();//有警告
// m1(new ArrayList());//Collection coll = new ArrayList();//错误
//编译看左边,左边泛型擦除,此处泛型按照Object处理,右边泛型指定啥都没用
m2(new ArrayList<Object>());//Collection coll = new ArrayList();
m2(new ArrayList<>());//Collection coll = new ArrayList<>();//与上面完全等价
m2(new ArrayList());//Collection coll = new ArrayList();//泛型擦除
m2(new ArrayList<String>());//Collection coll = new ArrayList();
m3(new ArrayList<Object>());//Collection coll = new ArrayList();
m3(new ArrayList<>());//Collection<> coll = new ArrayList<>();//与上面完全等价
m3(new ArrayList());//Collection coll = new ArrayList();//有警告
m3(new ArrayList<String>());//Collection coll = new ArrayList();
}
}
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class TestWildcard {
public static void m4(Collection<?> coll){
for (Object o : coll) {
System.out.println(o);
}
}
public static void main(String[] args) {
//右边泛型指定为任意类型或不指定都可以
m4(new ArrayList<Object>());//Collection> coll = new ArrayList();
m4(new ArrayList<>());//Collection> coll = new ArrayList<>();
m4(new ArrayList());//Collection> coll = new ArrayList();
m4(new ArrayList<String>());//Collection> coll = new ArrayList();
}
}
类型通配符 ? 有三种使用形式:
案例:
声明一个集合工具类MyCollections,要求包含:
package com.atguigu.wildcard;
import java.util.Collection;
public class MyCollections {
public static boolean different(Collection<?> c1, Collection<?> c2){
for (Object o : c1) {
if(c2.contains(o)){
return false;
}
}
return true;
}
public static <T> void addAll(Collection<? super T> c1, T... args){
for (int i = 0; i < args.length; i++) {
c1.add(args[i]);
}
}
public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
for (T t : src) {
dest.add(t);
}
}
}
测试类
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class MyCollectionsTest {
public static void main(String[] args) {
Collection<Integer> c1 = new ArrayList<Integer>();
MyCollections.addAll(c1,1,2,3,4,5);
System.out.println("c1 = " + c1);
Collection<String> c2 = new ArrayList<String>();
MyCollections.addAll(c2,"hello","java","world");
System.out.println("c2 = " + c2);
System.out.println("c1 != c2 " + MyCollections.different(c1, c2));
Collection<Object> c3 = new ArrayList<>();
MyCollections.copy(c3,c1);
MyCollections.copy(c3,c2);
System.out.println("c3 = " + c3);
}
}
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class TestWildcardProblem {
public static void main(String[] args) {
Collection<?> c1 = new ArrayList<>();
// c1.add(new Object());
// c1.add("hello");
// c1.add(1);
//不能添加,c1只读
Collection<? extends Object> c2 = new ArrayList<>();
// c2.add(new Object());
// c2.add("hello");
// c2.add(1);
//不能添加,c2只读
Collection<? super Number> c3 = new ArrayList<>();
// c3.add(new Object());
// c3.add("hello");
c3.add(1);
//可以添加Number对象或Number子类对象
}
}
1 != c2 " + MyCollections.different(c1, c2));
Collection c3 = new ArrayList<>();
MyCollections.copy(c3,c1);
MyCollections.copy(c3,c2);
System.out.println("c3 = " + c3);
}
}
### 12.5.4 使用类型通配符来指定类型参数的问题
```java
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class TestWildcardProblem {
public static void main(String[] args) {
Collection> c1 = new ArrayList<>();
// c1.add(new Object());
// c1.add("hello");
// c1.add(1);
//不能添加,c1只读
Collection extends Object> c2 = new ArrayList<>();
// c2.add(new Object());
// c2.add("hello");
// c2.add(1);
//不能添加,c2只读
Collection super Number> c3 = new ArrayList<>();
// c3.add(new Object());
// c3.add("hello");
c3.add(1);
//可以添加Number对象或Number子类对象
}
}