栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java泛型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java泛型

1.什么是泛型

 

百度百科:

Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

2.java泛型提出的背景 

百度百科:

java集合(Collection)中元素的类型是多种多样的。例如,有些集合中的元素是Byte类型的,而有些则可能是String类型的,等等。Java允许程序员构建一个元素类型为Object的Collection,其中的元素可以是任何类型在Java SE 1.5之前,没有泛型(Generics)的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要作显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以在预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。因此,为了解决这一问题,J2SE 1.5引入泛型也是自然而然的了 

3.泛型的作用 

第一是泛化。可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。

第二是类型安全。泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。

第三是消除强制类型转换。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。

第四是向后兼容。支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。 

4.泛型的基本使用 

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

1.将方法重载改为泛型

重载方法代码:

package com.company.test.genericity;

import java.math.BigDecimal;


public class TestUtile {


    public static int multiply(int a, int b) {
        System.out.println(a + "*" + b + "=" + (a * b));
        return a * b;
    }

    public static float multiply(float a, float b) {
        System.out.println(a + "*" + b + "=" + (a * b));
        return a * b;
    }

    public static double multiply(double a, double b) {
        BigDecimal a1 = new BigDecimal(Double.toString(a));

        BigDecimal b1 = new BigDecimal(Double.toString(b));
        System.out.println(a + "*" + b + "=" + (a1.multiply(b1).doubleValue()));
        return a1.multiply(b1).doubleValue();
    }

}
public class Test {

    public static void main(String[] args) {
        TestUtile.multiply(2, 3);

        TestUtile.multiply(2.3f, 2.5f);

        TestUtile.multiply(2.66, 2.77);

    }
}

输出结果:

改为泛型方法: 

package com.company.test.genericity;

import java.math.BigDecimal;


public class TestUtile {

    
    public static  BigDecimal multiply(T a, T b) {
        BigDecimal a1 = new BigDecimal(String.valueOf(a));

        BigDecimal b1 = new BigDecimal(String.valueOf(b));
        System.out.println(a + "*" + b + "=" + (a1.multiply(b1).doubleValue()));
        return a1.multiply(b1);
    }
}
public class Test {

    public static void main(String[] args) {
        TestUtile.multiply(2, 3);

        TestUtile.multiply(2.3f, 2.5f);

        TestUtile.multiply(2.66, 2.77);

    }
}

 输出结果:

2.泛型的类

(1)简单泛型

public class Demo1 {
    private T t ;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

}

public class Test {

    public static void main(String[] args) {

        Demo1 demo1 = new Demo1<>();
        demo1.setT("泛型测试");
        System.out.println("输出结果:"+demo1.getT());
    }
}

输出结果:

(2)多元泛型

package com.company.test.genericity;



public class Demo2 {
    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;
    }

    @Override
    public String toString() {
        return "Demo2{" +
                "key=" + key +
                ", value=" + value +
                '}';
    }
}
public class Test {

    public static void main(String[] args) {

        Demo2 demo2 = new Demo2<>();
        demo2.setKey("百度");
        demo2.setValue("www.baidu.com");
        System.out.println("输出结果:"+demo2.toString());
    }
}

输出结果:

 3.泛型方法
package com.company.test.genericity;

import java.lang.reflect.InvocationTargetException;


public class ObjectFactory {

    
    public T getObject(Class c) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //c.newInstance(); jdk9以上这个方法过时了 不过依然可以用
        //创建对象
        T t = c.getDeclaredConstructor().newInstance();
        return t;
    }
}
public class Test {

    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        ObjectFactory objectFactory = new ObjectFactory();
        Object object = objectFactory.getObject(Class.forName("com.company.test.genericity.Person"));
        Person person = (Person)object;
        person.setAge(12);
        person.setName("张三");
        person.setSex("男");
        person.setPhoneNumber("12345678945");
        System.out.println("输出结果"+person);
    }
}

说明:

1.定义泛型方法时,必须在返回值前边加一个,来声明这是一个泛型方法,持有一个泛型T,才可以返回泛型T

2.Class类型的变量c,可以用来创建泛型类的对象,因为不知道具体的T的类型,所以不能直接new一个对象需要使用全限定名来指定具体的参数类型,比如传入的全限定名"com.company.test.genericity.Person"则Class中的T就是Person,那么返回的Object也就是Person

