Java泛型和内部类


一、泛型的概述

1.概念

我们都用过Java集合类,比如 ArrayList、 LinkedList 、HashMap等,都可以用来存放数据,但如果不使用泛型那么就会变成一个通用的集合类。
来看一个列子:

public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(1);
        list.add(150.50);
        list.add("泛型");
        System.out.println(list);
}    

运行结果
在这里插入图片描述
我们发现这个集合可以存任意类型的数据,相当于一个裸类型,显然这不是我们希望的。我们希望的是存一组相同类型的数据。那么就需要用到泛型了。

2.泛型的定义

注意:泛型的参数一定是一个引用类型

class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
} 
class ClassName<T1, T2, ..., Tn> {
}

【规范】类型形参一般使用一个大写字母表示,常用的名称有:

  • E 表示 Element(集合中使用的数组)
  • K 表示 Key(键)
  • V 表示 Value(值)
  • N 表示 Number(数值类型)
  • T 表示 Type(Java类)
  • S, U, V 等等 - 第二、第三、第四个类型

3.泛型的使用

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
ArrayList<Integer> arrayList2 = new ArrayList<Integer>();
ArrayList<Integer> arrayList1 = new ArrayList<>();//可以推导出实例化需要的类型实参为 Integer

同样使用ArrayList,利用泛型为它指定能存放的数据类型后。它就只可以存放指定的类型了,一旦想存放其它类型的元素在编译期间就会报错。
在这里插入图片描述
我们自己定义了一个栈指定了存放元素类型为 Interger,那么它就只能存Integer的元素

class Stack<T> {
    public T[] objects;
    public int top;

    public Stack() {
        this.objects = (T[])new Object[10];
    }

    public void push(T obj) {
        objects[this.top++] = obj;
    }

    public T get() {
        return objects[this.top-1];
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack);
    }
}

4.泛型的意义

其实就是将类型参数化了

1.存放数据的时候,进行类型的检查

2.取数据的时候,进行类型的自动转换

5.泛型是如何编译的?

泛型是在编译期间的一种机制,这种机制叫做:类型擦除机制。
编译的时候,会把这个泛型参数,擦为 Object

注意:是类中的泛型被擦成Object了,其它地方的泛型都是被擦没了。
比如下面的泛型参数都是在编译期间都被擦没了。
在这里插入图片描述
而类里的泛型参数就变被擦成了 Object,比如这里的 T
在这里插入图片描述

这里要说到泛型的一个坑:就是泛型的数组是不能 new 的,因为泛型是先检查后编译的,当检查的时候并不指定你的泛型参数是什么。我们可以看一下ArrayList的源码,它传的是一个 E类型的参数,实质上 new 的是一个Object类型的数组。所以要记住泛型的数组是不能 new 的!

在这里插入图片描述
在这里插入图片描述

二、泛型的进一步使用

1.泛型类的定义-类型边界

假如我们要写一个泛型类来求一种元素中的最大值,那么就会遇到一个很尴尬的问题。那就是引用类型不能直接用大于小于号比较。
在这里插入图片描述
我们知道引用类型比较大小可以使用 ComparbleComparator,前面说过类中的泛型参数 T会被擦成 Object、但Object中并没有实现这两个接口,就无法对引用类型进行比较。
在这里插入图片描述
当被擦到Object后,没有实现对应的接口,所以我们就要控制不要擦除到Object,指定泛型的擦除边界。

语法:使用extends 关键字设置擦除边界

Algorithm<T>//擦除到Object
Algorithm<T extends Comparable<T>>//擦除到Comparable

代码示例:

class Algorithm<T extends Comparable<T>>{
    public T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0) {
                T tmp = max;
                max = array[i];
                array[i] = tmp;
            }
        }
        return max;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Algorithm<Integer> algorithm = new Algorithm<>();
        Integer[] array = {1,2,3,100,80,156,20,25};
        int ret = algorithm.findMax(array);
        System.out.println("最大的数为:"+ret);
    }
}

运行结果:
在这里插入图片描述
下面这个代码代表着泛型的上界

注意:泛型只有上界没有下界

下面这个代码的意思就是:擦除到了Comparable这个接口的地方,换句话说就是将来这个T类型一定是实现了这个接口。
在这里插入图片描述

Number类

如果以后看见一下写法,那么 E 一定是实现了 Number 的子类

public class MyArrayList<E extends Number> {
}

Number 的子类有下面这几个
在这里插入图片描述

2.泛型方法

上面通过的泛型类来求最大值的时候,每次都要 new 对象非常的麻烦。为了不那么麻烦,就可以用到泛型方法了。
我们给泛型方法直接添加了一个 static并指定了擦除边界,就可以直接通过类名来调用了。

class Algorithm{
    public static<T extends Comparable> T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0) {
                T tmp = max;
                max = array[i];
                array[i] = tmp;
            }
        }
        return max;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Integer[] array = {1,2,3,100,80,156,20,25};
        int ret = Algorithm.findMax(array);
        System.out.println("最大的数为:"+ret);
    }
}

