Java泛型學習筆記--Java泛型和C#泛型比較學習(一)


  總結Java的泛型前,先簡單的介紹下C#的泛型,通過對比,比較學習Java泛型的目的和設計意圖。C#泛型是C#語言2.0和通用語言運行時(CLR)同時支持的一個特性(這一點是導致C#泛型和Java泛型區別的最大原因,后面會介紹)。C#泛型在.NET CLR支持為.NET框架引入參數化變量支持。C#泛型更類似C++模板,可以理解,C#泛型實際上可以理解為類的模板類。我們通過代碼實例來看C# 2.0泛型解決的問題,首先,我們通過一個沒有泛型的迭代器的代碼示例說起,代碼實現如下:

        interface IEnumerable
    {
        void Add(Object x);
        IEnumerator GetEnumerator();
    }
    interface IEnumerator
    {
        Object Next();
        bool HasNext();
    }
    class NoSuchElementException : ArgumentException { }
    class LinkedList : IEnumerable
    {
        private class Node
        {
            public Object Data { get; set; }
            public Node Next { get; set; }
            public Node(Object data)
            {
                this.Data = data;
            }
        }

        class DataEnumerator : IEnumerator
        {
            public DataEnumerator(Node ptr)
            {
                this.head = ptr;
            }
            private Node head;
            public object Next()
            {
                if (head != null)
                {
                    Object data = head.Data;
                    head = head.Next;
                    return data;
                }
                else
                {
                    throw new NoSuchElementException();
                }
            }
            public bool HasNext()
            {
                return head != null;
            }
        }
        private Node head = null, tail = null;
        public void Add(Object data)
        {
            if (head == null)
            {
                head = new Node(data);
                tail = head;
            }
            else
            {
                tail.Next = new Node(data);
                tail = tail.Next;
            }
        }
        public IEnumerator GetEnumerator()
        {
            return new DataEnumerator(head);
        }
    }
    

  通過以上C#代碼對迭代器的實現,我們發現代碼至少存在以下幾個問題:

  1. 如果我不想在對LinkedList集合對象存儲的時候發生裝箱拆箱的性能損失,只是對特點類型的元素集合進行操作。如:我們LinkedList存儲了100w的整數,用LinkedList存儲的時候,每次都把整數 轉出 Object,相當於在堆內存中對數字進行了100w次打包,其中的性能損失我們不能不考慮。我們考慮性能的話,我們可以修改LinkedList的實現,將內嵌類Node的Data數據類型改為int類型,修改后的實現如下: 
        interface IEnumerable { void Add(int x); IEnumerator GetEnumerator(); } interface IEnumerator { int Next(); bool HasNext(); } class NoSuchElementException : ArgumentException { } class LinkedList : IEnumerable { private class Node { public int Data { get; set; } public Node Next { get; set; } public Node(int data) { this.Data = data; } } class DataEnumerator : IEnumerator { public DataEnumerator(Node ptr) { this.head = ptr; } private Node head; public int Next() { if (head != null) { int data = head.Data; head = head.Next; return data; } else { throw new NoSuchElementException(); } } public bool HasNext() { return head != null; } } private Node head = null, tail = null; public void Add(int data) { if (head == null) { head = new Node(data); tail = head; } else { tail.Next = new Node(data); tail = tail.Next; } } public IEnumerator GetEnumerator() { return new DataEnumerator(head); } } 
    用LinkedList存儲整數雖然沒有問題了,但是下一次我們需要用LinkedList存儲100w的字符的話,我們還要修改LinkedList的實現。分析我們的代碼實現我們會發現,當我們存儲不同的數據結構的時候,我們大部分代碼都是相同的,只是LinkedList存儲的數據類型不一樣。  
  2. LinkedList linkedList = new LinkedList(),linkedList集合存儲的都是Object類型的元素,對集合進行操作的時候,如果對集合存儲的代碼調用時元素類型信息無法直接確定,無法判斷集合存儲的具體類型。例如,我們有如下代碼的調用:
    class Program
        {
            static void Main(string[] args)
            {
                LinkedList list = new LinkedList();
                list.Add(new A());
                list.Add(new B());
    
                IEnumerator e = list.GetEnumerator();
                while (e.HasNext())
                {
                    B b = e.Next() as B; //此處類型轉換,當轉換不成功時候,捕獲異常,返回類型為空                
                    if(b != null)
                      b.MethodB();
                }
            }
        }
        class A
        {
            public void MethodA()
            {
              Console.WriteLine("Invoke MethodA");
            }
        }
        class  B:A
        {
            public void MethodB()
            {
                 Console.WriteLine("Invoke MethodA");
            }
        }
                        

    以上代碼在調用 B b = e.Next() as B 語句的時候,此處類型轉換,當轉換不成功時候,捕獲異常,返回類型為空。

  3. LinkedList集合對象在存儲元素的時候,對元素進行隱式類型轉換(子類轉換成Object類型)或者(值類型發生裝箱操作)。

  下面我們通過C#2.0開始支持的泛型來解決了以上的問題,泛型化修改后的代碼如下:

    interface IEnumerable<T>
    {
        void Add(T x);
        IEnumerator<T> GetEnumerator();
    }
    interface IEnumerator<T>
    {
        T Next();
        bool HasNext();
    }
    class NoSuchElementException<T> : ArgumentException { }
    class LinkedList<T> : IEnumerable<T>
    {
        private class Node
        {
            public T Data { get; private set; }
            public Node Next { get; set; }
            public Node(T data)
            {
                this.Data = data;
            }
        }
        class DataEnumerator : IEnumerator<T>
        {
            public DataEnumerator(Node ptr)
            {
                this.head = ptr;
            }

            private Node head;
            public T Next()
            {
                if (head != null)
                {
                    T data = head.Data;
                    head = head.Next;
                    return data;
                }
                else
                {
                    throw new NoSuchElementException<T>();
                }
            }
            public bool HasNext()
            {
                return head != null;
            }
        }

        private Node head = null, tail = null;
        public void Add(T data)
        {
            if (head == null)
            {
                head = new Node(data);
                tail = head;
            }
            else
            {
                tail.Next = new Node(data);
                tail = tail.Next;
            }
        }
        public IEnumerator<T> GetEnumerator()
        {
            return new DataEnumerator(head);
        }
    }
    

