泛型最酷的地方是什么,为什么要使用它们呢?

时间:2020-03-05 18:57:19  来源:igfitidea点击:

我想我会把这个垒球提供给任何想从公园里踢出去的人。什么是泛型,泛型的优点是什么,为什么,在哪里,应该如何使用它们?请保持基本。谢谢。

解决方案

回答

泛型允许我们创建强类型化的对象,但不必定义特定类型。我认为最有用的示例是List和类似的类。

使用通用列表,我们可以随心所欲拥有一个列表列表,并且我们始终可以引用强类型,而不必进行转换或者类似数组或者标准列表的操作。

回答

泛型避免了装箱和拆箱对性能的影响。基本上,请看ArrayList vs List <T>。两者都具有相同的核心功能,但是List <T>会快很多,因为我们不必对对象进行装箱。

回答

泛型使我们可以对应该包含任何对象的对象和数据结构使用强类型。从通用结构(装箱/拆箱)中检索对象时,它还消除了繁琐而昂贵的类型转换。

一个同时使用两者的示例是一个链表。如果只能使用对象Foo,则链表类有什么好处?要实现可以处理任何类型对象的链表,如果我们希望链表仅包含一种类型的对象,则链表和假设节点内部类中的节点必须是通用的。

回答

如果集合包含值类型,则在将它们插入到集合中时,它们不需要对对象进行装箱/拆箱操作,因此性能会大大提高。诸如reshaper之类的很酷的插件可以为我们生成更多代码,例如foreach循环。

回答

我喜欢它们是因为它们为我们提供了一种定义自定义类型的快速方法(无论如何,我还是会使用它们)。

因此,例如,不必定义由字符串和整数组成的结构,然后必须实现关于如何访问这些结构的数组的整套对象和方法等等,我们只需制作一个Dictionary

Dictionary<int, string> dictionary = new Dictionary<int, string>();

而编译器/ IDE将完成其余的繁重工作。特别是使用Dictionary,我们可以将第一种类型用作键(没有重复值)。

回答

  • 允许我们编写代码/使用类型安全的库方法,即保证List <string>是字符串列表。
  • 由于使用了泛型,因此编译器可以对代码进行编译时检查,以确保类型安全,即我们是否试图将int放入该字符串列表中?使用ArrayList会导致该错误不太透明。
  • 它比使用对象要快,因为它可以避免装箱/拆箱(.net必须将值类型转换为引用类型,反之亦然),也可以将对象强制转换为所需的引用类型。
  • 允许我们编写适用于许多具有相同基础行为的类型的代码,即Dictionary <string,int>使用与Dictionary <DateTime,double>相同的基础代码;使用泛型,框架团队也只需要编写一段代码就可以达到上述两个优点。

回答

正如Mitchel指出的那样,主要优点是无需定义多个类即可进行强类型化。

这样,我们可以执行以下操作:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

如果没有泛型,则必须将blah [0]强制转换为正确的类型才能访问其功能。

回答

无论如何,jvm强制转换...它隐式创建将通用类型视为"对象"的代码,并针对所需的实例创建强制转换。 Java泛型只是语法糖。

回答

使用泛型(尤其是集合/列表)的另一个优点是我们可以进行编译时类型检查。当使用通用列表而不是对象列表时,这确实很有用。

回答

最主要的原因是它们提供了类型安全性

List<Customer> custCollection = new List<Customer>;

相对于

object[] custCollection = new object[] { cust1, cust2 };

作为一个简单的例子。

回答

总而言之,泛型使我们可以更精确地指定要执行的操作(更强的键入)。

这对我们有几个好处:

  • 由于编译器了解更多有关我们要做什么的信息,因此它可以省略很多类型转换,因为它已经知道该类型将兼容。
  • 这也可以使我们获得有关程序校正的早期反馈。以前在运行时会失败的事情(例如,因为无法将对象强制转换为所需的类型),现在在编译时失败了,我们可以在测试部门提交神秘的错误报告之前修复错误。
  • 编译器可以进行更多优化,例如避免装箱等。

回答

要添加/扩展的几件事(从.NET角度而言):

泛型类型使我们可以创建基于角色的类和接口。这已经用更基本的术语说过了,但是我发现我们开始使用以与类型无关的方式实现的类来设计代码,这导致了高度可重用的代码。

方法的通用论点可以做同样的事情,但是它们也有助于将"告诉不问"原理应用于转换,即"给我我想要的,如果不能,请告诉我原因"。

回答

我知道这是一个Cquestion,但泛型也用于其他语言,并且它们的用途/目标非常相似。

