我们在学习JAVA中经常会写这样一些类用来联系,比如超级数组,我们先写一个这样的类
SuperArray.java
package com;
import java.util.Arrays;
public class SuperArray {
// 定义我们操作的数组,初始化长度为10
private int[] num = new int[10];
// 定义游标
private int current = -1;
// 空参构造
public SuperArray() {
}
// 实现在数组的最后面加我们期望的值
public void add(int a) {
current++;
expan();
num[current] = a;
}
// 实现打印数组
public String ToString() {
StringBuilder str = new StringBuilder();
str.append('[');
for (int i = 0; i < current; i++) {
str.append(num[i]);
str.append(',');
}
str.append(num[current]);
str.append(']');
return str.toString();
}
// 数组成倍数扩容
private void expan() {
if (current == num.length) {
int[] temp = new int[num.length * 2];
for (int i = 0; i < num.length; i++) {
temp[i] = num[i];
}
num = temp;
}
}
public static void main(String[] args) {
SuperArray superArray = new SuperArray();
superArray.add(2);
System.out.println(superArray.ToString());
}
}
我们上面的代码实现了一个非常简单的功能,就是自动扩容的数组,当然我们很多功能还没有实现,可是这个代码有很大的局限性,我们只能对int类型的数据进行操作,一旦数据改变这个超级数组就没用了,
解决方案一、一旦有新的数据类型,我们就重写一个超级数组类(很笨,代码冗余)
解决方案二、我们将传入的数据类型换位Object的基类,这样什么数据都可以传入
解决方案三、泛型
解决方案一我们就不讲了
解决方案二
我们将上面的文件稍作修改就可以实现
package com;
public class SuperArray {
// 定义我们操作的数组,初始化长度为10
private Object[] num = new Object[10];
// 定义游标
private int current = -1;
// 空参构造
public SuperArray() {
}
// 实现在数组的最后面加我们期望的值
public void add(Object a) {
current++;
expan();
num[current] = a;
}
// 实现打印数组
public String ToString() {
StringBuilder str = new StringBuilder();
str.append('[');
for (int i = 0; i < current; i++) {
str.append(num[i]);
str.append(',');
}
str.append(num[current]);
str.append(']');
return str.toString();
}
// 数组成倍数扩容
private void expan() {
if (current == num.length) {
Object[] temp = new Object[num.length * 2];
for (int i = 0; i < num.length; i++) {
temp[i] = num[i];
}
num = temp;
}
}
public static void main(String[] args) {
SuperArray superArray = new SuperArray();
superArray.add(new Animals("dog",13));
superArray.add(new Animals("dog",13));
System.out.println(superArray.ToString());
}
}
Animals.java
package com;
public class Animals {
String name;
int age;
public Animals(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
这里利用到的原理就是,Object是所有类的父类,可是这样也存在一定的问题,就比如说,我们想让这个类只存Animals,但是我们目前的代码是什么都可以存的,这样并不好,我们无法约束存入值得行为,所以我们可以接着改进,先判断一下传入值的类型,是不是我们想要的。所以可以将超级数组再进行一层抽象。其实这正是泛型所做的。
二、泛型的定义看表面的意思,泛型就是指广泛的、普通的类型。泛型能够帮助我们把类型明确的工作推迟到创建对象或调用方法的时候。
意思就是:我定义类的时候不用管到底是什么类型,new这个对象或者调用这个对象的方法时才确定具体的类型。
泛型类泛型类也就是把泛型定义在类上,这样用户在使用类的时候才把类型给确定下来。
package com; import java.util.Arrays; public class SuperArray{ // 定义我们操作的数组,初始化长度为10 private Object[] num = new Object[10]; // 定义游标 private int current = -1; // 空参构造 public SuperArray() { } // 实现在数组的最后面加我们期望的值 public void add(T a) { current++; expan(); num[current] = a; } // 实现打印数组 public String ToString() { StringBuilder str = new StringBuilder(); str.append('['); for (int i = 0; i < current; i++) { str.append(num[i]); str.append(','); } str.append(num[current]); str.append(']'); return str.toString(); } // 数组成倍数扩容 private void expan() { if (current == num.length) { Object[] temp = new Object[num.length * 2]; for (int i = 0; i < num.length; i++) { temp[i] = num[i]; } num = temp; } } public static void main(String[] args) { SuperArray superArray = new SuperArray<>(); superArray.add(new Animals("dog",13)); superArray.add(new Animals("dog",13)); System.out.println(superArray.ToString()); } }
其实我们只改了两个地方,我们把add方法的输入类型由object改为了T,T随着我们输入类型的变化而变化
泛型方法有时候只关心某个方法,那么使用泛型时可以不定义泛型类,而是只定义一个泛型方法,如下:
public class Test2 {
public T show(T t) {
System.out.println(t);
return t;
}
public static T show2(T one) { //这是正确的
return null;
}
public static void main(String[] args) {
Test2 test2 = new Test2();
String show = test2.show("123");
Integer show1 = test2.show(123);
}
}
继承关系
泛型类在继承时,可以明确父类(泛型类)的参数类型,也可以不明确。
我们定义一个泛型接口
// 泛型类 public interface Comparator{ int compare(T o1, T o2); }
在实现这个接口的时候我们可以明确类型
public class UserAgeComparator implements Comparator{ @Override public int compare(User o1, User o2) { return o1.getAge() - o2.getAge(); } }
此时我们需要这样调用,其实已经和普通的类没有什么区别
UserAgeComparator userAgeComparator = new UserAgeComparator();
也可以不明确类型
public class UserAgeComparatorimplements Comparator { @Override public int compare(T o1, T o2) { return o1.equals(o2) ? 0 : 1; } }
此时我们需要这样调用,和泛型类的调用很相似,在调用的时候传入类型
UserAgeComparator三、类型通配符 无界userAgeComparator = new UserAgeComparator<>();
public static void printSuperArray(SuperArray> superArray){
for (int i = 0;i
"?"可以接收任何类型
上界
我们可以使用(SuperArray extends Dog> superArray)的形式来约定传入参数的上界,意思就是泛型只能是Dog的或者Dog的子类。
public static void printSuperArray(SuperArray extends Dog> superArray){
for (int i = 0;i
下界
我们可以使用(SuperArray super Dog> superArray)的形式来约定传入参数的下界,意思就是泛型只能是Dog的或者Dog的超类。
public static void printSuperArray(SuperArray super Dog> superArray){
for (int i = 0;i
四、类型擦除
为了兼容性,使用原始类型(没有类型参数的泛型)是合法的,泛型被添加进来时,还存在大量不使用泛型的代码。保持所有这些代码合法并与使用泛型的新代码兼容被认为是关键的。将参数化类型的实例传递给设计用于原始类型的方法必须是合法的,反之亦然。
为了保持这种兼容性,Java的泛型其实是一种伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义SuperArray
五、静态方法
public class Test2 {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
但是要注意区分下面的一种情况:
public class Test2 {
public static T show(T one){
return null;
}
}
这定义的是一个泛型方法。



