JavaSE基础篇:泛型说明

简介: JavaSE基础篇:泛型说明

第一章:泛型概述

一:泛型概念

JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。

Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构

泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。

二:泛型类

泛型标识可以理解称为一个类型的形参,我们拿着这个泛型标识可以去声明泛型变量+方法的返回值类型和参数类型。

常用的泛型标识T、E、K、V其中KV都是结合使用。

1:泛型类使用

package com.dashu.nettytest;
public class Generic<T> {
    private T t;
    public Generic(T t){
        this.t = t;
    }
    public T getT(){
        return t;
    }
    public void setT(T t){
        this.t = t;
    }
}

这里的泛型就是一个泛型标识,它做了成员变量的类型,普通方法的入参和出参,构造方法的入参。

我们在创建对象的时候来指定T的具体类型,假如我们传入的是String类型,那么就说明我们要在这个类里边玩String类型了。

这里T是有外部使用类的时候传入的。

创建泛型类对象时如果并没有指定泛型,那么这里边的泛型将会按照Object类型来进行操作。

泛型类不支持基本数据类型,仅仅支持引用类型。我们真正使用T的时候,是转换成了Object,在使用类里边的泛型成员的时候,会在一个适当的机会将Object传递过来的数据类型转换为适当的类型,而我们的基本数据类型并不是继承自Object,玩具发进行转换。

使用泛型之后的对象的Class对象也都是一个。他们的Class对象的地址值也是一个。

2:泛型类派生子类

1):子类也是泛型类

这里我们定义的父类,我们使用E作为泛型标志。

package com.dashu.nettytest;
public class Parent<E> {
    private E value ;
    public E getValue(){
        return this.value;
    }
    public void setValue(E e){
        this.value = e;
    }
}

父类是泛型类,子类也是泛型类的这种情况,子类和父类的泛型标志要一样来标识子类和父类是同一个泛型。

假如我们在这里不给父类声明泛型变量的话:

public class Son<T> extends Parent {
    public Object getValue(){
        return super.getValue();
    }
}

不给父类声明泛型变量,那么他默认就是Object那么我们使用开发工具将父类的getValue()方法重写之后就返回的是Object类型的变量。

假如我们给父类声明变量但是父子泛型变量不一致的话:

public class Son<T> extends Parent<E> {
    public E getValue(){
        return super.getValue();
    }
}

就会发生下面的问题:

所以,子类继承泛型父类的时候,父子类要是都使用泛型功能的的话,标志必须要一致。这样做的话也是好理解的,我们创建子类对象的时候,必须先去创建父类对象,创建父类对象的话就会把泛型标识传递给父类。当然,子类泛型和父类泛型一致即可,我们也可以在子类的泛型当中进行扩展,只要保证父类的泛型标志在子类当中都有即可。

2):子类不是泛型类

子类不是泛型类,父类必须明确泛型的数据类型

public class Son extends Parent<Integer>{
}

我们如果定义的时候这样定义就会报错:

编译报错原因就是必须要指定具体的引用类型,而且这里也明确提示了,请创建E这个类,说明编译器在这里也认为E是一个具体类型,但是没有被创建而已。

三:泛型接口

实现类不是泛型类,接口要明确数据类型

实现类也是泛型类,实现类和接口的泛型类型要一致。

声明泛型接口和普通接口是一样的,声明泛型接口,添加的泛型可以作为方法的入参和返回值。

1:实现类不是泛型类

package com.dashu.nettytest;
public interface Generator<T> {
    T getKey();
}
class Apple implements Generator {
    @Override
    public Object getKey() {
        return null;
    }
}
class Banana implements Generator<String> {
    @Override
//    public Object getKey() {
//指定了泛型类型为String类型之后,这里不能返回Object了,重写返回类型要一致。    
    public String getKey() {
        return null;
    }
}

子类不是一个泛型类,必须指明接口泛型,如果不指明的话默认就是Object。

2:实现类也是泛型类

子类是泛型类的话,父类如果也要使用泛型类的话,父子类型需要一致。当然子类可以拓展,但是必须包含父类的泛型标志。

四:泛型方法

泛型类是在实例化类的时候,指明泛型的具体类型

泛型方法是在调用方法的时候指明泛型的具体类型。

1:概念

我们上述案例中的方法不是泛型方法,只不过是一个普通的成员方法,只不过类型使用了泛型类的类型。

修饰符后边和返参结果之前的那个区域叫做泛型列表,只有声明了这个区域的方法才叫泛型方法。

方形类中使用了泛型的成员方法并不是泛型方法。

