第十五章 泛型

    科技2024-08-20  25

    文章目录

    1.概念2.简单泛型一个元组类库 3.泛型接口4.泛型方法一个set使用工具 5.匿名匿名内部类6.构建复杂模型7.擦除的神秘之处迁移兼容性边界处的动作 8.擦除的补偿泛型数组 9.边界10.通配符通配符无界通配符 11.泛型中的问题任何基本类型都不能作为类型参数。由于擦除的原因,泛型不能重载 12.自限定的类型自限定参数协变 13.动态类型安全14.异常catch语句不能捕获泛型类型的异常,但可以作为方法签名上throws 异常部分 15.混型使用装饰器模型动态代理可以实现近似的混型 16.潜在类型机制 17.对缺乏潜在类型机制的补偿反射18.将函数对象用作策略

    1.概念
    泛型实现了“参数化类型”的概念
    2.简单泛型

    泛型的主要目的:用来指定容器要持有什么类型对象,而且由编译器来保证类型的正确性

    一个元组类库
    (1) 元组(又称:数据传递对象或信使):它是将一组对象直接打包存储与其中的一个单一对象。这个容器对象允许读取其中元素,但不允许存放新对象(2) 元组隐含的保持了其中元素的次序(3) 元组中的对象可以是任意类型(4) 元组利用继承机制增加长度(5) 声明为final元素被构造出来便不能赋其他值,保护了public元素
    3.泛型接口
    生成器(generator):负责创建对象的类Java泛型的局限性:基本类型无法作为类型参数,但JavaSE5可以自动打包和拆包
    4.泛型方法
    是否拥有泛型方法,与其所在的类是否为泛型没有关系如果泛型方法可以取代将整个方法泛型化,那么只是用泛型方法泛型方法的格式:泛型参数列表置于返回值之前 public <T> void f(T x){} 编译器的“类型参数推断”:使用泛型方法,无需指明参数类型杠杆利用类型参数推断 (1) 类型推断只对赋值操作有效,其他时候不起作用(2) 调用泛型方法后,其返回值被赋给一个Object类型变量(3) 显示的类型说明 i.在点操作符与方法名之间插入尖括号,把类型置于括号内ii.f(New.<Person,List>map())
    一个set使用工具
    Set<T> res = new HashSet<T>(a) (1) res.addAll(b):合并a、b两个set (2) res.retainAll(b):取两个set交集 (3) res.removeAll(b):从a中删除b
    5.匿名匿名内部类
    泛型可以应用于内部类以及匿名内部类
    6.构建复杂模型
    package Chapter15.Test19; import java.util.ArrayList; public class CargoHold extends ArrayList<Container> { public CargoHold(int nContainers, int nProducts){ for(int i = 0; i < nContainers; i++) add(new Container(nProducts)); } } --------------------------------- package Chapter15.Test19; import java.util.ArrayList; //货船 public class CargoShip extends ArrayList<CargoHold> { private ArrayList<Crane> cranes = new ArrayList<Crane>(); private CommandSection cmdSection = new CommandSection(); public CargoShip(int nCargoHolds, int nContainers, int nProducts) {//货舱,集装箱,物品 for (int i = 0; i < nCargoHolds; i++) add(new CargoHold(nContainers, nProducts)); } public String toString() { StringBuilder stringBuilder = new StringBuilder(); for (CargoHold cargoHold : this) { for (Container container : cargoHold) { for (Product product : container) { stringBuilder.append(product); stringBuilder.append("\n"); } } } return stringBuilder.toString(); } } ------------------------------ package Chapter15.Test19; //指挥部 public class CommandSection { } --------------------------------- package Chapter15.Test19; import Chapter15.Test18.Generators; import java.util.ArrayList; //集装箱 public class Container extends ArrayList<Product> { public Container(int nProducts) { Generators.fill(this, Product.generator, nProducts); } } ----------------------------------------- package Chapter15.Test19; //起重机 public class Crane { } ----------------------------------------- package Chapter15.Test19; import net.mindview.util.Generator; import java.util.Random; class Product { private final int id; private String description; private double price; public Product(int IDnumber, String descr, double price){ id = IDnumber; description = descr; this.price = price; System.out.println(toString()); } public String toString() { return id + ": " + description + ", price: $" + price; } public void priceChange(double change) { price += change; } public static Generator<Product> generator = new Generator<Product>() { private Random rand = new Random(47); public Product next() { return new Product(rand.nextInt(1000), "Test", Math.round(rand.nextDouble() * 1000.0) + 0.99); } }; } ------------------------------- package Chapter15.Test19; public class Test19 { public static void main(String[] args) { System.out.println(new CargoShip(14, 5, 10));//14个货舱,5个集装箱,10个货物 } }
    7.擦除的神秘之处

    在泛型代码内部,无法获得任何有关泛型参数类型的信息

    使用泛型时,任何具体的类型信息都会被擦除,你唯一知道的就是你在使用的对象

    Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2);true

    可以给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型,这里使用extends关键字,泛型类型参数将擦除到他的第一个边界(可能会有很多边界)

    public class Store <T extends Coffee & Generator> {}

    (1)虽然T 被擦除为Coffee,但是同时可以使用Coffee,Generator的方法。

    (2)只是如:void set(T t)的方法, 此时编译器提示参数类型为Coffee,只实现Generator的参数传入,编译器会报错提示参数类型不正确。

    迁移兼容性
    (1) 为了使 泛型和非泛型代码共存(2) 在基于擦除实现中,泛型类型被当作第二类类型处理(3) 泛型只有在静态类期间才出现(编译器),在此之后,程序中所有的泛型都被擦除,替换为它们的非泛型上界(extends 后第一边界,默认是Object)
    边界处的动作
    (1) “边界就是发生动作的地方”(2) 对 传入(set) 的值进行二外的编译期检查,并插入对 传出(get) 的值进行转型
    8.擦除的补偿

    因为擦除带来的问题,任何运行时需要知道确切类型的操作都将无法工作(instanceof、new、==)

    引入类型标签补偿:显示的传递你的类型Class对象,以便可以在类型表达式中使用它【Class kind】定义一个Class对象

    对于kind.newInstance()创建对象实例的方式,如果没有默认的构造函数,运行会报错并且编译时无法检查,如Integer。【因此推荐使用工厂模式】

    如果说Class.newInstance是一种隐式的工厂,那么FactoryMain.newInstanse就是一种显示的工厂。

    创建对象实例还可以使用【模板方法设计模式】

    泛型数组
    (1) 任何想创建泛型数组的地方都是用ArrayList(2) 成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型
    9.边界
    边界使得你可以在用于泛型的参数类型上设置限制条件可以按照自己的边界类型来调用方法当发生擦除时(例:),会擦除到第一个边界,此时不仅可以使用Object方法,还可以使用边界类ArrayList的方法可以有多个边界,类必须放第一个,后面为多接口。
    10.通配符
    可以将导出类的数组赋予给基类型的数组引用 Fruit[] fruits = new Apple[10]; fruits[1] = new Apple(); fruits[0] = new Fruit(); //可以编译通过,运行报错ArrayStoreException

    (1) 因为数组的类型实际上是Apple,所以元素可以放入Apple及其子类。放入Fruit编译可以通过,运行会报错ArrayStoreException。 (2) 泛型正是为了:将类型性错误检测移到编译期。

    使用泛型容器代替 List<Fruit> fruit = new ArrayList<Apple>(); //编译错误

    编译错误的原因是Fruit的list 和 Apple 的list是两种容器的类型,而不是仅仅是不同的持有类型。泛型没有内建的协变类型,他们不能向上转型。

    协变:子类能向父类转换

    逆变:父类能向子类转换

    编译器会直接拒绝对参数列表中设计通配符的方法(例如:add())的调用

    编译器无法验证“任何事物”的类型安全(例如:<? extends Fruit>)

    通配符

    (1)【协变】通配符

    i.协变通配符

    public static void main(String[] args) { List<? extends Fruit> fruits = new ArrayList<>(); fruits.add(new Apple()); //Error 编译错误 fruits.add(new Fruit()); //Error 编译错误 fruits.add(null); Fruit fruit = fruits.get(0); //安全的 System.out.println(fruit); }

    ii.向上转型的关系,可以使用导出类通配符

    <? extends Fruit>,其意义是Fruit及其子类中的任意一种,具体哪一种不清楚,因此Fruit一定可以作为get方法的返回值,但不能set方法的参数(add同理)

    iii.泛型只是编译器检查,可以有各种方法绕过。比如利用反射向list中添加元素,或者向下面这样: List<? extends Fruit> fruits = Arrays.asList(new Apple(), new Fruit());

    (2)【逆变】通配符

    i.协变通配符

    public static void main(String[] args) { List<? super Fruit> fruits = Arrays.asList(); fruits.add(new Apple()); //安全 fruits.add(new Fruit()); //安全 fruits.add(null); Fruit fruit = fruits.get(0);//Error 编译错误 System.out.println(fruit); }

    ii.建立关系,还可以使用超类型通配符 <? super Fruit> ,其意义是Fruit及其父类中的任意一种,具体哪一种不清楚,因此Fruit一定可以作为set方法的参数(add同理),但却不能作为get方法的返回值。

    iii.不能对泛型参数给出一个超类型边界,即不能声明

    无界通配符

    (1) 无界通配符

    public static void main(String[] args) { List<?> fruits = Arrays.asList(); fruits.add(new Apple()); //Error 编译错误 fruits.add(new Fruit()); //Error 编译错误 fruits.add(null); Fruit fruit = fruits.get(0);//Error 编译错误 fruits.add(new Object()); //Error 编译错误 Object obj = fruits.get(0); //安全 System.out.println(obj); }

    (2) List<?> 无界通配符表示“任意事物”中的一种,哪一种还是不知道,因此没有类型能作为set方法的参数(add同理),除了Object,其他类型也都不能作为get方法的返回值。由此可知 List(或List) 和 List<?> 只是长得很像。

    (3) 确切类型替换通配符的好处是:可以用泛型参数来做更多事

    (4) 使用通配符使得你必须接受范围更宽的参数化类型作为参数

    11.泛型中的问题
    任何基本类型都不能作为类型参数。
    (1) ArrayList ×(2) ArrayList √ 一个类不能实现同以泛型接口的两种变体 interface Payable<T> {} class Employee implements Payable<Employee> {} class Hourly extends Employee implements Payable<Hourly> {} 转型和警告 使用带有泛型类型的参数的转型或instanceof不会有效
    由于擦除的原因,泛型不能重载
    错误: public void f(List<T> list){} public void f(List<W> list){} 正确: public void f1(List<T> list){} public void f2(List<W> list){} 基类劫持了接口 Base类确定了接口I1泛型为String,那么C1 继承了Base ,再次实现接口I1,泛型不能是Integer,只能是String(被劫持)
    12.自限定的类型
    自限定
    (1) public class SelfBounded<T extends SelfBounded<T>> {} 擦除,去掉 extends 和 T public class SelfBounded<SelfBounded>{} (2) 的意思是:自己限定自己,SelfBounded的泛型参数是SelfBounded自己(3) 的意思是:泛型参数为SelfBound及其子类(4) 自限定参数的意义在于:它可以保证类型参数必须与正在被定义的类相同
    参数协变
    (1) 自限定类型的价值在于他们可以产生协变参数类型(2) 如果 不使用 自限定,将重载参数类型(3) 如果 使用 自限定,只能获得某一个方法的一个版本(覆盖)
    13.动态类型安全
    静态方法:checkedCollection()、checkedList()、checkedMap()、chenckedSet()、checkedSortedMap()、chenckedSortedSet()这些方法每一个都会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受
    14.异常
    catch语句不能捕获泛型类型的异常,但可以作为方法签名上throws 异常部分
    interface Test2<T extends Exception> { void test() throws T; }
    15.混型

    概念:混合多个类的能力,以生产一个可以表示混型中所有类型的类

    价值:他们可以将特性和行为一致地应用于多个类上

    java语法上不支持多继承,那么最简单实现方式,就是单继承多接口 + 代理了(内部类实现多继承也是一种代理)

    使用装饰器模型
    (1) 装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口(2) 无论对象是否被修饰,你都拥有一个可以向对象发送的公共消息集(3) 装饰器是通过使用组合和形式化结构来实现的(4) 对于装饰器来说,其明显的缺陷是它只能有效的工作于装饰中的一层,而混型显得更自然些
    动态代理可以实现近似的混型
    package Chapter15.Test39.DynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author:YiMing * @version:1.0 */ //说话的能力 interface ISay { void say(); } class SayImpl implements ISay { @Override public void say() { System.out.println("hello"); } } //报时的能力 interface IDate { void now(); } class DateImpl implements IDate { @Override public void now() { System.out.println(new Date()); } } //唱歌的能力 interface ISing { void sing(); } class SingImpl implements ISing { @Override public void sing() { System.out.println("lalalalalala!"); } } class Mix implements InvocationHandler { //动态代理 private Map<String, Object> delegates = new HashMap<>(); public Mix(Object... args) { for (Object obj : args) { Class<?> clazz = obj.getClass(); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (!delegates.containsKey(method.getName())) { delegates.put(method.getName(), obj); } } } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object delegate = delegates.get(method.getName()); return method.invoke(delegate, args); } public static Object newInstance(Class[] clazzes, Object[] objects) { return java.lang.reflect.Proxy.newProxyInstance(Mix.class.getClassLoader(), clazzes, new Mix(objects)); } } public class Proxy { public static void main(String[] args) { //混合三种能力 Object mixObj = Mix.newInstance( new Class[]{ISing.class, IDate.class, ISay.class}, new Object[]{new SingImpl(), new DateImpl(), new SayImpl()}); ISing singObj = (ISing) mixObj; singObj.sing(); ISay sayObj = (ISay) mixObj; sayObj.say(); IDate dateObj = (IDate) mixObj; dateObj.now(); } }
    16.潜在类型机制
    只要求实现某个方法子集,而不是特定的类或者接口,使得你可以横跨类继承结构,调用不属于公共接口的方法简单来说:我不关心你是什么类型,只要你可以say() 和 sing() 就可以了Python(动态类型语言)和C++(静态类型语言)由于不具体要求具体类型,因此代码可以更泛化Java语法不支持,潜在类型机制

    17.对缺乏潜在类型机制的补偿

    反射

    package Chapter15; // Using Reflection to produce latent typing. import java.lang.reflect.*; import static net.mindview.util.Print.*; // Does not implement Performs: class Mime { public void walkAgainstTheWind() {} public void sit() { print("Pretending to sit"); } public void pushInvisibleWalls() {} public String toString() { return "Mime"; } } // Does not implement Performs: class SmartDog { public void speak() { print("Woof!"); } public void sit() { print("Sitting"); } public void reproduce() {} } class CommunicateReflectively { public static void perform(Object speaker) { Class<?> spkr = speaker.getClass(); try { try { Method speak = spkr.getMethod("speak"); speak.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot speak"); } try { Method sit = spkr.getMethod("sit"); sit.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot sit"); } } catch(Exception e) { throw new RuntimeException(speaker.toString(), e); } } } public class LatentReflection { public static void main(String[] args) { CommunicateReflectively.perform(new SmartDog()); CommunicateReflectively.perform(new Mime()); } } /* Output: Woof! Sitting Mime cannot speak Pretending to sit *///:~
    18.将函数对象用作策略
    泛型在策略模式中的使用 package Chapter15; /** * @author:YiMing * @version:1.0 */ interface Strategy<T, R> { R operation(T obj1, T obj2); } //整数相加操作 class IntegerAddOperation implements Strategy<Integer, Integer> { @Override public Integer operation(Integer obj1, Integer obj2) { return obj1 + obj2; } } //字符串比较操作 class StringCompareOperation implements Strategy<String, String> { @Override public String operation(String obj1, String obj2) { Integer result = obj1.compareTo(obj2); if ( result == 0) { return obj1 + " == " + obj2; } else if (result > 0) { return obj1 + " > " + obj2; } else { return obj1 + " < " + obj2; } } } public class Context { public static void execute( Object obj1, Object obj2,Strategy strategy) { Object result = strategy.operation(obj1, obj2); System.out.println(result); } public static void main(String[] args) { execute(1, 2, new IntegerAddOperation()); execute("today", "tomorrow", new StringCompareOperation()); } }
    Processed: 0.008, SQL: 8