1、一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码。这种刻板的限制对代码的束缚就会很大。
2、凡是需要说明类型的地方,如果都使用基类,确实能够具备更好的灵活性。但是,考虑到除了final类不能扩展,其他任何类都可以被扩展,所以这种灵活性大多数时候也会有一些性能损耗。
3、泛型实现了参数化类型的概念,使代码可以应用于多种类型。
15.1 与C++比较
15.2 简单泛型
Java泛型的核心概念;告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
class Automobile{
}
public class Holder3<T> {
private T a;
public Holder3(T a) {
this.a = a;
}
public T get() {
return a;
}
public void set(T a) {
this.a = a;
}
public static void main(String[] args) {
//当创建holder3对象的时候,必须指明想持有什么类型的对象,将其置于尖括号内,从Holder3种取出持有的对象时,
//自动地就是正确的类型
Holder3<Automobile> holder3 = new Holder3<>(new Automobile());
Automobile a = holder3.get();
// holder3.set(1); Error
}
}
15.2.1 一个元组类库
1、元组:将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。(这个概念也称为数据传送对象或信使)
2、通常,元组具有任意长度,同时元组中的对象可以是任意不同的类型。不过,希望能够为每一个对象指明其类型,并且从容器中读取出来时,能够得到正确的类型。要处理不同长度的问题,需要创建多个不同的元组。
3、继承机制实现长度更长的元组
//有了泛型,可以很容易创建元组,令其返回一组任意类型的对象。而你所需要做的,就是编写表达式。
package com15;
/**
* Created by Panda on 2018/5/15.
*/
class TwoTuple<A,B>{
public final A first;
public final B second;
public TwoTuple(A first, B second) {
this.first = first;
this.second = second;
}
@Override
public String toString() {
return "TwoTuple{" +
"first=" + first +
", second=" + second +
'}';
}
}
class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A first, B second, C third) {
super(first, second);
this.third = third;
}
@Override
public String toString() {
return "ThreeTuple{" +
"first=" + first +
", second=" + second +
", third=" + third +
'}';
}
}
class Amphibian{}
class Vehicle{}
public class TupleTest{
static TwoTuple<String,Integer> f(){
return new TwoTuple<String,Integer>("hi",47);
}
static ThreeTuple<Amphibian,String ,Integer> g(){
return new ThreeTuple<Amphibian,String,Integer>(new Amphibian(),"hi",47);
}
public static void main(String[] args) {
TwoTuple<String, Integer> twoTuple=f();
/// twoTuple.first="three"; Compile error final
System.out.println(twoTuple);
System.out.println(g());
}
}
15.2.2一个堆栈类
1、不用LinkedList,实现内部链式存储机制
package com15;
/**
* Created by Panda on 2018/5/15.
*/
public class LinkedStack<T> {
private static class Node<U>{
U item;
Node<U> next;
Node(){item=null;next=null;}
public Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
boolean end(){return item==null&&next==null;}
}
private Node<T> top=new Node<T>();
public void push(T item){
top=new Node<T>(item,top);
}
public T pop(){
T result = top.item;
if(!top.end()){
top=top.next;
}
return result;
}
public static void main(String[] args) {
LinkedStack<String> linkedStack = new LinkedStack<>();
for (String s:"Phasers on stun".split(" ")) {
linkedStack.push(s);
}
String s;
while ((s=linkedStack.pop())!=null){
System.out.println(s);
}
}
}
15.2.3RandomList
1、容器的另外一个例子:需要一个持有特定类型对象的列表,每次调用其上的select()方法时,可以随机地选取一个元素。如果希望以此构建一个可以应用于各种类型的对象的工具,就需要使用泛型。
15.3泛型接口
1、泛型也可以应用于接口。例如:生成器,是一种专门负责创建对象的类。实际上,是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。
2、接口使用泛型与类使用泛型没有什么区别。
3、
/**
* Created by Panda on 2018/5/15.
*/
interface Generator<T>{T next();}
//基本类型无法作为类型参数
public class Fibonacci implements Generator<Integer> {
private int count =0;
@Override
public Integer next() {
return fib(count++);
}
private int fib(int n){
if(n<2) return 1;
return fib(n-2)+fib(n-1);
}
public static void main(String[] args) {
Fibonacci fibonacci = new Fibonacci();
for (int i = 0; i <18 ; i++) {
System.out.print(fibonacci.next()+" ");
}
}
}
15.4 泛型方法
1、是否拥有泛型方法,与其所在的类是否是泛型没有关系。
2、泛型基本指导规则:无论何时,只要能做到就应该尽量使用泛型方法。也就是说,使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为泛型方法可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
3、定义泛型方法,只需要将泛型参数列表置于返回值之前。
4、当使用泛型类的时候,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必致命参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断。
5、显示的类型说明:在泛型方法中,可以显示地指明类型。不过这种语法很少使用,要显示地指明类型,必须在点操作符和方法名之间插入尖括号,然后把类型置于尖括号内。如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。
15.4.2可变参数与泛型方法
package com15;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Panda on 2018/5/15.
*/
public class Test1 {
public static <T> List<T> makeList(T...args){
List<T> result=new ArrayList<>();
for (T item:args) {
result.add(item);
}
return result;
}
public static void main(String[] args) {
List<String> ls=makeList("A");
System.out.println(ls);
ls=makeList("A","B","C");
System.out.println(ls);
ls=makeList(("ABCD").split(" "));
System.out.println(ls);
}
}
15.4.3用于Generator的泛型方法
15.4.4一个通用的Generator
package com15;
/**
* Created by Panda on 2018/5/15.
*/
//这个类提供了一个基本的实现,用以生成某个类的对象。这个类必须具备两个特点:
//①它必须声明为public(因为BasicGenerator要处理的类在不同的包中,所以该类必须声明为public
//并且不只具有包内访问权限。②它必须具备默认的构造器(无参构造器)。要创建这样的BasicGenerator
//对象,只需调用create()方法,而不必执行new
public class BasicGenerator<T> implements Generator<T>{
private Class<T> type;
public BasicGenerator(Class<T> type) {
this.type = type;
}
@Override
public T next() {
try{
return type.newInstance();
}catch (Exception e){
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type){
return new BasicGenerator<>(type);
}
public static void main(String[] args) {
Generator<BasicGeneratorDemo> generatorDemoGenerator =BasicGenerator.create(BasicGeneratorDemo.class);
for (int i = 0; i < 5; i++) {
System.out.println(generatorDemoGenerator.next());
}
}
}
class BasicGeneratorDemo{
private static long count=0;
private final long id=count++;
public long id(){return id;}
public String toString(){return "demo"+id;}
}
/**
demo0
demo1
demo2
demo3
demo4
*/
15.4.5简化元组的使用
15.4.6一个Set实用工具
package com15;
import java.util.HashSet;
import java.util.Set;
/**
* Created by Panda on 2018/5/15.
*/
public class Sets {
//将两个参数合并在一起
public static <T> Set<T> union(Set<T> a, Set<T> b){
Set<T> result=new HashSet<>(a);
result.addAll(b);
return result;
}
//交集
public static <T> Set<T> intersection(Set<T> a,Set<T> b){
Set<T> result =new HashSet<>(a);
result.retainAll(b); //交集
return result;
}
//移除部分元素 从superset中移除subset包含的元素
public static <T> Set<T> difference(Set<T> superset,Set<T> subset){
Set<T> result=new HashSet<>(superset);
result.removeAll(subset);
return result;
}
//除交集之外的所有元素
public static <T> Set<T> complement(Set<T> a,Set<T> b){
return difference(union(a,b),intersection(a,b));
}
}
15.5 匿名内部类
1、泛型还可以应用于内部类以及匿名内部类。
package com15;
import java.util.*;
/**
* Created by Panda on 2018/5/15.
*/
class Customer{
private static long counter=1;
private final long id=counter++;
private Customer(){}
public String toString(){return "customer:"+id;}
public static Generator<Customer> generator(){
return new Generator<Customer>() {
@Override
public Customer next() {
return new Customer();
}
};
}
}
class Teller{
private static long counter=1;
private final long id=counter++;
private Teller(){}
public String toString(){return "Teller:"+id;}
public static Generator<Teller> generator=new Generator<Teller>() {
@Override
public Teller next() {
return new Teller();
}
};
}
class Generators{
public static <T> Collection<T> fill(Collection<T> coll,Generator<T> generator,int n){
for (int i = 0; i <n ; i++) {
coll.add(generator.next());
}
return coll;
}
}
public class BankTeller {
public static void server(Teller teller ,Customer customer){
System.out.println(teller+" servers "+customer);
}
public static void main(String[] args) {
Random random = new Random(47);
Queue<Customer> line =new LinkedList<>();
Generators.fill(line,Customer.generator(),15);
List<Teller> tellers=new ArrayList<>();
Generators.fill(tellers,Teller.generator,4);
for (Customer c:line) {
server(tellers.get(random.nextInt(tellers.size())),c);
}
}
/**
* Teller:3 servers customer:1
Teller:2 servers customer:2
Teller:3 servers customer:3
Teller:1 servers customer:4
Teller:1 servers customer:5
Teller:3 servers customer:6
Teller:1 servers customer:7
Teller:2 servers customer:8
Teller:3 servers customer:9
Teller:3 servers customer:10
Teller:2 servers customer:11
Teller:4 servers customer:12
Teller:2 servers customer:13
Teller:1 servers customer:14
Teller:1 servers customer:15
*/
}
15.6 构建复杂模型
1、使用泛型类构建复杂模型。即使每个类都是作为一个构建块创建的,但是整个还是包含许多部分。
package com15;
import java.util.ArrayList;
import java.util.Random;
/**
* Created by Panda on 2018/5/15.
*/
class Product{
private int id;
private String description;
private double price;
public Product(int id, String description, double price) {
this.id = id;
this.description = description;
this.price = price;
toString();
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", description='" + description + '\'' +
", price=" + price +
'}';
}
public void priceChange(double change){
price+=change;
}
public static Generator<Product> generator= new Generator<Product>() {
private Random random = new Random(47);
@Override
public Product next() {
return new Product(random.nextInt(1000),"Test",Math.round(random.nextDouble()*1000.0)+0.99);
}
};
}
class Shelf extends ArrayList<Product>{
public Shelf(int nProducts){
Generators.fill(this,Product.generator,nProducts);
}
}
class Aisle extends ArrayList<Shelf>{
public Aisle(int nShelves,int nProducts){
for (int i = 0; i < nShelves; i++) {
add(new Shelf(nProducts));
}
}
}
class CheckOut{}
class Office{}
public class Store extends ArrayList<Aisle> {
private ArrayList<CheckOut> checkOuts=new ArrayList<>( );
private Office office = new Office();
public Store(int nAisles,int nShelves,int nProducts){
for (int i = 0; i < nAisles; i++) {
add(new Aisle(nShelves,nProducts));
}
}
public String toString(){
StringBuffer stringBuffer = new StringBuffer();
for (Aisle a:this) {
for(Shelf s:a){
for (Product p:s) {
stringBuffer.append(p);
stringBuffer.append("\n");
}
}
}
return stringBuffer.toString();
}
public static void main(String[] args) {
System.out.println(new Store(14,5,10));
}
}
15.7 擦除的神秘之处
//在泛型代码内部,无法获得任何有关泛型参数类型的信息。
public class Test2 {
public static void main(String[] args) {
Class c1=new ArrayList<String>().getClass();
Class c2=new ArrayList<Integer>().getClass();
System.out.println(c1==c2); //true
}
}
15.7.1C++的方式
1、 声明T必须具有类型B或者从B导出的类型。
15.7.2
1、擦除减少了泛型的泛化性。泛型在Java中仍旧是游泳的,只是不如本来设想的游泳,而原因就是擦除。
2、在基于擦除的实现中,泛型类型被当做第二类类型处理,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦书,替换为它们的非泛型上界。
3、Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义;而且还要支持迁移兼容性,使得类库按照自己的步调变为泛型的,并且当某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。
15.7.3擦除的问题
1、擦除主要的正当理由是从非泛华代码到泛华代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。
2、擦除的代价是显著的。泛型不能用于显示地引用运行时类型的操作之中,例如转型instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了。
15.7.4边界处的动作
package com15;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Created by Panda on 2018/5/15.
*/
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind){this.kind=kind;}
T[] create(int size){
//在泛型中创建数组,使用Array.newInstance() 推荐
return (T[]) Array.newInstance(kind,size);
}
public static void main(String[] args) {
ArrayMaker<String> stringArrayMaker = new ArrayMaker<>(String.class);
String[] strings=stringArrayMaker.create(9);
System.out.println(Arrays.toString(strings));
}
}
1、擦除在方法或类内部移除了有关实际类型的信息,编译器仍旧可以确保在方法或类中使用的类型的内部一致性。
2、擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。对传递进来的值进行额外的编译期检查,并插入对传递出去的值的类型。
15.8 擦除的补偿
package com15;
/**
* Created by Panda on 2018/5/15.
*/
//对使用instanceof的尝试失败,因为类型信息已经被擦除了。如果引入类型标签,就可以转而使用动态的isInstance()
class Building{}
class House extends Building{}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> buildingClassTypeCapture = new ClassTypeCapture<>(Building.class);
System.out.println(buildingClassTypeCapture.f(new Building())); //true
System.out.println(buildingClassTypeCapture.f(new House())); //true
ClassTypeCapture<House> houseClassTypeCapture = new ClassTypeCapture<>(House.class);
System.out.println(houseClassTypeCapture.f(new Building())); //false
System.out.println(houseClassTypeCapture.f(new House())); //true
}
}
15.8.1 创建类型实例
package com15;
/**
* Created by Panda on 2018/5/15.
*/
//java无法对new T()进行实现,部分原因是擦除,另一部分原因是因为编译器不能验证T具有默认(无参)构造qi
//Java中解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签
//就可以使用newInstance()来创建这个类型的新对象。
class ClassAsFactory<T>{
T x;
public ClassAsFactory(Class<T> kind){
try {
x = kind.newInstance();
}catch (Exception e){
e.printStackTrace();
}
}
}
class Employee{}
public class Test3 {
public static void main(String[] args) {
ClassAsFactory<Employee> employeeClassAsFactory = new ClassAsFactory<>(Employee.class);
System.out.println("employee succeeded"); //运行正常
try{
ClassAsFactory<Integer> factory = new ClassAsFactory<>(Integer.class);
}catch (Exception e){
e.printStackTrace();
System.out.println("Integer failed"); //运行出错,因为Integer没有任何默认的构造器。
}
}
}
package com15;
/**
* Created by Panda on 2018/5/15.
*/
//建议使用工厂模式
interface Factory<T>{
T create();
}
class Foo2<T>{
private T x;
public <F extends Factory<T>> Foo2(F factory) {
x=factory.create();
}
}
class IntegerFactory implements Factory<Integer>{
@Override
public Integer create() {
return new Integer(0);
}
}
class Wideget{
public static class Fac implements Factory<Wideget>{
public Wideget create(){
return new Wideget();
}
}
}
public class Test4 {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Wideget>(new Wideget.Fac());
}
}
/**
* Created by Panda on 2018/5/15.
*/
//模板方法设计模式
abstract class GenericWithCreate<T>{
T element;
GenericWithCreate(){element=create();}
abstract T create();
}
class X{}
class Creator extends GenericWithCreate<X>{
@Override
X create() {
return new X();
}
void f(){
System.out.println(element.getClass().getSimpleName()); //X
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator creator = new Creator();
creator.f();
}
}
15.8.2
/**
* Created by Panda on 2018/5/15.
*/
//1、成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。
public class Test5<T> {
private T[] array;
public Test5 (int size){
array=(T[])new Object[size];
}
public void put(int index ,T item){
array[index]=item;
}
public T get(int index){return array[index];}
public T[] rep(){return array;}
public static void main(String[] args) {
Test5<Integer> test5 = new Test5<>(10);
// Integer[] integers = test5.rep(); //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer
Object[] objects = test5.rep(); //运行正常
}
}
2、因为有了擦除,数组的运行时类型就只能是Object[] 。如果立即将其转型为T[],那么在编译期该数组的实际类型就将丢失,而编译器可能会错过某些潜在的错误检查。正是因为这样,最好在集合内部使用Object[],然后当你使用数组元素时,添加一个对T的转型。
3、对于新代码,应该传递一个类型标记。
package com15;
import java.lang.reflect.Array;
/**
* Created by Panda on 2018/5/15.
*/
public class Test6<T> {
private T[] array;
public Test6(Class<T> type,int size){
array=(T[]) Array.newInstance(type,size);
}
public void put(int index ,T item){
array[index]=item;
}
public T get(int index){return array[index];}
public T[] rep(){return array;}
public static void main(String[] args) {
Test6<Integer> test6 = new Test6<>(Integer.class,10);
Integer[] integers = test6.rep(); //运行正常
}
}
15.9 边界
1、边界使得可以在用于泛型的参数类型上设置限制条件。
2、通配符被限制为单一边界
15.10 通配符
15.10.1 编译器多聪明
package com15;
/**
* Created by Panda on 2018/5/15.
*/
public class Holder<T> {
private T value;
public Holder(){}
public Holder(T val){value=val;}
public void set(T val){value=val;}
public T get(){return value;}
public boolean equals(Object object){return value.equals(object);}
public static void main(String[] args) {
Holder<Apple> appleHolder = new Holder<Apple>(new Apple());
Apple d = appleHolder.get();
appleHolder.set(d);
// Holder<Fruit> fruitHolder = appleHolder; //cannot upcast
Holder<?extends Fruit> holder = appleHolder; //ok
Fruit p = holder.get();
d=(Apple)holder.get();
try{
Orange orange = (Orange)holder.get(); //No waring
}catch (Exception e){
// holder.set(new Apple()); // not ok
// holder.set(new Fruit()); // not ok
System.out.println(holder.equals(d));
}
}
}
15.10.2 逆变
限定通配符总是包括自己
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符 上届
如果你想把对象写入一个数据结构里,使用 ? super 通配符 下届
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界