通過以上對C#泛型分析,簡單總結下C#引入,在我看來至少解決了三個問題:

  1. 封裝和復用通用代碼邏輯;
  2. 增強了編譯期類型檢查,減少了運行時發生InvalidCastException異常的幾率;
  3. 解決集合操作時候的裝箱和拆箱的效率問題。

  簡答介紹了C#泛型引入解決的問題后,下面我們開始介紹今天的主角—Java泛型。我們會對比以上三個問題來對比分析Java引入泛型所解決的問題。下面同樣來一段Java實現的迭代器的實現的代碼,如下:

      interface Collection { 
              public void add (Object x); 
           public Iterator iterator (); 
       } 
      
       interface Iterator { 
              public Object next (); 
           public boolean hasNext (); 
       } 
      
       class NoSuchElementException extends RuntimeException {} 
      
       class LinkedList implements Collection { 
      
           protected class Node { 
                  Object elt; 
               Node next = null; 
               Node (Object elt) { this.elt = elt; } 
          } 
      
           protected Node head = null, tail = null; 
      
           public LinkedList () {} 
      
           public void add (Object elt) { 
               if (head == null) { head = new Node(elt); tail = head; } 
               else { tail.next = new Node(elt); tail = tail.next; } 
           } 
      
           public Iterator iterator () { 
      
               return new Iterator () { 
                   protected Node ptr = head; 
                   public boolean hasNext () { return ptr != null; } 
                   public Object next () { 
                       if (ptr != null) { 
                          Object elt = ptr.elt; ptr = ptr.next; return elt;
                       } else throw new NoSuchElementException (); 
                   } 
              }; 
           } 
       }

對比C#和Java的迭代器實現,不得不說Java的迭代器實現看起來更優雅。Java對內部類的支持,讓Java在類型實現上更靈活多樣。下面我們來看看Java泛型迭代器的實現如下:

interface Collection<A> { 
           public void add(A x); 
           public Iterator<A> iterator(); 
       } 
      
       interface Iterator<A> { 
           public A next(); 
           public boolean hasNext(); 
       } 
      
       class NoSuchElementException extends RuntimeException {} 
      
       class LinkedList<A> implements Collection<A> { 
           protected class Node { 
               A elt; 
               Node next = null; 
               Node (A elt) { this.elt = elt; } 
           } 
      
          protected Node head = null, tail = null; 
      
           public LinkedList () {} 
      
           public void add (A elt) { 
               if (head == null) { head = new Node(elt); tail = head; }
               else { tail.next = new Node(elt); tail = tail.next; } 
           } 
      
           public Iterator<A> iterator () { 
               return new Iterator<A> () { 
                   protected Node ptr = head; 
                   public boolean hasNext () { return ptr != null; } 
                   public A next () { 
                       if (ptr != null) { 
                           A elt = ptr.elt; ptr = ptr.next; return elt;
                      } else throw new NoSuchElementException (); 
                   } 
               }; 
            } 
       }

通過以上的代碼的實現,我們可能覺得Java泛型和C#都能很好解決以上三個問題。看過下面的一段代碼之后,可能你對Java泛型的會有不同的認識,代碼如下:

public class Program {
    public static void main(String[] args) {        
Class<?> c1 = new ArrayList<String>().getClass();
        Class<?> c2 = new ArrayList<Integer>().getClass();
        if(c1 == c2)
        {
            System.out.println("類型相同");
        }
    }
}
//類型相同

按C#對泛型的理解,泛型實質就是類的模板。我們認為很容易知道類ArrayList<String>和ArrayList<Integer>應該是不同的類型。但是上面的代碼輸出結果,Java運行結果判斷ArrayList<String>和ArrayList<Integer>是相同的類型。

  我們馬上會有疑問,在運行狀態,我們的泛型參數類型信息哪去了?我們的泛型怎么了?對java泛型有一定了解的同學馬上就知道,在運行狀態,我們無法獲取泛型的參數信息。Java的泛型不是真正的泛型,只是編譯器的泛型,不是運行時的泛型。關於為什么會這樣設計,我們就需要去追溯java的歷史。

 Java泛型歷史

  Java泛型是Java 1.5開始加入的語言特性,在Java 1.5之前很長一段時間,Java程序已經在我們身邊的個人設備中運行。為了支持泛型出現以前的運行在Java開發的軟件中的各種庫,Java語言的設計師采取一種折中的方案,Java泛型不僅能向后兼容,而且保證現有的代碼和類文件也合法,仍然保持原來的意思。

 Java泛型實現機制

 Java泛型為了向前兼容,采取運行期類型擦出泛型參數的方式來實現。這就意味着,你在使用泛型的時候,任何具體的類型都已經被擦除。因此,以上的ArrayList<String>和ArrayList<Integer>實際上都恢復到他們的原生類型List,是同一種類型。正確理解Java泛型的類型擦除,能幫我們理解Java泛型中的很多奇怪特性。今天關於泛型就先寫到這里,下一節開始介紹Java泛型的類型的擦除機制引起的很多奇怪泛型特性、Java泛型設計做出的一些彌補。

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM