深入解析Java泛型:从定义到实战应用

🚀前言

在这里插入图片描述

大家好!我是 EnigmaCoder
本文主要介绍java泛型部分,包含泛型的定义、泛型类、泛型接口、泛型方法、通配符、上下限等。

🤔泛型的定义

定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>,则称为泛型类、泛型接口、泛型方法,它们统称为泛型。

例如

public class ArrayList<E>{
  ...
}

作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力。这样可以避免强制类型转换,及其可能出现的异常。

代码示例

public class Test {
    public static void main(String[] args) {
        ArrayList <String>list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add(23);

        for(String str:list){
            System.out.println(str);
        }
    }
}
  • 由于使用了泛型,这段代码中list.add(23);会报错,只能使用String类型。如果不使用泛型,则遍历时需要进行强制类型转换,容易出现异常。
  • 泛型的本质:把具体的数据类型作为参数传给类型变量。

🐧泛型类

在Java中,泛型类是一种可以操作多种数据类型的类,它通过类型参数化来实现代码的复用和类型安全。泛型类的定义格式如下:

修饰符 class 类名<类型变量,类型变量,...>{
}

注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等。

代码示例

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}
 
  • Box是一个泛型类,类型参数为T
  • itemBox类的一个私有成员变量,类型为T
  • setItem方法用于设置item的值,参数类型为T
  • getItem方法用于获取item的值,返回类型为T

使用场景

泛型类在需要处理多种数据类型时非常有用,特别是在集合类中。例如,Java标准库中的ArrayListHashMap等都是泛型类。通过使用泛型类,可以避免类型转换的麻烦,并提高代码的类型安全性。

🌟泛型接口

Java中,泛型接口是一种允许在接口定义中使用类型参数的机制。通过使用泛型接口,可以创建更加灵活和可重用的代码,因为它允许接口方法操作多种类型的数据,而不需要为每种类型都编写一个单独的接口。

格式如下

修饰符 interface 接口<类型变量,类型变量...>{
      ...
}

注意:同样建议使用大写英文字母,如:E、T、K、V等。

代码示例

public interface Box<T> {
    void add(T item);
    T get();
}
public class StringBox implements Box<String> {
    private String item;

    @Override
    public void add(String item) {
        this.item = item;
    }

    @Override
    public String get() {
        return item;
    }
}

  • 在这个例子中,StringBox类实现了Box接口,并指定类型参数为String。因此,add方法接受一个String类型的参数,get方法返回一个String类型的值。
  • 泛型接口是Java中一种强大的工具,它允许开发者编写更加通用和可重用的代码。通过使用泛型接口,可以避免为每种数据类型编写单独的接口,从而提高代码的灵活性和可维护性。在实际开发中,泛型接口广泛应用于集合框架、回调机制、数据存储等场景。

✍️泛型方法、通配符、上下限

💯泛型方法

泛型方法是Java中一种强大的特性,它允许在方法中使用类型参数,从而使方法能够处理多种类型的数据,而不需要为每种类型编写单独的方法。泛型方法在集合框架、工具类等场景中广泛应用,能够提高代码的复用性和类型安全性。

格式如下

修饰符<类型变量,类型变量,...>返回值类型 方法名(形参列表){
      ...
}

代码示例

public class GenericMethodExample {

    // 定义一个泛型方法
    public static <T> void printValue(T value) {
        System.out.println("Value: " + value);
    }

    public static void main(String[] args) {
        // 调用泛型方法,传入不同类型的参数
        printValue("Hello, World!");  // 输出: Value: Hello, World!
        printValue(123);              // 输出: Value: 123
        printValue(3.14);             // 输出: Value: 3.14
    }
}
 

💯 通配符与上下限

Java泛型编程中,通配符和上下限是处理泛型类型的重要概念,它们提供了更灵活的类型处理方式。

⚙️通配符(Wildcard)

通配符用“?”表示,它可以在使用泛型时代表任意类型。通配符通常用于泛型方法的参数类型或泛型类的类型参数中,以增加代码的灵活性。例如,在定义一个方法时,如果希望该方法能够接受任何类型的List,可以使用通配符:

public void printList(List<?> list) {
    for (Object elem : list) {
        System.out.println(elem);
    }
}

在这个例子中,List<?>表示可以接受任何类型的List,无论是List<String>List<Integer>还是其他类型的List。

⚙️泛型上下限