4.泛型接口
public interface DemoService {

    T sayHello(T t);
}


public class DemoServiceImpl implements DemoService{
    @Override
    public T sayHello(T t) {
        return t;
    }
}


public class Test {

    public static void main(String[] args) {

      DemoService  demoService =  new DemoServiceImpl<>();
        String hello = demoService.sayHello("hello");
        System.out.println("输出结果:"+hello);
    }
}

输出结果:

 5.泛型的上下限

无限制通配符
extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类


使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

1.泛型上限 

public class Demo3 {
    private T t ;        // 定义泛型变量

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Demo3{" +
                "t=" + t +
                '}';
    }
}


public class Test {

    public static void main(String[] args) {

        Demo3 demo3 = new Demo3<>();
        demo3.setT(1245);
        System.out.println("输出结果:"+demo3);
    }
}

输出结果:

2.泛型下限 

package com.company.test.genericity;


public class Demo4 {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Demo4{" +
                "t=" + t +
                '}';
    }
    public static void test(Demo4 param){
        System.out.println("参数结果:"+param);
    }
    public static void main(String[] args) {
        Demo4 stringDemo4 = new Demo4<>();
        stringDemo4.setT("参数下限");
        test(stringDemo4);
        Demo4 objectDemo4 = new Demo4<>();
        objectDemo4.setT(123456);
        test(objectDemo4);
        Demo4 intDemo4Demo4 = new Demo4<>();
        //test(intDemo4Demo4); idea会提示 报错:Required type:Demo4

    }
}
 

 输出结果:

5.泛型的擦除 1.什么是泛型擦除 

类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上 。类型擦除可以简单的理解为将泛型java代码转换为普通java代码(其对应的原生态类型),就像完全没有泛型一样。类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。2.移除所有的类型参数。所以Java的泛型是伪泛型。

2.泛型擦除的原则

1.消除类型参数声明,即删除<>及其包围的部分。

2.根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。

3.为了保证类型安全,必要时插入强制类型转换代码。

4.自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”

1.简单案例 

   public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("name", "aaa");
        map.put("age", "22");
        System.out.println("姓名:"+map.get("name")+"年龄:"+map.get("age"));

        Map map1 = new HashMap<>();
        map1.put("max", 999);
        map1.put("min", 0);

        System.out.println("最大值:"+map1.get("max")+",最小值:"+map1.get("min"));

        System.out.println("类型比较"+(map.getClass()==map1.getClass()));
    }

类型擦除后
   public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("name", "aaa");
        map.put("age", "22");
        System.out.println("姓名:"+(String)map.get("name")+"年龄:"+(String)map.get("age"));

        Map map1 = new HashMap<>();
        map1.put("max", 999);
        map1.put("min", 0);

        System.out.println("最大值:"+(Integer)map1.get("max")+",最小值:"+(Integer)map1.get("min"));

        System.out.println("类型比较"+(map.getClass()==map1.getClass()));
    }

输出结果:

 从输出结果可以看出类型已经擦除了

2.无限制类型擦除 

即形如类型的无边界,当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object

 3.上下限类型擦除

当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,形如的类型参数被替换为Number,被替换为Object

4.泛型方法擦除

  
    public T test(T t){
         return t;
    }

    
    public Number test(Number t){
        return t;
    }

5.通过反射证明泛型类型擦除

public class Demo5 {

    
    public static void main(String[] args) throws Exception {

        List list = new ArrayList();

        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer

        list.getClass().getMethod("add", Object.class).invoke(list, "我是string类型");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

 根据输出结果可以得出,泛型擦除后只剩下原始类型了

 6.泛型的理解 1.泛型擦除后的原始类型

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换 

Object原始类无上下限:

//Object

class Demo {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
} 

//原始类型为Object
class Demo {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}
  

如果泛型有上下限:

//原始类型为number
public class Demo {}

 指定泛型和不指定泛型的区别:

public class Demo5 {
    
    public static void main(String[] args) {

        
        Integer integer = Demo5.add(41, 20); //正常编译 两个参数都是integer
        Number number = Demo5.add(41, 22.2f); //一个为integer 一个为float 他们的父级是Number
        Object object = Demo5.add(41, "asd"); //这两个参数一个是Integer,一个是String 父级为Object

        
        int i = Demo5.add(41, 20); //正常
        int j = Demo5.add(41, 22.2f); //编译错误,idea提示报错
        Number number1 = Demo5.add(41, 22.2f); //Number类型
    }

    //这是一个简单的泛型方法
    public static  T add(T x,T y){
        return y;
    }
}

数组的Object泛型:

public class Demo5 {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add("121");
        list.add(new Date());
        list.add(2.2);
    }

}
 2.泛型的编译期检查

既然说类型变量会在编译的时候擦除掉,那为什么我们往 List 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

如下列代码 list.add("121");在编译前的时候就回报错

public class Demo5 {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add("121");//idea提示报错
    }

}

 从如下代码可以得出结论:类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象

