包装类&简单了解泛型

包装类&简单了解泛型


文章目录

  • 包装类&简单了解泛型
  • 包装类
    • 基本数据类型对应的包装类
    • 装箱和拆箱
  • 泛型
    • 什么是泛型
    • 引出泛型
    • 泛型语法
    • 泛型类的使用
  • 裸类型(了解)
  • 泛型如何编译
    • 擦除机制
    • 为什么不能实例化泛型类数组
  • 泛型的上界
  • 泛型的方法


包装类

在 Java 中,包装类(Wrapper class)是为 8 种基本数据类型提供的对应的引用类型。它们将基本数据类型包装成对象,以便在需要对象的场景中使用(如集合框架、泛型等)

基本数据类型对应的包装类

基本数据类型 对应的包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

装箱和拆箱

1.装箱
将基本数据类型转换为对应的包装类对象

public class BoxingExample {
    public static void main(String[] args) {
        // 基本数据类型
        int intValue = 100;
        boolean booleanValue = true;
        char charValue = 'A';
        double doubleValue = 3.14;

        // 1. 显式装箱(手动转换)
        Integer intObj1 = Integer.valueOf(intValue);  // int -> Integer
        Boolean boolObj1 = Boolean.valueOf(booleanValue);  // boolean -> Boolean
        Character charObj1 = Character.valueOf(charValue);  // char -> Character
        Double doubleObj1 = Double.valueOf(doubleValue);  // double -> Double

        // 2. 自动装箱(Java 5+ 特性,编译器自动完成)
        Integer intObj2 = intValue;  // 等价于 Integer.valueOf(intValue)
        Boolean boolObj2 = booleanValue;  // 等价于 Boolean.valueOf(booleanValue)
        Character charObj2 = charValue;  // 等价于 Character.valueOf(charValue)
        Double doubleObj2 = doubleValue;  // 等价于 Double.valueOf(doubleValue)

        // 3. 装箱在集合中的应用
        java.util.List<Integer> numbers = new java.util.ArrayList<>();
        numbers.add(10);  // 自动装箱:int -> Integer
        numbers.add(20);  // 自动装箱
    }
}

2.拆箱
将包装类对象转换为对应的基本数据类型

public class UnboxingExample {
    public static void main(String[] args) {
        // 包装类对象
        Integer intObj = Integer.valueOf(100);
        Boolean boolObj = Boolean.valueOf(true);
        Character charObj = Character.valueOf('A');
        Double doubleObj = Double.valueOf(3.14);

        // 1. 显式拆箱(手动转换)
        int intValue1 = intObj.intValue();  // Integer -> int
        boolean boolValue1 = boolObj.booleanValue();  // Boolean -> boolean
        char charValue1 = charObj.charValue();  // Character -> char
        double doubleValue1 = doubleObj.doubleValue();  // Double -> double

        // 2. 自动拆箱(Java 5+ 特性,编译器自动完成)
        int intValue2 = intObj;  // 等价于 intObj.intValue()
        boolean boolValue2 = boolObj;  // 等价于 boolObj.booleanValue()
        char charValue2 = charObj;  // 等价于 charObj.charValue()
        double doubleValue2 = doubleObj;  // 等价于 doubleObj.doubleValue()

        // 3. 拆箱在集合中的应用
        java.util.List<Integer> numbers = java.util.Arrays.asList(10, 20, 30);
        int sum = 0;
        for (Integer num : numbers) {
            sum += num;  // 自动拆箱:Integer -> int
        }
        System.out.println("总和: " + sum);  // 输出 60

        // 4. 注意:拆箱可能导致空指针异常
        Integer nullObj = null;
        // int errorValue = nullObj;  // 运行时抛出 NullPointerException
    }
}

泛型

什么是泛型

泛型(Generics)是 Java 中一种参数化类型的机制,它允许在定义类、接口和方法时使用类型参数,而不是具体的数据类型。这些类型参数可以在使用时被具体的类型(如String、Integer等)替换,从而实现 “一套代码适配多种数据类型” 的效果

引出泛型

为什么需要泛型?

在没有泛型的情况下,处理不同类型的数据需要重复编写相似代码,或通过Object类型实现通用性但失去类型安全性。泛型解决了这两个问题:

1.类型安全:编译时检查类型匹配,避免运行时出现ClassCastException(类型转换异常)

2.代码复用:一套代码可以处理多种数据类型,无需为每种类型重复编写逻辑

3.消除强制类型转换:从集合等容器中获取元素时,无需手动转换类型(编译器自动处理)

无泛型:

import java.util.ArrayList;
import java.util.List;

