深入理解Java包装类与泛型的应用

深入理解Java包装类与泛型的应用

今天我将带领大家进入Java包装类和泛型应用的学习。

我的个人主页

            我的Java-数据结构专栏Java-数据结构,希望能帮助到大家。

一、Java包装类基础

二、Java泛型基础

三、Java包装类与泛型的结合

四、Java泛型进阶

五、Java包装类与泛型实战

一、Java包装类基础

在Java中,装箱(boxing)是指将基本数据类型(如int, char, double等)转换为对应的包装类对象(如Integer, Character, Double等)的过程。相反,拆箱(unboxing)是指将包装类对象转换回基本数据类型的过程。

从Java 5(JDK 1.5)开始,Java引入了自动装箱和拆箱机制,以简化基本数据类型和包装类之间的转换。这意味着在需要的时候,Java编译器会自动进行装箱和拆箱操作,而不需要程序员显式地调用转换方法。

  1. 包装类的定义与作用:包装类(Wrapper Classes)在Java编程语言中,指的是将基本数据类型(也称为原始数据类型,如int、char、double等)封装为对象的类。这些类使得基本数据类型可以作为对象进行处理,从而提供了更多的灵活性和功能。

Java中的包装类:在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了 一个包装类型。

注意:​​​​​​​除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。

 自动装箱和拆箱示例代码

public class BoxingUnboxingExample { public static void main(String[] args) { // 自动装箱 int primitiveInt = 10; Integer boxedInt = primitiveInt; // 编译器会自动调用 Integer.valueOf(int) // 自动拆箱 int unboxedInt = boxedInt; // 编译器会自动调用 boxedInt.intValue() // 在算术运算中自动拆箱 Integer anotherBoxedInt = 20; int result = boxedInt + anotherBoxedInt; // 实际上会拆箱为 int,然后执行加法运算,再装箱为 Integer(如果赋值给 Integer 变量) // 但由于 result 是 int 类型,所以这里只涉及拆箱,不涉及装箱 // 显式装箱和拆箱(通常不需要这样做,因为自动装箱和拆箱已经足够) Integer explicitlyBoxedInt = Integer.valueOf(primitiveInt); // 显式装箱 int explicitlyUnboxedInt = explicitlyBoxedInt.intValue(); // 显式拆箱 // 打印结果以验证 System.out.println("Primitive int: " + primitiveInt); System.out.println("Boxed int: " + boxedInt); System.out.println("Unboxed int: " + unboxedInt); System.out.println("Result of boxed int addition: " + result); System.out.println("Explicitly boxed int: " + explicitlyBoxedInt); System.out.println("Explicitly unboxed int: " + explicitlyUnboxedInt); } }

​​​​​​​

  1. 性能考虑:虽然自动装箱和拆箱为程序员提供了便利,但在性能敏感的应用中,过多的装箱和拆箱操作可能会导致性能下降。因为装箱操作需要创建新的对象,而拆箱操作则涉及方法调用。
  2. 空指针异常:由于包装类对象是引用类型,因此它们可以是null。在进行拆箱操作时,如果包装类对象为null,则会抛出NullPointerException。例如,Integer nullInt = null; int value = nullInt; 这段代码会抛出空指针异常。
  3. 缓存机制:Java对某些包装类(如IntegerBooleanByteShortCharacterLong,但不适用于FloatDouble)的值进行了缓存。对于Integer来说,缓存范围是-128到127。在这个范围内的值被装箱时,会返回缓存中的对象,而不是创建新的对象。这有助于提高性能并减少内存使用。
  4. 比较操作:当比较两个包装类对象时,应该使用equals()方法而不是==运算符,因为==比较的是对象的引用而不是值。但是,对于缓存范围内的Integer值,使用==可能会得到正确的结果(因为它们是同一个对象的引用),但这是一种不可靠的做法,因为它依赖于Java的内部实现。
  5. 包装类的常用方法
    • 转换方法(如valueOf, intValue等)
    • 比较方法(如compareTo, equals等)
    • 静态方法(如parseInt, parseDouble等)

二、Java泛型基础:

  1. 泛型的引入与意义​​​​​​​​​​​​​​
    • 在Java 5之前,集合类(如ArrayList、HashMap等)只能存储Object类型的对象。这意 味着在存储和取出元素时,需要进行强制类型转换,这不仅繁琐,而且容易出现类型错误,如ClassCastException。为了解决这个问题,Java引入了泛型机制,允许在定义类、接口和方法时,使用类型参数来指代具体的类型,从而实现代码的通用性和类型安全性。
    • 类型安全
      • 泛型可以在编译时期检查数据类型的合法性,避免出现类型不匹配导致的运行时错误。
      • 编译器可以在编译期验证数据结构中的类型使用是否正确,降低运行时错误的概率。
    • 代码复用
      • 泛型使得代码能够操作多种数据类型,而无需为每种类型单独实现一个版本。
      • 一个泛型数据结构可适配多种类型,提高了代码的复用性。
    • 代码清晰与简洁
      • 使用泛型可以使代码更加清晰、易懂,降低了代码阅读的难度。
      • 避免了不必要的类型转换,减少了代码的冗余,提高了代码的可读性。
    • 性能提升
      • 由于泛型避免了不必要的类型转换,所以在一定程度上可以提高程序的性能。
    • 增强程序的健壮性
      • 通过在编译时期进行类型检查,泛型可以帮助开发者更早地发现并修复类型相关的错误,从而增强程序的健壮性。
    • 泛型类
      • 在定义类时使用泛型参数,可以将具体的数据类型作为参数传递给类,并在类内部使用这些数据类型。
    • 泛型方法
      • 在方法的返回值前使用泛型参数,可以将具体的数据类型作为参数传递给方法,并在方法内部使用这些数据类型。
    • 泛型接口
      • 在定义接口时使用泛型参数,可以将具体的数据类型作为参数传递给接口,并在实现接口的类中使用这些数据类型。
    • Java泛型的引入极大地增强了代码的类型安全性和可读性,提高了代码的复用性和维护性。无论是标准库中的集合类,还是自定义的数据结构,都可以通过泛型实现更灵活、更高效的代码设计。在数据结构中,泛型为开发者提供了统一性和扩展性,同时也为程序的安全性和健壮性保驾护航。

泛型类是指在定义类时使用类型参数(也称为类型占位符)的类。类型参数在类名后面的尖括号<>中指定。

// 定义一个泛型类 Box public class Box<T> { // T 是类型参数,表示任意类型 private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } }

使用泛型类时,可以指定具体的类型参数: 

Box<Integer> integerBox = new Box<>(); integerBox.setContent(100); Integer content = integerBox.getContent();

泛型接口与泛型类的定义类似,只是在接口名后使用尖括号指定类型参数。

// 定义一个泛型接口 Pair public interface Pair<K, V> { K getKey(); V getValue(); }

 实现泛型接口时,需要指定具体的类型参数:

public class OrderedPair<K, V> implements Pair<K, V> { private K key; private V value; public OrderedPair(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } }

泛型方法是指在方法定义时使用类型参数的方法。类型参数在方法返回类型前和方法名后的尖括号中指定。 

// 定义一个泛型方法 printArray public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); }

使用泛型方法时,无需指定类型参数,因为编译器会根据方法调用时的实际参数类型进行推断: 

Integer[] intArray = {1, 2, 3, 4, 5}; printArray(intArray); // 输出: 1 2 3 4 5

Java集合框架中的许多类都是泛型的,如ArrayListHashMap等。使用泛型集合可以避免类型转换和潜在的ClassCastException。 

ArrayList<String> stringList = new ArrayList<>(); stringList.add("Hello"); stringList.add("World"); for (String s : stringList) { System.out.println(s); }

泛型通配符?用于表示未知的类型。它主要有两种形式:无界通配符(?)、上界通配符(? extends SomeType)和下界通配符(? super SomeType)。

// 使用无界通配符 List<?> unknownList = new ArrayList<String>(); // 使用上界通配符 List<? extends Number> numberList = new ArrayList<Integer>(); // numberList 可以是 Integer、Double、Float 等 Number 子类的列表,但不能添加除 null 以外的元素 // 使用下界通配符 List<? super Integer> integerSuperList = new ArrayList<Number>(); // integerSuperList 可以是 Number 或 Number 的父类(如 Object)的列表,可以添加 Integer 或 Integer 的子类对象

三、Java包装类与泛型的结合

