Android泛型详解

参考文献:https://pingfangx.github.io/java-tutorials/java/generics/types.html 

1,什么是泛型?

Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了 编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数类型,也就是说所操作的数据类型被指定为一个参数。 泛型不存在于JVM虚拟机。

通俗点讲,就是将类型参数化,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这样提高了代码的类型安全性,使你在编译时可以检测到更多错误。

2,为什么要使用泛型?

泛型在定义类,接口和方法时使类型(类和接口)成为参数。与方法声明中使用的形式参数非常相似,类型参数为你提供了一种使用不同输入重复使用相同代码的方法。区别在于形式参数的输入是值,而类型参数的输入是类型。
下面来看一个官方的例子
使用非泛型的代码如下:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);//需要强制转换

list.add(1);
s =(String) list.get(1); //在编译时期没有任何错误提示 在运行时期会报错

使用泛型的代码如下:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); //不需要进行强制转换

list.add(1);
s =list.get(0); //会在编译期就报错 Required type:String  Provided:int

通过上面官方例子,我们不难发现,与非泛型代码相比,使用泛型的代码具有以下优点:

1,在编译时进行更强的类型检查。Java编译器将强类型检查应用于通用代码,如果代码违反类型安全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到。

2,消除类型转换。当使用泛型重写时,代码不需要强制转换。

3,使程序员能够实现通用算法。通过使用泛型,程序员可以实现对不同类型的集合进行工作,可以自定义并且类型安全且易于阅读的泛型算法。

3,泛型类的创建

泛型类是通过类型进行参数化的通用类。

泛型类的定义格式如下:

class 类名<T1, T2, ..., Tn> {
    
    private 泛型标识  变量名;

}
//例如:
class Student<T> {
    
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }

}

上面T可以随便定义,只要是在<>中,你甚至可以定义为dnadnandn都行,注意:尽量不要定义为关键字,以免引起冲突。

注意:类型参数与类型变量的区别:

Student<T>中的T为类型参数

Student<String>中的String为类型变量

常用的类型参数名称是:

T - Type类型 最常用
E - 元素或Exception异常
K - Key
V - Value
N - 数字
S,U,V 等:第二,第三,第四个类型

下面看一个简单的例子

泛型类:

public class MyList<T> {

    private List<T> list;

    public void add(T t){
        list.add(t);
    }

    public T get(int i){
       return list.get(i);
    }
    public int size(){
        return list.size();
    }

}

不使用泛型的类:

public class MyList1 {

    private List<Object> list;

    public void add(Object t){
        list.add(t);
    }

    public Object get(int i){
        return list.get(i);
    }

    public int size(){
        return list.size();
    }
}

在使用时:

 //泛型类的使用
 MyList<String> myList =new MyList<>();
 myList.add("aaa");
 //myList.add(1);  在编译时就报错
 for (int i = 0; i < myList.size(); i++) {
   String s1 = myList.get(i);
   System.out.println("s1="+s1);
 }

 //非泛型类的使用
 MyList1 myList1 =new MyList1();
 myList1.add(11);//不会报错
 myList1.add("111");
 for (int i = 0; i < myList1.size(); i++) {
     String s1 = (String) myList1.get(i);//1,需要强转 2,在运行时会报错
     System.out.println("s1="+s1);
 }

泛型类还可以传入多个泛型参数,下面看一个 两个参数的泛型类:

public class People<K,V> {

    private K name;

    private V achievement;

    public People(K name, V achievement) {
        this.name = name;
        this.achievement = achievement;
    }

    public void setName(K name) {
        this.name = name;
    }

    public void setAchievement(V achievement) {
        this.achievement = achievement;
    }

    public K getName() {
        return name;
    }

    public V getAchievement() {
        return achievement;
    }
}
People<String,Integer> p1 =new People<>("张三",98);
People<String,Integer> p2 =new People<>("赵武",99);
//   People<String,Integer> p3 =new People<>("赵武","100");//编译阶段报错

需要注意的是泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数,例如:

public class Test<T> {

    private static T name;//编译时报错'com.yuanzhen.Test.this' cannot be referenced from a     static context

    public static T getName(){//编译时报错'com.yuanzhen.Test.this' cannot be referenced from a static context
        return name;
    }
}

