Java:泛型

1,为什么要使用泛型

现在需要设计一个可以表示出坐标点的类,坐标由X和Y组成,坐标的表示方法有3中:

(1)整数表示:x=10,y=20;
(2)小数表示:x=10.5,y=20.6;
(3)字符串表示:x="东经180度",y="北纬210度"

首先想到必须建立一个Point,在类中有两个属性分别用来表示x坐标和y坐标,但是x和y中所保存的数据类型会有3种(int、float、String),而要想使用一个类型可以同时接收3种类型数据,则只能使用Object,因为Object类可以接受任何类型的数据,都会自动发生向上转型操作。

int——自动装箱成Integer——向上转型Object
float——自动装箱成Float——向上转型Object
String——向上转型Object
class Point{
    private Object x;
    private Object y;
    public Object getX() { return x; }
    public void setX(Object x) { this.x = x; }
    public Object getY() { return y; }
    public void setY(Object y) { this.y = y; }
}
public class HelloWord {
    public static void main(String[] args) {
        Point point = new Point();
        point.setX(10);point.setY(20);
        int x = (Integer) point.getX();int y = (Integer) point.getY();
        System.out.println("X:"+x+",y:"+y);
        point.setX(10.5f);point.setY(20.6f);
        float m = (Float) point.getX();float n = (Float) point.getY();
        System.out.println("M:"+m+",N:"+n);
        point.setX("西经10");point.setY("北纬20");
        int p = (Integer) point.getX();float q = (Integer) point.getY();
        System.out.println("P:"+p+",Q:"+q);
    }
}
=============================
X:10,y:20
M:10.5,N:20.6
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at Package2.HelloWord.main(HelloWord.java:24)

程序可以正常的编译成功,但是程序运行时会发生错误,因为String无法向Integer转换,这样就造成了Point类中的类型使用Object类型进行接收的安全问题,而要想解决这样的问题就必须使用泛型。

【Object和泛型】

  • 使用Object时,我们需要在运行时进行类型转换,这种方式可能会导致类型转换错误,并在运行时出现ClassCastException等异常。同时,在使用Object时,我们无法利用编译器进行类型检查,因此容易出现类型不匹配的错误。
  • 而泛型则可以解决这些问题。泛型提供了编译时类型检查的功能,可以避免在运行时出现类型转换异常等错误。同时,泛型可以让我们在编写代码时更加具有通用性和重用性,可以避免重复编写类似的代码。
  • 另外,泛型还提供了一些额外的好处,比如可以通过泛型进行参数化和通配符等操作,使得代码更加灵活和可读性更好。

2,泛型应用

2.1,泛型的定义

Java 泛型是一种强类型机制,它提供了在编译时期检查代码类型安全的能力,并且可以在编写通用代码时避免代码重复和类型转换。泛型可以应用于类、接口和方法,让这些程序元素可以接受多种类型参数。

【案例1】比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。

【案例2】例如,可以使用泛型来创建一个通用的容器类,可以在其中存储不同类型的对象,而不需要每次都定义一个新的类。Java 中常见的泛型有 List<T>、Map<K, V>、Set<T> 等。在使用泛型时,应该遵循的规则包括使用尽可能具体的类型参数,避免使用原始类型和非受限通配符类型等。

[访问权限] class 类名<泛型类型标识1,泛型类型标识2,...>{
    [访问权限] 泛型类型标识 变量名称;
    [访问权限] 泛型类型标识 方法名称();
    [访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){};
}
class Point<T>{
    private T var;
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
}
public class HelloWord {
    public static void main(String[] args) {
        Point<Integer> point = new Point<Integer>();
        point.setVar(30);
        System.out.println(point.getVar());
        Point<String> point2 = new Point<String>();
        point2.setVar("价格是30");
        System.out.println(point2.getVar());
        Point<Float> point3 = new Point<Float>();
        point3.setVar(30.5f);
        System.out.println(point3.getVar());
    }
}
===========================================
30
价格是30
30.5

提问:为什么上面代码中设置泛型时不直接将Integer类型设置成int类型而使用Point<int>的形式?

