自学内容网 自学内容网

Java之泛型基础

泛型

1 问题引入

在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。

观察下面代码:

public static void main(String[] args) {
    //没有给泛型参数传值,那么泛型默认表示为Object类型
    Collection c = new ArrayList();
    c.add("hello1");
    c.add("hello2");
    c.add("hello3");
    c.add(1);

    for(Object obj : c) {
        String str = (String) obj;
        System.out.println(str);
    }
}

//运行结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    

程序在运行时发生了问题**java.lang.ClassCastException**。

由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。

Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

2 泛型概述

泛型(Generics)的概念是在JDK1.5中引入的,它的主要目的是为了解决类型安全性和代码复用的问题。

泛型是一种强大的特性,它允许我们在定义类、接口和方法时使用参数化类型

泛型基本语法为定义在<>中,例如下面案例:

//T是数据类型,但是不是确定的数据类型
//程序员在使用该类的时候,传入什么具体的类型给T,T就代表什么类型
public class MyClass<T> {
    private T value;

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

    public T getValue() {
        return value;
    }
}

MyClass是一个泛型类,使用类型参数T。我们可以在创建对象时指定具体的类型,例如MyClass<Integer>MyClass<String>

泛型能够使我们编写出来通用的代码,提高代码的可读性和重用性。通过使用泛型,我们可以在类、接口和方法中使用类型参数,使得代码可以处理不同类型的数据,同时保持类型安全。

3 泛型应用

了解泛型的意思之后,接下来可以再看下之前学习过的集合中的泛型:

//Collection接口定义,其是一个泛型接口
public interface Collection<E> {
    //省略...
    
    boolean add(E e);
}

Collection是一个泛型接口,泛型参数是E,add方法的参数类型也是E类型。

在使用Collection接口的时候,给泛型参数指定了具体类型,那么就会防止出现类型转换异常的情况,因为这时候集合中添加的数据已经有了一个规定的类型,其他类型是添加不进来的。

例如下面案例中,我们指定了集合c只能存储String类型数据,则Integer类型的1就无法添加成功。

public static void main(String[] args) {
    Collection<String> c = new ArrayList<String>();
    
    c.add("hello1");
    c.add("hello2");
    c.add("hello3");
    //编译报错,add(E e) 已经变为 add(String e)
    //int类型的数据1,是添加不到集合中去的
    //c.add(1);

    for(String str : c) {
        System.out.println(str);
    }
}

可以看出,传入泛型参数后,add方法只能接收String类型的参数,其他类型的数据无法添加到集合中,同时在遍历集合的时候,也不需要我们做类型转换了,直接使用String类型变量接收就可以了,JVM会自动转换的

Collection<String> c = new ArrayList<String>();

可简写为菱形泛型形式:

Collection<String> c = new ArrayList<>();

菱形泛型(Diamond Operator)是JDK7中引入的一种语法糖,用于简化泛型的类型推断过程。

Map接口使用泛型:

//Map接口也是泛型接口
public interface Map<K,V> {
    //省略...
    
    V put(K key, V value);
    Set<Map.Entry<K, V>> entrySet();
}

案例:

public static void main(String[] args) {
    Map<Integer,String> map = new HashMap<>();
    
    //根据泛型类型的指定,put方法中的key只能是Integer类型,value只能是String类型
    map.put(1,"hello1");
    map.put(2,"hello2");
    map.put(3,"hello3");
    map.put(4,"hello4");

    //根据上面列出的源码可知,当前指定Map的泛型类型为:Map<Integer,String> map
    //entrySet方法返回的类型就应该是Set<Map.Entry<Integer, String>>
    Set<Map.Entry<Integer, String>> entrySet = map.entrySet();

    for(Map.Entry entry:entrySet){
        System.out.println(entry.getKey()+" : "+entry.getValue());
    }
}

4 自定义泛型

Java中泛型使用情况有三种:

  • 泛型类
  • 泛型接口
  • 泛型方法

注意:刚开始工作,我们自定义泛型类或接口的情况并不多,大家掌握定义泛型类、实例化泛型类对象的固定格式即可。

1)泛型类

如果泛型参数定义在类上面,那么这个类就是一个泛型类

泛型类定义格式:

[修饰符] class 类名<泛型类型名1,泛型类型名2,...> { 
0个或多个数据成员;
    0个或多个构造方法;
    0个或多个成员方法;
}

//注意:之前用确定数据类型的地方,现在使用自定义泛型类型名替代

例如:JDK中HashSet泛型类定义如下

在这里插入图片描述

泛型类实例化对象格式:

泛型类名<具体类型1,具体类型2,...> 对象名 = new 泛型类名<>(实参列表);

案例展示:

​ 定义一个泛型类Circle,包含x,y坐标和radius半径,然后进行功能测试。

基础泛型类:

package com.briup.chap08.bean;

