Java泛型和内部类
Posted 爱敲代码的三毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.泛型类的定义-类型边界
假如我们要写一个泛型类来求一种元素中的最大值,那么就会遇到一个很尴尬的问题。那就是引用类型不能直接用大于小于号比较。
我们知道引用类型比较大小可以使用 Comparble 和 Comparator,前面说过类中的泛型参数 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.通配符 ?
我们来写一个泛型方法和通配符方法来打印集合里的元素
运行结果
这两种方法好像没有什么太大区别,来看一下它们的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.泛型的限制
- 泛型类型参数不支持基本数据类型
- 无法实例化泛型类型的对象
- 无法使用泛型类型声明静态的属性
- 无法使用 instanceof 判断带类型参数的泛型类型
- 无法创建泛型类数组
- 无法 create、catch、throw 一个泛型类异常(异常不支持泛型)
- 泛型类型不是形参一部分,无法重载
三、内部类
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就可以运行,有的书上会说匿名内部中只能是常量。其实是在运行过程中没有发生改变的量!
完!
以上是关于Java泛型和内部类的主要内容,如果未能解决你的问题,请参考以下文章