精华内容
下载资源
问答
  • 016_泛型常见通配符

    2021-05-11 22:40:12
    1. 泛型常见通配符 1.1. T Type类型, java类。 1.2. K Key类型, 键值对中的键。 1.3. V Value类型, 键值对中的值。 1.4. E Element元素类型, 集合中使用。 1.5. N Number数值类型。 1.6. ? 表示不确定的java...

    1. 泛型常见通配符

    1.1. T Type类型, java类。

    1.2. K Key类型, 键值对中的键。

    1.3. V Value类型, 键值对中的值。

    1.4. E Element元素类型, 集合中使用。

    1.5. N Number数值类型。

    1.6. ? 表示不确定的java类型, 无限制通配符类型。

    2. 界定通配符的上边界

    2.1. 类型1指定一个数据类型, 那么类型2就只能是类型1或者是类型1的子类:

    Vector<? extends 类型1> x = new Vector<类型2>();
    
    Vector<? extends Number> x = new Vector<Integer>(); // 这是正确的
    Vector<? extends Number> x = new Vector<String>(); // 这是错误的

    3. 界定通配符的下边界

    3.1. 类型1指定一个数据类型, 那么类型2就只能是类型1或者是类型1的父类:

    Vector<? super 类型1> x = new Vector<类型2>();
    
    Vector<? super Integer> x = new Vector<Number>(); // 这是正确的
    Vector<? super Integer> x = new Vector<Byte>(); // 这是错误的

     

    展开全文
  • 泛型常用的通配符有哪些?1 Java泛型1.1 泛型的使用-泛型类1.2 泛型的使用-泛型方法1.3 泛型的使用-泛型接口1.4 泛型定义和使用的注意事项2 泛型擦除2.1 通过两个例子证明Java类型的类型擦除2.2 类型擦除后保留的...

    1 Java泛型

    Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

    1.1 泛型的使用-泛型类

    直接上代码:

    public class Pair <U,V>{
        U first;
        V second;
    
        public Pair(U first, V second) {
            this.first = first;
            this.second = second;
        }
    
        public U getFirst() {
            return first;
        }
    
        public V getSecond() {
            return second;
        }
    }
    

    Pair就是一个泛型类,与普通的类的区别体现在:

    • 类名后面多了 一下
    • first和second的类型都是T
      T表示类型参数,泛型就是类型化参数,处理的数据类型不是固定的,而是可以作为参数传入

    如何使用上面的泛型Pair类:

    Pair<String, Integer> pair = new Pair<String, Integer>("ha", 1);
    

    1.2 泛型的使用-泛型方法

    依然直接上代码:

    public static <T> int indexOf(T[] arr, T elm) {
            for(int i=0; i<arr.size; i++) {
                if(arr.get(i).equals(elm)) {
                    return i;
                }
            }
            return -1;
        }
    }
    

    这个方法就是一个泛型方法,类型参数为T,放在返回值前面,有如下两种调用方式:

    indexOf(new Integer[]{1, 3, 5}, 10)
    
    indexOf(new String[]{"容器", "泛型"}, "类")
    

    1.3 泛型的使用-泛型接口

    接口也可以是泛型的,比如Comparable和Comparator接口,代码如下:

    public interface Comparable<T> {
    	public int compareTo(T o);
    }
    
    public interface Comparator<T> {
    	int compare(T o1, T o2);
    	boolean equals(Object obj);
    }
    

    1.4 泛型定义和使用的注意事项

    使用泛型类、方法和接口时,一些要注意的地方:

    • 基本类型不能用于实例化类型参数
    • 运行时类型信息不适用与泛型
    • 类型擦除可能会引发一些冲突

    在定义泛型类、方法和接口时也有一些需要注意的地方,比如:

    • 不能通过类型参数创建对象
    • 泛型类类型参数不能用于静态变量和方法
    • 了解多个类型限定的语法

    2 泛型擦除

    以下转载于 吴庆龙的技术轮子https://www.cnblogs.com/wuqinglong/p/9456193.html

    大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

    如在代码中定义List<Object>List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。

    2.1 通过两个例子证明Java类型的类型擦除

    例1.原始类型相等:

    public class Test {
    
        public static void main(String[] args) {
    
            ArrayList<String> list1 = new ArrayList<String>();
            list1.add("abc");
    
            ArrayList<Integer> list2 = new ArrayList<Integer>();
            list2.add(123);
    
            System.out.println(list1.getClass() == list2.getClass());
        }
    
    }
    

    在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型的,只能存储字符串;一个是ArrayList<Integer>泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型StringInteger都被擦除掉了,只剩下原始类型。

    例2.通过反射添加其它类型元素:

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            ArrayList<Integer> list = new ArrayList<Integer>();
    
            list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
    
            list.getClass().getMethod("add", Object.class).invoke(list, "asd");
    
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
        }
    
    }
    

    在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。

    2.2 类型擦除后保留的原始类型

    在上面,两次提到了原始类型,什么是原始类型?

    原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

    例3.原始类型Object:

    class Pair<T> {  
        private T value;  
        public T getValue() {  
            return value;  
        }  
        public void setValue(T  value) {  
            this.value = value;  
        }  
    }  
    

    Pair的原始类型为:

    class Pair {  
        private Object value;  
        public Object getValue() {  
            return value;  
        }  
        public void setValue(Object  value) {  
            this.value = value;  
        }  
    }
    

    因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object

    从上面的例2中,我们也可以明白ArrayList<Integer>被擦除类型后,原始类型也变为Object,所以通过反射我们就可以存储字符串了。

    如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。

    比如: Pair这样声明的话

    public class Pair<T extends Comparable> {}
    

    那么原始类型就是Comparable

    要区分原始类型和泛型变量的类型。在调用泛型方法时,可以指定泛型,也可以不指定泛型。

    • 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
    • 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
    public class Test {  
        public static void main(String[] args) {  
    
            /**不指定泛型的时候*/  
            int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
            Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
            Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object  
    
            /**指定泛型的时候*/  
            int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类  
            int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float  
            Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
        }  
    
        //这是一个简单的泛型方法  
        public static <T> T add(T x,T y){  
            return y;  
        }  
    }
    

    其实在泛型类中,不指定泛型的时候,也差不多,只不过这个时候的泛型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList可以存储任意的对象。

    例4.Object泛型:

    public static void main(String[] args) {  
        ArrayList list = new ArrayList();  
        list.add(1);  
        list.add("121");  
        list.add(new Date());  
    }  
    

    2.3 类型擦除引起的问题及解决方法

    因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀问题,但是也引起来许多新问题,所以,SUN对这些问题做出了种种限制,避免我们发生各种错误。

    2.3.1 先检查,再编译以及编译的对象和引用传递问题

    Question:既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

    Answer: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

    例如:

    public static  void main(String[] args) {  
    
        ArrayList<String> list = new ArrayList<String>();  
        list.add("123");  
        list.add(123);//编译错误  
    }
    

    在上面的程序中,使用add方法添加一个整型,在IDE中,直接会报错,说明这就是在编译之前的检查,因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该允许任意引用类型添加的。可实际上却不是这样的,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。

    那么,这个类型检查是针对谁的呢?我们先看看参数化类型和原始类型的兼容。以 ArrayList举例子,以前的写法:

    ArrayList list = new ArrayList();  
    

    现在的写法:

    ArrayList<String> list = new ArrayList<String>();
    

    如果是与以前的代码兼容,各种引用传值之间,必然会出现如下的情况:

    ArrayList<String> list1 = new ArrayList(); //第一种 情况
    ArrayList list2 = new ArrayList<String>(); //第二种 情况
    

    这样是没有错误的,不过会有个编译时警告。不过在第一种情况,可以实现与完全使用泛型参数一样的效果,第二种则没有效果。因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

    举例子:

    public class Test {  
    
        public static void main(String[] args) {  
    
            ArrayList<String> list1 = new ArrayList();  
            list1.add("1"); //编译通过  
            list1.add(1); //编译错误  
            String str1 = list1.get(0); //返回类型就是String  
    
            ArrayList list2 = new ArrayList<String>();  
            list2.add("1"); //编译通过  
            list2.add(1); //编译通过  
            Object object = list2.get(0); //返回类型就是Object  
    
            new ArrayList<String>().add("11"); //编译通过  
            new ArrayList<String>().add(22); //编译错误  
    
            String str2 = new ArrayList<String>().get(0); //返回类型就是String  
        }  
    
    }  
    

    通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

    泛型中参数话类型为什么不考虑继承关系?

    在Java中,像下面形式的引用传递是不允许的:

    ArrayList<String> list1 = new ArrayList<Object>(); //编译错误  
    ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
    

    我们先看第一种情况,将第一种情况拓展成下面的形式:

    ArrayList<Object> list1 = new ArrayList<Object>();  
    list1.add(new Object());  
    list1.add(new Object());  
    ArrayList<String> list2 = list1; //编译错误
    

    实际上,在第4行代码的时候,就会有编译错误。那么,我们先假设它编译没错。那么当我们使用list2引用用get()方法取值的时候,返回的都是String类型的对象(上面提到了,类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)。

    再看第二种情况,将第二种情况拓展成下面的形式:

    ArrayList<String> list1 = new ArrayList<String>();  
    list1.add(new String());  
    list1.add(new String());
    
    ArrayList<Object> list2 = list1; //编译错误
    

    没错,这样的情况比第一种情况好的多,最起码,在我们用list2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。再说,你如果又用list2往里面add()新的对象,那么到时候取得时候,我怎么知道我取出来的到底是String类型的,还是Object类型的呢?

    所以,要格外注意,泛型中的引用传递的问题。

    2.3.2 自动类型转换

    因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?

    看下ArrayList.get()方法:

    public E get(int index) {  
    
        RangeCheck(index);  
    
        return (E) elementData[index];  
    
    }
    

    可以看到,在return之前,会根据泛型变量进行强转。假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date)elementData[index]。所以我们不用自己进行强转。当存取一个泛型域时也会自动插入强制类型转换。假设Pair类的value域是public的,那么表达式:

    Date date = pair.value;
    

    也会自动地在结果字节码中插入强制类型转换。

    2.3.3 类型擦除与多态的冲突和解决方法

    现在有这样一个泛型类:

    class Pair<T> {  
    
        private T value;  
    
        public T getValue() {  
            return value;  
        }  
    
        public void setValue(T value) {  
            this.value = value;  
        }  
    }
    

    然后我们想要一个子类继承它。

    class DateInter extends Pair<Date> {  
    
        @Override  
        public void setValue(Date value) {  
            super.setValue(value);  
        }  
    
        @Override  
        public Date getValue() {  
            return super.getValue();  
        }  
    }
    

    在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型。

    public Date getValue() {  
        return value;  
    }  
    
    public void setValue(Date value) {  
        this.value = value;  
    }
    

    所以,我们在子类中重写这两个方法一点问题也没有,实际上,从他们的@Override标签中也可以看到,一点问题也没有,实际上是这样的吗?

    分析:实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:

    class Pair {  
        private Object value;  
    
        public Object getValue() {  
            return value;  
        }  
    
        public void setValue(Object  value) {  
            this.value = value;  
        }  
    }  
    

    再看子类的两个重写的方法的类型:

    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  
    @Override  
    public Date getValue() {  
        return super.getValue();  
    }
    

    先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。

    我们在一个main方法测试一下:

    public static void main(String[] args) throws ClassNotFoundException {  
            DateInter dateInter = new DateInter();  
            dateInter.setValue(new Date());                  
            dateInter.setValue(new Object()); //编译错误  
    }
    

    如果是重载,那么子类中两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,却是是重写了,而不是重载了。

    为什么会这样呢?原因是这样的,我们传入父类的泛型类型是Date,Pair<Date>,我们的本意是将泛型类变为如下:

    class Pair {  
        private Date value;  
        public Date getValue() {  
            return value;  
        }  
        public void setValue(Date value) {  
            this.value = value;  
        }  
    }
    

    然后再子类中重写参数类型为Date的那两个方法,实现继承中的多态。

    可是由于种种原因,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法啊。

    于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法

    首先,我们用javap -c className的方式反编译下DateInter子类的字节码,结果如下:

    class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
      com.tao.test.DateInter();  
        Code:  
           0: aload_0  
           1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
           4: return  
    
      public void setValue(java.util.Date);  //我们重写的setValue方法  
        Code:  
           0: aload_0  
           1: aload_1  
           2: invokespecial #16                 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V  
           5: return  
    
      public java.util.Date getValue();    //我们重写的getValue方法  
        Code:  
           0: aload_0  
           1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
           4: checkcast     #26                 // class java/util/Date  
           7: areturn  
    
      public java.lang.Object getValue();     //编译时由编译器生成的桥方法  
        Code:  
           0: aload_0  
           1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;  
           4: areturn  
    
      public void setValue(java.lang.Object);   //编译时由编译器生成的桥方法  
        Code:  
           0: aload_0  
           1: aload_1  
           2: checkcast     #26                 // class java/util/Date  
           5: invokevirtual #30                 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V  
           8: return  
    }
    

    从编译的结果来看,我们本意重写setValuegetValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvaluegetValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。

    所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。不过,要提到一点,这里面的setValuegetValue这两个桥方法的意义又有不同。setValue方法是为了解决类型擦除与多态之间的冲突。而getValue却有普遍的意义,怎么说呢,如果这是一个普通的继承关系:

    那么父类的setValue方法如下:

    public ObjectgetValue() {  
        return super.getValue();  
    }
    

    而子类重写的方法是:

    public Date getValue() {  
        return super.getValue();  
    }
    

    其实这在普通的类继承中也是普遍存在的重写,这就是协变。
    关于协变:。。。。。。

    并且,还有一点也许会有疑问,子类中的巧方法Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

    2.3.4 泛型类型变量不能是基本数据类型

    不能用类型参数替换基本类型。就比如,没有ArrayList<double>,只有ArrayList<Double>。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。

    2.3.5 编译时集合的instanceof

    ArrayList<String> arrayList = new ArrayList<String>();
    

    因为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了。

    那么,编译时进行类型查询的时候使用下面的方法是错误的:

    ArrayList<String> arrayList = new ArrayList<String>();
    

    2.3.6 泛型在静态方法和静态类中的问题

    泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

    举例说明:

    public class Test2<T> {    
        public static T one;   //编译错误    
        public static  T show(T one){ //编译错误    
            return null;    
        }    
    }
    

    因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

    但是要注意区分下面的一种情况:

    public class Test2<T> {    
    
        public static <T >T show(T one){ //这是正确的    
            return null;    
        }    
    }
    

    因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。

    3 泛型通配符

    转载于:https://juejin.cn/post/6844903917835419661

    我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?

    常用的 T,E,K,V,?

    本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:

    • ? 表示不确定的 java 类型
    • T (type) 表示具体的一个java类型
    • K V (key value) 分别代表java键值中的Key Value
    • E (element) 代表Element

    3.1 “?” 无界通配符

    先从一个小例子看起,我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表,我的第一个想法是像这样的:

    List<Animal> listAnimals
    

    但是老板的想法确实这样的:

    List<? extends Animal> listAnimals
    

    为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

    static int countLegs (List<? extends Animal > animals ) {
        int retVal = 0;
        for ( Animal animal : animals )
        {
            retVal += animal.countLegs();
        }
        return retVal;
    }
    
    static int countLegs1 (List< Animal > animals ){
        int retVal = 0;
        for ( Animal animal : animals )
        {
            retVal += animal.countLegs();
        }
        return retVal;
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
     	// 不会报错
        countLegs( dogs );
    	// 报错
        countLegs1(dogs);
    }
    

    当调用 countLegs1 时,就会飘红,提示的错误信息如下:
    在这里插入图片描述
    所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。像 countLegs 方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 countLegs1 就不行。

    3.2 上界通配符 < ? extends E>

    上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
    

    在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

    • 如果传入的类型不是 E 或者 E 的子类,编译不成功
    • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
    private <K extends A, E extends B> E test(K arg1, E arg2){
        E result = arg2;
        arg2.compareTo(arg1);
        //.....
        return result;
    }
    

    类型参数列表中如果有多个类型参数上限,用逗号分开!

    3.3 下界通配符 < ? super E>

    下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
    

    在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

    private <T> void test(List<? super T> dst, List<T> src){
        for (T t : src) {
            dst.add(t);
        }
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        List<Animal> animals = new ArrayList<>();
        new Test3().test(animals,dogs);
    }
    // Dog 是 Animal 的子类
    class Dog extends Animal {
    
    }
    

    dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。

    3.4 ? 和 T 的区别

    在这里插入图片描述?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :

    // 可以
    T t = operate();
    
    // 不可以
    ? car = operate();
    
    

    简单总结下:

    T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

    区别1:通过 T 来 确保 泛型参数的一致性

    // 通过 T 来 确保 泛型参数的一致性
    public <T extends Number> void
    test(List<T> dest, List<T> src)
    
    //通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
    public void
    test(List<? extends Number> dest, List<? extends Number> src)
    

    像下面的代码中,约定的 T 是 Number 的子类才可以,但是申明时是用的 String ,所以就会飘红报错。
    在这里插入图片描述
    不能保证两个 List 具有相同的元素类型的情况:

    GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric<>();
    List<String> dest = new ArrayList<>();
    List<Number> src = new ArrayList<>();
    glmapperGeneric.testNon(dest,src);
    

    上面的代码在编译器并不会报错,但是当进入到 testNon 方法内部操作时(比如赋值),对于 dest 和 src 而言,就还是需要进行类型转换。

    区别2:类型参数可以多重限定而通配符不行

    在这里插入图片描述
    使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。

    区别3:通配符可以使用超类限定而类型参数不行

    类型参数 T 只具有 一种 类型限定方式:

    T extends A
    

    但是通配符 ? 可以进行 两种限定:

    ? extends A
    ? super A
    

    Class 和 Class<?> 区别:

    前面介绍了 ? 和 T 的区别,那么对于,Class 和 <Class<?> 又有什么区别呢?
    Class 和 Class<?>

    最常见的是在反射场景下的使用,这里以用一段发射的代码来说明下。

    // 通过反射的方式生成  multiLimit 
    // 对象,这里比较明显的是,我们需要使用强制类型转换
    MultiLimit multiLimit = (MultiLimit)
    Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();
    

    对于上述代码,在运行期,如果反射的类型不是 MultiLimit 类,那么一定会报 java.lang.ClassCastException 错误。

    对于这种情况,则可以使用下面的代码来代替,使得在在编译期就能直接 检查到类型的问题:
    在这里插入图片描述
    Class 在实例化的时候,T 要替换成具体类。Class<?> 它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明:

    // 可以
    public Class<?> clazz;
    // 不可以,因为 T 需要指定类型
    public Class<T> clazzT;
    

    所以当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。
    在这里插入图片描述那如果也想 public Class clazzT; 这样的话,就必须让当前的类也指定 T ,

    public class Test3<T> {
        public Class<?> clazz;
        // 不会报错
        public Class<T> clazzT;
    
    展开全文
  • Linux之常见通配符

    2021-11-30 10:24:20
    通配符

    文件通配符

    *匹配零个或多个字符,但不匹配"."开头的文件,即隐藏文件
    ?匹配任何单个字符,一个汉字也算一个字符
    ~当前用户的家目录
    . 和 ~+当前工作目录
    ~~前一个工作mu
    [0-9]匹配数字范围
    [a-z]一个字母
    [A-Z]一个字母
    [wang]匹配列表中的任何一个字符
    [^wang]匹配列表中的所有字符以外的字符
    [^a-z]匹配列表中的所有字符以外的字符
    [:digit:]任意数字,相当于0~9
    [:lower:]任意小写字母,相当于a~z
    [:upper:]任意大写字母,相当于A~Z
    [:alpha:]任意大小写字母
    [:alnum:]任意数字或字母
    [:blank:]水平空白字符
    [:space:]水平或垂直空白字符
    [:punct:]标点符号
    [:print:]可打印字符
    [:cntrl:]控制(非打印)字符
    [:graph:]图形字符
    [:xdigit:]十六进制字符

     

    展开全文
  • 在Linux平台下find是常用命令,并且经常是配合通配符(wildcard)一起使用,毕竟我们只能记住某个文件一部分;我们需要寻找当前目录下所有的sh文件,随手敲一条命令:find . -name *.sh,目测逻辑完美,可以运行,只...

    本文长度500多字,阅读大概需要2分钟。

    在Linux平台下find是常用命令,并且经常是配合通配符(wildcard)一起使用,毕竟我们只能记住某个文件一部分;我们需要寻找当前目录下所有的sh文件,随手敲一条命令:find . -name *.sh,目测逻辑完美,可以运行,只实际上这条命令在执行时有些许问题。

    find使用中的错误

    手边有环境的话,可以执行下会发现,结果是这样:

    d00a61a28328ad2a2efb2d598527dadf.png

    这种错误很奇怪,如果没看下文内容,完全不知道它想给我们什么提示。

    为了说明问题,我们先编写一小段c代码。

    #include

    int main(int argc, char *argv[]) {

    int i;

    printf("参数个数=%d\n", argc);

    for(i=0;i < argc; i++){

    printf("参数 %d: '%s'\n", i, argv[i]);

    }

    return 0;

    }

    执行gcc test.c,编译完毕后,直接执行a.out:./a.out,得到结果:

    162ec37ecc23fbbb34338863e928c7f5.png

    也就是说,我们在没有任何参数的情况下,程序认为有一个参数,就是程序名本身。

    再次测试,./a.out *.sh

    c922fbad0ed1d95f9196cfef7f87a09d.png

    这就是文章想说的错误,在使用了通配符的情况下,程序先匹配了所有的sh文件,并将他们当成了参数。

    回到文章开头的部分,多个sh文件名作为参数传递给了find,所以find . -name *.sh实际执行时是:find . -name change_es_log_level.sh change_es_mapping.sh data.sh es-docker.sh…,change_es_log_level.sh被find识别成了path参数,实际却是单个文件,导致了错误。

    解决

    先说解决方法,只需要稍微修改下,./a.out “*.sh”

    3f0e489adc1e21827243f3f0598c6f5e.png

    这样就符合预期了,同理推论,需要查找当前目录下所有的sh文件,应该是find . -name “*.sh”,对比之前命令,只是多了一对引号。

    为什么会这样?

    这里只讲一个unix平台下的概念,globbing,我们可以在环境中执行man 7 glob,看看说明。

    Long ago, in UNIX V6, there was a program /etc/glob that would expand wildcard patterns. Soon afterward this became a shell built-in.

    These days there is also a library routine glob(3) that will perform this function for a user program.

    The rules are as follows (POSIX.2, 3.13).

    A string is a wildcard pattern if it contains one of the characters ‘?’, ‘*’ or ‘[‘. Globbing is the operation that expands a wild‐

    card pattern into the list of pathnames matching the pattern. Matching is defined by:

    A ‘?’ (not between brackets) matches any single character.

    A ‘*’ (not between brackets) matches any string, including the empty string.

    一个字符串包含了通配符时,shell会将通配符展开,将当前目录下,匹配到的文件名称作为参数传递给程序,所以才出现了我们上面看到的诡异现象。

    这种特性内置在shell中,受影响的不紧紧是find命令,使用通配符做参数时还是要小心,需要加上引号,比如你想计算44,这时可能被当作是通配符,而不是乘号。

    shell中还有很多诡异的错误,都是有原因,只是我们没有时间或者懒得去找。

    展开全文
  • linux通配符含义

    2021-05-18 14:51:25
    linux通配符含义:. 当前目录****.. 当前目录的上一级目录***** 通配符,代表任意0个或多个字符*****? 通配符,代表重复0个或一个0前面的字符: 连续不同命令的分隔符*****# 配置文件注释*****| 管道*****~ 用户的家...
  • 泛型一般三种使用方式:泛型类、泛型接口、泛型方法 1.泛型类: //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic<T>{ ...
  • } } 泛型有哪些限制?为什么? 泛型的限制一般是由泛型擦除机制导致的。擦除为 Object 后无法进行类型判断 只能声明不能实例化 T 类型变量 泛型参数不能是基本类型。因为基本类型不是 Object 子类,应该用基本类型...
  • ORACLE学习笔记系列(9)通配符和特殊字符一、字符匹配:[NOT] LIKE ‘’1、可以是一个完整的字符串,也可以含有通配符的字符串。2、能与LIKE一起使用的通配符包括“%”、“_”。%(百分号):表示任意数量的字符,或者...
  • 泛型通配符 当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。 但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。 通配符的基本使用 ...
  • linux通配符速记.md

    2021-05-16 16:11:18
    ## 序言总是忘记通配符应该怎么使用,这里做一个速记,方便日后回忆。网上很多通配符教程每次看后就忘。这里结合案例,方便新手记录。```1. | #管道符,或者(正则)2. > #输出重定向3. >> #输出追加重定向4....
  • 1、文件通配符通配符(wildcard pattern)才用特定的符号,表示特定的含义,此符号称为元 meta 字符1.1 常见通配符* 匹配零个或多个字符,但不匹配 '.' 开头的文件,即隐藏文件? 匹配任何单个字符~ 当前用户家目录~...
  • (一)常见的Linux通配符 Linux通配符主要用户模糊查找文件,常用的Linux通配符如下表所示: 名称 作用 * 可以匹配任意数量任意字符 ? 可以匹配一个任意字符 [] 可以匹配括号中的任意一个字符 使用中...
  • 在编译期间,两种语法之间几个区别:>使用第一种语法,您可以添加元素到someList,但第二种,你不能.这通常被称为PECS,并且通常被称为PUT和GET原理.>使用第一种语法,您可以使用类型参数T的句柄,以便您可以使用它...
  • is needed,添加 --nodeps rpm -ivh percona-xtrabackup-24-2.4.21-1.el6.x86_64.rpm --force --nodeps ------------------------------------- #常见报错 (1) libev.so.4()(64bit) is needed by percona-...
  • 最最常见的符号如下所示:数字 [0-9]字母 [a-z]非字母[^a-z]非数字[^0-9]任意符号 *转义符号 \大写 [[:upper:]]小写 [[:lower:]]数字 [[:digit:]]字母 [[:alpha:]]数字+字母[[:alnum:]]空格 [[:...
  • Java泛型的通配符

    2021-02-22 15:57:45
    Java的泛型通配符使用大写字母定义,没有强制性的要求,但是一般的通用约定如下: 符号 含义 ? 不确定的Java类型 T 某一个具体的Java类型(Tpye) K V 代表key value类的键值 E 表示了Element List...
  • 本文介绍谨慎使用CSS中的星号(*)通配符的总结在关于CSS3的文章中都提到了通配符的使用(如果你对CSS3中的选择符使用感兴趣可以参考我翻译的详解CSS3中的属性选择符)。里面讲到了CSS3中多种通过符的使用方法,如星号(*...
  • 常见的定位行或行区间的方式如下: (1)line 表示用数字指定行号,如打印文件第1行内容 sed -n '1p' /etc/passwd #打印第1行 (2)line1,line2 表示行号范围从line1到line2,如打印第2行到第5行的文件内容 sed -n '...
  • 关注“Java后端技术全栈”回复“面试”获取全套面试资料当下,注解非常流行,以前很长篇的代码,现在基本上一个注解就能搞定。那,在Mybatis中又有哪些注解呢?Mybatis中的注解基本...
  • 1、需求当工作在UNIX Shell下时,我们想使用常见通配符项目(即:.py,Dat[0-9].csv等)来对文本做匹配。2、解决方案fnmatch模块提供了两个函数:fnmatch()和fnmatchcase(),可用使用的来执行这样的匹配,使用起来...
  • 常见通配符:* :匹配零个或多个字符,但不匹配"."开头的文件,即隐藏文件? :匹配任何单个字符[0-9]:匹配数字范围[^0-9]:匹配数字之外的字符预定义的字符类[:digit:] 任意数字,相当于0-9[:lower:] 任意小写字母[:...
  • 只是在编译期才作用,运行期就会失去作用,我们将两个不同泛型的同一类进行编译为.class会发现他们里面就不含有泛型的信息了。 二、通配符 2.1 ?通配符 如果说想传入的参数是继承的类,我们可以用?,比如下面: ...
  • 在使用 Java™ 语言的泛型时,通配符非常令人困惑,并且最常见的一个错误就是在使用通配符的两种形式的其中之一(“? super T” 和 “? extends T”)时出现错误。您出错了吗?别沮丧,即使是专家也会犯这种错误,...
  • 消息队列的使用场景很多,最常见的使用场景有哪些? 1、商品秒杀 比如,我们在做秒杀活动时,会发生短时间内出现爆发式的用户请求,如果不采取相关的措施,会导致服务器忙不过来,响应超时的问题,轻则会导致...
  • 这篇文章 W3Cschool 小编来为大家解答一下前端中常见的兼容性问题有哪些?如何解决?浏览器兼容性兼容性问题的出现,是因为浏览器内核的不同,导致的解析差异。我们常见的浏览器内核主要两种,分别是渲染引擎和js...
  • css常见选择器有哪些?优先级是什么?下面本篇文章就来给大家介绍一下css常见选择器以及它们的优先级。一定的参考价值,需要的朋友可以参考一下,希望对大家有所帮助。选择器也称为选择符,所有的html语言中的...
  • (或者,甚至更好,记住对get_category的调用,以便常见的单词具有快速查找,但是你不会浪费内存存储你永远不会查找的单词。) 该代码假定每个单词只在一个类别中。 (如果没有,唯一的变化是get_category返回一个列表...
  • 在给网站安装ssl证书时,需要清楚的就是,ssl证书的类型比较多,接下来大家一起看看ssl证书类型有哪些什么区别吧。 ssl证书类型有哪些? ssl证书根据证书的域名数量来进行区分,ssl证书被分为ssl单域名证书...
  • java零基础入门-高级特性篇(六) 泛型 中泛型的使用位置,除了最常见的约束集合元素,还可以使用在接口,类,方法上面。最本质的原因就是为了在使用接口,类,方法的时候,可以将类型作为参数,进行类型的参数传递。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 71,962
精华内容 28,784
关键字:

常见的通配符有哪些