 包装类与泛型的结合:

当包装类与泛型结合使用时,可以创建更加灵活和类型安全的集合和数据结构。例如,ArrayList<Integer>是一个使用Integer包装类的泛型集合,它可以存储整数值,并且提供了类型安全的保证。

import java.util.ArrayList; import java.util.List; public class WrapperGenericsExample { public static void main(String[] args) { // 创建一个存储Integer对象的ArrayList List<Integer> integerList = new ArrayList<>(); // 向列表中添加整数(自动装箱) integerList.add(10); integerList.add(20); integerList.add(30); // 遍历列表并打印每个元素(自动拆箱) for (Integer integer : integerList) { System.out.println(integer); } // 使用包装类的方法 int sum = 0; for (Integer integer : integerList) { sum += integer.intValue(); // 使用intValue()方法将Integer转换为int } System.out.println("Sum: " + sum); } }
ArrayList<Integer>是一个泛型集合,它使用Integer包装类来存储整数。由于使用了泛型,我们得到了类型安全的保证:我们不能向integerList中添加除Integer对象以外的任何对象(除了null,但在Java 5及更高版本中,泛型集合通常不允许存储null以避免潜在的NullPointerException)。 
四、Java泛型进阶:

泛型擦除示例:

import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; // 定义一个简单的泛型类 public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } // 一个用于反射获取泛型类型信息的方法(注意:这个方法在泛型擦除后无法直接获取到T的具体类型) public Type getGenericType() { return ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("Hello, World!"); System.out.println(stringBox.getContent()); // 通过反射尝试获取泛型类型信息(实际上在运行时已经擦除) Type genericType = stringBox.getGenericType(); System.out.println("Generic type (at runtime, may be erased): " + genericType); // 但我们可以通过子类化并立即获取泛型信息来“绕过”擦除(这种方法不适用于所有情况) class StringBox extends Box<String> {} Type stringBoxType = StringBox.class.getGenericSuperclass().getActualTypeArguments()[0]; System.out.println("Generic type of StringBox (inferred at compile time): " + stringBoxType); // 注意到,直接使用Box<String>的实例在运行时无法获取到T的具体类型,因为泛型信息已被擦除 } }

 Java的类型推断机制允许编译器根据上下文自动推断出变量的类型,从而简化了代码的编写。以下是一个使用类型推断的示例:

import java.util.ArrayList; import java.util.List; public class TypeInferenceExample { public static void main(String[] args) { // 在Java 7及更高版本中,可以在右侧的构造函数调用中省略泛型类型参数 List<String> list1 = new ArrayList<>(); // 类型推断为List<String> // 在Java 10及更高版本中,可以使用var关键字进一步简化变量声明 var list2 = new ArrayList<String>(); // 类型推断为ArrayList<String>(但var的使用应谨慎,以避免降低代码可读性) // 方法调用中的类型推断 printList(list1); // 编译器可以推断出list1的类型为List<String> // 泛型方法调用中的类型推断 List<Integer> numbers = createListWithNumbers(1, 2, 3, 4, 5); // 编译器可以推断出返回类型为List<Integer> } // 一个简单的泛型方法,用于创建并返回一个包含指定元素的列表 public static <T> List<T> createListWithElements(T... elements) { List<T> list = new ArrayList<>(); for (T element : elements) { list.add(element); } return list; } // 一个用于打印列表内容的方法(注意:这里不是泛型方法,只是接受泛型类型的参数) public static void printList(List<?> list) { for (Object element : list) { System.out.println(element); } } // 一个具体的泛型方法调用示例,用于创建并返回一个包含数字的列表 public static List<Integer> createListWithNumbers(Integer... numbers) { return createListWithElements(numbers); // 这里再次调用了泛型方法createListWithElements,并进行了类型推断 } }
五、Java包装类与泛型实战

在实际开发中,包装类与泛型经常一起使用。例如,当需要将基本数据类型存储在集合中时,由于集合的泛型参数必须是对象类型,因此需要使用包装类。

import java.util.ArrayList; import java.util.List; public class WrapperAndGenericsExample { public static void main(String[] args) { // 创建一个存储Integer对象的列表 List<Integer> integerList = new ArrayList<>(); integerList.add(10); integerList.add(20); integerList.add(30); // 遍历列表并打印元素 for (Integer integer : integerList) { System.out.println(integer); } // 创建一个存储Double对象的列表 List<Double> doubleList = new ArrayList<>(); doubleList.add(1.1); doubleList.add(2.2); doubleList.add(3.3); // 使用泛型方法来打印Double列表 printList(doubleList); } // 泛型方法,用于打印列表中的元素 public static <T> void printList(List<T> list) { for (T element : list) { System.out.println(element); } } }

Read more

鸿蒙金融理财全栈项目——上线与运维、用户反馈、持续迭代优化

鸿蒙金融理财全栈项目——上线与运维、用户反馈、持续迭代优化

《鸿蒙APP开发从入门到精通》第25篇:鸿蒙金融理财全栈项目——上线与运维、用户反馈、持续迭代优化 🚀📱🔧 内容承接与核心价值 这是《鸿蒙APP开发从入门到精通》的第25篇——上线与运维、用户反馈、持续迭代优化篇,100%承接第24篇的生态合作、用户运营优化、数据产品变现优化架构,并基于金融场景的上线与运维、用户反馈、持续迭代优化要求,设计并实现鸿蒙金融理财全栈项目的上线与运维、用户反馈、持续迭代优化功能。 学习目标: * 掌握鸿蒙金融理财项目的上线与运维优化设计与实现; * 实现应用上线优化、应用运维优化、应用监控优化; * 理解用户反馈在金融场景的核心优化设计与实现; * 实现用户反馈收集优化、用户反馈分析优化、用户反馈处理优化; * 掌握持续迭代优化在金融场景的设计与实现; * 实现持续集成优化、持续部署优化、持续交付优化; * 优化金融理财项目的用户体验(上线与运维、用户反馈、持续迭代优化)。 学习重点: * 鸿蒙金融理财项目的上线与运维优化设计原则; * 用户反馈在金融场景的优化应用; * 持续迭代优化在金融场景的设计要点。 一、

By Ne0inhk
Flutter 组件 shared_aws_api 的适配 鸿蒙Harmony 实战 - 驾驭跨平台 AWS 云服务通讯、实现鸿蒙端签名版本 4 (SigV4) 自动审计与高性能 API 鉴权方案

Flutter 组件 shared_aws_api 的适配 鸿蒙Harmony 实战 - 驾驭跨平台 AWS 云服务通讯、实现鸿蒙端签名版本 4 (SigV4) 自动审计与高性能 API 鉴权方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 shared_aws_api 的适配 鸿蒙Harmony 实战 - 驾驭跨平台 AWS 云服务通讯、实现鸿蒙端签名版本 4 (SigV4) 自动审计与高性能 API 鉴权方案 前言 在鸿蒙(OpenHarmony)生态的全球化云办公场景、海量存储资产分发、以及涉及亚马逊云科技(AWS)基础设施的各类混合云应用开发中,“安全的云服务通讯”是工程交互的第一道铁闸。面对需要从鸿蒙应用中直接调用 S3 存储桶、DynamoDB 数据库或是 Lambda 无服务器函数。如果仅仅依靠手写原始的 HTTP 请求,那么不仅会导致复杂的签名版本 4(AWS Signature Version 4, SigV4)

By Ne0inhk

如何在 Ubuntu 20.04 系统的服务器上用k3s实现轻量级 Kubernetes 集群并部署微服务最佳实践

如何在 Ubuntu 20.04 服务器上搭建轻量级 Kubernetes(k3s)集群,并部署典型的微服务架构。A5数据将覆盖硬件选型、系统准备、k3s 安装、网络与存储方案、部署示例、CI/CD 集成及性能评估,对每个步骤提供具体配置与代码示例。 注意:本文假设您有至少两台可以访问互联网的物理服务器或云主机,并具备基本的 Linux 权限和网络规划经验。 一、香港服务器www.a5idc.com硬件与系统选型 由于 k3s 设计用于边缘计算、IoT 和轻量容器平台,推荐在中等规格服务器上运行。以下是我们在生产预研阶段使用的典型硬件配置: 节点CPU内存存储网络master-14 核 Intel Xeon8 GB ECC120 GB NVMe1 Gbpsworker-14 核 Intel Xeon8 GB ECC120 GB

By Ne0inhk

node-llama-cpp错误处理与调试:解决本地AI开发常见问题

node-llama-cpp错误处理与调试:解决本地AI开发常见问题 【免费下载链接】node-llama-cppRun AI models locally on your machine with node.js bindings for llama.cpp. Force a JSON schema on the model output on the generation level 项目地址: https://gitcode.com/gh_mirrors/no/node-llama-cpp node-llama-cpp是一款强大的工具,它提供了llama.cpp的node.js绑定,让你能够在本地机器上运行AI模型,并在生成级别强制模型输出JSON模式。对于新手和普通用户来说,在使用过程中可能会遇到各种错误和问题,本文将详细介绍常见错误的处理方法和调试技巧,帮助你顺利进行本地AI开发。 常见错误类型及解决方法 二进制文件未找到错误(NoBinaryFoundError)

By Ne0inhk