• 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泛型设计做出的一些弥补。

       

  • 相关阅读:
    #computer architecture#如何设计一个处理器4
    #computer architecture#如何设计一个处理器3
    #computer architecture#如何设计一个处理器2
    #computer architecture#指令集学习
    seq2seq聊天模型(三)—— attention 模型
    sequence_loss的解释
    seq2seq聊天模型(二)——Scheduled Sampling
    seq2seq聊天模型(一)
    rxjs与vue
    rxjs——subject和Observable的区别
  • 原文地址:https://www.cnblogs.com/Johnnie/p/4025008.html
Copyright © 2020-2023  润新知