Effective Java笔记(31)利用有限制通配符来提升 API 的灵活性

        参数化类型是不变的( invariant ) 。 换句话说,对于任何两个截然不同的类型 Typel 和 Type2 而言, List<Type1 >既不是 List<Type 2 > 的子类型,也不是它的超类型 。虽然 L ist<String>不是 List<Object>的子类型,这与直觉相悖,但是实际上很有意义 。你可以将任何对象放进一个List<Object>中,却只能将字符串放进 List<String>中 。由于 List<String>不能像 List<O句ect> 能做任何事情,它不是一个子类型 。

        有时候,我们需要的灵活性要比不变类型所能提供的更多 。比如第 29 条中的堆楼 。 提醒一下,下面就是它的公共 API:

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

        假设我们想要增加一个方法,让它按顺序将一系列的元素全部放到堆枝中 。 第一次尝试如下:

public void pushAll(Iterable<E> src) {
    for(E e : src)
        push(e);
}

        这个方法编译时正确无误,但是并非尽如人意 。 如果 Iterable 的 src 元素类型与堆栈的完全匹配,就没有问题 。 但是假如有一个 Stack<Number>,并且调用了 push (intVal),这里的工ntVal 就是 Integer 类型 。 这是可以的,因为 Integer 是 Number 的一个子类型 。 因此从逻辑上来说,下面这个方法应该可行 :

Stack <Number> numberStack = new Stack<>() ;
Iterable<Integer> integers = ...;
numberStack. pushAll(integers);

但是,如果尝试这么做,就会得到下面的错误消息,因为参数化类型是不可变的:

        幸运的是,有一种解决办法 。Java 提供了一种特殊的参数化类型,称作有限制的通配符类型(bounded wildcard type ),它可以处理类似的情况 。pushAll 的输入参数类型不应该为“ E 的 Iterable 接口”,而应该为“ E 的某个子类型的 Iterable 接口”通配符类型Iterable<?extends E >正是这个意思 。 (使用关键字 ex ten也有些误导 :回忆一下第29 条中的说法,确定了子类型( subtype )后,每个类型便都是自身的子类型,即使它没有将自身扩展 。)我们修改一下 pushAll 来使用这个类型:

public void pushAll(Iterable<? extends E> src) {
    for(E e : src)
        push(e);
}

        修改之后,不仅 Stack 可以正确无误地编译,没有通过初始的 pushAll 声明进行编译的客户端代码也一样可以 。 因为 Stack 及其客户端正确无误地进行了编译,你就知道一切都是类型安全的了 。

        现在假设想要编写一个 pushAll 方法,使之与 popAll 方法相呼应 。popAll 方法从堆校中弹出每个元素,并将这些元素添加到指定的集合中 。 初次尝试编写的 popAll 方法可能像下面这样 :