public class Demo5 {

    public static void main(String[] args) {
        List list1 = new ArrayList();
        list1.add("1");
        //list1.add(1)编译不通过
        System.out.println("list1的元素类型"+list1.get(0).getClass());
        List list2 = new ArrayList();
        list2.add(1);//正常编译
        list2.add("1");//正常编译
        Object o = list2.get(0);
        Object o1 = list2.get(1);
        System.out.println("list2的元素类型"+o.getClass()+","+o1.getClass());
    }

}

注意下列写法是不允许的,因为Java不允许进行这样的引用传递

public class Demo5 {

    public static void main(String[] args) {


       // List list1 = new ArrayList(); 编译错误
        // List list2 = new ArrayList(); 编译错误

        List list3 = new ArrayList();
        list3.add(new Object());
        list3.add(new Object());
        //List str = list3; 编译错误

        List list4 = new ArrayList();
        list4.add(new String());
        list4.add(new String());
        //List obj2 = list4; 编译错误 
        
    }

} 
 3.泛型的“多态” 

类型擦除会造成多态的冲突,而JVM解决方法就是桥接方法

 如下代码示例:

public class Parent01 {

    private T value;

    public T getValue() {
        return value;
    }

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


public class Child01 extends Parent01{

    @Override
    public void setValue(Date value) {
        super.setValue(value);
    }

    @Override
    public Date getValue() {
        return super.getValue();
    }
}

上面的代码示例在类型擦除后的方法如下:

public class Parent01 {

    private Object value;

    public Object getValue() {
        return value;
    }

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

 如果按照我们继承的概念来看,如果是普通的类继承的话,父类是Object子类是Date,所以对于普通类来说应该是重载而不是重写,但是从下面的编辑代码来看child01.setValue(new Object());这句代码是错误的,所以说对于泛型的继承来说是重写了,从@Override注解也可以看出来是重写。

public class Test {

    public static void main(String[] args) {

        Child01 child01 = new Child01();
        child01.setValue(new Date());
        //child01.setValue(new Object()); 编译错误  说明子类重写了父类的方法
        System.out.println(child01.getValue());
        Method[] methods =  Child01.class.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            System.out.println(method.toString());
            System.out.println("是否桥接=>:" + method.isBridge());
        });
    }
}

我们的本意是子类进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突,但是我们在进行运行代码的时候我们依然能完成我们子类类型的重写,这是为什么呢,答案就是jvm给我们实现了一个桥接方法来解决的。从下面的运行结果可以看出。 

 使用idea的插件查看child01的字节码文件,可以看到生成的桥接方法

// class version 55.0 (55)
// access flags 0x21
// signature Lcom/company/test/genericity/Parent01;
// declaration: com/company/test/genericity/Child01 extends com.company.test.genericity.Parent01
public class com/company/test/genericity/Child01 extends com/company/test/genericity/Parent01 {

  // compiled from: Child01.java

