java复习 07
我感觉泛型蛮难的,哎。。。算了算了,多练练吧!
1 泛型概述
泛型:是 JDK5 中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型。
它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,然后在使用 / 调用时传入具体的类型。
这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
泛型定义格式:
- <类型>:指定一种类型的格式,这里的类型可以看成是形参
- <类型 1, 类型 2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
- 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
2 泛型类
package PTA_training.GenericsTest;public class Main {public static void main(String[] args) {Generic<String> a = new Generic<String>();a.setT("wow");System.out.println(a.getT());Generic<Boolean> flag = new Generic<Boolean>();flag.setT(false);System.out.println(flag.getT());}
}
package PTA_training.GenericsTest;public class Generic <T>{private T t ;public T getT() {return t;}public void setT(T t) {this.t = t;}
}
3 方法重载、泛型类和泛型方法的区别
在 Java 中,方法重载、泛型类和泛型方法是三种不同的代码复用机制,它们各有特点。下面通过一个show
方法的例子来对比它们的区别。
1. 方法重载(Overloading)
方法重载是指在同一个类中定义多个同名但参数列表不同的方法。编译器根据传入的参数类型来决定调用哪个方法。
public class OverloadingExample {// 重载方法:打印int类型public void show(int num) {System.out.println("Integer: " + num);}// 重载方法:打印double类型public void show(double num) {System.out.println("Double: " + num);}// 重载方法:打印String类型public void show(String str) {System.out.println("String: " + str);}// 测试方法public static void main(String[] args) {OverloadingExample example = new OverloadingExample();example.show(10); // 调用show(int)example.show(3.14); // 调用show(double)example.show("Hello"); // 调用show(String)}
}
特点:
- 方法名相同,但参数类型、数量或顺序不同
- 每个方法处理特定类型的数据
- 编译时根据参数类型确定调用哪个方法
- 代码冗余度较高(需要为每种类型编写单独的方法)
2. 泛型类改进
泛型类是指在类的定义中使用类型参数,使得类可以处理多种类型的数据。
public class GenericClassExample<T> {private T value;public GenericClassExample(T value) {this.value = value;}// 泛型类中的方法,可以处理T类型的数据public void show() {System.out.println("Generic: " + value);}public static void main(String[] args) {// 创建Integer类型的泛型类实例GenericClassExample<Integer> intExample = new GenericClassExample<>(10);intExample.show(); // 打印Integer值// 创建Double类型的泛型类实例GenericClassExample<Double> doubleExample = new GenericClassExample<>(3.14);doubleExample.show(); // 打印Double值// 创建String类型的泛型类实例GenericClassExample<String> stringExample = new GenericClassExample<>("Hello");stringExample.show(); // 打印String值}
}
特点:
- 类的定义中使用类型参数
<T>
- 整个类都可以使用该类型参数
- 需要为每种类型创建单独的实例
- 类型安全,编译时检查类型错误
- 减少代码冗余,但使用时需要创建多个实例
3. 泛型方法改进
泛型方法是指在方法的定义中使用类型参数,使得方法可以处理多种类型的数据,而不依赖于类的泛型定义。
public class GenericMethodExample {// 泛型方法:使用<T>声明类型参数public <T> void show(T value) {System.out.println("Generic Method: " + value);}// 泛型方法:可以指定多个类型参数public <T, U> void show(T value1, U value2) {System.out.println("Generic Method: " + value1 + ", " + value2);}public static void main(String[] args) {GenericMethodExample example = new GenericMethodExample();// 调用泛型方法,传入不同类型的参数example.show(10); // 传入Integerexample.show(3.14); // 传入Doubleexample.show("Hello"); // 传入Stringexample.show(true); // 传入Boolean// 调用接受两个参数的泛型方法example.show("Hello", 123); // 传入String和Integerexample.show(3.14, "World"); // 传入Double和String}
}
特点:
- 方法签名中使用
<T>
声明类型参数 - 可以在非泛型类中定义泛型方法
- 同一个实例可以处理多种类型的数据
- 类型安全,编译时检查类型错误
- 最灵活的代码复用方式,不需要创建多个实例
三者对比总结
特性 | 方法重载 | 泛型类 | 泛型方法 |
---|---|---|---|
类型处理 | 为每种类型编写单独方法 | 一个类处理多种类型 | 一个方法处理多种类型 |
代码复用 | 低(重复代码) | 中(类级复用) | 高(方法级复用) |
实例创建 | 无需特殊处理 | 需要为每种类型创建实例 | 同一个实例可处理多种类型 |
类型安全 | 是 | 是 | 是 |
适用场景 | 参数类型固定且有限 | 类整体需要处理多种类型 | 单个方法需要处理多种类型 |
泛型方法通常是最灵活的选择,因为它可以在不创建泛型类的情况下实现类型参数化,并且同一个实例可以处理多种类型的数据。
4 关于泛型接口
泛型接口(Generic Interface)详解
泛型接口是 Java 泛型编程中的重要概念,它允许接口在定义时使用类型参数,使得实现类可以灵活地处理不同类型的数据。下面从定义、实现和应用场景等方面详细介绍泛型接口。
1. 泛型接口的定义与语法
泛型接口的定义与普通接口类似,只需在接口名称后添加类型参数声明(如<T>
),类型参数可在接口的方法签名中使用。
java
// 定义泛型接口:声明类型参数<T>
public interface GenericInterface<T> {// 接口方法使用类型参数TT getData();void setData(T data);String showType();
}
语法说明:
- 类型参数(如
T
)可自定义名称,常用T
(Type)、E
(Element)、K
(Key)、V
(Value)等 - 接口中的方法返回值、参数类型均可使用类型参数
- 可声明多个类型参数,如
interface Map<K, V>
2. 泛型接口的实现方式
实现泛型接口时,有两种方式指定类型参数:明确指定具体类型或保留类型参数。
方式一:实现时指定具体类型
java
// 实现类明确指定类型为Integer
public class IntGenericClass implements GenericInterface<Integer> {private Integer data;@Overridepublic Integer getData() {return data;}@Overridepublic void setData(Integer data) {this.data = data;}@Overridepublic String showType() {return "类型为 Integer";}
}// 实现类明确指定类型为String
public class StringGenericClass implements GenericInterface<String> {private String data;@Overridepublic String getData() {return data;}@Overridepublic void setData(String data) {this.data = data;}@Overridepublic String showType() {return "类型为 String";}
}
方式二:实现时保留类型参数(让子类决定类型)
java
// 实现类保留类型参数T,由子类或实例化时指定
public class GenericImpl<T> implements GenericInterface<T> {private T data;@Overridepublic T getData() {return data;}@Overridepublic void setData(T data) {this.data = data;}@Overridepublic String showType() {return "类型为 " + data.getClass().getSimpleName();}
}// 使用示例
public class GenericInterfaceDemo {public static void main(String[] args) {// 方式一:实现类指定具体类型GenericInterface<Integer> intImpl = new IntGenericClass();intImpl.setData(100);System.out.println(intImpl.getData() + " - " + intImpl.showType());GenericInterface<String> strImpl = new StringGenericClass();strImpl.setData("Hello Generic");System.out.println(strImpl.getData() + " - " + strImpl.showType());// 方式二:实现类保留类型参数,实例化时指定GenericInterface<Double> doubleImpl = new GenericImpl<>();doubleImpl.setData(3.14);System.out.println(doubleImpl.getData() + " - " + doubleImpl.showType());}
}
3. 泛型接口的应用场景
泛型接口在 Java 框架和实际开发中广泛应用,常见场景包括:
(1)集合框架中的接口
Java 集合框架的核心接口几乎都是泛型接口,例如:
List<E>
:有序集合,元素可重复Set<E>
:无序集合,元素唯一Map<K, V>
:键值对映射
java
// 示例:使用泛型接口List
List<String> stringList = new ArrayList<>();
stringList.add("Java");
stringList.add("泛型");List<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(20);
(2)自定义通用接口
当需要定义一组处理不同类型数据的通用行为时,可使用泛型接口。例如:
java
// 定义数据处理器接口,处理不同类型的数据
public interface DataProcessor<T> {T process(T data); // 处理数据T validate(T data); // 验证数据
}// 实现类:处理整数数据
public class IntProcessor implements DataProcessor<Integer> {@Overridepublic Integer process(Integer data) {return data * 2;}@Overridepublic Integer validate(Integer data) {return data > 0 ? data : 0;}
}// 实现类:处理字符串数据
public class StringProcessor implements DataProcessor<String> {@Overridepublic String process(String data) {return data.toUpperCase();}@Overridepublic String validate(String data) {return data != null ? data : "";}
}
(3)泛型接口与多态结合
泛型接口可与多态结合,实现更灵活的代码架构:
java
// 定义泛型接口
public interface Printer<T> {void print(T data);
}// 实现类1:打印整数
public class IntPrinter implements Printer<Integer> {@Overridepublic void print(Integer data) {System.out.println("整数:" + data);}
}// 实现类2:打印字符串
public class StringPrinter implements Printer<String> {@Overridepublic void print(String data) {System.out.println("字符串:" + data);}
}// 通用打印服务,接受泛型接口作为参数(多态应用)
public class PrintService {public <T> void printData(Printer<T> printer, T data) {printer.print(data);}
}// 测试多态应用
public class PolymorphismDemo {public static void main(String[] args) {PrintService service = new PrintService();// 使用整数打印机service.printData(new IntPrinter(), 123);// 使用字符串打印机service.printData(new StringPrinter(), "泛型接口演示");}
}
4. 泛型接口与泛型类、泛型方法的对比
特性 | 泛型接口 | 泛型类 | 泛型方法 |
---|---|---|---|
定义位置 | 接口层面(影响所有实现方法) | 类层面(影响类中所有成员) | 方法层面(仅影响当前方法) |
主要作用 | 定义通用行为规范 | 封装通用数据结构 | 实现方法级别的类型复用 |
实现方式 | 由实现类决定类型参数 | 实例化时指定类型参数 | 调用时推断或指定类型参数 |
典型应用 | 集合接口(如 List<E>) | 集合类(如 ArrayList<E>) | 工具方法(如 Collections.sort) |
与多态的结合 | 强(实现类可作为接口类型使用) | 中(实例可作为泛型类型变量) | 弱(方法级复用,不影响类型层次) |
5. 泛型接口的注意事项
- 类型擦除:泛型接口在编译后会擦除类型参数(如
T
会被擦除为Object
),但编译时仍会进行类型检查。 - 不能使用基本类型:类型参数不能直接使用
int
、double
等基本类型,需使用包装类(如Integer
、Double
)。 - 接口继承:泛型接口可以继承其他泛型接口,例如:
java
public interface ChildInterface<T> extends GenericInterface<T> {// 扩展方法T convert(); }
- 静态成员限制:泛型接口中不能定义静态成员变量或静态泛型方法(静态成员属于类,而非实例,与泛型类型参数冲突)。
总结
泛型接口通过类型参数化,将接口的行为抽象为可处理不同类型数据的通用规范,实现了代码的高度复用和类型安全。它在 Java 集合框架、框架设计和业务逻辑分层中扮演重要角色,是泛型编程中连接不同组件的桥梁。通过合理使用泛型接口,可使代码架构更灵活、可维护性更强。
5 类型通配符
为了表示各种泛型 List 的父类,可以使用类型通配符
- 类型通配符:<?>
- List<?>:表示元素类型未知的 List,它的元素可以匹配任何的类型
- 这种带通配符的 List 仅表示它是各种泛型 List 的父类,并不能把元素添加到其中
如果说我们不希望 List<?> 是任何泛型 List 的父类,只希望它代表某一类泛型 List 的父类,可以使用类型通配符的上限~
- 类型通配符上限:<? extends 类型>
- List<? extends Number>:它表示的类型是 Number 或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符下限:<? super 类型>
- List<? super Number>:它表示的类型是 Number 或者其父类型
=========================================================================
通配符:指定类型范围,避免开发不知道该数据只支持的范围,设置错误类型导致出现bug!
=========================================================================
6 可变参数
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
- 格式:修饰符 返回值类型 方法名 (数据类型... 变量名){ }
- 范例:public static int sum (int... a) { }
可变参数注意事项
这里的变量其实是一个数组(a是数组)
如果一个方法有多个参数,包含可变参数,可变参数要放在最后!!!
- Arrays 工具类中有一个静态方法:
public static <T> List<T> asList(T... a)
:返回由指定数组支持的固定大小的列表- 返回的集合不能做增删操作,可以做修改操作
- List 接口中有一个静态方法:
public static <E> List<E> of(E... elements)
:返回包含任意数量元素的不可变列表- 返回的集合不能做增删改操作
- Set 接口中有一个静态方法:
public static <E> Set<E> of(E... elements)
:返回一个包含任意数量元素的不可变集合- 在给元素的时候,不能给重复的元素
- 返回的集合不能做增删操作,没有修改的方法
代码解释(以 Java 语言为例 )
- Arrays.asList 方法
import java.util.Arrays;
import java.util.List;
public class Main {public static void main(String[] args) {String[] arr = {"a", "b", "c"};// 使用 Arrays.asList 将数组转换为 ListList<String> list = Arrays.asList(arr);// 可以修改元素,因为支持修改操作list.set(0, "A");System.out.println(list); // 输出: [A, b, c]// 不能进行增删操作,会抛出 UnsupportedOperationException 异常// list.add("d"); // list.remove(0);}
}
Arrays.asList
接收可变参数(本质是数组),返回的 List
是由原数组支持的固定大小列表。它基于数组,所以长度不可变(增删会改变长度,不允许 ),但元素内容可通过 set
等修改方法变更。
2. List.of 方法
java
import java.util.List;
public class Main {public static void main(String[] args) {// 使用 List.of 创建不可变 ListList<String> list = List.of("x", "y", "z");// 不能进行增删改操作,以下操作都会抛出 UnsupportedOperationException 异常// list.add("w");// list.remove(0); // list.set(0, "X");System.out.println(list); // 输出: [x, y, z]}
}
List.of
接收可变参数,创建的是不可变 List
。其内部结构不允许进行元素的增加、删除、修改操作,主要用于创建一个内容固定、后续无需改变的列表场景,比如存储常量集合。
3. Set.of 方法
java
import java.util.Set;
public class Main {public static void main(String[] args) {// 元素不能重复,否则编译或运行报错(如传入两个 "m" 会抛 IllegalArgumentException)Set<String> set = Set.of("m", "n", "p"); // 不能进行增删操作,以下操作会抛出 UnsupportedOperationException 异常// set.add("q"); // set.remove("m");System.out.println(set); // 输出: [m, n, p]}
}
Set.of
接收可变参数创建不可变 Set
,遵循 Set
元素不重复的特性,若传入重复元素会报错。且创建后的集合不支持增删操作,常用于创建固定、不重复且后续无需变动的集合,像存储一组唯一的常量标识等场景 。
7 基本数据类型和引用数据类型
在 Java 里,基本数据类型和引用数据类型是重要概念,二者有明显区别,下面为你详细介绍:
一、基本数据类型
Java 中基本数据类型共 8 种,是语言内置、非对象的简单数据类型,直接存于栈内存,特点如下:
- 分类及取值范围
- 整数类型:
byte
:占 1 字节(8 位),范围 -128 ~ 127 ,适合存储小整数值,如byte num = 10;
。short
:占 2 字节(16 位),范围 -32768 ~ 32767 ,如short s = 1000;
。int
:占 4 字节(32 位),范围 -2147483648 ~ 2147483647 ,日常整数默认类型,像int age = 20;
。long
:占 8 字节(64 位),范围极大,声明时需加L
或l
(推荐L
避免混淆),如long bigNum = 10000000000L;
。
- 浮点类型:
float
:占 4 字节(32 位),单精度,声明时加F
或f
,如float price = 3.14F;
。double
:占 8 字节(64 位),双精度,是浮点型默认类型,如double weight = 60.5;
。
- 字符类型:
char
:占 2 字节(16 位),存 Unicode 字符,用单引号,如char ch = 'A';
、char ch2 = '中';
。
- 布尔类型:
boolean
:只有true
和false
两个值,用于条件判断,如boolean isReady = true;
。
- 整数类型:
- 特性:
直接存储值,变量名对应数据值,非引用地址;栈内存存取快;不是对象,无toString()
、equals()
等对象方法(但 Java 会自动装箱让基本类型可调用部分方法,如Integer.valueOf(10).toString()
);赋值是按值传递,新变量获原变量值副本,相互独立 。
二、引用数据类型
引用数据类型基于类,是对象类型,变量存堆内存中对象的引用地址(指针),栈内存存引用地址,特点如下:
- 常见类型:
- 类(Class):如自定义类
Person
、系统类String
、ArrayList
等,Person p = new Person();
中p
存对象在堆内存的引用。 - 接口(Interface):像
List
、Map
等接口,List<String> list = new ArrayList<>();
里list
存ArrayList
对象的引用。 - 数组(Array):无论基本还是引用类型数组,数组变量是引用类型,
int[] arr = new int[5];
中arr
存数组在堆内存的引用 。 - 枚举(Enumeration):Java 5.0 引入,如
enum Season { SPRING, SUMMER }
,Season s = Season.SPRING;
中s
存枚举实例引用。
- 类(Class):如自定义类
- 特性:
堆内存 存对象实际数据,栈内存 存引用地址;变量名 存引用地址,非数据本身;是对象,有属性和方法;赋值是传递引用地址,多个变量可能指向同一对象,操作会相互影响(如Person p1 = new Person(); Person p2 = p1; p2.setName("Tom");
,p1
对应的对象名称也会改变 );比较相等性时,==
比较引用地址(是否同一对象),equals
(需重写才比较内容,如String
类重写了equals
方法 )比较对象内容 。
三、二者关键区别总结
对比项 | 基本数据类型 | 引用数据类型 |
---|---|---|
存储位置 | 栈内存直接存值 | 栈存引用地址,堆存对象实际数据 |
类型本质 | 非对象,是简单数据值 | 是对象,基于类创建的实例 |
赋值方式 | 按值传递,新变量获值副本,相互独立 | 传递引用地址,多个变量可能指向同一对象 |
相等性比较 | == 比较值 | == 比较引用地址,equals (合理重写后)比较内容 |
方法调用 | 本身无对象方法(自动装箱后可调用部分) | 可调用对象的属性和方法 |
理解这些区别,有助于在 Java 编程中合理使用不同数据类型,避免因类型处理不当引发 bug ,比如集合框架(如 List
、Map
)需用引用类型,基本数据类型需借助包装类(如 Integer
、Double
)才能存入集合 。
四、为什么要把这两个数据类型做区分?
Java 区分基本数据类型和引用数据类型,主要基于 内存管理、性能、使用场景 等角度设计,背后是语言对效率、复杂度、数据特性的权衡,以下从核心原因展开:
1. 内存存储与效率需求
- 基本数据类型:直接存值(如
int a = 10
,a
变量在栈内存直接存10
)。这类数据简单、大小固定(如int
占 4 字节 ),栈内存读写速度极快,适合频繁使用的 “简单数据”(像数值计算、布尔判断 ),满足程序高效处理基础操作的需求。 - 引用数据类型:存的是对象在堆内存的地址(如
String s = new String("Hello")
,s
在栈存地址,堆存实际字符串内容 )。对象往往复杂、大小不固定(如一个ArrayList
可动态扩容 ),堆内存用于灵活存储 “复杂数据结构”,但访问需通过地址跳转,开销比栈大。分开存储能让简单数据走高效栈,复杂数据用灵活堆,平衡效率与功能。
2. 数据特性与使用场景
- 基本数据类型:代表 “原子性” 数据(数字、字符、布尔等 ),无需拆分,直接存值就能满足需求(如统计年龄用
int
,判断状态用boolean
)。 - 引用数据类型:代表 “复合型” 数据(对象、数组、集合等 ),需包含多个属性或功能(如
Person
类有姓名、年龄,List
可存一系列元素 )。通过 “引用” 关联堆中对象,能实现 数据共享、复杂逻辑封装(多个变量可指向同一对象,修改一处联动更新 ),支撑面向对象编程的 “封装、继承、多态” 特性。
3. 语言设计的实用性
- 避免性能浪费:若所有数据都按 “对象” 存堆(类似其他语言的纯对象模式 ),简单操作(如
a + b
)会因频繁创建对象、跳转堆内存,拖慢程序。基本数据类型用栈直接存值,让基础运算 “轻量化”。 - 兼容历史与习惯:Java 设计参考 C/C++ ,保留类似 “基本类型” 的高效用法,降低开发者学习成本,同时通过 包装类(如
Integer
对应int
),让基本类型也能 “伪对象化”,适配泛型、集合等需要对象的场景,灵活又高效。
简单说,区分是为了 “让简单的更快,让复杂的更灵活”:基本类型专攻高效基础操作,引用类型支撑复杂对象逻辑,共同构建 Java 灵活又高效的编程体系。
======
心情复杂,我觉得我还没有掌握这些,泛型还是有点抽象的