public void popAll(Col1ection<E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

        此外,如果目标集合的元素类型与堆栈的完全匹配,这段代码编译时还是会正确无误,并且运行良好 。 但是,也并不意味着尽如人意 。 假设你有一个 Stack<Number >和 Object 类型的变量 。 如果从堆校中弹出 一个元素,并将它保存在该变量中,它的编译和运行都不会出错,那你为何不能也这么做呢?

Stack<Number> numberStack = new Stack<Number>() ;
Collection<Object> objects = ...;
numberStack.popAll(objects) ;

        如果试着用上述 的 popAll 版本编译这段客户端代码,就会得到一个非常类似于第一次用 pushAll 时所得到的错误:Collection<Object >不是 Collection<Number>的子类型 。 这一次通配符类型同样提供了一种解决办法 。popAll 的输入参数类型不应该为“ E 的集合”,而应该为“ E 的某种超类的集合”(这里的超类是确定的,因此 E 是它自身的一个超类型)。 仍有一个通配符类型正符合此意:Collection<? super E > 。 让我们修改 popAll 来使用它:

public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop();
}

        做了 这个变动之后,Stack 和客户端代码就都可以正确无误地编译了 。

        结论很明显:为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型 。 如果某个输入参数既是生产者,又是消费者,那么通配符类型对你就没有什么好处了:因为你需要的是严格的类型匹配,这是不用任何通配符而得到的 。

        下面的助记符便于让你记住要使用哪种通配符类型 :

        PECS 表示 producer-extends,consumer-super

        换句话说,如果参数化类型表示一个生产者 T ,就使用<? extends T >;如果它表示一个消 费者 T ,就使用 <? super T > 。 在我们的 Stack 示例中,pushAll 的 src 参数产生 E 实 例供 Stack 使用 ,因 此 src 相 应的类型为 Iterable<? extends E> ; popAll的 dst 参数通过 Stack 消费 E 实例,因此 dst 相应的类型为 Collection<? s uper E > 。PECS 这个助记符突 出了使用通配符类型的基本原则 。Naftalin 和 Wadler 称之为 Get αnd Put Principle。

        如果使用得当,通配符类型对于类的用户来说几乎是无形的 。 它们使方法能够接受它们应该接受的参数,并拒绝那些应该拒绝的参数 。 如果类的 用 户必须考虑通配符类型,类的API 或许就会出错

        一般来说, 如果类型参数只在方法声明中出现一次,就可以用通配符取代它 。 如果是无限制的类型参数,就用无限制的通配符取代它;如果是有限制的类型参数,就用有限制的通配符取代它。

        总而言之,在 API 中使用通配符类型虽然比较需要技巧,但是会使 API 变得灵活得多 。 如果编写 的是将被广泛使用的类库, 则一定要适当地利用通配符类型 。 记住基本的原则:producer-extends,consumer-super(PECS ) 。 还要记住所有的 comparable 和comparator 都是消费者 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 利用带有通配符的like实现模糊查询的特点是可以根据需要匹配任意字符,从而实现更加灵活的查询。通配符可以用来代替一个或多个字符,例如%代表任意字符,_代表一个字符。这样,我们就可以根据需要进行模糊匹配,从而找到符合条件的数据。同时,使用like查询还可以进行大小写不敏感的匹配,从而提高查询的准确性和效率。 ### 回答2: like语句是一种非常常见的SQL语句,用于模糊查询。它可以根据某个字符或字符串的部分匹配度来查询数据,其语法格式为:“where column_name like pattern”,其中pattern可以是一个带有通配符的字符串。 通配符的作用是,在字符串中表示任意一个或多个字符的位置。有两种通配符常用于like语句中,它们分别是“%”和“_”。 “%”表示匹配0个或多个任意字符。比如说,如果要查询用户中所有姓张的人,可以使用“where name like '张%'”,这样就能匹配到所有以张开头的姓氏。 “_”表示匹配1个任意字符。比如说,如果要查询用户名中包含一个字的用户,可以使用“where name like '_字%'”,这样就能匹配到所有用户名中包含一个字的用户。 需要注意的是,like语句虽然可以实现模糊查询,但是其查询效率比较低,因为它需要去匹配每一条数据,所以当数据量很大时,需要使用其他方式进行查询优化,比如使用索引等。 总之,like语句能够实现一些简单的模糊查询,通过使用通配符,可以灵活地匹配不同的字符或字符串。但在使用时需要注意效率问题,尽量避免全表扫描,以提高查询效率。 ### 回答3: 在使用SQL语句进行查询时,我们经常会遇到需要进行模糊查询的情况。在这种情况下,我们可以使用带有通配符的like语句来实现模糊查询。 通配符是指能够匹配多个字符的特殊符号,如“%”、“_”等。在使用like语句时,我们可以将通配符加入到查询条件中,使其能够匹配到不同的文本。具体来说,like语句有两个通配符: 1. %:表示零个或多个任意字符,例如:’%a‘表示以‘a’结尾的任意字符串,‘a%’表示以‘a’开头的任意字符串,‘%a%’表示任意包含‘a’的字符串。 2. _:表示一个任意字符,例如:’_n%‘表示以任意字符加上‘n’开头的任意字符串,‘%_n’表示以任意字符加上‘n’结尾的任意字符串,‘%_n_%‘表示任意包含‘n’的任意字符串。 利用这些通配符,我们可以轻松实现模糊查询,例如: SELECT * FROM 表名 WHERE 字段名 LIKE ‘%abc%’; 以上语句可以查询到所有包含‘abc’的记录。同样的,我们也可以使用‘_’通配符来实现类似的功能。 需要注意的是,like查询是比较消耗资源的一种操作,特别是当需要在大量数据中进行模糊查询时,查询时间会比较长。因此,在实际开发中,需要根据具体情况选择合适的查询方法,并尽可能减少like查询的使用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值