登神长阶 第六阶 包装类&初识泛型
目录
😀一.包装类
😄1.基本数据类型以及其对应的包装类
😂2.装箱和拆箱
😇2.1.装箱(Boxing)
😉2.2.拆箱(Unboxing)
🫠2.3.注意事项
🥰二.泛型
😍1. 泛型类和泛型接口
🤩2.泛型类的具体使用
😜三.泛型方法
🤗1. 声明泛型方法
🫡2. 调用泛型方法
😛3. 类型推断
🤨4. 泛型方法与泛型类的关系
😷5. 泛型方法的优势
🤤四.泛型类的具体编译
🥴1. 类型擦除(Type Erasure)
😵💫2. 桥接方法(Bridge Method)
🤠3. 示例
🧐五.泛型的上界
😎1. 声明上界
🥹2.使用上界的好处
😣3. 上界通配符
😤4.注意事项
🤓六.总结与反思
😀一.包装类
定义
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。包装类用于将基本数据类型转换为对象。它们允许在对象上执行额外的操作,例如将基本数据类型作为参数传递给方法,或者在集合类中存储基本数据类型。
😄1.基本数据类型以及其对应的包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Interger |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
除了 Integer 和 Character , 其余基本类型的包装类都是首字母大写
😂2.装箱和拆箱
在Java中,装箱(boxing)和拆箱(unboxing)是指将基本数据类型转换为对应的包装类对象,以及将包装类对象转换为基本数据类型的过程。在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。
😇2.1.装箱(Boxing)
装箱是指将基本数据类型转换为对应的包装类对象。在Java中,这个过程是自动进行的,称为自动装箱(autoboxing)。例如,将int
类型的值装箱为Integer
对象:
int num = 10;
Integer wrappedNum = num; // 自动装箱
😉2.2.拆箱(Unboxing)
拆箱是指将包装类对象转换为对应的基本数据类型。同样,这个过程也是自动进行的,称为自动拆箱(unboxing)。例如,将Integer
对象转换为int
类型:
Integer wrappedNum = 10;
int num = wrappedNum; // 自动拆箱
🫠2.3.注意事项
- 自动装箱和拆箱可以使基本数据类型与其对应的包装类对象之间转换更加方便。
- 自动装箱和拆箱发生在编译时,编译器会自动插入相应的代码。
- 在使用自动装箱和拆箱时,需要注意空指针异常(NullPointerException),因为如果包装类对象为
null
,拆箱操作可能会导致异常。
总的来说,装箱和拆箱提供了方便的方式来在基本数据类型和包装类对象之间进行转换,同时避免了手动进行类型转换的繁琐工作。
🥰二.泛型
在Java中,泛型(Generics)是一种强大的特性,用于在编译时检查和确保程序的类型安全性。泛型允许类、接口和方法在定义时使用一个或多个类型参数,以便在实际使用时可以指定具体的类型。
💡 一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《 Java 编程思想》对泛型的介绍。
😍1. 泛型类和泛型接口
- 可以通过在类名后面使用尖括号
<>
来声明泛型类。 - 泛型类可以包含一个或多个类型参数,用于指定具体类型。
- 泛型接口的定义与泛型类类似,可以在接口中定义泛型类型参数。
语法
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
举例
class MyArray<T> {
public T[] array = (T[])new Object[10];//1
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();//2
myArray.setVal(0,10);
myArray.setVal(1,12);
int ret = myArray.getPos(1);//3
System.out.println(ret);
myArray.setVal(2,"bit");//4
}
}
- E 表示 Element
- K 表示 Key
- V 表示 Value
- N 表示 Number
- T 表示 Type
- S, U, V 等等 - 第二、第三、第四个类型
🤩2.泛型类的具体使用
语法
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
MyArray<Integer> list = new MyArray<Integer>();
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
MyArray < Integer > list = new MyArray <> (); // 可以推导出实例化需要的类型实参为 Integer
😜三.泛型方法
泛型方法是在方法声明时带有类型参数的方法。它们可以独立于所属类进行参数化,并且可以在调用时指定具体类型。以下是关于泛型方法的详细描述:
🤗1. 声明泛型方法
- 在方法声明时,在返回类型之前使用尖括号
<>
来声明类型参数。
泛型方法可以有一个或多个类型参数,这些参数在方法中可以被用作参数类型、返回类型或局部变量类型。
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
🫡2. 调用泛型方法
- 在调用泛型方法时,可以在方法名之前使用尖括号
<>
来指定具体的类型参数。 - 编译器会根据指定的类型参数来进行类型检查和类型推断。
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"Hello", "World"};
printArray(intArray); // 调用泛型方法并传入整型数组
printArray(strArray); // 调用泛型方法并传入字符串数组
😛3. 类型推断
- 在调用泛型方法时,如果可以通过上下文推断出类型参数,可以省略类型参数的显式指定。
- 编译器会根据方法参数的类型来推断类型参数。
Integer[] intArray = {1, 2, 3, 4, 5};
printArray(intArray); // 编译器会推断类型参数为Integer
🤨4. 泛型方法与泛型类的关系
- 泛型方法可以独立于所属类进行参数化,即使所属类不是泛型类也可以定义泛型方法。
- 泛型方法可以在普通类、泛型类、接口中声明和调用。
public class Utils {
public static <T> T getLastElement(T[] array) {
return array[array.length - 1];
}
}
Integer[] intArray = {1, 2, 3, 4, 5};
Integer lastElement = Utils.getLastElement(intArray);
😷5. 泛型方法的优势
- 提高代码的重用性和灵活性。
- 可以在方法级别实现类型安全。
总的来说,泛型方法是Java中一种强大的特性,它允许在方法级别使用泛型,提高了代码的可复用性和类型安全性。通过类型推断简化调用。
🤤四.泛型类的具体编译
泛型类在Java中的编译过程涉及到类型擦除和桥接方法等概念。
以下是泛型类的具体编译过程:
🥴1. 类型擦除(Type Erasure)
- 在编译阶段,泛型信息会被擦除,即将泛型类中的类型参数擦除为它们的边界或Object类型。
- 泛型类中的类型参数会被替换为对应的边界类型或Object类型。
- 泛型类的实例化和操作都是基于擦除后的类型进行的。
😵💫2. 桥接方法(Bridge Method)
- 当泛型类中包含泛型方法时,在编译生成字节码时,编译器会自动生成桥接方法来保证泛型方法的类型安全性。
- 桥接方法是编译器为了兼容Java泛型擦除机制而生成的方法,用于确保泛型类或泛型接口的方法能够正确地调用。
- 桥接方法会根据擦除后的方法签名生成一个桥接方法,以便正确调用泛型方法。
🤠3. 示例
假设有以下泛型类定义:
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在编译后,上述代码会被转换为类似如下的形式:
public class Box {
private Object value;
public Box(Object value) {
this.value = value;
}
public Object getValue() { // 编译后的桥接方法
return value;
}
}
这样,在泛型类的编译过程中,会进行类型擦除和桥接方法生成等操作,以确保在运行时可以正确处理泛型类的实例化和操作。
🧐五.泛型的上界
在Java的泛型中,上界(Upper Bound)是指限定泛型类型参数必须是某个类或其子类。通过使用上界,可以在泛型中指定类型参数的范围,从而提高类型安全性和灵活性。
😎1. 声明上界
- 在使用泛型时,可以使用
extends
关键字声明上界。 - 语法格式为
<T extends ClassName>
,表示泛型类型参数T
必须是ClassName
类或其子类。public class Box<T extends Number> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } }
🥹2.使用上界的好处
- 类型限制:通过上界,可以限制泛型类型参数的范围,只允许特定类型或其子类作为参数。
- 类型安全:使用上界可以避免在泛型中处理不符合要求的类型,提高类型安全性。
- 方法调用:可以在泛型类或方法中调用上界类的方法,保证对应的操作是支持的。
😣3. 上界通配符
- 上界通配符也可以在方法参数中使用,用于接受指定上界类型的对象。
public void processList(List<? extends Number> list) {
// 对于Number及其子类的列表进行处理
}
😤4.注意事项
- 上界是单继承的,只能指定一个类作为上界。
- 如果需要多个上界,请考虑使用接口作为上界。
- 超类或接口可以放在泛型类型参数的位置,以限定类型参数的范围。
通过使用上界,可以有效地约束泛型类型参数的范围,提高代码的可读性、可维护性,并确保类型安全性。通过使用上界,可以有效地约束泛型类型参数的范围,提高代码的可读性、可维护性,并确保类型安全性。
🤓六.总结与反思
💡失败固然痛苦,但更糟糕的是从未去尝试。——西奥多.罗斯福
学习Java中的泛型是一次深刻的体验。泛型不仅提升了代码的类型安全性,还增加了代码的灵活性和复用性。初次接触时,可能会感觉有些晦涩难懂,需要花费一些时间去理解其工作原理和特性。然而,通过持续的学习和实践,逐渐能够领悟到泛型所带来的价值。
泛型使得在编译时就能够检查和确保数据类型的匹配,避免了在运行时可能出现的类型转换错误,从而提高了代码的健壮性。同时,泛型也允许我们编写更加通用和抽象的代码,可以独立于具体类型来设计和实现方法或类。这种灵活性使得我们可以更轻松地编写可复用的代码,同时提高了代码的可维护性和可扩展性。
尽管初期可能会面临一些学习曲线和挑战,但是通过不断地学习和实践,逐渐能够理解泛型的工作原理,并能够熟练地运用于实际项目中。泛型所带来的类型安全性和代码重用性是非常宝贵的,它能够帮助我们编写更加清晰、健壮和可维护的代码,从而提高了开发效率和代码质量。
因此,学习泛型是Java编程中不可或缺的一步。通过持续学习和实践,我们能够更好地利用泛型这一强大工具,开发出高效、可靠的程序,迈向优秀的Java开发者之路。
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