public class WithoutGenerics {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Hello");  // 存入String
        list.add(123);      // 存入Integer(编译不报错,但实际不合理)
        
        // 取出元素时必须强制转换,且可能运行时出错
        String str = (String) list.get(0);  // 正常
        String num = (String) list.get(1);  // 运行时抛出ClassCastException
    }
}

有泛型:

import java.util.ArrayList;
import java.util.List;

public class WithGenerics {
    public static void main(String[] args) {
        // 定义时指定类型参数为String
        List<String> list = new ArrayList<>();
        list.add("Hello");  // 正常
        // list.add(123);   // 编译直接报错(类型不匹配)
        
        // 取出元素时无需转换,编译器已知类型为String
        String str = list.get(0);  // 安全且简洁
    }
}

泛型语法

Java 泛型的语法围绕类型参数展开,用于定义泛型类、接口、方法等。以下是泛型的核心语法规则及示例:

  1. 类型参数声明
    泛型通过尖括号 <> 声明类型参数,格式为:
    <类型参数1, 类型参数2, …>
    类型参数通常使用单个大写字母表示(约定):
    T:Type(任意类型)
    E:Element(集合元素类型)
    K:Key(键类型)
    V:Value(值类型)
    N:Number(数值类型)
  2. 泛型类
    在类名后声明类型参数,作用于整个类
class 类名<类型参数> {
    // 可以使用类型参数定义变量、方法参数、返回值等
    private 类型参数 变量名;
    
    public 类型参数 方法名(类型参数 参数) {
        // 逻辑实现
    }
}

interface 接口名<类型参数> {
    类型参数 方法名();  // 抽象方法使用类型参数
}

修饰符 <类型参数> 返回值类型 方法名(参数列表) {
    // 方法体
}

// T必须是A类的子类(或A类本身)
<T extends A>

// T必须实现B接口(或B接口的子接口)
<T extends B>

// T必须是A类的子类且实现B接口(类在前,接口在后,用&连接)
<T extends A & B>

泛型类的使用

泛型类使用要点:

1.定义泛型类:
在类名后使用<类型参数>声明,如class Pair
类型参数可以有多个,用逗号分隔
类型参数可用于定义成员变量、方法参数和返回值类型

2.实例化泛型类:
必须指定具体类型,如new Pair()
Java 7 + 支持菱形语法new Pair<>(),编译器会自动推断类型
类型参数只能是引用类型,不能是基本类型(需用包装类如Integer)

3.带类型边界的泛型类:
使用extends关键字限制类型范围,如class NumberCalculator
确保只能传入符合边界要求的类型,增强类型安全性
可以安全调用边界类型的方法(如Number的doubleValue())

4.泛型类的优势:
一套代码可处理多种数据类型,提高复用性
编译时进行类型检查,避免运行时类型转换错误
代码更简洁,无需手动类型转换

import java.util.ArrayList;
import java.util.List;

// 1. 定义简单泛型类
class Pair<T, U> {
    private T first;
    private U second;
    
    // 构造方法
    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }
    
    // getter和setter
    public T getFirst() {
        return first;
    }
    
    public void setFirst(T first) {
        this.first = first;
    }
    
    public U getSecond() {
        return second;
    }
    
    public void setSecond(U second) {
        this.second = second;
    }
    
    // 自定义方法
    public void printPair() {
        System.out.println("Pair: (" + first + ", " + second + ")");
    }
}

// 2. 定义带类型边界的泛型类
class NumberCalculator<T extends Number> {
    private List<T> numbers;
    
    public NumberCalculator() {
        numbers = new ArrayList<>();
    }
    
    public void addNumber(T number) {
        numbers.add(number);
    }
    
    // 计算总和(只能处理数值类型)
    public double calculateSum() {
        double sum = 0.0;
        for (T num : numbers) {
            sum += num.doubleValue(); // 可以安全调用Number类的方法
        }
        return sum;
    }
    
    // 计算平均值
    public double calculateAverage() {
        if (numbers.isEmpty()) {
            return 0.0;
        }
        return calculateSum() / numbers.size();
    }
}