原因是泛型在对象创建时才知道是什么类型,但是静态方法属于类,调用getName方法实际调用的Test类的方法,而类在编译阶段就存在了,所以虚拟机根本不知道方法中引用的泛型是什么类型
初始化时:对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等,所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西

4,泛型接口的创建

泛型接口的定义和泛型类的定义一样,区别就在于一个是接口需要实现各个接口方法,一个是类,需要实现对应的抽象方法。

泛型接口的定义格式如下:

public interface 接口名<类型参数> {
    ...
}
//例如
public interface IPeople<T> {
    T getName();
    void setName(T t);
}

泛型的具体使用方式:

①直接在实现类中指定泛型的具体类型

public class Student<String> implements IPeople<String>{
    @Override
    public String getName() {
        return null;
    }

    @Override
    public void setName(String string) {

    }
}

②在实现类中继续使用泛型,在实例化实现类对象的时候指定泛型的具体类型

public class Teacher<T> implements IPeople<T>{
    @Override
    public T getName() {
        return null;
    }

    @Override
    public void setName(T t) {

    }
}
Teacher<String> teacher =new Teacher<>();
teacher.setName("张三");

③在接口继承接口中指定泛型的具体类型。

public interface ITeacher<String> extends IPeople<String>{

}

5,泛型方法的创建

泛型方法是指引入自己的类型参数的方法。这类似于声明一个泛型方法,但类型参数的范围仅限于声明它的方法。允许使用静态和非静态的泛型方法,也允许使用泛型类构造函数。
泛型方法的语法包括类型参数列表,在尖括号内,该列表出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。
泛型方法的格式如下:
public <类型参数1,类型参数2...> 返回类型 方法名(类型参数1 变量名1,类型参数2 变量名2 ...) {
    ...
}
//例如:
public class Doctor {
    
    /**
    * 泛型方法
    */
    public <T> void doWork(T t){

    }
}

注意:只有在方法声明中声明了<>的方法才是泛型方法

下面看个例子:

public class Doctor<T> {
    
    private T name;
    
    /*
    * 不是泛型方法 只是普通方法
    * */
    public Doctor(T name) {
        this.name = name;
    }
    /*
     * 不是泛型方法 只是普通方法
     * */
    public void setName(T name) {
        this.name = name;
    }
    /*
     * 不是泛型方法 只是普通方法
     * */
    public T getName() {
        return name;
    }
    /*
     * 泛型方法 因为它定义了自己的<T>
     * */
    public <T> void doWork(T t){

    }
}

调用泛型方法:

Doctor<String> doctor =new Doctor<>("张三");
doctor.<String>doWork("看病");//调用泛型方法的完整语法
doctor.doWork("看病");//因为类型推断,所以可以省略<String> 

类型推断:上文中可以看出,因为类型推断,可以省略<>,那么什么是类型推断呢?

类型推断(Type Inference)是指 Java 编译器能查看每个方法的调用和相应声明,以确定调用合适的类型参数(Type Argument)或参数。推断算法决定参数的类型,如果可用,则指定被赋值的类型或返回的类型。最后,推断算法试图在一起工作的所有参数中找到最具体的类型。

引入类型推断的泛型方法,能够让你像调用普通方法一样调用泛型方法,而不需要在尖括号中指定类型。

5,限定类型参数

有时你可能想限制可以在参数化类型中用作类型参数的类型。例如,对数字进行操作的方法可能只希望接受 Number 或其子类的实例。这就是限定类型参数的用途。
要声明一个限定的类型参数,请列出类型参数的名称,然后列出 extends 关键字,然后列出其上限(在本示例中为 Number )。请注意,在这种情况下, extends 通常用于表示“扩展”(如在类中)或“实现” (如在接口中)。
限定类型参数格式:
<T extends B1> //单个限定
<T extends B1 & B2 & B3> //多重限定

举例如下:

public class Teacher<T extends Number>{
    private T age;

    public T getAge() {
        return age;
    }
    public void setAge(T t) {

    }
}
Teacher<String> teacher1 =new Teacher<>();//报错 因为已经限定了类型必须是Number及其子类
teacher1.setAge("张三");
//正确用法
Teacher<Integer> teacher2 =new Teacher<>();
teacher2.setAge(20);

6,通配符

