JAVA篇12 —— 泛型的使用
欢迎来到我的主页:【Echo-Nie】
本篇文章收录于专栏【JAVA学习】
如果这篇文章对你有帮助,希望点赞收藏加关注啦~
1 泛型介绍
先对集合进行说明,不能对加入到集合中的元素类型进行约束(不安全)。遍历的时候需要进行类型转换,如果集合中的数据量大,对效率有影响。
package Generic;
import java.util.ArrayList;
/**
* @ClassName Generic_
* @Date 2024/12/2 23:45
* @Version V1.0
*/
public class Generic_ {
public static void main(String[] args) {
//使用传统的方法来解决
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
//假如我们的程序员, 不小心, 添加了一只猫
arrayList.add(new Cat("招财猫", 8));
//遍历
for (Object o : arrayList) {
//向下转型 Object ->Dog
Dog dog = (Dog) o;
System.out.println(dog.getName() + "-" + dog.getAge());
}
//1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog
//2. 如果编译器发现添加的类型, 不满足要求, 就会报错
//3. 在遍历的时候, 可以直接取出 Dog 类型而不是 Object
//4. public class ArrayList<E> {} E 称为泛型,那么 Dog->E
ArrayList<Dog> arrayList2 = new ArrayList<Dog>();
arrayList2.add(new Dog("旺财", 10));
arrayList2.add(new Dog("发财", 1));
arrayList2.add(new Dog("小黄", 5));
//假如我们的程序员, 不小心, 添加了一只猫
//arrayList.add(new Cat("招财猫", 8));
System.out.println("===使用泛型====");
for (Dog dog : arrayList2) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
优点:编译时,检查元素类型,更安全。减少类型转换的次数,提高效率且不再发生编译警告。如:
- 不使用泛型:Dog(转Object) → Object → 取出(转Dog) → Dog
- 使用泛型:放入是Dog,取出也是Dog
泛型,即广泛的数据类型。又称为参数化类型,是jdk5.0之后出现的新特性,解决数据类型安全问题。Java泛型可以保证如果程序在编译过程时没有发出警告,运行时就不会产生ClassCastException异常,代码更加简洁。可以作为属性、方法返回值、参数类型。
package Generic;
/**
* @ClassName GenericTest
* @Date 2024/12/3 9:05
* @Version V1.0
*/
public class GenericTest {
public static void main(String[] args) {
Test<String> stringTest = new Test<String>("哈哈哈");
stringTest.show();
Test<Integer> integerTest = new Test<Integer>(100);
integerTest.show();
}
}
class Test<E>{
E s;
public Test(E s){
this.s=s;
}
public E getS() {
return s;
}
public void show(){
System.out.println("RunClass is "+s.getClass());
}
}
1.1 泛型使用案例
package Generic;
import java.util.*;
/**
* @ClassName GenericTest
* @Description* 创建 3 个学生对象
* 放入到 HashSet 中学生对象, 使用.
* 放入到 HashMap 中, 要求 Key 是 String name, Value 就是 学生对象
* 使用两种方式遍历
* @Date 2024/12/3 9:05
* @Version V1.0
*/
public class GenericTest {
public static void main(String[] args) {
//使用泛型方式给 HashSet 放入 3 个学生对象
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("jack", 18));
students.add(new Student("tom", 28));
students.add(new Student("mary", 19));
//遍历
System.out.println("增强for循环遍历");
for (Student student : students) {
System.out.println(student);
}
System.out.println("迭代器遍历");
Iterator<Student> iterator = students.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
System.out.println(student);
}
//使用泛型方式给 HashMap 放入 3 个学生对象
//K -> String V->Student
HashMap<String, Student> hm = new HashMap<String, Student>();
/*
public class HashMap<K,V> {}
*/
hm.put("xiaowang", new Student("xiaowang", 18));
hm.put("xiaohong", new Student("xiaohong", 48));
//迭代器 EntrySet
/*
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
*/
Set<Map.Entry<String, Student>> entries = hm.entrySet();
/*
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
*/
Iterator<Map.Entry<String, Student>> iterator2 = entries.iterator();
System.out.println("==============================");
while (iterator2.hasNext()) {
Map.Entry<String, Student> next = iterator2.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.2 泛型使用细节
Interface List{ } public class HashSet{ }
- T和E只能是引用类型如Integer,不能是int
- 可以传该类型或者其子类型 List list = new
- ArrayList();或List list = new ArrayList<>(); List
- list = new ArrayList();默认传的是E,E就是Object
package Generic;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName GenericDetail
* @Description
* @Author Echo-Nie
* @Date 2025/2/1 0:07
* @Version V1.0
*/
@SuppressWarnings({"all"})
public class GenericDetail {
public static void main(String[] args) {
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误
//2. 说明
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f();
//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
//在实际开发中,我们往往简写
//编译器会进行类型推断, 推荐使用下面写法
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();
//4. 如果是这样写 泛型默认是 Object
ArrayList arrayList = new ArrayList();//等价 ArrayList<Object> arrayList = new ArrayList<Object>();
/*
public boolean add(Object e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
*/
Tiger tiger = new Tiger();
/*
class Tiger {//类
Object e;
public Tiger() {}
public Tiger(Object e) {
this.e = e;
}
}
*/
}
}
class Tiger<E> {//类
E e;
public Tiger() {}
public Tiger(E e) {
this.e = e;
}
}
class A {}
class B extends A {}
class Pig<E> {//
E e;
public Pig(E e) {
this.e = e;
}
public void f() {
System.out.println(e.getClass()); //运行类型
}
}
1.3 实操练习
定义Employee类
1)该类包含:private成员变量name,sal,birthday,其中 birthday为 MyDate 类的对象
2)为每一个属性定义 getter, setter 方法;
3)重写 toString 方法输出 name, sal, birthday
- MyDate类包含: private成员变量month,day,year;并为每一个属性定义 getter,setter 方法;
5)创建该类的3个对象,并把这些对象放入 ArrayList 集合中(ArrayList 需使用泛型来定义),对集合中的元素进行排序,并遍历输出:
排序方式:调用ArrayList 的 sort 方法,传入 Comparator对象[使用泛型],先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
package Generic;
import lombok.Data;
import java.util.ArrayList;
import java.util.Comparator;
/**
* @ClassName GenericExercise
* @Description
* @Author Echo-Nie
* @Date 2025/2/1 0:13
* @Version V1.0
*/
// 定义MyDate类
@Data
class MyDate {
private int month;
private int day;
private int year;
// 构造方法
public MyDate(int month, int day, int year) {
this.month = month;
this.day = day;
this.year = year;
}
// 重写toString方法
@Override
public String toString() {
return year + "-" + month + "-" + day;
}
}
// 定义Employee类
@Data
class Employee {
private String name;
private double sal;
private MyDate birthday;
// 构造方法
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
// 重写toString方法
@Override
public String toString() {
return "Employee{name='" + name + "', sal=" + sal + ", birthday=" + birthday + "}";
}
}
public class GenericExercise {
public static void main(String[] args) {
// 创建MyDate对象
MyDate date1 = new MyDate(5, 15, 1990);
MyDate date2 = new MyDate(8, 22, 1985);
MyDate date3 = new MyDate(5, 15, 1990);
// 创建Employee对象
Employee emp1 = new Employee("Alice", 50000, date1);
Employee emp2 = new Employee("Bob", 60000, date2);
Employee emp3 = new Employee("Alice", 55000, date3);
// 创建ArrayList并添加Employee对象
ArrayList<Employee> employees = new ArrayList<>();
employees.add(emp1);
employees.add(emp2);
employees.add(emp3);
// 使用Comparator进行排序
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
// 先按name排序
int nameCompare = e1.getName().compareTo(e2.getName());
if (nameCompare != 0) {
return nameCompare;
}
// 如果name相同,按生日日期排序
return e1.getBirthday().toString().compareTo(e2.getBirthday().toString());
}
});
// 遍历输出排序后的Employee对象
for (Employee emp : employees) {
System.out.println(emp);
}
}
}
2 什么是自定义泛型?
理解
想象你有一个盒子,这个盒子可以装任何东西:书、水果、玩具等。为了让盒子更通用,你可以在盒子上贴一个标签,标明它里面装的是什么。这样,当你打开盒子时,你就知道里面装的是什么,而不会拿错东西。
在 Java 中,自定义泛型就是这个“盒子”,而类型参数就是盒子上的标签。通过泛型,我们可以定义一个通用的类或方法,然后在使用时指定具体的类型。
2.1 自定义泛型类
自定义泛型类 Container<T>
/**
* 自定义泛型类:Container<T>
* - T 是类型参数,表示容器中存储的数据类型。
* - 这个类可以存储任意类型的数据。
*/
public class Container<T> {
private T content; // 容器中的内容
/**
* 构造方法:初始化容器内容
* @param content 容器中的内容
*/
public Container(T content) {
this.content = content;
}
/**
* 获取容器中的内容
* @return 容器中的内容
*/
public T getContent() {
return content;
}
/**
* 设置容器中的内容
* @param content 新的内容
*/
public void setContent(T content) {
this.content = content;
}
/**
* 重写 toString 方法,方便打印容器内容
* @return 容器内容的字符串表示
*/
@Override
public String toString() {
return "Container{" +
"content=" + content +
'}';
}
}
Container<T>
类的使用
Container<T>
是一个泛型类,T
是类型参数。- 在实例化
Container
时,可以指定具体的类型(如Integer
、String
或Person
)。 - 泛型类在编译时会进行类型检查,确保类型安全。
public class Main {
public static void main(String[] args) {
// 创建一个存储整数的容器
Container<Integer> intContainer = new Container<>(100);
System.out.println(intContainer); // 输出: Container{content=100}
// 创建一个存储字符串的容器
Container<String> strContainer = new Container<>("Hello, Generics!");
System.out.println(strContainer); // 输出: Container{content=Hello, Generics!}
// 创建一个存储自定义对象的容器
Container<Person> personContainer = new Container<>(new Person("Alice", 25));
System.out.println(personContainer); // 输出: Container{content=Person{name='Alice', age=25}}
}
}
/**
* 自定义类:Person
*/
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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3 自定义泛型方法
自定义泛型方法 swap
/**
* 自定义泛型方法:swap
* - 这个方法可以交换任意类型的两个元素。
* - <T> 是类型参数,表示方法的泛型类型。
*/
public class Util {
/**
* 交换数组中的两个元素
* @param array 数组
* @param i 第一个元素的索引
* @param j 第二个元素的索引
* @param <T> 数组元素的类型
*/
public static <T> void swap(T[] array, int i, int j) {
if (i < 0 || j < 0 || i >= array.length || j >= array.length) {
throw new IllegalArgumentException("索引越界");
}
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
使用 swap
方法
public class Main {
public static void main(String[] args) {
// 交换整数数组中的两个元素
Integer[] intArray = {1, 2, 3, 4, 5};
Util.swap(intArray, 0, 4);
System.out.println(Arrays.toString(intArray)); // 输出: [5, 2, 3, 4, 1]
// 交换字符串数组中的两个元素
String[] strArray = {"a", "b", "c", "d"};
Util.swap(strArray, 1, 3);
System.out.println(Arrays.toString(strArray)); // 输出: [a, d, c, b]
}
}
4 多类型参数的泛型类
自定义泛型类 Pair<K, V>
/**
* 自定义泛型类:Pair<K, V>
* - K 和 V 是类型参数,分别表示键和值的类型。
* - 这个类可以存储一对键值对。
*/
public class Pair<K, V> {
private K key;
private V value;
/**
* 构造方法:初始化键值对
* @param key 键
* @param value 值
*/
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
/**
* 获取键
* @return 键
*/
public K getKey() {
return key;
}
/**
* 获取值
* @return 值
*/
public V getValue() {
return value;
}
/**
* 重写 toString 方法,方便打印键值对
* @return 键值对的字符串表示
*/
@Override
public String toString() {
return "Pair{" +
"key=" + key +
", value=" + value +
'}';
}
}
使用 Pair<K, V>
类
public class Main {
public static void main(String[] args) {
// 创建一个存储字符串和整数的键值对
Pair<String, Integer> pair1 = new Pair<>("age", 25);
System.out.println(pair1); // 输出: Pair{key=age, value=25}
// 创建一个存储字符串和自定义对象的键值对
Pair<String, Person> pair2 = new Pair<>("person", new Person("Bob", 30));
System.out.println(pair2); // 输出: Pair{key=person, value=Person{name='Bob', age=30}}
}
}
Pair<K, V>
是一个多类型参数的泛型类。
K
和 V
分别表示键和值的类型。