//自定义泛型类:圆
//class 类名<泛型类型1,泛型类型2,...>
//泛型类型名字可以自行定义
public class Circle<T, E> {
    //原来具体数据类型的地方,使用泛型类型名替换即可
private T x;
private T y;
private E radius;

//无参构造器没有任何改变
public Circle() {}
    //原来具体数据类型的地方,使用泛型类型名替换即可
public Circle(T x, T y, E radius) {
this.x = x;
this.y = y;
this.radius = radius;
}

public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
public E getRadius() {
return radius;
}
public void setRadius(E radius) {
this.radius = radius;
}

@Override
public String toString() {
return "Circle [x=" + x + ", y=" + y + ", radius=" + radius + "]";
}
}

测试类:

package com.briup.chap08.test;

import com.briup.chap08.bean.Circle;

public class Test014_GenericsClass {
public static void main(String[] args) {
//实例化泛型类对象:
//  泛型类<具体类型1,具体类型2,...> 对象 = new 泛型类<>(实参s);

//1.实例化具体类对象,2种泛型设置为Integer和Double
// 注意,泛型类可以是任意引用类型
Circle<Integer, Double> c1 = new Circle<>(2,3,2.5);
int x = c1.getX();
double r = c1.getRadius();
System.out.println("x: " + x + " radius: " + r);

        System.out.println("------------------");
        
//2.实例化具体类对象,2种泛型设置为Double和Integer
Circle<Double, Integer> c2 = new Circle<>(2.0,3.0,2);
double x2 = c2.getX();
int r2 = c2.getRadius();

System.out.println("x2: " + x2 + " r2: " + r2);
}
}

//运行结果:
x: 2 radius: 2.5
------------------
x2: 2.0 r2: 2

2)泛型接口

如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口

定义格式:

[修饰符] interface 接口名<泛型类型名1,泛型类型名2,...> { }

例如:JDK中Set泛型接口

在这里插入图片描述

在泛型接口中,我们使用T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定。

public interface Action<T> {...}

public static void main(String[] args) {
    //创建匿名内部类
    Action<String> a = new Action<>() {
        //重写方法...
    };
}

泛型接口使用跟泛型类使用类似,在此不专门举例说明。

3)泛型方法

如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法。

泛型方法定义格式:

[修饰符] <泛型类型名> 返回值类型 方法名(形式参数列表) { 
方法具体实现;
}

泛型方法调用格式:

类对象.泛型方法(实参列表);

类名.static泛型方法(实参列表);

注意:泛型方法调用时不需要额外指定泛型类型,系统会自动识别泛型类型。

案例展示:

​ 在上述Circle泛型类中,补充泛型方法disp()和static show()并调用,验证上述格式。

基础类Circle:

​ 其他代码不变,核外补充下面2个泛型方法即可!

public class Circle<T,E> {
    //省略...
    
    //泛型类中定义 泛型方法
    public <F> void disp(F f) {
        System.out.println("in 泛型方法disp, f: " + f);
    }

    // 下面写法虽然不会报错,不建议大家这样写
    // 因为泛型方法上的 T 会和 泛型类上的T 产生歧义
    public static <T> void show(T t) {
        System.out.println("in 泛型static方法show, t: " + t);
    }
}

测试类:

public static void main(String[] args) {
    Circle<Integer,Integer> c = new Circle<>();

    //public <F> void disp(F f);
    //调用时系统自动识别泛型方法类型
    c.disp(1);//Integer
    c.disp(2.3);//Double
    c.disp("hello");//String
    c.disp('h');//Character

    System.out.println("--------------");

    //public static <T> void show(T t);
    //通过类名可以直接调用,不需要额外指定泛型类型
    Circle.show(2.3);
    Circle.show(2);
    Circle.show("hello");
}

//运行结果:
in 泛型方法disp, f: 1
in 泛型方法disp, f: 2.3
in 泛型方法disp, f: hello
in 泛型方法disp, f: h
--------------
in 泛型static方法show, t: 2.3
in 泛型static方法show, t: 2
in 泛型static方法show, t: hello

5 注意事项

先看两种错误情况:

//编译通过
//父类型的引用,指向子类对象
Object o = new Integer(1);

//编译通过
//Object[]类型兼容所有的【引用】类型数组
//arr可以指向任意 引用类型 数组对象
Object[] arr = new Integer[1];

//编译失败
//注意,这个编译报错,类型不兼容
//int[] 是基本类型数组
Object[] arr = new int[1];

//编译失败
//错误信息:ArrayList<Integer>无法转为ArrayList<Object>
//在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系
ArrayList<Object> list = new ArrayList<Integer>();

注意,= 号俩边的所指定的泛型类型,必须是要一样的

这里说的泛型类型,指的是<>中所指定的类型

虽然IntegerObject的子类型,但是ArrayList<Integer>ArrayList<Object>之间没有子父类型的关系,它们是两种不同的数据类型

所以:

Object o = new Integer(1);编译通过

ArrayList<Object> list = new ArrayList<Integer>();编译报错

也就是说,两种类型,如果是当做泛型的指定类型的时候,就没有多态的特点了


原文地址:https://blog.csdn.net/u012135697/article/details/140622467

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!