答案:只能使用包装类,在泛型的指定中是无法指定基本数据类型,必须设置成一个类,这样在设置一个数字时就必须使用包装类,而再JDK1.5之后提供了自动装箱的操作,操作时也不会太复杂。

提问:上面代码中能否省略第二个类型声明?

答案:可以,Java7之后Java允许在构造器后不需要带完整的泛型信息,只要给出一个尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。

2.2,泛型擦除

​​​​​​​Java中的泛型擦除是指在编译期间将泛型类型的信息擦除掉,将泛型类型替换成其原始类型(raw type),以保证与之前的JVM规范兼容。在编译期间,Java编译器会将泛型类型转换为其原始类型。例如,一个List<String>类型的变量在编译期间会被转换为List类型,而不是List<String>类型。这就意味着在编译后的字节码中,泛型信息已经被擦除了,程序运行时无法获取泛型类型的具体信息。

泛型擦除的主要目的是为了保证泛型与之前的JVM规范的兼容性。但同时也带来了一些限制和问题。例如,无法在运行时获取泛型类型的具体信息,因为泛型信息已经被擦除。这也是为什么Java中不能直接创建泛型数组的原因。另外,泛型擦除也可能会导致类型安全问题。例如,如果我们将一个List<Integer>类型的变量赋值给一个List类型的变量,在编译期间是可以通过的,但在运行时可能会导致ClassCastException等异常。

为了解决这些问题,Java引入了一些解决方案,例如泛型通配符和反射等机制。泛型通配符可以在一定程度上解决类型安全问题,而反射则可以通过获取泛型类型的元数据信息来弥补泛型擦除带来的限制。

2.3,泛型的构造方法

构造方法可以为类中的属性初始化,那么如果类中的属性通过泛型指定,而又需要通过构造方法设置属性内容时,就可以将泛型应用再构造方法上,此时的构造方法的i当以与之前无异。

class Point<T>{
    private T var;
    public Point(T var){ this.var = var; }
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
}
public class HelloWord {
    public static void main(String[] args) {
        Point<String> p = null;
        p = new Point<String>("燕双嘤");
        System.out.println(p.getVar());
    }
}
======================================
燕双嘤

2.4,指定多个泛型类型

class Notepad<K,V>{
    private K key;
    private V 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; }
}
public class HelloWord {
    public static void main(String[] args) {
        Notepad<String,Integer> t = null;
        t = new Notepad<String,Integer>();
        t.setKey("燕双嘤");t.setValue(30);
        System.out.println("姓名:"+t.getKey()+",年龄:"+t.getValue());
    }
}
==================================
姓名:燕双嘤,年龄:30

2.5,泛型的安全警告

在泛型应用中最好在声明类对象时指定好其内部的数据类型,如Info<String>,如果不指定类型,这样用户再使用这样的类时,就会出现不安全的警告信息。

public class HelloWord {
    public static void main(String[] args) {
        Notepad t = new Notepad();    //不指定泛型类型
        t.setKey("燕双嘤");t.setValue(30);
        System.out.println("姓名:"+t.getKey()+",年龄:"+t.getValue());
    }
}

但是运行程序仍然不会出现任何问题,因为JAVA会自动按照Object处理,相当于:

Notepad<Object,Object> i = new Notepad<Object,Object>();

3,通配符

3.1,匹配任意类型的通配符

在开发中对象的引用传递时最常见的,但如果在泛型类的操作中,在进行引用传递时泛型类型必须匹配才可以传递,否则时无法传递的。

class Info<T>{
    private T var;

    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
    public String toString() { return "Info{" + "var=" + var + '}'; }
}
public class HelloWord {
    public static void main(String[] args) {
        Info<String> i = new Info<String>();
        i.setVar("燕双嘤");
        fun(i);
    }
    public static void fun(Info<Object> temp){
        System.out.println(temp);
    }
}
========================
E:\IDEA\workspace\Java\src\Package2\HelloWord.java:17:13
java: 不兼容的类型: Package2.Info<java.lang.String>无法转换为Package2.Info<java.lang.Object>

