前言:
泛型的知识其实在前面 Java 的泛型和包装类 这章介绍过了一些,但那些知识是为后面介绍 Java 集合框架做的铺垫,而今天这章再配合之前那章,将会完整的介绍 Java 中的泛型!
在之前那章我们介绍了泛型类的基本定义,这里我们直接来创建并使用一个使用了泛型的栈来回顾泛型的定义
// 出现的 <T> 就表示当前的类是一个泛型类,T 是一个占位符
class Stack<T>{
private T[] elem;
private int usedSize;
public Stack(){
this.elem=(T[])new Object[10];
}
// 入栈(不考虑栈满)
public void push(T val){
this.elem[this.usedSize++]=val;
}
// 出栈(不考虑栈空)
public T pop(){
this.usedSize--;
return this.elem[this.usedSize];
}
}
public class TestDemo{
public static void main(String[] args){
Stack<Integer> stack=new Stack<Integer>();
stack.push(1);
stack.push(2);
int val=stack.pop();
System.out.println(val);
System.out.println(stack);
}
}
// 结果为:2 和 Stack@1b6d3586
注意: 上述代码的构造方法为什么代码块是这样的:this.elem=(T[])new Object[10];
this.elem=new T[10];
,那么我们在编译时根本不知道具体的类型是什么,因此不能直接使用泛型去实例化对象this.elem=new T[10];
就等价于代码是这样的 this.elem=new Object[10];
this.elem=(T[])new Object[10];
证明方式:
toString
方法,输出某个类的实例化对象,结果为:类型@对象地址
Stack@1b6d3586
,而不是 Stack<Integer>@1b6d3586
,即泛型的的信息在编译期间就被擦除了class 泛型类名称<类型形参>{
// 该代码块中可以直接使用类型参数
}
class 泛型类名称<类型形参1, 类型形参2, ..., 类型形参n>{
// 该代码块中可以直接使用所有类型参数
}
class 泛型类名称<类型形参> extends 父类名称<类型形参>{
// 该代码块中可以直接使用所有类型参数
}
interface 泛型类名称<类型形参>{
// 该代码块中可以直接使用类型参数
}
常用类型形参: 类型形参一般使用一个大写字母表示,常有名称如下
E
:表示 Element,即元素,运用在集合中K
:表示 Key,即键V
:表示 Value,即值N
:表示 Number,即数值类型T
:表示 Type,即 Java 类型?
:表示不确定的 Java 类型class Stack<T>{
private T[] elem;
private int usedSize;
public Stack(){
this.elem=(T[])new Object[10];
}
// 入栈(不考虑栈满)
public void push(T val){
this.elem[this.usedSize++]=val;
}
// 出栈(不考虑栈空)
public T pop(){
this.usedSize--;
return this.elem[this.usedSize];
}
}
定义在类内部的类叫做内部类
分类:
示例代码:
class OuterClass{
// 在外部类中成员变量都是可以正常定义的
public int data1=1;
public static int data2=2;
private int data3=3;
// 定义实例内部类
class InnerClass{
public int data4=4;
// 实例内部类中静态变量无法定义
// public static int data5=5; 该变量无法定义
// 但是增加一个 final 就可以定义了
public static final int data5=5;
private int data6=6;
public void func(){
System.out.println("这是一个实力内部类的 func 方法,也可以正常定义");
System.out.println(data1);
System.out.println(data2);
System.out.println(data3);
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
}
}
}
结论1: 在实例内部类当中,是不可以定义一个静态的成员变量
因为实例内部类的调用是需要依赖对象的,而 static 修饰的成员是静态的,是不依赖对象的,就如普通的方法中定义静态的变量也是不行的
结论2: 如果加一个 final,那么就可以在实例内部类中使用 static
因为此时表示的是常量了,而常量在编译期间就已经确定了
结论3: 实例化实例内部类的方式是:先实例化外部类,再通过下面第二行代码的形式去实例化
OuterClass outerClass=new OuterClass();
OuterClass.InnerClass innerClass=outerClass.new InnerClass();
结论4: 实例内部类中的方法也可以调用外部类的一些成员变量
innerClass.func();
// 结果为:
// 这是一个实力内部类的 func 方法,也可以正常定义
// 1 2 3 4 5 6
结论5: 如果实例内部类中定义的变量名和外部类中的某个变量名相同,那么实例内部类默认调用的是内部类的变量。即使用 this,也表示的是此时内部类的对象,如果要使用外部类的同名变量,则可以通过:外部类名.this.外部类变量名
来调用
结论6: 当我们去我们看我们定义的静态内部类的字节码文件时,它其实是这样的
应用:
比如我们自己创建链表时,Node 节点是定义在 LinkedList 类外部的,但是可以将 Node 类写成它的一个实例内部类
示例代码:
class OuterClass{
// 在外部类中成员变量都是可以正常定义的
public int data1=1;
public static int data2=2;
private int data3=3;
// 定义静态内部类
static class InnerClass{
public int data4=4;
public static final int data5=5;
private int data6=6;
public void func(){
System.out.println("这是一个实力内部类的 func 方法,也可以正常定义");
System.out.println(data1);
System.out.println(data2);
System.out.println(data3);
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
}
}
}
结论1: 以下是实例化静态内部类的方法,相比实例内部类,它不需要外部类去创建对象
OuterClass.InnerClass innerClass=new OuterClass.InnerClass();
结论2: 在静态内部类当中,不能调用外部类的普通成员变量
因为普通成员变量需要靠外部类的对象来调用
结论3: 如果要想在静态内部类中调用外部类的普通成员变量,则可以在静态内部类当中实例化一个外部类的对象,通过这个引用就可以访问外部类的普通成员变量
static class InnerClass{
public OuterClass out=new OuterClass();
System.out.println(out.data1);
}
结论4: 当内部类和外部类有同名的静态变量时,默认调用的是内部类本身的。要想调用外部类的,则可以通过:外部类名.变量名
来使用
实例代码:
不使用匿名内部类来实现抽象方法
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class TestDemo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
// 结果为:eat something
如果上述 Child 类只使用一次,那么单独写一个类出来就比较麻烦,所以可以使用匿名内部类
abstract class Person {
public abstract void eat();
}
public class TestDemo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
// 结果为:eat something
结论1: 由于没有名字,所以匿名内部类只能使用一次
结论2: 使用匿名内部类的前提是:必须继承一个父类或实现一个接口
结论3: 匿名内部类的形式就是直接在声明的对象后面接一个大括号,里面就写该类需要使用的内容
应用:
最常用的情况就是在多线程的实现上,因为要实现多线程必须继承 Thread 类或是继承 Runnable 接口
泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造方法实参);
Stack<Integer> stack=new Stack<Integer>();
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
上述示例就可以省略后面一个类型实参
Stack<Integer> stack=new Stack<>();
概念:
裸类型是一个泛型类但没有带着类型参数
示例: 上述代码创建的泛型类 Stack<T>
,如果将 Stack
单拿出来不加 <T>
去使用的话,那么它就是一个裸类型,我们可以直接使用它去实例化对象
Stack list = new Stack();
注意:
我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制。如果使用他的话,就跟不用泛型没两样了,泛型的作用和意义也就没了
在定义泛型类时,有时需要对传入的类型参数做一定的约束,可以通过类型边界来约束
注意:
泛型只有上界,没有下界
class 泛型类名称<类型参数 extends 类型边界>{
}
上述泛型类可以传入的类型参数必须是类型边界的类或者子类
示例一: 让泛型参数只接受数值类 Number 的子类型
class Stack<T extends Number>{
}
故此时泛型参数传 Integer 是可以的,但传 String 是不行的
Stack<Integer> l1; // 正确,因为 Integer 是 Number 的子类型
Stack<String> l2; // 编译错误,因为 String 不是 Number 的子类型
示例二: 写一个泛型类 Algorithm,我们要这个类中有一个方法可以实现找到数组的最大值
class Algorithm<T>{
public T findMax(T[] array){
T max=array[0];
for(int i=0;i<array.length;i++){
if(array[i]>max){
max=array[i];
}
}
return max;
}
}
但是报错了,自己一想估摸是泛型参数其实是类类型,即大小比较的是引用值,那么估摸要使用 Comparable 接口或者 Comparator 接口
class Algorithm<T extends Comparable<T>>{
public T findMax(T[] array){
T max=array[0];
for(int i=0;i<array.length;i++){
if(array[i].compareTo(max)>0){
max=array[i];
}
}
return max;
}
}
这里使用了类型边界来进行了一个约束,代表在进行擦除时,擦除到了 Comparable 接口的地方。通俗点讲,就是这样写,那么这个 T 就一定要实现 Comparable 接口,并且擦除时不会擦除成 Object,而是擦除成了 Comparable
问题: 示例二继承了 Comparable 接口为什么没有重写 compareTo 方法?
因为我们要传入的参数类型是本身一定要实现 Comparable 这个接口的,既然本身已经实现了,那么 compareTo 这个方法在这个参数类型中就得到了重写
补充: 编译器在类型擦除阶段所做什么?
bridge method
保证多态的正确性示例一: 擦除后为 Object
class Stack<T>{
}
示例二: 擦除后为类型边界(这里是 Comparable)
class Stack<T extends Comparable<T>{
}
以下这个代码的目的是遍历顺序表
class Generic{
public static<T> void print(ArrayList<T> list){
for(T t: list){
System.out.print(t+" ");
}
System.out.println();
}
}
上述代码中我们使用了泛型,并且指定了它的类型参数是 T,故我们使用时这个方法已经知道它的类型是 T 了。而这个 T 是我们指定的,有时这个方法本身也不知道传入的这个顺序表的参数类型是什么?那该怎么写呢?
这里就要使用到通配符 ?
class Generic{
// 既然不知道具体类型,那么 static 后面也不需要加 <T> 了
public static void print(ArrayList<?> list){
// 由于不知道具体类型是什么,就使用 Object
for(Object obj: list){
System.out.println(obj+" ");
}
System.out.println();
}
}
语法:
<? extends 上界>
表示可以传入的类型实参是上界类型的子类的任意类型
示例:
// Stack 对象中可以传入的类型实参是 Number 子类的任意类型的 Stack
public static void printAll(Stack<? extends Number> stack){
}
// 以下调用都是正确的
printAll(new Stack<Integer>());
printAll(new Stack<Double>());
printAll(new Stack<Number>());
// 以下调用是编译错误的
printAll(new Stack<String>());
printAll(new Stack<Object>());
语法:
<? super 下界>
表示可以传入的类型实参是下界类型的父类的任意类型
示例:
// Stack 对象中可以传入的类型实参是 Integer 父类的任意类型的 Stack
public static void printAll(Stack<? Super Integer> stack){
}
// 以下调用都是正确的
printAll(new Stack<Integer>());
printAll(new Stack<Object>());
printAll(new Stack<Number>());
// 以下调用是编译错误的
printAll(new Stack<String>());
printAll(new Stack<Double>());
我们知道 Object
是 Number
的父类型,Number
是 Integer
的父类型
但是类如 Stack<Object>
就不是 Stack<Number>
的父类型, Stack<Number>
也不是 Stack<Integer>
的父类型。
因为泛型的参数类型不参与类型的组成
如果要确定泛型的父子类型,则需要使用通配符,如
Stack<?>
是 Stack<? extends Number>
的父类型, Stack<? extends Number>
也是 Stack<Integer>
的父类型
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表){
}
示例一: 写一个泛型类 Algorithm,我们要这个类中有一个方法可以实现数组中两个值的交换,要求使用这个方法不需要实例化对象
class Algorithm{
public static<T> swap(T[] array,T i, T j){
T tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
}
示例二: 写一个泛型类 Algorithm,我们要这个类中有一个方法可以实现找到数组的最大值,要求使用这个方法不需要实例化对象
class Algorithm{
public static<T extends Comparable<T>> T findMax(T[] array){
T max=array[0];
for(int i=1;i<array.length;i++){
if(array[i].compareTo(max)>0){
max=array[i];
}
}
return max;
}
}
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
示例:通过示例中的示例二的 Algorithm 类,去找到数组的最大值
Integer[] array={1,4,2,9,10};
// 使用 <Integer> 表示我们要传入的值都是 Integer 类型的
Integer ret=Algorithm.<Integer>findMax(array);
但是由于我们通过上文可以判断这个值是 Integer 类型的,所以上述代码可以省略 <Integer>
Integer[] array={1,4,2,9,10};
Integer ret=Algorithm.findMax(array);
instanceof
判断带类型参数的泛型类型create
、catch
、throw
一个泛型类异常,即异常不支持泛型版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://t4dmw.blog.csdn.net/article/details/121265464
内容来源于网络,如有侵权,请联系作者删除!