注意:这里没有指定泛型的参数,但它会根据 形参的类型 推导出 整个泛型的类型参数

当然你也可以自己指定参数,不过一般不这么写

int ret1 = Algorithm.findMax(array);
int ret2 = Algorithm.<Integer>findMax(array);//一般会省略这个

3.泛型中的父子类型

注意一个问题:

<> 里面的内容,不构成类型的组成

MyArrayList 不是 MyArrayList 的父类型
MyArrayList 也不是 MyArrayList 的父类型

4.通配符 ?

我们来写一个泛型方法和通配符方法来打印集合里的元素
![在这里插入图片描述](https://img-blog.csdnimg.cn/8fb1bd636b214d4b828b5640391c186a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA54ix5pWy5Luj56CB55qE

运行结果
在这里插入图片描述
这两种方法好像没有什么太大区别,来看一下它们的add和get方法。
我们发现,通配符的add方法在添加元素的时候并不知道添加的是什么元素。
而获取元素,而get方法则是只需要个下标就可以了

也就是说通配符只适合读数据,所以通配符一般只是存在于源码当中。
在这里插入图片描述

通配符上界

<? extends 上界>

用法和泛型类似

代码示例:

// 可以传入类型实参是 Number 子类的任意类型的 MyArrayList
public static void printAll(MyArrayList<? extends Number> list) {
...
} 
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Object>())

通配符下界

语法:

<? super 下界>

传入的类型是下界的夫类或者是下界的本身

代码示例:

// 可以传入类型实参是 Integer 父类的任意类型的 MyArrayList
public static void printAll(MyArrayList<? super Integer> list) {
...
} 
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Double>());

5.泛型的限制

  1. 泛型类型参数不支持基本数据类型
  2. 无法实例化泛型类型的对象
  3. 无法使用泛型类型声明静态的属性
  4. 无法使用 instanceof 判断带类型参数的泛型类型
  5. 无法创建泛型类数组
  6. 无法 create、catch、throw 一个泛型类异常(异常不支持泛型)
  7. 泛型类型不是形参一部分,无法重载

三、内部类

1.本地内部类

方法里面定义的类:无任何意义

2.实例内部类

1.实例内部类中不能定义静态的成员变量

因为静态的成员变量不依赖于对象
在这里插入图片描述
如果非要定义可以定义为 static final修饰的常量,也就是说只要编译期间能确定的值就可以
在这里插入图片描述

2.如何在其它类拿到一个实例内部类的对象

通过 外部类.内部类 变量名 = 外部类对象的引用.new 内部类

class ATest {
    public int a = 10;
    class InnerClass {
        public int a = 100;
        public static final int c = 20;
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        ATest aTest = new ATest();
        ATest.InnerClass innerClass = aTest.new InnerClass();
    }
}

有的书上是简写

ATest.InnerClass innerClass1 = new ATest().new InnerClass();

3.实例内部类当中如何访问和外部类相同的成员变量

外部类名.this.变量名

class ATest {
    public int a = 10;
    class InnerClass {
        public int a = 100;
        public static final int c = 20;
        public void fuc() {
            System.out.println(ATest.this.a);
        }
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        ATest aTest = new ATest();
        ATest.InnerClass innerClass = aTest.new InnerClass();
        innerClass.fuc();
    }
}

运行结果

在这里插入图片描述

3.静态内部类

1.如何拿到静态内部类的对象

直接通过 外部类.静态类名就好了

class ATest {
    public int a = 10;
    static class InnerClass {
        public int a = 100;
        public static final int c = 20;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        ATest.InnerClass innerClass = new ATest.InnerClass();
    }
}

2.静态内部类中,不能访问外部类的非静态的数据成员。

在这里插入图片描述

如果非要在静态内部类中访问非静态的成员变量,也是能实现的。
在内部类中提供一个外部类的成员变量,给静态内部类当中提供一个带一个参数的构造方法,参数是外部类对象,通过这个成员变量来访问.

在这里插入图片描述

4.匿名内部类

当你的类只想定义一次且没有名字时,就可以使用匿名内部类。

接口使用

public class TestDemo {
    interface A {
        public void func();
    }

    A a = new A(){
        @Override
        public void func() {
            System.out.println("当前是个匿名内部类,实现了A接口,重写了接口的方法");
        }
    };
    public static void main(String[] args) {
   
    }
}

匿名子类

class ATest {
    public void func() {
        System.out.println("func()");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        new ATest(){
            @Override
            public void func() {
                System.out.println("重写的func方法");
            }
        }.func();
    }
}

运行结果:

在这里插入图片描述
注意事项:
匿名内部类中:一定是程序在运行的过程当中没有发生改变的量
在这里插入图片描述
如果不修改tmp就可以运行,有的书上会说匿名内部中只能是常量。其实是在运行过程中没有发生改变的量
在这里插入图片描述


完!

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的三毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值