编译无法通过,运行就会报错。如果想要继续执行,将fun()方法中定义的Info<Object>修改为Info,即不指定泛型。

但是,我String是Object子类,为啥不行?在3.3节。

public static void fun(Info temp){
    System.out.println(temp);
}
==================================
Info{var=燕双嘤}

以上程序在编译时不会出现任何语法问题,程序也可以正常使用,但是在编写fun()方法时Info没有指定任何的泛型类型,这样明显时不妥当的,所以为了解决这个问题,Java中引入了通配符"?",表示可以接受此类型的任意参数。​​​​​​​

public static void fun(Info<?> temp){
    System.out.println(temp);
}
====================================
Info{var=燕双嘤}

如果使用?接收泛型对象时,则不能设置被泛型指定的内容,因为无法将内容设置给var属性,但是可以设置null。

Info<?> i = new Info<String>();
i.setVar(null);

3.2,受限泛型

在引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和下限。范围上限使用extends关键字声明,表示泛型的类型可能时所指顶的类型或者是此类型的子类,而范围的下限使用super进行声明,表示泛型的类型可能时所指定的类型,或者时此类型的父类型,或是Object类。

泛型的上限

现在假设一个方法中能接收的泛型对象只能是数字(Byte,Short,Long,Integer,Float,Double)类型,此时在定义方法参数接收对象时,就必须指定泛型的上限。因为所有的数字包装类都是Number类型的子类,如果传递的是一个String类的泛型对象,则编译时将出现错误。

public static void fun(Info<? extends Number> temp){
    System.out.println(temp);
}
================================
E:\IDEA\workspace\Java\src\Package2\HelloWord.java:17:13
java: 不兼容的类型: Package2.Info<java.lang.String>无法转换为Package2.Info<? extends java.lang.Number>

同时,也可以直接在类的声明处指定泛型的上限范围如果此时声明的泛型对象是Number的子类,则肯定不会出现问题,但是如果声明的不是数字类型,则肯定会出错。

class Info<T extends Number>{
    private T var;
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
    public String toString() { return "Info{" + "var=" + var + '}'; }
}

泛型的下限

当使用的泛型只能在本类及其父类类型上应用时,就必须使用泛型的范围下限进行配置。

public static void fun(Info<? super String> temp){
    //只接收String或Object类型的泛型
    System.out.println(temp);
}

上面代码的fun()方法中,Info进行了下限的配置,所以只能接收泛型是String及Object类型的引用。所以,一旦此时传递了其他泛型类型的对象,编译将出现错误。

3.3,泛型与子类继承的限制

一个类的子类可以通过对象多态为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接收的,例如上面的 ,Info<String>不能使用Info<Object>接收。

问题:为什么这里不能使用向上转型?

答案:泛型中无法向上转型,以商场购物为例,现在假设把以上两个对象Info<Object>和Info<String>分别当作商场的全部商品和个人已购的商品信息。一个人所购买的肯定是商场中很少一部分商品,而如果现在使用Info<Object> = Info<String> 就相当于在个人已购买的商品加入了商场的全部商品,相当于个人把商场全部买走了,这基本是不可能的,所以程序无法编译通过。

4,泛型接口

4.1,定义泛型接口

interface Info<T>{
    public T getVar();
}

4.2,泛型接口的两种实现方式

1,在子类的定义上声明泛型类型

