《Java核心技术卷1》第八章


定义泛型类

public class Hello {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Px<Integer> s = new Px<>(1);
		System.out.println(s.getx());
		Px<String> t = new Px<>("hello");
		System.out.println(t.getx());
	}

}

class Px<T> {
	private T x;

	public Px(T x) {
		this.x = x;
	}

	public T getx() {
		return x;
	}

}

静态泛型方法

定义静态泛型方法时,应该要在static后面加上

public class Hello {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(Px.getx("hello", "world", "hh"));
        //will print hh
	}

}

class Px {
	public static <T> T getx(T... a) {
		return a[a.length - 1];
	}
}

类型变量的限定

import java.io.Serializable;

public class Hello {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(Px.min("abba", "abbc"));
		System.out.println(Px.min(4, 3));
	}

}

class Px {
    //用"&"分隔限定类型
    //限定中至多有一个类,如果用一个类作为限定,必须放在限定列表的第一个
	public static <T extends Comparable & Serializable> T min(T a, T b) {
		T smaller = a;
		if (a.compareTo(b) > 0)
			smaller = b;
		return smaller;
	}
}

类型擦除

无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type )。原始类型 的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用 Object)。

这是编译器看到的代码:

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

这是JVM看到的代码:

public class Pair {
    private Object first;
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}

Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

类型擦除带来的局限性

  • 不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型:

    ArrayList<int> arr = new ArrayList<>();//compile error
  • 无法取得带泛型的Class:

    public class Hello {
    
    	public static void main(String[] args) {
    		ArrayList<Integer> arr1 = new ArrayList<>();
    		ArrayList<String> arr2 = new ArrayList<>();
    		Class cl1 = arr1.getClass();
    		Class cl2 = arr2.getClass();
    		System.out.println(cl1);
            //will print class java.util.ArrayList
    		System.out.println(cl2);
            //will print class java.util.ArrayList
    		System.out.println(cl1 == cl2);
            //will print true
    	}
    
    }
  • 不能在泛型类中实例化T类型:

    class p<T> {
    	private T first;
    	private T second;
    
    	p() {
    		first = new T();
    		second = new T();
    	}
    }
    //compile error

    要实例化T类型,应当传入Class参数:

    public class Hello {
    
    	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
            //创建对象
    		P<String> p = new P(String.class);
    	}
    
    }
    
    class P<T> {
    	public T first;
    	public T second;
    
    	P(Class<T> clazz) throws InstantiationException, IllegalAccessException {
    		first = clazz.newInstance();
    		second = clazz.newInstance();
    	}
    }

不恰当的覆写方法

定义泛型方法时,如果类型擦除后和Object的方法相同,编译器会报错。

class P<T> {
	
    boolean equals(T t) {
    	return this ==t;
    }
}

这是因为,定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object的。

修改:

class P<T> {

	boolean equals(T t1, T t2) {
		return t1 == t2;
	}
}

或者

class P<T> {

	boolean same(T t) {
		return this == t;
	}
}

泛型的继承规则

如图所示,Manager是Employee的子类,但是ArrayList和AraayList之间没有任何继承关系。

通配符

extends通配符

  • 使用Pair<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型。

    public class Hello {
    
    	public static void main(String[] args) {
    		Pair<Integer> p = new Pair(123, 456);
    		System.out.println(m1(p));
            //will print 579
    
    	}
    
    	public static int m1(Pair<? extends Number> p) {
    		return p.getA().intValue() + p.getB().intValue();
    	}
    
    	public static int m2(Pair<? extends Number> p) {
    		p.setA(123);
    		p.setB(456);
            //compile error
    		return p.getA().intValue() + p.getB().intValue();
    	}
    
    }
    
    class Pair<T> {
    	private T a;
    	private T b;
    
    	public Pair(T a, T b) {
    		this.a = a;
    		this.b = b;
    	}
    
    	public T getA() {
    		return a;
    	}
    
    	public void setA(T a) {
    		this.a = a;
    	}
    
    	public T getB() {
    		return b;
    	}
    
    	public void setB(T b) {
    		this.b = b;
    	}
    
    }

    注意:不能调用set方法。编译器只知道需要某个Number 的子类型,但不知道具体是什么类型,比如说原来的值是Double类型,传入的Integer,这就出了问题。它拒绝修改。使用 get就不存在这个问题。extends是只读的。

  • 使用extends限定T类型

    在定义泛型类型Pair的时候,也可以使用extends通配符来限定T的类型:

    public class Hello {
    
    	public static void main(String[] args) {
    		Pair<Integer> p1 = null;
            //allowed
    		Pair<Double> p2 = null;
            //allowed
    		Pair<Object> p3 = null;
            //error
    		Pair<String> p4 = null;
            //error
    	}
    
    }
    
    class Pair<T extends Number> {
    }

    如上,因为String和Object不是Number类型或Number的子类,所以无法通过编译。

super通配符

使用Pair<? super Integer>使得方法接收所有泛型类型为Interger或Interger父类的Pair类型。

public class Hello {

	public static void main(String[] args) {
		pair<Number> p = new pair<>();
		m2(p, new Integer(100));
		Object o = m1(p);//使用Object来接收返回值
		System.out.println(o.toString());
	}

	public static Object m1(pair<? super Integer> p) {
		return p.getA();
	}

	public static void m2(pair<? super Integer> p, Integer x) {
		p.setA(x);
	}
}

class pair<T> {
	private T a;

	public T getA() {
		return a;
	}

	public void setA(T a) {
		this.a = a;
	}

}
  • 方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;
  • 方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst()。

extends与super的比较

  • 允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • 允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

PECS原则

如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i); // src是producer
            dest.add(t); // dest是consumer
        }
    }
}

PECS原则的原因:向上转型安全,向下转型不安全。

无限定通配符

  • 不允许调用set(T)方法并传入引用(null除外);

  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

  • 只能做一些null判断

    static boolean isNull(Pair<?> p) {
        return p.getFirst() == null || p.getLast() == null;
    }
  • Pair<?>是Pair的超类。所以,如Pair可以安全地向上转型。

    Pair<Integer> p = new Pair<>(123, 456);
    Pair<?> p2 = p; // 安全地向上转型

泛型和反射

部分反射api也是泛型。如Class,Constructor

public class Hello {

	public static void main(String[] args) throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		Class<String> clazz = String.class;
		String str = clazz.getConstructor(String.class).newInstance("hello");
		System.out.println(str);
        // will print hello
		Class<Integer> clazz2 = Integer.class;
		Constructor<Integer> cons = clazz2.getConstructor(int.class);
		Integer n = cons.newInstance(123);
		System.out.println(n);
        //will print 123
	}
}

文章作者: 淡夜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 淡夜 !
评论
  目录