• Java 泛型


      一般的类和方法中只能使用具体的类型,如果要使编写的代码可以应用于多种类型,可以使用多态或泛型。但是多态在定义时必须指定相应的基类或接口,由于类可以不断扩展,因此单继承体系会带来一定的性能损耗;而接口对程序约束性太强,代码必须使用特定的接口。使用泛型则可以编写更加通用的代码,使得代码能够应用于“某种不具体的类型”,而不是一个具体的类或接口。Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。Java中的容器类最常使用泛型,用来指定容器中持有对象的类型,并且由编译器来保证类型的正确性。

    1. 泛型类

    a. 容器类(栈)

    public class LinkedStack<T> {
        private static class Node<U> {
            U item;
            Node<U> next;
            public Node() { item = null; next = null;}
            public Node(U item, Node<U> next) { this.item = item; this.next = next; }
            public boolean end(){ return item == null && next == null; }
        }
        private Node<T> top = new Node<T>();
        
        public void push(T item) {
            top = new Node<T>(item, top);
        }
        public T pop() {
            T result = top.item;
            if(!top.end()) top = top.next;
            return result;
        }
        public static void main(String[] args) {
            LinkedStack<String> stack = new LinkedStack<String>();
            for(String s : "first second third".split(" ")) stack.push(s);
            String s;
            while((s = stack.pop()) != null) System.out.println(s);
        }
    }

    b. 元组

      方法的return只能返回一个对象,如果要返回多个对象可以创建一个类来存放这些对象。为了用一个通用的类来解决这个问题,可以使用泛型类来创建一组对象,称为元组。这个容器对象允许读取其中元素,但不能修改元素。

    class TwoTuple <A, B> {
        public final A first;     // public表示可以随时获取该元素,final确保无法再次赋值,保证了元素的安全性
        public final B second;
        public TwoTuple(A a, B b) { first = a; second = b; }
        public String toString() { 
            return "(" + first + ", " + second + ")"; 
        }
    }
    public class ThreeTuple <A, B, C> extends TwoTuple<A, B> { // 可随意扩展元组的元素
        public final C third;
        public ThreeTuple(A a, B b, C c) { super(a, b); third = c; }
        public String toString() { 
            return "(" + first + ", " + second + ", " + third + ")"; 
        }
        public static void main(String[] args) {
            System.out.println(new TwoTuple<String, Integer>("abc", 5));
            System.out.println(new ThreeTuple(1, "2", 3.0));
        }
    }

    2. 泛型接口 

    interface Generator<T> {
        public T generate();
    }
    
    class Fibonacci implements Generator<Integer> { // 基本类型不能作为类型参数,不过Java SE5的封装类实现了自动封装机制
        private static int count = 0;
        
        @Override
        public Integer generate() {
            return fib(count++);
        }
        private int fib(int n) {
            if(n < 2) return 1;
            return fib(n - 2) + fib(n - 1);
        }
    }
    
    public class IterableFibonacci extends Fibonacci implements Iterable<Integer>{ // 适配器,添加新功能且不更改原始类
        int n = 0;
        public IterableFibonacci(int n){
            this.n = n;
        }
        @Override
        public Iterator<Integer> iterator() {
            return new Iterator<Integer>(){
                public boolean hasNext() { return n>0; } 
                public Integer next() { n--; return IterableFibonacci.this.generate(); } // generate方法是其父类Fibonacci的方法
            };
        }
        public static void main(String[] args) {
            for(int i : new IterableFibonacci(10))
                System.out.println(i);
        }
    }

    3. 泛型方法

      泛型方法所在的类可以使泛型类,也可以不是泛型类。泛型方法使得方法可以独立于类而产生变化,应尽量使用泛型方法。使用泛型方法的时候,通常不用指明参数类型,编译器会根据参数值自动推断类型。泛型方法的定义和普通方法定义不同的地方在于需要在修饰符和返回类型之间加一个泛型类型参数的声明,例如:public static <T> int func(List<T> list) { ... }

    a. 对象生成类(简化语法)

    class Tuple {
        public static <A,B> TwoTuple<A,B> twoTuple(A a, B b){
            return new TwoTuple<A,B>(a, b);
        }    
        public static void main(String[] args) {
              System.out.println(Tuple.twoTuple("s", 1));
        }
    }

    b. 通用的对象生成类

    public class BasicGenerator<T> implements Generator<T>{
        private Class<T> type;
        private BasicGenerator(Class<T> type){
            this.type = type;
        }
        public static BasicGenerator<T> create(Class<T> type) {
            return new BasicGenerator<T>(type);
        }
        public T next() {
            try {
                return type.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    匿名内部类

    class Customer {
        private Customer() {} // 私有构造器强制你使用Generator来生成对象
        public static Generator<Customer> generator(){
            return new Generator<Customer>(){ // 匿名内部类
                public Customer next() { return new Customer(); }
            };
        }    
        public static void main(String[] args) {
              System.out.println(Customer.generator());
        }
    }

    构建复杂模型

    class Student {
        int no;
        public Student(int no) {
            this.no = no;
        }
    }
    class Grade extends ArrayList<Student> {
        public Grade(int n) {
            for(int i = 0; i < n; i++)
                add(new Student(i));
        }
    }
    class School extends ArrayList<Grade> { // 多层容器
        public School(int m, int n) {
            for(int i = 0; i < m; i++)
                add(new Grade(n));
        }
    }

    擦除 

      Java泛型的处理几乎都在编译器中进行,编译器生成的bytecode是不包含泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。擦除使我们在泛型代码内部,无法获得任何有关参数类型的信息。使用擦除的主要原因是为了“迁移兼容性”,即允许泛型代码与非泛型代码共存。为了兼容以前的代码,使得某个类库变为泛型时不会破坏依赖于它的代码,所以采取了折中的办法。擦除的主要作用是在不破坏现有类库的情况下,实现从非泛化代码到泛化代码的转变,将泛型融入Java语言。

    类型擦除的主要过程如下:
         1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
         2.移除所有的类型参数。

    泛型数组

      在Java中可以使用ArrayList,避免使用中出现类型转换错误。

    class Generic<T> {
        T value;
        public Generic(T value) {
            this.value = value;
        }
    }
    
    class GenericArray<T> {
        List<T> array1;   // 一般使用ArrayList代替数组
        T[] array2;
        T[] array3;
        Object[] array4;
        public GenericArray(int size, Class<T> type) {
            array1 = new ArrayList<T>(size);
            array2 = (T[]) Array.newInstance(type, size); // type参数为实际类型,使得擦除得到恢复
            array3 = (T[]) new Object[size];  // 不能写成 array3 = new T[size],array3因为擦除在运行时的类型为Object[]
            array4 = new Object[size];        // 使用Object[]我们比较容易记住数组运行时的类型,从而及早解决问题
        }
        public T[] getArray3() {
            return array3;        // class [Ljava.lang.Object;
        }
        public T[] getArray4() {
            return (T[]) array4;  // class [Ljava.lang.Object;
        }
        public void setArrayElem(int index, T a) {
            array3[index] = a;        
        }
    }
    
    public class GenericTest {
        public static void main(String[] args) {
            int size = 2;         
            // 类型转换错误 [Runtime Error] java.lang.ClassCastException: java.lang.Object cannot be cast to Generic
            // Generic<Integer>[] array1 = (Generic<Integer>[]) new Object[size];  
            // 不能创建确切类型的数组 [Compile Error] Cannot create a generic array of Generic<Integer>
            // Generic<Integer>[] array1 = new Generic<Integer>[size]; 
            Generic<Integer>[] array1 = (Generic<Integer>[]) new Generic[size];
        
            GenericArray<String> array = new GenericArray<String>(size, String.class);
            String[] str1 = array.getArray3();  // 实际运行类型为Object[],而不是确切类型String[]  [Runtime Error] java.lang.ClassCastException 
            String[] str2 = array.getArray4();  // 实际运行类型为Object[],而不是确切类型String[]  [Runtime Error] java.lang.ClassCastException     
        }
    }

      JDK 中的ArrayList使用Object数组作为基础数据结构,当创建的数组元素是有确切类型时,获取时的类型转换(T[])就不会出错。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        transient Object[] elementData; // non-private to simplify nested class access
         public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity]; // 数组的创建跟普通Object数组一样
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
            }
        }
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of a's runtime type
                return (T[]) Arrays.copyOf(elementData, size, a.getClass()); 
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    
        public E get(int index) {
            rangeCheck(index);
            return elementData(index); 
        }
        
        public E set(int index, E element) { // element是有类型信息的,因此存放到数组的元素是有确切类型的
            rangeCheck(index);
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
        
        @SuppressWarnings("unchecked")
        E elementData(int index) {
            return (E) elementData[index];
        }
    }
  • 相关阅读:
    js函数的属性和方法
    php中str_repeat函数
    html5中的空格符
    php实现简单算法3
    php intval函数
    什么是全栈工程师
    配置Log4j(非常具体)
    【解决】/usr/bin/ld: cannot find -lc
    Java的位运算符具体解释实例——与(&amp;)、非(~)、或(|)、异或(^)
    【小白的java成长系列】——顶级类Object源代码分析
  • 原文地址:https://www.cnblogs.com/anxiao/p/7473727.html
Copyright © 2020-2023  润新知