effective java 第28条 利用有限制通配符来提升API的灵活性

有这样一个类:

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack(){
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public static <E> Set<E> union(Set<E> s1,Set s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

    public void push(E e){
        ensureCapacity();
        elements[size++] = e;
    }
    public boolean isEmpty(){
        return size == 0;
    }

    public E pop(){
        if(size == 0)
            throw new EmptyStackException();
        E result = elements[size--];
        elements[size] = null;
        return result;
    }

    /**
     * 检验是否到达顶点,到达的话,扩充数组
     */
    private void ensureCapacity(){
        if(elements.length == size){
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }

}

现在需要增加一个方法:

    public void pushAll(Iterable<E> src){
        src.forEach(e->push(e));
    }

这个方法在编译的时候正确无误,但是也不能总是正确,比如:

    public static void main(String[] args) {
        Stack<Number> s = new Stack<>();
        List<Integer> list = new ArrayList<>();
        s.pushAll(list);
    }

在这种情况下,由于泛型的不可变性,导致不能添加,编译无法通过,但是从理解层面上来说,这应该是被允许的。number是可以接受integer类型的


为了增加方法的灵活性,可以这样编写:

    public void pushAll(Iterable<? extends E> src){
        src.forEach(e->push(e));
    }

与pushAll相对应的,我们在新增一个popAll 方法:

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

和上面方法相似,这个方法初一看并没有什么不妥。但是并不总是正确:

比如我想传递一个List<Object>进去接收,就像这样:

    public static void main(String[] args) {
        Stack<Number> s = new Stack<>();
        List<Object> list = new ArrayList<>();
        s.popAll(list);
    }

同样编译无法通过,Collection<Number> c = new ArrayList<Object>()这是错误的:

但是从实际角度出发,这应该是被允许的,List<Object> 列表是可以添加Number类型的,所以这个方法依然有漏洞:

这个时候可以修改如下:

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

这样的话,编译可以通过,而且类型也是安全的:

结论很明显。为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用统配符类型。如果某个输入参数既是生产者也是消费者,那么统配符就不在适用了。

PECS: producer-extends,cunsumer-super;

个人理解:如果参数表示这个类的生产者,就应该用extends,如果是这个类的消费者,就应该用super.

对 union进行修改:

    public static <E> Set<E> union(Set<E> s1,Set s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

由于s1,s2,对于整个类来说是属于生产者,所以应该用extends:

    public static <E> Set<E> union(Set<? extends E > s1,Set<? extends E> s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

注意返回类型依然是set<E>.不要用通配符类型作为返回类型。除了为用户提供额外额灵活性外,它也会要求用户必须使用通配符类型。

统配符类型对于用户来说应该是无形的,如果用户必须考虑通配符类型,类的API或许就会出错。

有这样一个方法:

   public static <T extends Comparable<T>> T max(List<T> list){
       Iterator<T> i = list.iterator();
       T result = i.next();
       while(i.hasNext()){
           T t = i.next();
           if(t.compareTo(result) > 0)
               result = t;
       }
       return result;
   }

根据原则该如何修改呢?

   public static <T extends Comparable<? super T>> T max(List<? extends T> list){
       Iterator<? extends T> i = list.iterator();
       T result = i.next();
       while(i.hasNext()){
           T t = i.next();
           if(t.compareTo(result) > 0)
               result = t;
       }
       return result;
   }

我们来分析一下:

  1. list中的泛型,对于类来说,无疑是生产者,生产出最大值,所以应该是extends
  2. comparable中的泛型,对于整个类来说,是用来消费产生顺序关系的,所以应该用super

针对于一下方法:

   public static <E> void swap(List<E> list,int i,int j){};
   public static void swap(List<?> list,int i,int j);

这两种方法,那种方法更好呢?第二种会更好一些,因为它更加简单,在整个静态方法中,泛型其实只出现了一次,,这种情况下,是可以用通配符取代它的。但是第二个方法有一个问题:

   public static void swap(List<?> list,int i,int j){
       list.set(i,list.set(j,list.get(i)));
   };

无法编译,这里可以使用一个辅助方法来捕捉通配符类型:

   public static void swap(List<?> list,int i,int j){
       swapHelper(list,i,j);
   };
   private static <E> void swapHelper(List<E> list,int i,int j){
        list.set(i,list.set(j,list.get(i)));
   }
这样既能提供简洁的api,也能达到捕获通配符的目的:












  • 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、付费专栏及课程。

余额充值