从Java 1.5开始,Java集合使用泛型。因此,使用它们的一个好地方是在创建自己的类似集合的对象时。

我几乎在每个地方都可以看到一个示例,Pair类具有两个对象,但是需要以通用的方式处理这些对象。

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}

每当使用此Pair类时,我们都可以指定要处理的对象类型,并且任何类型转换问题都将在编译时(而不是运行时)显示。

泛型也可以使用关键字" super"和" extends"来定义范围。例如,如果要处理通用类型,但要确保它扩展了名为Foo的类(具有setTitle方法):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

尽管它本身并不是很有趣,但是知道每当我们处理FooManager时,我们都知道它将处理MyClass类型,并且MyClass扩展了Foo,这很有用。

回答

我曾经在这个话题上发表过演讲。我们可以在http://www.adventuresinsoftware.com/generics/上找到我的幻灯片,代码和音频记录。

回答

泛型的最大好处是代码重用。假设我们有很多业务对象,并且我们将为每个实体编写非常相似的代码以执行相同的操作。 (即Linq to SQL操作)。

使用泛型,我们可以创建一个类,该类将能够给定从给定基类继承的任何类型,或者实现给定接口的操作,如下所示:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

请注意,在上面的DoSomething方法中,如果给定了不同的类型,则相同服务的重用。真正的优雅!

在工作中使用泛型还有很多其他重要的原因,这是我的最爱。