表明该方法将使用泛型类型T,此时才可以在方法汇总使用泛型类型T

T可以为任意标识,常见的就是T、E、K、V

2:具体使用

class Test{
    public <B> B getArrayList(ArrayList<B> shift){
        return shift.get(0);
    }
}
class Test{
    public <B,N,M> B getArrayList(ArrayList<B> shift){
        return shift.get(0);
    }
}

上述这两种写法都是没有问题的。泛型方法的泛型类是在方法被调用的时候被指定的。

3:静态泛型方法

下面是之前我一直不会的泛型方法和静态修饰符连用:

class Test{
    public static final <B,N,M>  B getArrayList(ArrayList<B> shift){
        return shift.get(0);
    }
    public static void main(String[] args) {
//        ArrayList<Object> list = new ArrayList();
//        Object arrayList = getArrayList(list);
        ArrayList<String> list = new ArrayList();
        String arrayList = getArrayList(list);
    }
}

这里我们入参当中反省是Object方法执行后返参就是Object,String的话返参就是String

4:泛型方法与可变参数

class Shit{
    public static final <B>  B getArrayList(B...b){
        for (B b1 : b) {
            System.out.println(b);
        }
        return null;
    }
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        String arrayList = getArrayList("a","b","c");
        Integer shit = getArrayList(1,2,3);
    }
}

总结:

1:泛型方法可以独立于泛型类使用泛型的能力

2:如果static方法需要使用泛型鞥哪里,就必须成为泛型方法,不能简单使用泛型类的泛型变变量。

第二章:泛型通配符

一:什么是类型通配符

类型通配符就是一个?代替具体的类型实参,类型通配符是类型实参,而不是类型形参。

二:类型通配符上限

class Box<E>{
    private E first;
    public E getFirst(){
        return this.first;
    }
    public void setFirst(E first){
        this.first = first;
    }
}
class Test {
    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        showBox(box);
    }
    public static void showBox(Box<? extends Number> box){
        Number first = box.getFirst();
        System.out.println(first);
    }
}

类/接口 <? extends 实参类型> 要求该泛型的类型,只能是是参类型或者是实参类型的子类类型。

这里要注意的是,类型通配符上限的这样的list是不能填充元素的。因为我们采用的是上线通配符。不知道里边的类型有多下限,所以限制填充元素。

三:类型通配符下限

类/接口<? super 实参类型> 要求该泛型的类型,只能是实参类型或者是实参类型的父类类型。

类型通配符下限这种方式,可以填充元素,但是不能保证元素的类型的 约束要求,因为可以把实参类型的子类给添加进去。

集合遍历的时候,都会使用Object来接收。

第三章:类型擦除

泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为–类型擦除

Java中的泛型信息,只存在于编译阶段,进入到JVM之后就没有所谓的泛型类型了。

一:无限制类型擦除

泛型标识是T,生成字节码文件过程中T都用Object替换了。我们通过反射拿对应成员变量的类型的时候,就变成了Object。

二:有限制类型擦除

有上限类型,就把T转换成了上限类型,我们通过反射拿对应成员变量的类型的时候,就变成了Number

三:擦除方法中类型参数

这里T变Number,反射去拿就行。

四:桥接方法

生成了一个桥接方法,来保证实现关系。

package com.dashu.nettytest;
import java.lang.reflect.Method;
public interface Apple <T>{
    T info (T t);
}
class infoImpl implements Apple<Integer>{
    @Override
    public Integer info(Integer integer) {
        return integer;
    }
}
class Test999{
    public static void main(String[] args) {
        Class<infoImpl> infoClass = infoImpl.class;
        Method[] declaredMethods = infoClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.getName()+":"+declaredMethod.getReturnType().getSimpleName());
        }
    }
    //info:Integer
    //info:Object
    //
    //Process finished with exit code 0
}

有两个info方法,保证泛型,还有一个桥接方法。字节码反编译之后,没有这个了。

第四章:泛型与数组

泛型数组的创建

可以声明带泛型的数组的引用,但是不能直接创建带泛型的数组的对象。

可以通过java.lang.relect.Array的newInstance(Class,int)来创建T[]数组

一:带泛型数组的对象不可以直接创建

如图所示,直接创建的

话是无法编译过去的。我们可以声明左边这样的引用。

非泛型的可以直接创建这是没有任何问题的。