interface Info<T>{
    public T getVar();
}
class InfoImpl<T> implements Info<T>{
    private T var;
    public T getVar() {
        return this.var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public InfoImpl(T var){
        this.setVar(var);
    }
}
public class HelloWord {
    public static void main(String[] args) {
        Info<String> i = null;
        i = new InfoImpl<String>("燕双嘤");
        System.out.println("内容:"+i.getVar());
    }
}
================================
内容:燕双嘤

2,直接在接口中指定具体类型

interface Info<T>{
    public T getVar();
}
class InfoImpl implements Info<String>{
    private String var;
    public String getVar() {
        return this.var;
    }
    public void setVar(String var){
        this.var = var;
    }
    public InfoImpl(String var){
        this.setVar(var);
    }
}
public class HelloWord {
    public static void main(String[] args) {
        Info<String> i= null;
        i = new InfoImpl("燕双嘤");
        System.out.println("内容:"+i.getVar());
    }
}
============================
内容:燕双嘤

5,泛型方法

之前所有泛型操作都是将整个类进行泛型化,但同样也可以在类中定义泛型化的方法。泛型方法的定义与其所在的类是否是泛型类是没有任何关系的,所在的类可以是泛型类,也可以不是泛型类。

5.1,定义泛型方法

在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

class Demo{
    public <T> T fun(T t){
        return t;
    }
}
public class HelloWord {
    public static void main(String[] args) {
        Demo demo = new Demo();
        String str = demo.fun("燕双嘤");
        System.out.println(str);
    }
}
================================
燕双嘤

以上程序中的fun()方法是将接收的参数直接返回,而且因为在方法接收参数中使用了泛型操作,所以此方法可以接收任意类型的数据,而且此方法的返回值类型将由泛型指定。

5.2,通过泛型方法返回泛型类实例

如想通过泛型方法返回一个泛型类的实例对象,则必须在方法的返回类型声明处明确地指定泛型标识。

class Info<T extends Number>{
    private T var;
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
    public String toString() { return "Info{" + "var=" + var + '}'; }
}
public class HelloWord {
    public static void main(String[] args) {
        Info<Integer> i = fun(30);
        System.out.println(i.getVar());
    }
    public static <T extends Number> Info<T> fun(T param){
        Info<T> temp = new Info<T>();
        temp.setVar(param);
        return temp;
    }
}

<T extends Number>:表示方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定。

5.3,使用泛型统一性—传入的参数类型

如果要求一个传入的泛型对象的泛型类型一致,也可以通过泛型方法指定。

class Info<T>{
    private T var;
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
    public String toString() { return this.var.toString(); }
}
public class HelloWord {
    public static void main(String[] args) {
        Info<String> i1 = new Info<String>();
        Info<String> i2 = new Info<String>();
        i1.setVar("Hello");i2.setVar("Java");
        add(i1,i2);
    }
    public static <T > void add(Info<T> i1,Info<T> i2){
        System.out.println(i1.getVar()+"  "+i2.getVar());
    }
}
================================================
Hello  Java

add()方法中的两个Info对象的泛型类型必须一致。如果设置的类型不一致,则在编译时将出现错误。

6,泛型数组和嵌套设置

6.1,数组

public class HelloWord {
    public static void main(String[] args) {
        Integer i[] = fun1(1,2,3,4,5,6);
        fun2(i);
    }
    public static <T> T[] fun1(T...arg){
        return arg;
    }
    public static <T> void fun2(T param[]){
        System.out.print("接收泛型数组:");
        for (T t:param){
            System.out.print(t+"、");
        }
        System.out.println();
    }
}
===========================================
接收泛型数组:1、2、3、4、5、6、

6.2,泛型的嵌套设置

class Info<T,V>{
    private T var;
    private V value;
    public Info(T var,V value){this.setVar(var);this.setValue(value);}
    public T getVar() { return var; }
    public void setVar(T var) { this.var = var; }
    public V getValue() { return value; }
    public void setValue(V value) { this.value = value; }
}
class Demo<S>{
    private S info;
    public Demo(S info){this.setInfo(info);}
    public S getInfo() { return info; }
    public void setInfo(S info) { this.info = info; }
}
public class HelloWord {
    public static void main(String[] args) {
        Demo<Info<String,Integer>> d = null;
        Info<String,Integer> i = null;
        i = new Info<String,Integer>("燕双嘤",30);
        d = new Demo<Info<String,Integer>>(i);
        System.out.println("内容一:"+d.getInfo().getVar());
        System.out.println("内容二:"+d.getInfo().getValue());
    }
}
============================================
内容一:燕双嘤
内容二:30
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值