回答

  • 类型化的集合-即使我们不想使用它们,也可能不得不从其他库或者其他来源处理它们。
  • 类创建中的泛型键入:公共类Foo <T> {public T get()...
  • 避免强制转换-我一直不喜欢new Comparator {public int compareTo(Object o){if(o instanceof classIcareAbout)...

本质上,我们在检查仅应存在的条件,因为接口是以对象表示的。

我对仿制药的最初反应类似于"太混乱,太复杂"。我的经验是,使用它们一段时间后,我们会习惯它们,没有它们的代码感觉不太清晰,也不太舒服。除此之外,java世界的其余部分都使用它们,因此我们最终将不得不使用该程序,对吗?

回答

我在使用SpringORM和Hibernate的GenericDao中使用了它们,例如

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

通过使用泛型,我对该DAO的实现迫使开发人员仅通过将GenericDao子类化即可将它们仅为其设计的实体传递给他们。

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

我的小框架更加健壮(具有过滤,延迟加载,搜索之类的功能)。我只是在这里简化了一个例子

我像史蒂夫和你一样,在一开始就说"太凌乱和复杂",但现在我看到了它的优势

回答

使用泛型进行收集非常简单明了。即使我们在其他任何地方都花钱,从收藏中获得的收益对我来说也是一个胜利。

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

或者

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

仅凭这一点就值得仿制药的边际"成本",而且我们不必成为仿制药的专家就可以使用它并获得价值。

回答

如果要在1.5版本发布之前搜索Java错误数据库,则使用NullPointerException的错误将是ClassCastException的七倍。因此,查找错误或者至少经过一点烟雾测试后仍然存在的错误似乎不是一个很棒的功能。

对我而言,泛型的巨大优势在于它们可以在代码中记录重要的类型信息。如果我不希望将类型信息记录在代码中,那么我将使用动态类型的语言,或者至少使用具有更多隐式类型推断的语言。

保持对象的集合本身不是坏样式(但是常见的样式是有效地忽略封装)。而是取决于我们在做什么。将泛型传递给"算法"会更容易检查(在编译时或者在编译时)。

回答

举一个很好的例子。假设我们有一个名为Foo的课程

public class Foo
{
   public string Bar() { return "Bar"; }
}

例子1
现在,我们要具有Foo对象的集合。我们有两个选项,LIst或者ArrayList,这两个选项的工作方式相似。

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

在上面的代码中,如果我尝试添加FireTruck类而不是Foo,则ArrayList将添加它,但是Foo的通用列表将引发异常。

例子二。

现在,我们有了两个数组列表,并且要在每个列表上调用Bar()函数。由于ArrayList充满了对象,因此必须先强制​​转换它们才能调用bar。但是由于Foo的通用列表只能包含Foos,因此我们可以直接在它们上调用Bar()。

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

回答

从Sun Java文档中,响应"我为什么要使用泛型?":

"泛型为我们提供了一种将集合类型与编译器进行通信的方式,以便可以对其进行检查。一旦编译器知道集合的元素类型,编译器便可以检查我们是否一致地使用了该集合并可以插入正确地对从集合中取出的值进行强制转换...使用泛型的代码更清晰,更安全....编译器可以在编译时验证在运行时不违反类型约束[强调我的]。程序编译时没有警告,我们可以肯定地说它不会在运行时引发ClassCastException。使用泛型的最终效果是提高可读性和鲁棒性,尤其是在大型程序中。

回答

我真的很讨厌重复自己。我讨厌经常输入相同的东西。我不喜欢在有些许差异的情况下多次重述。

而不是创建:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

我可以建立一个可重用的类...(如果出于某种原因我们不想使用原始集合)

class MyList<T> {
   T get(int index) { ... }
}

现在,我的效率提高了3倍,并且只需要维护一份副本即可。为什么我们不想维护更少的代码?

对于非集合类(例如必须与其他类进行交互的Callable <T>或者Reference <T>)也是如此。我们是否真的要扩展Callable <T>和Future <T>以及所有其他相关类以创建类型安全的版本?

我不。

回答

泛型还使我们能够创建更多可重用的对象/方法,同时仍提供特定于类型的支持。在某些情况下,我们还会获得很多性能。我不了解Java泛型的完整规范,但是在.NET中,我可以在Type参数上指定约束,例如Implements a Interface,Constructor和Derivation。

回答

不要忘记,泛型不仅被类使用,它们也可以被方法使用。例如,使用以下代码段:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

它很简单,但是可以非常优雅地使用。令人高兴的是,该方法返回的结果与给出的结果相同。当我们处理需要重新抛出给调用者的异常时,这会有所帮助:

...
} catch (MyException e) {
    throw logAndReturn(e);
}

关键是,我们不会通过传递方法来丢失类型。我们可以抛出正确类型的异常,而不仅仅是抛出" Throwable",这就是没有泛型时我们可以做的所有事情。

这只是通用方法的一种简单使用示例。泛型方法还可以做很多其他的整洁的事情。在我看来,最酷的是使用泛型进行类型推断。请看以下示例(摘自Josh Bloch的Effective Java 2nd Edition):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

这并不能起到很多作用,但是当泛型类型很长(或者嵌套;即Map <String,List <String >>`)时,确实可以减少混乱。

回答

不需要类型转换是Java泛型的最大优点之一,因为它将在编译时执行类型检查。这将减少在运行时抛出ClassCastException的可能性,并可以导致更健壮的代码。

但是我怀疑我们完全意识到这一点。

Every time I look at Generics it gives
  me a headache. I find the best part of
  Java to be it's simplicity and minimal
  syntax and generics are not simple and
  add a significant amount of new
  syntax.

起初,我也没有看到泛型的好处。我从1.4语法开始学习Java(即使当时还没有Java 5),当我遇到泛型时,我觉得要编写的代码更多,我真的不明白这样做的好处。

现代IDE使使用泛型编写代码更加容易。

大多数现代的,体面的IDE都足够聪明,可以协助使用泛型编写代码,尤其是代码完成。

这是一个用HashMap制作Map <String,Integer>的例子。我必须输入的代码是:

Map<String, Integer> m = new HashMap<String, Integer>();

确实,要创建一个新的" HashMap",要输入很多内容。但是,实际上,我只需要在Eclipse知道我需要什么之前键入以下内容即可:

Map <String,Integer> m = new Ha`Ctrl + Space

没错,我确实需要从候选列表中选择HashMap,但是基本上IDE知道要添加的内容,包括通用类型。使用正确的工具,使用泛型还不错。

另外,由于类型是已知的,因此从通用集合中检索元素时,IDE会像该对象已经是其声明类型的对象一样起作用-无需强制IDE强制转换即可知道该对象的类型是什么。 。

泛型的主要优势来自其与Java 5新功能的良好配合方式。这是一个将整数扔进Set中并计算其总数的示例:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

在这段代码中,提供了三个Java 5新功能:

  • 泛型
  • 自动装箱和拆箱
  • 每个循环

首先,原语的泛型和自动装箱允许以下几行:

set.add(10);
set.add(42);

整数" 10"将自动装箱到值为" 10"的"整数"中。 (与" 42"相同)。然后,将" Integer"扔入已知持有" Integer"的" Set"中。尝试抛出String会导致编译错误。

接下来,for-each循环将所有这三个条件都采用:

for (int i : set) {
  total += i;
}

首先,在for-each循环中使用包含" Integer"的" Set"。每个元素都声明为一个" int",并且由于" Integer"被取消装箱回到原始" int",因此被允许。并且这种拆箱操作发生的事实是众所周知的,因为使用泛型来指定Set中保存有Integer。

泛型可以是将Java 5中引入的新功能整合在一起的粘合剂,它只是使编码更简单,更安全。而且大多数时候,IDE足够聪明,可以为我们提供良好的建议,因此,通常来说,键入的内容不会太多。

坦率地说,从"设置"示例中可以看出,我觉得利用Java 5功能可以使代码更加简洁和健壮。

编辑没有泛型的示例

下面是上面的" Set"示例的说明,没有使用泛型。可能,但并不完全令人满意:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(注意:以上代码将在编译时生成未经检查的转换警告。)

使用非泛型集合时,输入到集合中的类型是"对象"类型的对象。因此,在此示例中,将"对象"添加到集合中。

set.add(10);
set.add(42);

在上述各行中,自动装箱正在起作用-原始的" int"值" 10"和" 42"被自动装箱到"整数"对象中,这些对象被添加到"集合"中。但是,请记住,由于没有类型信息可以帮助编译器知道Set应该期望的类型,因此Integer对象被当作Object来处理。

for (Object o : set) {

这是至关重要的部分。 for-each循环起作用的原因是因为Set实施了Iterable接口,该接口返回带有类型信息(如果存在)的Iterator。 (即Iterator <T>`。)

但是,由于没有类型信息,因此Set会返回Iterator,它将It Set中的值作为Object返回,这就是为什么在for-each循环中要检索的元素必须类型为"对象"。

现在,从"集合"中检索"对象",需要将其手动转换为"整数"以执行添加操作:

total += (Integer)o;

在此,从"对象"到"整数"执行类型转换。在这种情况下,我们知道这将始终有效,但是手动类型转换始终使我感到这是易碎的代码,如果在其他位置进行较小的更改,则可能会损坏代码。 (我觉得每个类型转换都是一个等待发生的ClassCastException,但我离题了...)

现在,将"整数"解包到"整数"中,并允许将其执行到"整数"变量"总数"中的加法运算。

我希望我能说明Java 5的新功能可以与非泛型代码一起使用,但是它不像使用泛型编写代码那样简洁明了。而且,我认为,要充分利用Java 5的新功能,应该研究泛型,如果至少允许编译时检查,以防止无效的类型转换在运行时引发异常。

回答

我们是否从未编写过方法(或者类),其中方法/类的关键概念未紧密绑定到参数/实例变量的特定数据类型(请考虑链接列表,max / min函数,二进制搜索, 等等。)。

我们是否从未希望过可以重用算法/代码,而不必求助于n-paste重用或者损害强类型(例如,我想要一个String的" List",而不是我希望是字符串的" List")! )?

这就是为什么我们应该使用泛型(或者更好的东西)的原因。

回答

Java中的泛型促进了参数多态性。通过类型参数,可以将参数传递给类型。正如像String foo(String s)这样的方法不仅针对特定字符串,而且针对任何字符串s都对某些行为进行建模一样,像List <T>这样的类型也对某些行为进行建模,而不仅针对特定类型,但适用于任何类型。 List <T>表示,对于任何类型的T,都有一种类型为List的List,其元素为T。所以List实际上是一个类型构造函数。它以一个类型作为参数,并构造另一个类型作为结果。

这是我每天使用的泛型类型的几个示例。首先,一个非常有用的通用接口:

public interface F<A, B> {
  public B f(A a);
}

这个接口说,对于某些两个类型," A"和" B",有一个函数(称为" f"),该函数取一个" A"并返回一个" B"。当我们实现此接口时,只要提供提供前者并返回后者的函数" f"," A"和" B"就可以是我们想要的任何类型。这是该接口的示例实现:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

在泛型之前,通过使用extends关键字进行子类化来实现多态。使用泛型,我们实际上可以消除子类化,而可以使用参数多态性。例如,考虑用于计算任何类型的哈希码的参数化(通用)类。而不是重写Object.hashCode(),我们将使用如下通用类:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

这比使用继承要灵活得多,因为我们可以坚持使用合成和参数多态的主题,而不必锁定脆弱的层次结构。

Java的泛型虽然不是完美的。例如,我们可以对类型进行抽象,但不能对类型构造函数进行抽象。也就是说,我们可以说"对于任何类型T",但不能说"对于任何采用类型参数A的类型T"。

我在这里写了一篇关于Java泛型的限制的文章。

泛型的一个巨大胜利就是它们可以避免子类化。子类化往往会导致脆弱的类层次结构难以扩展,并且很难在不查看整个层次结构的情况下单独理解类。

在泛型之前,我们可能会拥有诸如由FooWidgetBarWidgetBazWidget扩展的Widget之类的类,有了泛型,我们可以拥有一个采用Foo,Bar或者Baz在其构造函数中,以提供Widget &lt;Foo>,Widget <Bar>和Widget <Baz>。