在通用代码中,称为通配符的问号( ? )表示未知类型。通配符可以在多种情况下使用:作为参数,字 段或局部变量的类型;有时作为返回类型(尽管更具体的做法是更好的编程习惯)。通配符从不用作泛 型方法调用,泛型类实例创建或超类型的类型参数。

①上限通配符

格式如下:

<? extends T>
//例如:
<? extends Foo> 
//其中,Foo可以是任何类型,匹配Foo和Foo的任何子类型

下面来看一个具体的使用案例:

public class Fruit {

    @Override
    public String toString() {
        return "水果";
    }
}
public class Apple extends Fruit {

    @Override
    public String toString() {
        return "苹果";
    }
}
ArrayList<Fruit> fruits =new ArrayList<Apple>();//这样会在编译期报错

这样为什么会报错呢?Java不是可以将子类对象赋值给一个父类对象的引用吗?

下来再来看一下这段代码:

public class Banana extends Fruit {

    @Override
    public String toString() {
        return "香蕉";
    }
}
ArrayList<Apple> apples =new ArrayList<Apple>();
Apple apple =new Apple();
apples.add(apple);
//----------------上面的代码不会报错 是正常逻辑------------
ArrayList<Fruit> fruits =apples;//假如这行代码在编译期不报错的话
fruits.add(new Banana());
Apple a =fruits.get(1);//这行代码在运行期间就会报类型转换异常的错误

所以泛型是不支持这种向上转型的。

但是如果我们一定要这么写呢?也不是不可以,用通配符就可以做到

ArrayList<? extends Fruit> fruits =new ArrayList<Apple>();//编译期不会报错
Apple apple =new Apple();
fruits.add(apple);//但是在添加的时候会在编译期报错

大家想一想为什么会在添加的时候会在编译期报错呢?

因为如果放开的话,我还是同样的可以添加香蕉,苹果等子类,这样在运行期间就可能会出现更大的错误,所以编译器直接就不让你添加了,这样就不会有问题了。

但是这样做有什么意义呢?

当你只想让用户往外取值,不想让用户进行写入时,<? extends Fruit>的意义就体现出来了,简而言之就是只能读取不能写入

②下限通配符

格式如下:

<? super T>
//例如:
<? supper Zoo>
//其中,Zoo可以是任何类型,匹配Zoo和Zoo的任何超类

下面来看一个例子:

ArrayList<Apple> fruits =new ArrayList<Fruit>();//会在编译期报错
Apple apple =new Apple();
fruits.add(apple);

为什么会在编译期报错呢?同上限通配符一样的道理,泛型也不支持这种转型

那么需要怎么做呢?请看下面:

ArrayList<? super Apple> fruits =new ArrayList<Fruit>();//不报错 正常运行
Apple apple =new Apple();
fruits.add(apple);
Object object = fruits.get(0);

使用下限通配符为什么可以添加呢? 因为往里面添加的都是apple的父类,其归根结底,都会用一个最终的父类表示,所以不会有问题。但是往外读的时候,我不知道是苹果还是水果,所以不建议读取,虽然可以用Object接收。

简而言之,与上限通配符相反,下线通配符只能写入,不能读取

③通配符使用准则

在开发中,我们应该在什么时候使用上限通配符和下限通配符呢?

输入 变量:输入变量将数据提供给代码。一个具有两个参数的复制方法: copy(src, dest) 。
src参数提供要复制的数据,因此它是输入参数。
输出 变量:输出变量保存要在其它地方使用的数据。在复制示例 copy(src, dest) 中,dest参数接
受数据,因此它是输出参数。
通配符准则:
使用上限通配符定义输入变量,使用 extends 关键字。
使用下限通配符定义输出变量,使用 super 关键字。
如果可以使用 Object 类中定义的方法访问输入变量,请使用无界通配符( ? )。
如果代码需要同时使用输入和输出变量来访问变量,则不要使用通配符。
这些准则不适用于方法的返回类型。应该避免使用通配符作为返回类型

7,类型擦除

Java语言引入了泛型,以在编译时提供更严格的类型检查并支持泛型编程。 为了实现泛型,Java编译器将类型擦除应用于:
1,如果类型参数不受限制,则将通用类型中的所有类型参数替换为其边界(上下限)或 Object 。因此,产生的字节码仅包含普通的类,接口和方法。
2,必要时插入类型转换,以保持类型安全。
3,生成桥接方法以在扩展的泛型类型中保留多态。
类型擦除可确保不会为参数化类型创建新的类;因此,泛型不会产生运行时开销。
在类型擦除过程中,Java编译器将擦除所有类型参数,如果类型参数是有界的,则将每个参数替换为其第一个边界;如果类型参数是无界的,则将其替换为 Object 。