class Test999{
    public static void main(String[] args) {
        ArrayList[] list = new ArrayList[5];
        ArrayList<String>[] listArr = list;
        ArrayList<Integer> intList = new ArrayList<>();
        intList.add(100);
        list[0] = intList;
        String s = listArr[0].get(0);
        System.out.println(s);
        //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        //  at com.dashu.nettytest.Test999.main(Apple.java:28)
        //
        //Process finished with exit code 1
    }
}

这种方式使用数组是不安全的。我们声明的是泛型的ArrayList数组,指向了一个原生类型的ArrayList的数组对象,原生类型的话,可以添加任何类型的元素,导致后续类型转换报错。我们应该这么写:

class Test999{
    public static void main(String[] args) {
//        ArrayList[] list = new ArrayList[5];
//        ArrayList<String>[] listArr = list;
        ArrayList<String>[] listArr = new ArrayList[5];
        ArrayList<String> stringList = new ArrayList<>();
        stringList.add("abc");
//        list[0] = intList;
        listArr[0] = stringList;
        String s = listArr[0].get(0);
        System.out.println(s);
        //abc
    }
}

我们使用泛型数组的时候,我们都是拿着泛型类型去创建一个这样的引用,而创建的对象呢不能创建泛型的数据类型,而是创建原生的数据类型赋值给引用。操作的时候,我们拿着引用去操作即可。

泛型在编译器会进行泛型擦除,而数组会在整个编译器期间持有他的数据类型,他们在设计上就是有冲突的,所以不能直接创建带有泛型类型的数组对象。

我们可以通过反射的方式创建一个泛型的数组。

二:通过反射创建泛型的数组

这个是不允许的,类型都没搞清楚,就直接new对象,肯定不行。

应该这么来写:

class Test999{
    public static void main(String[] args) {
        Fruit<String> fruit = new Fruit<>(String.class,3);
        fruit.put(0,"aaa");
        fruit.put(1,"bbb");
        fruit.put(2,"ccc");
        System.out.println("Arrays.toString(fruit.getArray()) = " + Arrays.toString(fruit.getArray()));
    }
    //Arrays.toString(fruit.getArray()) = [aaa, bbb, ccc]
}
class Fruit<T> {
    private T[] array;
    public Fruit(Class<T> clz, int length){
        array =(T[]) Array.newInstance(clz,length);
    }
    public void put(int index,T t){
        array[index] = t;
    }
    public T get(int index){
        return array[index];
    }
    public T[] getArray(){
        return array;
    }
}

最好不用泛型数组,要用泛型集合代替。

第五章:泛型与反射

一:反射中常用的泛型类

Class

Constructor

二:泛型对反射的好处

class Test999{
    public static void main(String[] args) throws Exception {
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor();
        Person person = constructor.newInstance();
    }
}
class Person {
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}
相关文章
|
5月前
|
存储 安全 Java
Java的泛型(Generics)技术性文章
Java的泛型(Generics)技术性文章
25 1
|
2月前
|
安全 Java
【Java 第六篇章】泛型
Java泛型是自J2 SE 1.5起的新特性,允许类型参数化,提高代码复用性与安全性。通过定义泛型类、接口或方法,可在编译时检查类型安全,避免运行时类型转换异常。泛型使用尖括号`&lt;&gt;`定义,如`class MyClass&lt;T&gt;`。泛型方法的格式为`public &lt;T&gt; void methodName()`。通配符如`?`用于不确定的具体类型。示例代码展示了泛型类、接口及方法的基本用法。
11 0
|
5月前
|
存储
JavaSE&泛型
JavaSE&泛型
18 1
|
5月前
|
存储 SQL Java
【JavaSE语法】类和对象(二)
本文主要介绍了面向对象的三大特点之一封装,并引入了包的概念;还介绍了static修饰类的成员(变量+方法),最突出的特点就是static修饰的属于类,而不属于某个对象;最后介绍了四种代码块
48 7
|
5月前
|
存储 Java 编译器
【从Java转C#】第五章:泛型
【从Java转C#】第五章:泛型
|
11月前
|
存储 Java 编译器
【JavaSE语法】类和对象(一)
【JavaSE语法】类和对象(一)
41 0
|
12月前
|
存储 安全 Java
Javase ------> 泛型
Javase ------> 泛型
48 0
玩转Java面试-泛型类如何定义使用?
玩转Java面试-泛型类如何定义使用?
java202303java学习笔记第三十一天泛型 泛型类 泛型方法1
java202303java学习笔记第三十一天泛型 泛型类 泛型方法1
31 0
java202303java学习笔记第三十一天泛型 泛型类 泛型方法3
java202303java学习笔记第三十一天泛型 泛型类 泛型方法3
35 0