泛型上下限用于限制泛型类型的范围,使得泛型类型更加安全和可控。

  • 泛型上限(Upper Bound

泛型上限使用? extends T表示,其中T是一个具体的类或接口。它表示泛型类型必须是T或其子类。例如:

public void processCars(List<? extends Car> cars) {
    for (Car car : cars) {
        car.drive();
    }
}

在这个例子中,List<? extends Car>表示cars列表中的元素必须是Car类或其子类(如SportsCarSUV)。这样可以确保在processCars方法中,所有元素都至少具有Car类的方法。

  • 泛型下限(Lower Bound

泛型下限使用? super T表示,其中T是一个具体的类或接口。它表示泛型类型必须是T或其父类。例如:

public void addCar(List<? super Car> cars, Car car) {
    cars.add(car);
}

在这个例子中,List<? super Car>表示cars列表中的元素必须是Car类或其父类(如Vehicle)。这样可以确保在addCar方法中,可以向列表中添加Car对象或其子类对象。

⚙️应用场景

  • 通配符:适用于需要处理多种类型但不需要知道具体类型的场景,如通用的集合处理方法。
  • 泛型上限:适用于需要限制类型为某个类或其子类的场景,如处理特定类及其子类的集合。
  • 泛型下限:适用于需要限制类型为某个类或其父类的场景,如向集合中添加特定类或其子类的对象。

通过合理使用通配符和泛型上下限,可以使泛型代码更加灵活、安全和易于维护。

🦜泛型支持的类型

在Java中,泛型是一种强大的特性,它允许在定义类、接口和方法时使用类型参数。然而,泛型有一个重要的限制:它不支持基本数据类型(如int、char、boolean等),只能支持对象类型(即引用数据类型,如Integer、String、List等)。这是因为泛型的实现机制依赖于Java的类型系统,而基本数据类型并不属于对象类型。

⚙️泛型擦除

  • 泛型在Java中的实现是通过一种称为“泛型擦除”的机制来完成的。泛型擦除意味着泛型信息只在编译阶段有效,而在编译后的字节码中,所有的泛型类型参数都会被替换为它们的上界(通常是Object类型)。因此,在运行时,泛型类型信息是不可用的。例如,List<String>在编译后会被擦除为List<Object>,这意味着在运行时,你无法通过反射等方式获取到List中元素的类型信息。

  • 这种设计的主要目的是为了保持与Java早期版本的兼容性,因为泛型是在Java 5中引入的,而在此之前,Java的集合类都是使用Object类型来存储元素的。

💯包装类

由于泛型不支持基本数据类型,Java提供了包装类(Wrapper Classes)来将基本数据类型包装成对象类型。包装类位于java.lang包中,每个基本数据类型都有对应的包装类,例如Integer对应intDouble对应doubleBoolean对应boolean等。

基本数据类型包装类默认值字节数最小值最大值
byteByte01-128127
shortShort02-32,76832,767
intInteger04-2,147,483,6482,147,483,647
longLong0L8-9,223,372,036,854,775,8089,223,372,036,854,775,807
floatFloat0.0F41.4E-453.4028235E38
doubleDouble0.0D84.9E-3241.7976931348623157E308
charCharacter'\u0000'2'\u0000' (0)'\uffff' (65,535)
booleanBooleanfalse-falsetrue

⚙️自动装箱与自动拆箱

Java 5引入了自动装箱(Autoboxing)和自动拆箱(Unboxing)机制,使得基本数据类型和它们的包装类之间的转换更加方便。

  • 自动装箱:当需要将一个基本数据类型赋值给一个包装类对象时,Java会自动将基本数据类型转换为对应的包装类对象。例如:

    Integer i = 10;  // 自动装箱,int 10 被转换为 Integer 对象
    
  • 自动拆箱:当需要将一个包装类对象赋值给一个基本数据类型变量时,Java会自动将包装类对象转换为对应的基本数据类型。例如:

    int j = i;  // 自动拆箱,Integer 对象 i 被转换为 int 类型
    

⚙️常用功能

包装类不仅提供了基本数据类型与对象类型之间的转换功能,还提供了一些常用的实用方法:

  1. 将基本数据类型转换为字符串:每个包装类都提供了toString()方法,可以将基本数据类型的数据转换为字符串。例如:

    int num = 123;
    String str = Integer.toString(num);  // 将 int 转换为字符串 "123"
    
  2. 将字符串转换为基本数据类型:包装类还提供了parseXxx()方法,可以将字符串类型的数值转换为对应的基本数据类型。例如:

    String str = "456";
    int num = Integer.parseInt(str);  // 将字符串 "456" 转换为 int 类型
    
  3. 其他实用方法:包装类还提供了一些其他实用方法,如valueOf()compareTo()equals()等,用于处理基本数据类型的比较、转换等操作。

⚙️应用场景

包装类在Java编程中有着广泛的应用,特别是在需要使用泛型或集合类时。例如,当你需要在List中存储整数时,由于泛型不支持基本数据类型,你必须使用Integer包装类:

List<Integer> numbers = new ArrayList<>();
numbers.add(10);  // 自动装箱,int 10 被转换为 Integer 对象
int firstNumber = numbers.get(0);  // 自动拆箱,Integer 对象被转换为 int 类型

此外,包装类在处理数据库操作、网络通信等场景中也经常被使用,因为这些场景通常需要将数据以对象的形式进行传递和处理。

通过使用包装类,Java程序员可以更加灵活地处理基本数据类型和对象类型之间的转换,从而编写出更加健壮和可维护的代码。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值