下面来看几个例子:

public class Node<T> {
    private T data;
    private Node<T> next;
    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }
    public T getData() { return data; }
    
}
由于类型参数T是无界的,因此Java编译器将其替换为 Object :
public class Node {
    private Object data;
    private Node next;
    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }
    public Object getData() { return data; }
}
在下面的示例中,通用Node类使用限定类型参数:
public class Node<T extends Comparable<T>> {
    private T data;
    private Node<T> next;
    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }
    public T getData() { return data; }
}
Java编译器将绑定类型参数T替换为第一个绑定类 Comparable :
public class Node {
    private Comparable data;
    private Node next;
    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }
    public Comparable getData() { return data; }
}

8,对泛型的限制

①无法实例化具有基本类型的泛型类型

请看下面例子:

class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
}

创建对对象时,不能用基本类型替换类型参数K或V:

Pair<int, char> p = new Pair<>(8, 'a');//编译错误

你只能将非基本类型替换为类型参数K和V:

Pair<Integer, Character> p = new Pair<>(8, 'a'); 

②无法创建类型参数的实例

例如,以下代码会导致编译时错误:

public static <E> void append(List<E> list) {
    E elem = new E(); //编译期错误
    list.add(elem);
}

解决方法是,可以通过反射创建类型参数的对象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance(); // OK
    list.add(elem);
}

③无法声明类型为类型参数的静态字段

类的静态字段是该类的所有非静态对象共享的类级别变量。因此,不允许使用类型参数的静态字段

public class MobileDevice<T> {
    private static T os;
}

④无法将Casts或instanceof与参数化类型一起使用

因为Java编译器会擦除通用代码中的所有类型参数,所以你无法验证在运行时使用的是通用类型的参数 化类型
public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) { // 编译器错误
    // ...
    }
}

⑤无法创建参数化类型的数组

List<Integer>[] arrayOfLists = new List<Integer>[2];//编译期报错

⑥无法创建,捕获或抛出参数化类型的对象

泛型类不能直接或间接扩展 Throwable 类。
class MathException<T> extends Exception { /* ... */ } // 编译期错误
class QueueFullException<T> extends Throwable { /* ... */ }// 编译器错误
方法无法捕获类型参数的实例:
public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
    } catch (T e) { //编译期错误

    }
}
但是,你可以在 throws 子句中使用类型参数:
class Parser<T extends Exception> {
    public void parse(File file) throws T { // OK
    }
}

⑦无法重载每个重载的形式参数类型都擦除为相同原始(raw)类型的方法

一个类不能有两个重载的方法,这些方法在类型擦除后将具有相同的签名。
public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

9,kotlin中的泛型

Kotlin的泛型与Java的泛型是有一些不同的:

①Kotlin的泛型是支持限定泛型的类型的,使用关键字where进行限定,多个条件同时限定用,号分隔

interface CallBack{
     fun invoke()
}

class Test<T>() where T:CallBack, T:Runnable{
    fun add(t: T){
        t.run()
        t.invoke()
    }
}

class A : CallBack, Runnable{
    override fun invoke() {
        println("invoke A")
    }

    override fun run() {
        println("run A")
    }
}

open class B : CallBack{
    override fun invoke() {
        println("invoke B")
    }
}

class C : B(), Runnable{
    override fun invoke() {
        super.invoke()
    }

    override fun run() {
        println("run C")
    }
}

fun main() {
    val testA =Test<A>()
    testA.add(A())

//  这行代码报错,因为TEST接受的泛型,一定要实现Interface和Runnable接口,B仅实现了Runnable
//    val testB =Test<B>()
//    testB.add(B())

    //这行代码可以使因为C继承自B,B实现了Runnable,C又实现了Callback,因此满足TEST对泛型类的要求
    val testC =Test<C>()
    testC.add(C())
}

②Kotlin中的泛型是真泛型,可以通过泛型拿到泛型的类型

在 Java中使用泛型的时候,无法通过泛型来得到 Class,因此一般我们会将 Class通过参数传过去。原因是java会在编译后把泛型的类型都擦除,变成object,因此无法获取到真正的类型。