// 3. 泛型类的使用示例
public class GenericClassDemo {
    public static void main(String[] args) {
        // 使用简单泛型类:Pair
        Pair<String, Integer> person = new Pair<>("Alice", 30);
        person.printPair(); // 输出: Pair: (Alice, 30)
        
        // 获取和修改值
        String name = person.getFirst();
        int age = person.getSecond();
        person.setSecond(31);
        person.printPair(); // 输出: Pair: (Alice, 31)
        
        // 不同类型的Pair实例
        Pair<Double, Boolean> result = new Pair<>(3.14, true);
        result.printPair(); // 输出: Pair: (3.14, true)
        
        // 使用带类型边界的泛型类
        NumberCalculator<Integer> intCalculator = new NumberCalculator<>();
        intCalculator.addNumber(10);
        intCalculator.addNumber(20);
        intCalculator.addNumber(30);
        System.out.println("整数总和: " + intCalculator.calculateSum()); // 输出: 60.0
        System.out.println("整数平均值: " + intCalculator.calculateAverage()); // 输出: 20.0
        
        NumberCalculator<Double> doubleCalculator = new NumberCalculator<>();
        doubleCalculator.addNumber(1.5);
        doubleCalculator.addNumber(2.5);
        doubleCalculator.addNumber(3.5);
        System.out.println("小数总和: " + doubleCalculator.calculateSum()); // 输出: 7.5
        System.out.println("小数平均值: " + doubleCalculator.calculateAverage()); // 输出: 2.5
        
        // 编译错误:String不是Number的子类,不能用于NumberCalculator
        // NumberCalculator stringCalculator = new NumberCalculator<>();
    }
}

裸类型(了解)

在 Java 中,裸类型(Raw Type) 是指使用泛型类时不指定具体类型参数的形式。它是为了兼容泛型出现之前的旧代码而保留的语法,但在现代 Java 开发中不推荐使用

import java.util.ArrayList;
import java.util.List;

public class RawTypeExample {
    public static void main(String[] args) {
        // 泛型类的正确使用(指定类型参数)
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");  // 只能添加String类型
        String str = stringList.get(0);  // 无需类型转换
        
        // 裸类型(不指定类型参数)
        List rawList = new ArrayList();  // 裸类型
        rawList.add("World");  // 可以添加任意类型
        rawList.add(123);      // 编译不报错,但存在类型安全隐患
        
        // 从裸类型中获取元素需要强制转换,且可能运行时出错
        String item = (String) rawList.get(1);  // 运行时抛出ClassCastException
    }
}

裸类型的特点

1.失去类型安全性:裸类型可以存储任意类型的对象,编译时不进行类型检查,可能导致运行时ClassCastException

2.与泛型类的关系:裸类型本质上是泛型类去掉类型参数后的形式。例如List是List的裸类型,Map是Map的裸类型

3.兼容性目的:裸类型仅为了兼容 Java 5 之前没有泛型的代码而存在,新代码中应避免使用

4.自动转换:当泛型类与裸类型交互时,编译器会发出 “未检查转换” 的警告(Unchecked Cast Warning)

总之,裸类型是 Java 泛型体系中的一种兼容机制,新代码应始终使用带具体类型参数的泛型形式,以保证类型安全和代码清晰

泛型如何编译

擦除机制

Java 的类型擦除(Type Erasure) 是泛型实现的底层机制:在编译时,编译器会将泛型的类型参数信息 “擦除”,替换为其边界类型(若无边界则替换为Object),使得生成的字节码中不包含泛型类型信息

这一机制的目的是保证泛型代码与 Java 5 之前的非泛型代码兼容(因为 JVM 本身不直接支持泛型)

// 定义泛型类
class Box<T> {
    private T value;
    
    public T getValue() { return value; }
    public void setValue(T value) { this.value = value; }
}

// 编译后,类型擦除为:
class Box {
    private Object value;  // T被替换为Object
    
    public Object getValue() { return value; }  // 返回值类型变为Object
    public void setValue(Object value) { this.value = value; }  // 参数类型变为Object
}

为什么不能实例化泛型类数组

在 Java 中,不能直接直接实例化泛型类数组(如new T[10]或new List[5])的核心原因与类型擦除机制和数组的运行时类型检查特性之间的冲突有关。当将泛型数组或普通数组转换为Integer类型数组时,编译器常提示 “不安全”(Unchecked Cast)警告,这与 Java 的类型系统和泛型擦除机制密切相关

import java.util.ArrayList;
import java.util.List;

public class UnsafeCastExample {
    public static void main(String[] args) {
        // 场景1:泛型集合转数组后强转为Integer[]
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        
        // 编译警告:Unchecked cast from Object[] to Integer[]
        Integer[] intArray = (Integer[]) list.toArray();  // 危险!实际是Object[]
        
        
        // 场景2:泛型数组的强制转换
        // 编译警告:Unchecked cast from Object[] to Integer[]
        Integer[] numbers = (Integer[]) createGenericArray(3);
    }
    
    // 创建泛型数组(内部用Object[]实现)
    @SuppressWarnings("unchecked")
    public static <T> T[] createGenericArray(int size) {
        return (T[]) new Object[size];  // 擦除后实际是Object[]
    }
}

