什么是泛型
泛型就是将类型参数化,比如定义了一个栈,你必须在定义之前声明这个栈中存放的数据的类型,是int也好是double或者其他的引用数据类型也好,定义好了之后这个栈就无法用来存放其他类型的数据。如果这时候我们想要使用这个栈来存放String(字符串类型)的数据就需要重写一遍代码,并且把数据类型改成String,可以说是代码重复性极高。
类型参数化就可以很好的解决这个问题,我们在定义栈的时候,只需要给一个形参,在使用的时候将类型作为参数传递进去就可以使用这个栈来存放不同类型的数据了。
泛型可以使用在接口,方法,和类里面,分别被称为泛型接口,泛型方法
java中的泛型标识符
下表是规范,用来增强代码的可读性,并非必须使用例如在下面的代码中使用了abc照样可以编译运行,不过可读性差,而且不规范。
下面的例子具体不用弄懂,下面会讲解,只需要知道abc和**?**在这里是通配符就好
public static <abc> void printList(List<? extends abc> list) { for (abc element : list) { System.out.println(element); } }
标记符 | 含义 |
---|---|
E | Element (在集合中使用,因为集合中存放的是元素) |
T | Type(Java 类) |
K | Key(键) |
V | Value(值) |
N | Number(数值类型) |
? | 可以表示任意类型(或者用来表示未知类型) |
有限制的通配符
上限通配符-extends
这些所谓的上和下其实是描述在一个继承关系中的上下关系,对于一个类来说,它的父类的方向就是上,而子类的方向就是下。
所以这里的"上限"通配符的“上限”指的是,一个确定的父类,或者一个确定的接口来进行限制,该通配符表示的类型都是该父类的子类(确定的接口的实现类),或者子类的子类(继承关系在这个确定的父类以下的子类或者接口的实现类)
举个栗子
public class Add<T extends Number>{
private T num;
Add(T num){this.num = num;}
}
怎么使用呢?
正确调用
Add<Double> a = new Add<>(1.1);//正确
Add<Integer> i = new Add<>(3);//正确
错误调用
Add<String> s = new Add<>("1.1");//错误
通过这个例子我们应该就比较好理解了,Double和Integer都是Number类的子类,所以可以代替“T extends Number”中的T,而String不在Number的继承链中所以编译会报错。
虚线下面的类,确定的接口和确定的父类就是上限通配符可以表示的类。
下限通配符-super
下限通配符也是一种对泛型类型的限制,这里的“下限”指的是继承关系中的类型的下界。
再来一个栗子
这是一个泛型方法
// 泛型方法接受 Number 及其父类的参数
static void Generic(List<? super Number> list) {
// 方法体
}
方法调用
先看一个错误的示例
List<Integer> list = new ArrayList<>(); Generic(list);
下面都是正确的示例
List<Number> list = new ArrayList<>(); Generic(list);
List<Object> list = new ArrayList<>(); Generic(list);
OK从这个例子我们应该很明白啦,List<? super Number> list的尖括号中的数据类型必须是Number及其父类。
多重限定
用来限定类型,表示指定的类型参数必须是多个指定类或指定接口的子类。
public class GenericClass<T extends ClassA & InterfaceB & InterfaceC> { /** *方法和属性 */ } //表示T必须是ClassA的子类或者子类的子类并且实现了InterfaceB和InterfaceC接口,或者实现了接口的类的继承类
通过类型参数的限定,可以在泛型类或方法中对类型进行更精确的控制和约束,以提高代码的类型安全性和灵活性。。
好现在我们已经知道了泛型的常用的标识符了,现在我们来了解下泛型的三种应用
泛型类
单元泛型类
class Singl<T>{ // 这里的标识符可以随意命名,T是type的简称
private T var ;
public T getVar(){
return var ;
}
public void setVar(T var){
this.var = var ;
}
}
public class Generics{
public static void main(String args[]){
Singl<String> p = new Singl<String>() ; // 里面的var类型为String类型
p.setVar("it is love") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
多元泛型类
class Multal<K,V>{ // 这里指定了两个泛型类型 K和V
private K key ; //K表示Key,V表示Value
private V value ;
public K getKey(){
return this.key ;
}
public V getValue(){
return this.value ;
}
public void setKey(K key){
this.key = key ;
}
public void setValue(V value){
this.value = value ;
}
}
public class GenericsClass{
public static void main(String args[]){
Multal<String,Integer> t ; // 定义两个泛型类型的对象
t = new Multal<String,Integer>() ; // 里面的key为String,value为Integer
t.setKey("我爱罗") ; // 设置第一个内容
t.setValue(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getKey()) ; // 取得信息
System.out.print(",年龄;" + t.getValue()) ; // 取得信息
}
}
语法:定义泛型类
class:类名<泛型标识1,泛型标识2,泛型标识3.....>{
private 泛型标识1 变量名;
private 泛型标识2 变量名;
}
从泛型类派生子类
派生子类有两种情况:
- 子类(如果子类也是泛型类)与父类的类型保持一致(泛型标识保持一致),如果父类没有指明泛型类型,则按Object进行处理
- 子类不是泛型类时,父类的泛型类型必须指明 例如,如果父类没有指明,则按照Object操作
子类也是泛型类
示例一:单一类型参数的泛型类
如果子类也是泛型类,子类的泛型标识必须和父类保持一致
// 定义一个泛型类
class GenericClass<T> {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 从泛型类派生子类,子类也是泛型类
class SubGenericClass<U> extends GenericClass<U> {
public SubGenericClass(U value) {
super(value);
}
public void displayValue() {
System.out.println("Value: " + getValue());
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
SubGenericClass<String> stringInstance = new SubGenericClass<>("Hello, Generics!");
stringInstance.displayValue(); // 输出: Value: Hello, Generics!
SubGenericClass<Integer> integerInstance = new SubGenericClass<>(123);
integerInstance.displayValue(); // 输出: Value: 123
}
}
示例 2:多个类型参数的泛型类
// 定义一个带有多个类型参数的泛型类
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// 从泛型类派生子类,子类也是泛型类
class ExtendedPair<K, V> extends Pair<K, V> {
public ExtendedPair(K key, V value) {
super(key, value);
}
public void display() {
System.out.println("Key: " + getKey() + ", Value: " + getValue());
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
ExtendedPair<String, Integer> pair = new ExtendedPair<>("Age", 30);
pair.display(); // 输出: Key: Age, Value: 30
ExtendedPair<Double, String> anotherPair = new ExtendedPair<>(3.14, "Pi");
anotherPair.display(); // 输出: Key: 3.14, Value: Pi
}
}
子类不是泛型类
定义一个泛型类
子类不是泛型类时,父类的泛型类型必须指明
// 定义一个泛型类
class GenericClass<T> {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
从泛型类派生的子类
// 从泛型类派生子类
class StringGenericClass extends GenericClass<String> {
public StringGenericClass(String value) {
super(value);
}
public void printValue() {
System.out.println("Value: " + getValue());
}
}
调用子类方法
public class Main {
public static void main(String[] args) {
StringGenericClass stringInstance = new StringGenericClass("Hello, Generics!");
stringInstance.printValue(); // 输出: Value: Hello, Generics!
}
}
带多个类型参数的泛型类
// 定义一个带有多个类型参数的泛型类
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// 从泛型类派生子类
class StringIntegerPair extends Pair<String, Integer> {
public StringIntegerPair(String key, Integer value) {
super(key, value);
}
public void display() {
System.out.println("Key: " + getKey() + ", Value: " + getValue());
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
StringIntegerPair pair = new StringIntegerPair("Age", 30);
pair.display(); // 输出: Key: Age, Value: 30
}
}
泛型接口
泛型接口语法
interface 接口名 <泛型标识1...>{
泛型标识1 方法名(泛型标识1 变量名)
}
泛型接口的使用情况(2种)
-
实现类不是泛型
- 接口类型必须明确,如果接口类型不明确,值实现类按Object处理
-
实现类是泛型
实现类为泛型类
GenericInterface 是一个泛型接口,GenericClass 是一个泛型类,继承了这个接口。GenericClass 可以接受不同类型的参数。
// 定义一个泛型接口
interface GenericInterface<T> {
void display(T value);
}
// 实现泛型接口的泛型类
class GenericClass<U> implements GenericInterface<U> {
@Override
public void display(U value) {
System.out.println("Value: " + value);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
GenericClass<String> stringInstance = new GenericClass<>();
stringInstance.display("Hello, Generics!"); // 输出: Value: Hello, Generics!
GenericClass<Integer> integerInstance = new GenericClass<>();
integerInstance.display(123); // 输出: Value: 123
}
}
实现类为普通类
ConcreteClass 是一个普通类,它实现了泛型接口 GenericInterface,并将类型参数指定为 String。
// 定义一个泛型接口
interface GenericInterface<T> {
void display(T value);
}
// 实现泛型接口的普通类
class ConcreteClass implements GenericInterface<String> {
@Override
public void display(String value) {
System.out.println("Value: " + value);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
ConcreteClass concreteInstance = new ConcreteClass();
concreteInstance.display("Hello, Generics!"); // 输出: Value: Hello, Generics!
}
}
泛型方法
简单的泛型方法
public class GenericMethodExample {
// 定义一个泛型方法
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
GenericMethodExample example = new GenericMethodExample();
// 使用泛型方法打印字符串数组
String[] stringArray = {"Hello", "Generics", "!"};
example.printArray(stringArray); // 输出: Hello, Generics, !
// 使用泛型方法打印整数数组
Integer[] intArray = {1, 2, 3, 4, 5};
example.printArray(intArray); // 输出: 1, 2, 3, 4, 5
}
}
带有多个类型参数的泛型方法
public class PairUtil {
// 定义一个带有多个类型参数的泛型方法
public static <K, V> void printPair(K key, V value) {
System.out.println("Key: " + key + ", Value: " + value);
}
public static void main(String[] args) {
// 使用泛型方法打印键值对
printPair("Name", "Alice"); // 输出: Key: Name, Value: Alice
printPair(1, 100); // 输出: Key: 1, Value: 100
}
}
泛型方法,是在调用方法的时候指明泛型的具体类型。重点看下泛型的方法(图参考自:https://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html)
- 定义泛型方法语法格式
- 调用泛型方法语法格式
说明一下,定义泛型方法时,必须在返回值前边加一个<T>
,来声明这是一个泛型方法,持有一个泛型T
,然后才可以用泛型T作为方法的返回值。
Class<T>
的作用就是指明泛型的具体类型,而Class<T>
类型的变量c,可以用来创建泛型类的对象。
Class 的基本概念
这里着重讲一下“Class” 的含义
- Class 是一个表示某个类的对象,T 是这个类的类型参数。
- 每个类在 Java 中都有一个对应的 Class 对象,这个对象包含了该类的结构信息,如字段、方法、构造函数等。
如何获取 Class 对象
- 使用 ClassName.class
Class<String> stringClass = String.class;
- 使用 getClass() 方法
String str = "Hello";
Class<?> stringClass = str.getClass();
- 使用 Class.forName()
try {
Class<?> stringClass = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
以下示例展示了如何使用 Class 来获取类的信息:
public class ClassExample {
public static void main(String[] args) {
// 获取 String 类的 Class 对象
Class<String> stringClass = String.class;
// 打印类的信息
System.out.println("Class Name: " + stringClass.getName());
System.out.println("Simple Name: " + stringClass.getSimpleName());
System.out.println("Is Array: " + stringClass.isArray());
System.out.println("Is Interface: " + stringClass.isInterface());
System.out.println("Is Primitive: " + stringClass.isPrimitive());
// 获取父类信息
Class<?> superClass = stringClass.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
// 获取实现的接口
Class<?>[] interfaces = stringClass.getInterfaces();
System.out.println("Interfaces:");
for (Class<?> iface : interfaces) {
System.out.println(" - " + iface.getName());
}
}
}
注意事项
Class 是 Java 反射机制中的一个重要组成部分,能够让程序在运行时与类进行交互,获取类的结构信息,极大地增强了 Java 的灵活性和动态性。通过理解 Class,开发者可以更有效地利用反射和泛型编程。
使用泛型来实现栈
泛型栈的定义
public class Stack<E> {
private E[] arr = (E[]) new Object[10];
//E[] arr = new E[10];是不允许的
private int flag = 0;
public void add(E x) {
if(flag == arr.length ) {
E[] arrnew = (E[]) new Object[arr.length * 2];
for(int i = 0;i < arr.length;i++) {
arrnew[i] = arr[i];
}
arr = arrnew;
}
arr[flag] = x;
flag++;
}
public E get() {
if(flag==0) {
return null;
}else {
E x = arr[flag--];
return x;
}
}
}
泛型栈的调用
public class Demo1 {
public int x;
public Demo1(int x) {
this.x = x;
}
@Override
public String toString() {
return "Demo1 [x=" + x + "]";
}
}
public class Test {
public static void main(String[] args) {
Stack<Demo1> xx = new Stack<>();
for(int i = 0;i < 24;i++) {
xx.add(new Demo1(i));
}
for(int i = 0;i < 24;i++) {
System.out.println(xx.get());
}
}
}