Kotlin的真泛型只能用在函数中,而且必须和inline配合使用,定义真泛型需要额外使用一个关键字 reified

inline fun <reified T> 函数名体定义
  inline fun <reified T> Gson.fromJson(json:String) : T{
      //使用真泛型能拿到泛型的类型,因为kotlin有自动推导,所以可以根据接收的变量类型推导出T的类型
      return fromJson(json, T::class.java)
  }

在 Kotlin 中泛型引入了 in 和 out,什么是in和out呢?

out(协变) :如果你的类是将泛型仅作为方法的返回使用,那么可以用 out 修饰

//1.用out修饰时,泛型类型仅能作为返回值使用
//2.不能用于变量定义
interface Production<out T> {
    fun produce(): T
}

in(逆变) :如果你的类是将泛型仅作为方法的参数使用,那么可以用 in 修饰

//1.用in修饰时,泛型类型仅能作为参数使用
//2.不能用于变量定义
interface Consumer<in T> {
    fun consume(item: T)
}

不变:如果不加任何关键字修饰,则可以将泛型既当参数使用,又能当返回值使用,可以用在任何地方

//1.可以用于参数
//2.可以用于返回值
//3.可以用于定义变量
interface ProductionConsumer<T> {
   fun produce(): T
   fun consume(item: T)
}

10,在安卓中的使用

那么在了解了泛型的知识之后,我们在安卓开发中,到底用到了哪些泛型呢?

①网络请求

最常用的地方,就是解析http请求下来的json数据。因为http请求下来的数据,我们不知道需要转换成什么类型,只有在调用的地方才知道,所以我们采用泛型。

先定义一个回调接口:

public interface CallBack<T> {

    void  onError(Exception e);

    void onSuccess(T t);
}

在实际请求中传入匿名内部类: 

 OmniHttp.get(getHOST() + "/app-wn/login")
        .syncRequest(true)
        .execute(new CallBack<LoginBean>() {
            @Override
            public void onError(ApiException e) {
                       
            }

            @Override
            public void onSuccess(LoginBean loginBean) {
                        
            }
});

这只是简单的写了一个小例子,可以尝试在自己写一下

②findViewById()

 /**
 * FindViewById的源码,泛型封装,减少强转代码
 */
 public <T extends View> T findViewById(@IdRes int id) {
     return getDelegate().findViewById(id);
 }
//使用前
ImageView item = (ImageView)findViewById(R.id.item);
//使用后
ImageView item =findViewById(R.id.item);

③MVP架构

Android 使用kotlin+注解+反射+泛型实现MVP架构_袁震的博客-CSDN博客

11,总结

花了一天时间整理了下泛型的相关知识点,在android中的应用后续还会继续补充。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java泛型Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将类型参数化,实现代码的通用性。 一、泛型的基本语法 在声明类、接口、方法时可以使用泛型泛型的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类型参数,多个类型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个泛型类,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明中,`<T>`就是类型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型类的使用 在使用泛型类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个泛型类,`<String>`指定了具体的类型参数,即`data`字段的类型为`String`,`gc`对象被创建时没有指定类型参数,因为编译器可以根据上下文自动推断出类型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的类型参数,匿名内部类实现了该接口,并使用`String`作为类型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个泛型方法,`<T>`指定了类型参数,`T data`表示一个类型为`T`的参数,调用时可以传入任何类型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体类型,可以使用通配符`?`。通配符可以作为类型参数出现在方法的参数类型或返回类型中,但不能用于声明泛型类或泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类型为`List<?>`,表示可以接受任何类型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类型来遍历`List`中的元素。 四、泛型的继承 泛型类和泛型接口可以继承或实现其他泛型类或泛型接口,可以使用子类或实现类的类型参数来替换父类或接口的类型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类型参数`T`。 五、泛型的限定 有时候,我们需要对泛型的类型参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类型参数的上限,或使用`super`关键字来限定类型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类型参数`T`被限定为`Number`的子类,`GenericInterface`的类型参数`T`被限定为实现了`Comparable`接口的类。 六、泛型的擦除 在Java中,泛型信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取泛型的具体类型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类型为`List<String>`,但是在运行时,`getClass`返回的类型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型的类型推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类型推断等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

袁震

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值