泛型的上界

在 Java 泛型中,上界(Upper Bound) 用于限制类型参数只能是指定类型或其派生类型(子类),通过 extends 关键字实现。上界确保泛型代码只能操作符合边界要求的类型,增强了类型安全性并允许调用边界类型的方法

// 单个类型参数,指定上界
<类型参数 extends 上界类型>

// 多个类型参数,分别指定上界
< T extends 上界1, U extends 上界2 >

// 上界可以是类+接口(类在前,接口在后,用&连接)
< T extends 父类 & 接口1 & 接口2 >

示例 1:单个上界(类)
限制类型参数必须是 Number 或其子类(如 Integer、Double),确保可以安全调用 Number 类的方法(如 doubleValue())

public class NumberProcessor<T extends Number> {
    private T value;
    
    public NumberProcessor(T value) {
        this.value = value;
    }
    
    // 可以安全调用Number类的方法
    public double getDoubleValue() {
        return value.doubleValue(); // 所有Number子类都有该方法
    }
    
    // 计算两个数值的和(只能传入Number或其子类)
    public double add(T other) {
        return this.getDoubleValue() + other.doubleValue();
    }
    
    public static void main(String[] args) {
        // 合法:Integer是Number的子类
        NumberProcessor<Integer> intProcessor = new NumberProcessor<>(10);
        System.out.println(intProcessor.add(20)); // 输出:30.0
        
        // 合法:Double是Number的子类
        NumberProcessor<Double> doubleProcessor = new NumberProcessor<>(3.14);
        System.out.println(doubleProcessor.add(2.86)); // 输出:6.0
        
        // 编译错误:String不是Number的子类
        // NumberProcessor stringProcessor = new NumberProcessor<>("123");
    }
}

示例 2:上界为接口
限制类型参数必须实现某个接口(如 Comparable),确保可以调用接口中的方法(如 compareTo())

// 限制T必须实现Comparable接口
public class ComparableUtil<T extends Comparable<T>> {
    // 返回两个元素中较大的一个
    public T getMax(T a, T b) {
        // 调用Comparable接口的compareTo()方法
        return a.compareTo(b) > 0 ? a : b;
    }
    
    public static void main(String[] args) {
        ComparableUtil<Integer> intUtil = new ComparableUtil<>();
        System.out.println(intUtil.getMax(10, 20)); // 输出:20
        
        ComparableUtil<String> strUtil = new ComparableUtil<>();
        System.out.println(strUtil.getMax("apple", "banana")); // 输出:banana
    }
}

示例 3:上界为类 + 接口
类型参数可以同时继承一个类并实现多个接口(类必须放在最前面)

// T必须是Number的子类,且实现Comparable接口
class NumberComparator<T extends Number & Comparable<T>> {
    public T findMax(T[] array) {
        if (array == null || array.length == 0) return null;
        T max = array[0];
        for (T element : array) {
            // 既可以调用Number的方法,也可以调用Comparable的方法
            if (element.compareTo(max) > 0) {
                max = element;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        Integer[] ints = {3, 1, 4, 1, 5};
        NumberComparator<Integer> comparator = new NumberComparator<>();
        System.out.println(comparator.findMax(ints)); // 输出:5
    }
}

泛型的方法

泛型方法是指在方法声明中包含类型参数的方法,它可以在普通类、泛型类或接口中定义,用于实现 “同一方法处理多种数据类型” 的需求。与泛型类不同,泛型方法的类型参数独立于类的类型参数(如果是泛型类的话)

修饰符 <类型参数列表> 返回值类型 方法名(参数列表) {
    // 方法体
}

1.<类型参数列表>:声明一个或多个类型参数(如 、),必须放在方法返回值类型之前

2.类型参数作用范围:仅在当前方法内有效

具体事例:

public class GenericMethodBasic {
    // 泛型方法:打印任意类型的数组
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }
    
    public static void main(String[] args) {
        // 整数数组
        Integer[] intArray = {1, 2, 3, 4, 5};
        System.out.print("整数数组: ");
        printArray(intArray);  // 自动推断类型为Integer
        
        // 字符串数组
        String[] strArray = {"Apple", "Banana", "Cherry"};
        System.out.print("字符串数组: ");
        printArray(strArray);  // 自动推断类型为String
        
        // 自定义对象数组
        Person[] personArray = {
            new Person("Alice", 25),
            new Person("Bob", 30)
        };
        System.out.print("对象数组: ");
        printArray(personArray);  // 自动推断类型为Person
    }
    
    static class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public String toString() {
            return name + "(" + age + ")";
        }
    }
}

你可能感兴趣的:(java,算法)