  // access flags 0x1
  public ()V
   L0
    LINENUMBER 10 L0
    ALOAD 0
    INVOKESPECIAL com/company/test/genericity/Parent01. ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/company/test/genericity/Child01; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setValue(Ljava/util/Date;)V//重写的方法
   L0
    LINENUMBER 14 L0
    ALOAD 0
    ALOAD 1
    INVOKESPECIAL com/company/test/genericity/Parent01.setValue (Ljava/lang/Object;)V
   L1
    LINENUMBER 15 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/company/test/genericity/Child01; L0 L2 0
    LOCALVARIABLE value Ljava/util/Date; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  public getValue()Ljava/util/Date;//重写的方法
   L0
    LINENUMBER 19 L0
    ALOAD 0
    INVOKESPECIAL com/company/test/genericity/Parent01.getValue ()Ljava/lang/Object;
    CHECKCAST java/util/Date
    ARETURN
   L1
    LOCALVARIABLE this Lcom/company/test/genericity/Child01; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1041
  public synthetic bridge setValue(Ljava/lang/Object;)V//编译时由编译器生成的桥接方法
   L0
    LINENUMBER 10 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/util/Date
  //去调用我们重写的setValue方法)V
    INVOKEVIRTUAL com/company/test/genericity/Child01.setValue (Ljava/util/Date;)V
    RETURN
   L1
    LOCALVARIABLE this Lcom/company/test/genericity/Child01; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1041
  public synthetic bridge getValue()Ljava/lang/Object;//编译时由编译器生成的桥接方法
   L0
    LINENUMBER 10 L0
    ALOAD 0
    //去调用我们重写的getValue方法)V
    INVOKEVIRTUAL com/company/test/genericity/Child01.getValue ()Ljava/util/Date;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/company/test/genericity/Child01; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}
4.基本类型为何不能作为泛型

我们常用的是ArrayList而没有ArrayList,因为类型擦除后ArrayList

而Object类是不能存储int类型的只能引用Integer类型。但是注意我们在使用的过程中。比如list.add(1)这种做法是正常的,是因为基本类型存在拆装箱的存在。

5.为什么泛型类型不能实例化

不能实例化泛型类型, 这本质上是由于类型擦除决定的,因为泛型T在类型擦除后会变成Object类,这样就相当于new Object,如果想实例化一个泛型,可以使用反射方法

 

public class Test {

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        Child01 child01 = newTClass(Child01.class);
        child01.setValue(new Date());
        System.out.println(child01.getValue());
    }

    static  T newTClass (Class < T > c) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
       // T obj = clazz.newInstance();
        T obj = c.getDeclaredConstructor().newInstance();
        return obj;
    }
}

输出结果如下:

6.泛型数组 

因为在 Java 中是不能创建一个确切的泛型类型的数组的,除非是采用通配符的方式且要做显式类型转换才可以

    //List[] list1 = new ArrayList[10]; 编译错误,非法创建 
        //List[] list2 = new ArrayList[10]; 编译错误,需要强转类型 
        List[] list3 = (List[]) new ArrayList[10]; //OK list2的强转
        //List[] list4 = new ArrayList[10]; //非法创建 
        List[] list5 = new ArrayList[10]; //OK 
        List[] list6 = new ArrayList[10]; //OK

正确初始化数组

如果我们使用new ArrayList[10]这种方式来初始化一个数组的话。编译期不会报错,但是存在警告,有警告意味着就会存在我们不可预知的危险,所以初始化数组需要使用正确恰当的方法

 如下代码是初始化数组:

public class Demo6 {


    private T[] array;

    public Demo6(Class type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    public T[] create() {
        return array;
    }

    public static void main(String[] args) {
        Demo6 i = new Demo6<>(Integer.class, 10);
        Integer[] integers = i.create();
        i.put(0,1);
        System.out.println(integers[0]);
        Demo6 l = new Demo6<>(ArrayList.class, 10);
        List[] arrayLists = l.create();
        ArrayList list =new ArrayList<>();
        list.add("001");
        list.add("002");
        l.put(0,list);
        System.out.println(arrayLists[0]);

    }
}

输出结果如下:

 7.获取泛型参数类型

java.lang.reflect.Type是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class), 以上这些类型都实现Type接口。

public class Demo6 {


    private T[] array;

    public Demo6(Class type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    public T[] create() {
        return array;
    }

    public static void main(String[] args) {
        Demo6 in = new Demo6(Integer.class, 10){};
        Type superclass1 = in.getClass().getGenericSuperclass();
        Type[] types = ((ParameterizedType) superclass1).getActualTypeArguments();
        for(int i=0;i(ArrayList.class, 10){};
        Type superclass = a.getClass().getGenericSuperclass();
        //getActualTypeArguments 返回确切的泛型参数
        Type[] types1 = ((ParameterizedType) superclass).getActualTypeArguments();
        for(int i=0;i 

 

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号