泛型
什么是泛型
- 参数化类型
- 解决类型相关算法重用
- 编译器需要指定参数类型,否则无法通过编译
- 使用泛型类型转换都是自动或者隐式的进行的
- 泛型只能是引用类型
- 泛型可以用在类,方法,上面
泛型提升安全性的原理
- 确保类型安全
- 类型自动转换不需要手动进行类型转换
- 可以结合使用Object作为超类使用的时候进行类型转换出现的问题进行比较,使用Object 必须显示的进行类型转换
有界泛型
声明泛型的时候声明泛型的范围
形式 T extends superClassName
代码示例
public class Stats<T extends Number> {
T[] nums;
public Stats(T[] o) {
this.nums = o;
}
double arerage() {
double sum = 0.0;
for(int i=0;i<nums.length;i++) {
sum += nums[i].doubleValue();
}
return sum/nums.length;
}
public static void main(String[] args) {
Stats<Integer> s1 = new Stats<Integer>(new Integer[]{1,2,3,4}) ;
System.out.println(s1.arerage());
}
}
使用通配符参数
根据上面的例子比较两个平均数的大小,正好两个类型不一致,将无法通过编译,请看小面的这种情况
boolean sameAvg(Stats<T> obj) {
if(obj.arerage() == arerage())
return true;
return false;
}
Stats<Integer> s1 = new Stats<Integer>(new Integer[]{1,2,3,4}) ;
System.out.println(s1.arerage());
Stats<Double> s2 = new Stats<Double>(new Double[] {1.0,2.0,3.0,4.0});
System.out.println(s1.sameAvg(s2));
使用通配符?,代码示例:
boolean sameAvg(Stats<?> obj) {
if(obj.arerage() == arerage())
return true;
return false;
}
Stats<Integer> s1 = new Stats<Integer>(new Integer[]{1,2,3,4}) ;
System.out.println(s1.arerage());
Stats<Double> s2 = new Stats<Double>(new Double[] {1.0,2.0,3.0,4.0});
System.out.println(s1.sameAvg(s2))
通配符不会影响创建什么类型的对象,因为对象的类型是由 T extends className 来进行控制的,通配符只是匹配所有有效的类型。
有界通配符
代码示例1:(无界)
public class TwoD {
private Integer x;
private Integer y;
public TwoD(Integer x,Integer y) {
this.x = x;
this.y = y;
System.out.println("=====TwoD==========");
}
public Integer getX() {
return x;
}
public void setX(Integer x) {
this.x = x;
}
public Integer getY() {
return y;
}
public void setY(Integer y) {
this.y = y;
}
@Override
public String toString() {
return "TwoD [x=" + x + ", y=" + y + "]";
}
}
public class ThreeD extends TwoD {
private Integer z;
public ThreeD(Integer x, Integer y,Integer z) {
super(x, y);
this.z =z;
System.out.println("=====ThreeD==========");
}
public Integer getZ() {
return z;
}
public void setZ(Integer z) {
this.z = z;
}
@Override
public String toString() {
return super.toString() + "ThreeD [z=" + z + "]";
}
}
public class Point<T extends TwoD> {
T[] points;
public Point(T[] o) {
this.points = o;
}
public static void showXY(Point<?> p) {
for(int i=0;i<p.points.length;i++) {
System.out.println("x=" + p.points[i].getX() + "\ty=" + p.points[i].getY());
}
}
}
测试方法
public static void main(String[] args) {
Point<TwoD> p =new Point<>(new TwoD[] {new TwoD(1,2),new TwoD(3,4)});
Point.showXY(p);
}
代码示例2 (有界)
上面方法 showXY 只能获取到x,y的数值。不能获取到z,因为关键字 Point 所以不能获取到z.
增加下面的方法:
public static void showXYZ(Point<? extends ThreeD> p) {
for(int i=0;i<p.points.length;i++) {
System.out.println("x=" + p.points[i].getX() + "\ty=" + p.points[i].getY() + "\tz=" + p.points[i].getZ());
}
}
测试输出:
public static void main(String[] args) {
Point<ThreeD> p2 =new Point<>(new ThreeD[] {new ThreeD(1,2,4),new ThreeD(3,4,4)});
Point.showXYZ(p2);
}
x=1 y=2 z=4
x=3 y=4 z=4
以上创建的都是上界通配符,当然还有下界通配符。关键字supper
这种只有超类才是符合的参数
方法上面使用泛型
public static<T extends Comparable<T>,V extends T> boolean compareTo(T t,V[] vs) {
for(int i=0;i<vs.length;i++) {
if(t.equals(vs[i])){
return true;
}
}
return false;
}
public static void main(String[] args) {
Integer a = 10;
Integer[] arrs = new Integer[] {1,2,3,10,20};
boolean b1 = compareTo(a,arrs);
System.out.println(b1);
String s1 = "aa";
String[] arrs1 = new String[] {"bb","aa"};
System.out.println(compareTo(s1,arrs1));
}
泛型构造函数
可以将类的构造函数泛型化,即使此类不是泛型化的也可以
示例
public class ConstructorFX {
Double d;
public <T extends Number> ConstructorFX(T t) {
d = t.doubleValue();
}
}
泛型接口
示例
public interface InterfaceFX<T> {
T get(T t);
}
- 类实现泛型接口必须是泛型化的
- 类实现泛型接口也可指定泛型类型
原始类型和遗留代码
jdk5之前还没有泛型的概念,为了处理过度,声明泛型类可以不指定泛型的类型
public static void main(String[] args) {
Point p1 =new Point(new TwoD[] {new TwoD(1,2),new TwoD(3,4)});
Point.showXY(p1);
}
泛型自动类型推算
class-name<type-arg-list> var-name = class-name<>(arg-list)
后面的参数列表不需要填写具体的类型
泛型的实现原理
擦除
- java 使用擦除实现泛型
- 因为java5之前是没有泛型的,现在的jvm或者语法都要兼容以前的代码
- 在编译泛型代码的时候所有泛型信息都会被删除,使用泛型的界定类型替换类型参数,如果没有显示的指定界定类型则会使用Object类型,然后应用适当的类型转换,
- 也就是说运行时没有泛型,没有类型参数。编译器已经解决了
桥接方式
- 发生在字节码阶段
- 子类中重写的类型擦除不能与超类中产生相同的方法擦除
- 子方法重写了父类方法
模糊性问题
public class Mohu <T,V>{
T t;
V v;
public void set(T t) {this.t = t;}
public void set(V v) {this.v = v;}
}
上面的方法看起来是合理的,但是当使用擦除后,T和V如果类型相同 两个set方法就变成一样的了
泛型的限制
1.不能实例化泛型参数 new T(); 是错误的
2.不能声明静态变量和静态返回值 以下示例是不正确的
代码示例
static T t;
public static T get() {
return t;
}
}
3.不能创建引用类型数组 ,
错误示例:
T[] num = new T[10];
Point<TwoD>[] p = new Point<TwoD>[2];
但是可以使用下面的这种通配符的方式
public static void main(String[] args) {
// Point<TwoD>[] p = new Point<TwoD>[2];
Point<?>[] p = new Point<?>[2];
p[0] = new Point<TwoD>(new TwoD[] {new TwoD(1,2),new TwoD(3,4)});
p[1] = new Point<TwoD>(new ThreeD[] {new ThreeD(1,2,4),new ThreeD(3,4,4)});
for(int i=0;i<p.length;i++) {
Point.showXY(p[i]);
}
}
4.不能创建泛型异常类