zoukankan      html  css  js  c++  java
  • java-泛型

    一  概述

    1.   问题:什么类型的元素都可以存储。导致取出时,如果出现强转就会引发运行时 ClassCastException
      JDK1.5以后,出现了解决方案,使用容器时,必须明确容器中元素的类型。
    2. 泛型的原理:其实就是在操作的元素类型不确定时,通过传递参数的形式来明确类型。
    3. 泛型的体现就是 <参数类型变量>用于接收具体的实际元素类型
    4. 泛型技术在集合框架中应用非常广泛,只要记住:在使用类或者接口时,如果接口上有明确<>泛型
      在使用时,就传递所需的数据类型即可。不传递会出现警告类型不安全提示。
    5. 泛型的好处:
      将运行时期的ClassCastException异常转移到编译时期通过编译失败体现。
      避免了强制转换的麻烦。
    6. 其实泛型的使用就是往定义了泛型的类或者接口的<>中传递类型参数

    二 使用

    如果不使用泛型,你在运行时会发生编译异常,我们到运行时你在看到结果。

    //创建集合时,直接在集合上明确要存储的元素的类型。
            List<String> list = new ArrayList<String>();
            
            list.add("abc");
            list.add("zzzz");
    //        list.add(6);//只要不是指定的类型对象,编译器检查会 报错。这样将运行时的问题转移到编译时期
            
            for (Iterator<String> it = list.iterator(); it.hasNext();) {
    //            Object object = (Object) it.next();
    //            System.out.println(object.toString());
                //想要打印字符串的长度。
                String str = it.next();
                System.out.println(str.length());
                
            }

    2.1 类中使用

    在类中的使用,明确里面的类型,当类型不对时,编译会报错。

    1,我们把运行时异常,转到了编译时不通过,

    2, 通过会用泛型,也可以减少你不需要转型了。

    注意:下面是在类中的使用,以及你在使用栈和队列时也可以明确进栈或入队时你要的数据类型

    package cn.itcast.api.a.generic;
    
    import java.util.LinkedList;
    
    public class GenericDemo2 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
    //        Tool t = new Tool();
    //        t.setObject(6);
    //        String s = (String)t.getObject();
    //        System.out.println(s);
            
            Tool<String> t = new Tool<String>();
    //        t.setObject(6);//只要类型错误,编译失败。避免了运行时的类型转换异常。
            String s = t.getObject();//省去了强转的麻烦。
            System.out.println(s);
            
    
            
            Queue<String> queue = new Queue<String>();
            queue.myAdd("abcd1");
            queue.myAdd("abcd2");
            queue.myAdd("abcd3");
            while(!queue.isNull()){
                String string = queue.myGet();
                System.out.println(string);
            }
        }
    
    }
    //jdk1.5有了新技术,泛型,改成如下这样。
    //类中操作的对象确定不?不确定,用Object,需要转型,运行容易出异常。不爽。
    //在定义时,就将不确定的对象的类型,定义成参数。由使用该类的调用者来传递对象类型。
    class Tool<Q>{//将泛型定义在类上,泛型类。
        private Q object;
    
        public Q getObject() {
            return object;
        }
    
        public void setObject(Q object) {
            this.object = object;
        }
        
    }
    
    class Queue<E>{
        //封装了一个链表数据结构。
        private LinkedList<E> link;
        /*
         * 队列初始化时,对链表对象初始化。
         */
        Queue(){
            link = new LinkedList<E>();
        }
        
        /**
         * 队列的添加元素功能。
         */
        public void myAdd(E obj){
            //内部使用的就是链表的方法。
            link.addFirst(obj);
        }
        
        /**
         * 队列的获取方法。
         */
        public E myGet(){
            return link.removeLast();
        }
        
        /**
         * 判断队列中元素是否空,没有元素就为true。
         */
        public boolean isNull(){
            return link.isEmpty();
        }
    }
    
    
    
    /*
    //定义一个工具对对象进行操作,比如设置和获取。可以对任意对象进行操作。对共性类型Object操作。
    //定义Object就哦了。但用的时候,因为提升为了Object,想要使用特有内容,需要向下转型。容易引发ClassCastException:
    class Tool{
        
        private Object object;
    
        public Object getObject() {
            return object;
        }
    
        public void setObject(Object object) {
            this.object = object;
        }
        
        
    }
    */

    2.2 方法中使用

    注意:你在方法时使用时,注意调用的方法。

    package cn.itcast.api.a.generic;
    
    public class GenericDemo3 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            
            Util<String> util = new Util<String>();
            util.show("hehe");
    //        util.print(5);
            Util<Integer> util2 = new Util<Integer>();
            Util.print(5);
            util2.show("hehe");
            
        }
    }
    
    class Util<W>{
        //当方法要操作的类型不确定和类上的泛型不一定一样。这时可以将泛型定义在方法上。
        public  <Q> void show(Q q){//泛型方法
            System.out.println("show:"+q);
        }
        public static<E> void print(E e){//记住:如果方法是静态,还需要使用泛型,那么泛型必须定义在方法上。
            System.out.println("print:"+e);
        }
        public void method(W w){
            
        }
    }

    调用泛型方法语法格式如下:

    说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

           Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。

           为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

           泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。

           当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。

           为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。

    2.3 接口中使用

    package cn.itcast.api.a.generic;
    
    public class GenericDemo4 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            new InterImpl<String>().show("hehe");
        }
    
    }
    //泛型接口。
    interface Inter<E>{
        void show(E e);
    }
    /*
    class InterImpl implements Inter<String>{
        public void show(String e){}
    }
    */
    
    class InterImpl<T> implements Inter<T>{
    
        @Override
        public void show(T e) {
            
            
        }
        
    }

    三 泛型的通配符和限定

    3.1 通配符的使用

     当使用泛型类或者接口时,传递的具体的类型不确定,可以通过通配符(?)表示。

    使用了这种,你在使用方法时不能使用具体类的方法,你只能使用Object 里面的方法。例如:toStirng

    package cn.itcast.api.a.generic;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Set;
    
    import cn.itcast.domain.Student;
    
    public class GenericDemo5 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            Set<Student> list = new HashSet<Student>();
            list.add(new Student("lisi1",21));
            list.add(new Student("lisi2",22));
            list.add(new Student("lisi3",23));
            printList(list);
            List<String> list2 = new ArrayList<String>();
            list2.add("lisi11");
            list2.add("lisi22");
            list2.add("lisi33");
            printList(list2);
        }
    
        /*
         *  打印集合中的元素。
         *  
         *  当使用泛型类或者接口时,传递的具体的类型不确定,可以通过通配符(?)表示。
         *  
         */
        private static void printList(Collection<?> list2) {
            for (Iterator<?> it = list2.iterator(); it.hasNext();) {
                System.out.println(it.next().toString());// 不能使用具体的方法,
            }
        }
        
        
    
    }

    3.2 泛型的限定

    打印集合中的元素。

           当使用泛型类或者接口时,传递的具体的类型不确定,可以通过通配符(?)表示。
           如果想要对被打印的集合中的元素类型进行限制,只在指定的一些类型,进行打印。
           使用泛型的限定。
           
           只需要打印学生和工人的集合。找到学生和工人的共性类型Person。
           ? extends Person : 接收Person类型或者Person的子类型。
           
           总结:
           ? super E:接收E类型或者E的父类型。下限。
           ? extends E:接收E类型或者E的子类型。上限。
    package cn.itcast.api.a.generic;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Set;
    
    import cn.itcast.domain.Person;
    import cn.itcast.domain.Student;
    import cn.itcast.domain.Worker;
    
    public class GenericDemo6 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            Set<Student> list = new HashSet<Student>();
            
            list.add(new Student("lisi1",21));
            list.add(new Student("lisi2",22));
            list.add(new Student("lisi3",23));
            
            printList(list);
            
            List<Worker> list2 = new ArrayList<Worker>();
            
            list2.add(new Worker("lisi11",21));
            list2.add(new Worker("lisi22",22));
            list2.add(new Worker("lisi33",23));
            
            printList(list2);
    
            
        }
    
        /*
         *  打印集合中的元素。
         *  当使用泛型类或者接口时,传递的具体的类型不确定,可以通过通配符(?)表示。
         *  如果想要对被打印的集合中的元素类型进行限制,只在指定的一些类型,进行打印。
         *  使用泛型的限定。
         *  
         *  只需要打印学生和工人的集合。找到学生和工人的共性类型Person。
         *  ? extends Person : 接收Person类型或者Person的子类型。
         *  
         *  总结:
         *  ? super E:接收E类型或者E的父类型。下限。
         *  ? extends E:接收E类型或者E的子类型。上限。
         */
        private static void printList(Collection<? extends Person> list2) {
            for (Iterator<? extends Person> it = list2.iterator(); it.hasNext();) {
                
                Person p = it.next();
                System.out.println(p.getName());
            }
        }
        
    
    }

     3.3 泛型在API中的使用

     通配符? 在api中的体现。
           
    Collection接口: boolean containsAll(Collection<?> c) 
    package cn.itcast.api.a.generic;
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class GenericDemo7 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            /*
             * 通配符? 在api中的体现。
             * 
             * Collection接口: boolean containsAll(Collection<?> c) 
             */
    
            Collection<String> c1 = new ArrayList<String>();
            c1.add("haha");
            c1.add("hehe");
            
            Collection<Integer> c2 = new ArrayList<Integer>();
            c2.add(4);
            c2.add(5);
            
            boolean b = c1.containsAll(c2);//了解 containAll源码内判断是否包含的依据。依据是equals方法。
                                            //public  boolean equals(Object obj) "abc".equals(5);
            System.out.println("b="+b);
        }
    }
    
    /*模拟containaAll
     * class Collection<E>
     * {
     *         public boolean containsAll(Collection<?> c){
     *         }
     * }
     */

    3.4 泛型限定在API中的使用

    上限

    一个容器,你要添加元素,加个Person 是可以的,你如果加student 也应该是可以的。

    package cn.itcast.api.a.generic;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.TreeSet;
    import cn.itcast.domain.Person;
    import cn.itcast.domain.Student;
    
    public class GenericDemo8 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            /*
             *  泛型的限定在api中的使用。上限的体现。
             *  TreeSet(Collection<? extends E> c) 
             *  
             *  
             */
            //创建一个Collection.
            Collection<Student> c = new ArrayList<Student>();
            c.add(new Student("wangcai1",26));
            c.add(new Student("wangcai2",29));
    
            //TreeSet集合在创建时,就将c中的存储到Treeset集合。
            TreeSet<Person> ts = new TreeSet<Person>(c);
            
            ts.add(new Person("lisi",20));
            
            for (Iterator<Person> it = ts.iterator(); it.hasNext();) {
                Person person =  it.next();
                System.out.println(person);
                
            }
            
            
        }
    
    }
    
    /*
     * class TreeSet<E>{
     *         TreeSet(Collection<? extends E> c){}
     */

    下限

    创建一个学生对象,想要姓名排序,用comparator。

    加入工人,你也想用姓名排序,你也可以在建一个comparator

    但是这样代码的复用性就降低了。

    可以用Person;这时你应该定义Student的父类。

    package cn.itcast.api.a.generic;
    
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.TreeSet;
    
    import cn.itcast.domain.Person;
    import cn.itcast.domain.Student;
    import cn.itcast.domain.Worker;
    
    public class GenericDemo9 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            /*
             *  泛型的限定在api中的使用。下限的体现。
             *  TreeSet(Comparator<? super E> comparator) 
             *  
             *  
             */
            //创建一个集合存储的是学生对象。想要按照姓名排序。
            TreeSet<Student> ts = new TreeSet<Student>(new ComparatorByName());
            ts.add(new Student("abc",26));
            ts.add(new Student("aaa",29));
            ts.add(new Student("lisi",20));
            
            
            for (Iterator<Student> it = ts.iterator(); it.hasNext();) {
                Student student =  it.next();
                System.out.println(student);
                
            }
            
            //让工人按照姓名排序。
            TreeSet<Worker> ts2 = new TreeSet<Worker>(new ComparatorByName());
            ts2.add(new Worker("abc",26));
            ts2.add(new Worker("aaa",29));
            ts2.add(new Worker("lisi",20));
            
            
            for (Iterator<Worker> it = ts2.iterator(); it.hasNext();) {
                Worker worker =  it.next();
                System.out.println(worker);
                
            }
        }
    }
    
    
    class ComparatorByName implements Comparator<Person>{
    
        @Override
        public int compare(Person o1, Person o2) {
            int temp = o1.getName().compareTo(o2.getName());
            
            return temp==0? o1.getAge() - o2.getAge() : temp;
        }
        
    }
    /*
     * 以下两个比较器,都是通过姓名排序,就是类型不同,一个是student,一个是worker
     * 既然使用的都是Person的内容,为什么不定义一个Person的比较器。
     */
    /*
    //定义一个比较器。
    class ComparatorByName implements Comparator<Student>{
    
        @Override
        public int compare(Student o1, Student o2) {
            int temp = o1.getName().compareTo(o2.getName());
            
            return temp==0? o1.getAge() - o2.getAge() : temp;
        }
        
    }
    //定义一个工人的姓名比较器。
    class ComparatorByWorkerName implements Comparator<Worker>{
    
        @Override
        public int compare(Worker o1, Worker o2) {
            int temp = o1.getName().compareTo(o2.getName());
            
            return temp==0? o1.getAge() - o2.getAge() : temp;
        }
        
    }
    */
    /*
     * class TreeSet<E>{
     *         TreeSet(Comparator<? super E>  c){}
     */

     总结:

    如是你是存元素时(也可以理解为添加元素),你要用到上限 ? extends Person 

    如果你从集合中把元素取出来,你要拿个类型去接收,我需要把类型写的大点,用 ? super  student

    3.5 泛型的细节

    下面的写法是不正确的

           1  ArrayList<Dog> al = new ArrayList<Animal>();//使用泛型,要保证左右类型一致。
           2  ArrayList<String> al = new ArrayList<Object>();
    3 ArrayList<Animal> al = new ArrayList<Dog>();
        4  List<Integer> list = new ArrayList<Integer>();
    //        show(list);
        }
        public static void show(List<String> list){ // List <String> list =new ArrayList<Interger>
            
        }

    四 泛型限定的应用

    获取集合中中元素的最大值

    下面是不加泛型的结果

    package test;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    class test {
        public static void main(String[] args) {
            Collection<Student> coll = new ArrayList<Student>();
            coll.add(new Student("xioming1", 21));
            coll.add(new Student("xioming2", 26));
            coll.add(new Student("xioming3", 27));
            coll.add(new Student("xioming4", 22));
            Student stu = getMax(coll);
            System.out.println(stu);
    
        }
    
        public static Student getMax(Collection<Student> coll) {
            // 定义变量
            Iterator<Student> it = coll.iterator();
            Student max =  it.next();
            // 遍历
            while (it.hasNext()) {
                Student temp = it.next();
                if (temp.compareTo(max) > 0) {
                    max = temp;
    
                }
    
            }
            return max;
        }
    
    }

    这个功能虽然实现了,但是有局限性。因为这个功能只能对存储了student对象的集合进行最大值的获取

    我们需要扩大范围,使用泛型的限定

    package test;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    class test {
        public static void main(String[] args) {
            Collection<Student> coll = new ArrayList<Student>();
            coll.add(new Student("xioming1", 21));
            coll.add(new Student("xioming2", 26));
            coll.add(new Student("xioming3", 27));
            coll.add(new Student("xioming4", 22));
            Student stu = getMax(coll);
            System.out.println(stu);
    
        }
    
        public static  <T extends Comparable<? super T>> T  getMax(Collection<? extends T> coll) {
            // 定义变量
            Iterator<? extends T> it = coll.iterator();
            T max =  it.next();
            // 遍历
            while (it.hasNext()) {
                T o = it.next();
                if (o.compareTo(max) > 0) {
                    max = o;
                }
    
            }
            return max;
        }
    
    }

     五 工具类

    1,为了解决集合的更多需求。集合框架提供了Collections和Arrays两个工具类,方法都是静态的。
    2,Collections是用于操作集合的工具类。
      常见方法:对List集合排序,二分查找,对Collection集合进行最值获取。
      对排序进行逆序,将非同步的集合转成同步的集合。
    3,Arrays对数组操作的工具类:
      常见方法:对数组排序,二分查找,数组复制,将数组转成字符串等。
    4,数组集合互转。
      4.1将数组转成集合 Arrays.asList方法。
      目的:使用集合的方法操作数组元素。
      注意:不要使用集合的增删方法。因为数组转成List集合后长度是固定的。
      转成集合的数组中存储的元素最好是对象,如果是基本数据类型,会将这个数组作为元素存储到集合中。
      4.2 集合转成数组。
      Collection接口中的toArray方法。
      目的:限定对元素的增删,长度的改变。

    5.1 Collections

     1,获取Collection最值。

    2,对List集合排序,也可以二分查找。

    3,对排序逆序。

    4,可以将非同步的集合转成同步的集合

    package cn.itcast.api.b.tools;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    import cn.itcast.api.c.comparator.ComparatorByLength;
    
    public class CollectionsDemo {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            /*
             * Collections: 集合框架中的用于操作集合对象 工具类。
             * 都是静态的工具方法。
             * 1,获取Collection最值。
             * 2,对List集合排序,也可以二分查找。
             * 3,对排序逆序。
             * 4,可以将非同步的集合转成同步的集合。
             * Xxx synchronizedXxx(Xxx)  List synchronizedList(List) 
             */
            
            
            System.out.println("---------获取最值---------------");
            Collection<String> c = new ArrayList<String>();
            c.add("haha");
            c.add("zz");
            c.add("xixii");
            c.add("abc");
            String max = Collections.max(c,new ComparatorByLength());
            
            System.out.println("max="+max);
            System.out.println("-----------排序-------------");
            List<String> list  = new ArrayList<String>();
            list.add("hahaha");
            list.add("abc");
            list.add("xiix");
            list.add("z");
            list.add("java");
            Collections.sort(list,Collections.reverseOrder());
            System.out.println(list);
            
            
            System.out.println("------------------------");
            System.out.println("------------------------");
            
            
            
        }
    
    }

     5.2 Arrays

    工具类-集合和数组的互转

     Arrays:用于操作数组的工具类。

    类中定义的都是静态工具方法

    1,对数组排序。

    2,二分查找。

    3,数组复制。

    4,对两个数组进行元素的比较,判断两个数组是否相同。

    5,将数组转成字符串。

     
    package cn.itcast.api.b.tools;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.List;
    
    public class ArraysDemo {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            /*
             * Arrays:用于操作数组的工具类。
             * 类中定义的都是静态工具方法
             * 1,对数组排序。
             * 2,二分查找。
             * 3,数组复制。
             * 4,对两个数组进行元素的比较,判断两个数组是否相同。
             * 5,将数组转成字符串。
             */
            
            int[] arr = {34,21,67};
            System.out.println(Arrays.toString(arr));
            
            //将arr转成list集合。?如果数组中存储的是基本数据类型,那么转成集合,数组对象会作为集合中的元素存在。
            //数组中元素时引用数据类型时,转成,数组元素会作为集合元素存在。
             List<int[]> list1 = Arrays.asList(arr);
             System.out.println(list1);
            
            
            
            
            String[] strs = {"hah","hehe","xixi"};
            boolean b = contains(strs,"hehe");
            System.out.println(b);
            //将数组转成list集合。asList
            /*
             * 数组转成集合:就为了使用集合的方法操作数组中的元素。
             * 但是不要使用增删等改变长度的方法。add remove  发生UnsupportedOperationException
             */
            List<String> list = Arrays.asList(strs);
            System.out.println(list.contains("hehe"));
            System.out.println(list.get(2));
            System.out.println(list.indexOf("hehe"));
    //        list.add("java");//UnsupportedOperationException  数组长度的固定的,转成List集合长度也是固定的。
            
            
            
            
            //-------------------集合转成数组---------------------
            /*
             * 为什么集合转成数组呢?
             * 为了限制对元素的增删操作。
             */
            Collection<String> c = new ArrayList<String>();
            c.add("haha1");
            c.add("haha2");
            c.add("haha3");
            c.add("haha4");
            /*
             *  如果传递的数组的长度小于集合的长度,会创建一个同类型的数组长度为集合的长度。
             *  如果传递的数组的长度大于了集合的长度,就会使用这个数组,没有存储元素的位置为null。
             *  长度最好直接定义为和集合长度一致。
             */
            String[] str_arr = c.toArray(new String[c.size()]);
            System.out.println(Arrays.toString(str_arr));
    
        }
    
        public static boolean contains(String[] strs,String key) {
        
            for (int i = 0; i < strs.length; i++) {
                if(strs[i].equals(key)){
                    return true;
                }
            }
            
            return false;
        }
        
    }

     练习题目

    1,Collection和Collections的区别?【面试题】
    Collection是集合框架的顶层接口。
    下面有两个开发中常用的List 和 Set集合。
    根据数据结构的不同,也有了很多的具体子类集合对象。

    Collections:是集合框架中的用于操作集合的工具类。
    提供了很多的静态方法:比如:对list排序,二分查找,比如可以获取最值等。
    其中一组可以非同步集合转成同步 集合的方法。


    作者:8亩田
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.

    本文如对您有帮助,还请多帮 【推荐】 下此文。
    如果喜欢我的文章,请关注我的公众号
    如果有疑问,请下面留言

    学而不思则罔 思而不学则殆
  • 相关阅读:
    算法习题---线性表之控制变量个数获取数据最小值
    C语言复习---矩形法求定积分函数
    sql server系统表详细说明
    sp_addlinkedserver 方法应用
    ipseccmd命令解析
    缓存淘汰算法(LFU、LRU、ARC、FIFO、MRU)分析
    uml定义的使用的关系
    GIS公交查询-flex/java
    arcgis软件集合
    arcgis地图数据集合
  • 原文地址:https://www.cnblogs.com/liu-wang/p/8298999.html
Copyright © 2011-2022 走看看