泛型有什么优点,为什么要使用它们?


87

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


1
stackoverflow.com/questions/77632/…重复。但是简单的答案是,即使集合不是您的API的一部分,我也不喜欢在内部实现中进行不必要的强制转换。
马修·弗拉申

这个问题很相似,但是我认为公认的答案并不能回答这个问题。
汤姆·哈特芬


4
@MatthewFlaschen很奇怪,您的重复内容直接指向了这个问题……矩阵故障!
jcollum

@jcollum,我认为我发表评论的原始问题已合并到该问题。
马修·弗拉申

Answers:


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

9
啊,非常好,我喜欢项目符号清单。我认为这是迄今为止最完整但最简洁的答案。
MrBoJangles

整个解释是在“集合”上下文中进行的。我正在寻找更广阔的视野。有什么想法吗?
jungle_mole 2015年

一个好答案,我只想添加2个其他好处,泛型限制除了1之外还有2个好处。您可以在泛型类型中使用约束类型的属性(例如T:IComparable提供使用CompareTo的位置)2-编写该代码的人您知道执行该操作之后的代码
Hamit YILDIRIM

52

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

而不是创建:

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倍,并且只需要维护一份副本即可。为什么您不想维护更少的代码?

对于必须与其他类进行交互的非集合类(例如aCallable<T>或a )也是如此Reference<T>。您是否真的要扩展Callable<T>Future<T>其他所有关联的类来创建类型安全的版本?

我不。


1
我不确定我是否理解。我不建议您制作“ MyObject”包装纸,这太可怕了。我建议,如果您的对象集合是汽车的集合,那么您应该编写一个Garage对象。它不会有get(car),它会具有诸如buyFuel(100)之类的方法,该方法会购买100美元的燃料并将其分配给最需要的汽车。我说的是真正的业务类别,而不仅仅是“包装”。例如,几乎不应该获取(汽车)或在集合之外循环访问它们-您不要求对象作为其成员,而是要求它为您执行操作。
比尔K 2009年

即使在车库内,也应具有类型安全性。因此,您可以拥有List <Cars>,ListOfCars或在任何地方投射。我认为不必超过所需的最短时间来声明类型。
James Schek 09年

我同意类型安全性会很好,但由于它仅限于车库类,因此它的作用要小得多。然后将其封装并易于控制,所以我的观点是,它以增加新语法的代价为您提供了这一小优势。这就像修改美国宪法,使闯红灯是违法的-是的,它应该永远是违法的,但是否有理由进行修正?我还认为这鼓励人们在这种情况下不要使用封装,这实际上是有害的。
比尔K 2009年

比尔-您可能对不使用封装有一点看法,但是其余的论点比较不好。在这种情况下,学习新语法的成本很小(将其与C#中的lambda进行比较);将此与修改宪法相提并论是没有道理的。宪法无意指定交通代码。这更像是切换到海报板上的Serif-Font打印版本,而不是羊皮纸上的手写副本。含义相同,但更清晰易读。
James Schek 09年

该示例使该概念更加实用。感谢那。
RayLoveless

22

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

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

每次查看泛型时,都会感到头痛。我发现Java最好的部分是它的简单性,最小的语法和泛型并不简单,并且添加了大量的新语法。

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

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

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

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

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

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

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

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

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

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

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会自动装箱到Integer,其值为10。(与相同42)。然后将Integer其扔入Set已知持有Integer的。尝试抛出aString会导致编译错误。

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

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

首先,Set包含的Integers在for-each循环中使用。每个元素都声明为an,int并且由于将Integer取消装箱回到原语而被允许int。而发生这种拆箱众所周知,因为仿制药是用来指定,有这样的事实Integer在举行小号Set

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

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

编辑-没有泛型的示例

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

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

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

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

使用非泛型集合时,输入到集合中的类型是type的对象Object。因此,在这个例子中,Object就是被add编入套。

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

在上述各行中,自动装箱正在起作用-原始int10并将42其自动装箱到Integer对象中,然后将其添加到中Set。但是,请记住,Integer对象是作为Objects处理的,因为没有类型信息可以帮助编译器知道Set应该使用的类型。

for (Object o : set) {

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

但是,由于没有类型信息,因此Setwill将返回an Iterator,后者将返回Setas Objects中的值,这就是为什么在for-each循环中检索的元素必须为type的原因Object

现在Object已从中检索到Set,需要将其强制转换为Integer手动以执行添加:

  total += (Integer)o;

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

Integer现在拆箱成int并允许执行加法入int变量total

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


与增强的for循环的集成非常好(尽管我认为该死的循环已中断,因为我无法对非泛型集合使用完全相同的语法-它应该只使用cast / autobox进行int,它需要的所有信息!),但是您是对的,IDE中的帮助可能很好。我仍然不知道是否足以证明其他语法,但是至少我可以从中受益(这回答了我的问题)。
比尔K 2009年

@Bill K:我添加了一个使用Java 5功能而不使用泛型的示例。
coobird

这是一个好点,我没有使用过(我提到我一直停留在1.4上,并且已经存在了很多年)。我同意存在优势,但是我要得出的结论是:A)它们对那些没有正确使用OO的人来说是相当有利的,而对于那些没有正确使用OO的人来说是比较有利的,以及B)您的优势上面列出的是优点,但即使是很小的语法更改也不足以证明其合理性,更不用说真正使用Java实现的泛型了。但是至少有优势。
比尔K 2009年

15

如果你要搜索的Java bug数据库1.5发布之前,你会发现七倍的错误与NullPointerExceptionClassCastException。因此,查找错误或至少经过一点烟雾测试后仍然存在的错误似乎不是一个很棒的功能。

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

保持对象的集合本身不是坏样式(但是,常见的样式是有效地忽略封装)。而是取决于您在做什么。将泛型传递给“算法”稍微容易一点(在编译时或编译时)。


6
它不仅被记录在案(这本身就是一个巨大的胜利),而且是由编译器强制执行的。
James Schek 09年

很好,但是不是通过将集合包含在定义“此类对象的集合”的类中来实现的吗?
比尔K 2009年

1
詹姆斯:是的,这就是将其记录在代码中的意义所在
Tom Hawtin-大头钉

我认为我不知道使用用于“算法”的集合的好的库(可能暗示您在谈论“函数”,这使我相信它们应该是集合对象中的方法)。我认为JDK几乎总是提取一个干净的数组,该数组是数据的副本,因此不会被弄乱-至少经常发生。
比尔K 2009年

此外,是否没有对象确切描述集合的使用方式并限制其使用更好的代码内文档形式?我发现在泛型中给出的信息在良好的代码中极其多余。
比尔K

11

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

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

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

该接口表示,对于两种类型,Aand B,有一个函数(称为f)接受anA并返回a B当你实现这个接口,A并且B可以是任何你想要的类型,只要你提供一个函数f,是以前者,并返回后者。这是该接口的示例实现:

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泛型的限制的文章。

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

仿制药之前Wereas你可能有类,如Widget延长了FooWidgetBarWidgetBazWidget,与仿制药可以有一个通用类Widget<A>,需要一个FooBarBaz在其构造给你Widget<Foo>Widget<Bar>Widget<Baz>


如果Java没有接口,那么关于子类的一个好点。让FooWidget,BarWidtet和BazWidget全部实现Widget是否正确?我对此可能是错的。但是,如果我错了,那么这确实是一个好点。
比尔K 2009年

为什么需要动态通用类来计算哈希值?具有适当参数的静态函数能否更有效地完成工作?
Ant_222 '18

8

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


这就是它的本质,不是吗?真好
MrBoJangles

好吧,不完全是。它们对于类型安全性很有用,也避免了对引用类型的强制转换,而根本没有装箱/拆箱。
ljs

所以我想接下来的问题是:“为什么我要使用ArrayList?” 而且,冒一个答案,我想说ArrayLists很好,只要您不希望对它们的值进行太多装箱和拆箱。评论?
MrBoJangles

不,它们在.net 2以后已经不存在了;仅用于向后兼容。ArrayLists较慢,类型不安全,需要装箱/拆箱或强制转换。
ljs

如果您不存储值类型(例如int或structs),则使用ArrayList时不会装箱-这些类已经是“对象”。出于安全性的考虑,使用List <T>仍然要好得多,如果您确实要存储对象,则使用List <object>更好。
Wilka

6

泛型的最大好处是代码重用。假设您有许多业务对象,并且您将为每个实体编写非常相似的代码以执行相同的操作。(IE 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方法中,如果给定了不同的类型,则相同服务的重用。真正的优雅!

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


5

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

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

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

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


5
  • 类型化的集合-即使您不想使用它们,也可能不得不从其他库或其他来源来处理它们。

  • 类创建中的泛型键入:

    公共类Foo <T> {public T get()...

  • 避免强制转换-我一直不喜欢这样的事情

    新的Comparator {public int compareTo(Object o){if(o classofIcareAbout)...

实际上,您在检查仅应存在的条件的地方,因为接口是以对象表示的。

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


1
关于避免强制转换的准确性:根据Sun文档,强制转换是发生的,但是它是由编译器完成的。您只是避免在代码中编写强制类型转换。
黛米

也许,我似乎被一连串不愿超过1.4的公司所困,所以谁知道,我可能会在达到5之前退休。我也一直在思考分叉“ SimpleJava”可能是一个有趣的概念--Java将继续添加功能,对于我所见过的许多代码,这不是健康的事情。我已经与无法编写循环的人打交道-我不希望看到他们试图将泛型注入到他们毫无意义的展开循环中。
比尔K

@Bill K:很少有人需要使用仿制药的全部功能。仅在编写本身是通用的类时才需要这样做。
Eddie

5

举一个很好的例子。假设您有一个名为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();
}

我猜你在阅读问题之前已经回答了。这是两个对我没有多大帮助的情况(有问题的描述)。在其他情况下,它是否有用?
比尔K 2009年

4

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

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

这就是为什么你应该使用泛型(或更好的东西)。


我从来不需要复制/粘贴此类内容的重复使用(不确定这种情况怎么发生?),您可以实现一个适合您需要的接口,并使用该接口。不能执行此操作的罕见情况是编写纯实用程序(例如集合)时,对于那些(如我在问题中所述)的应用程序,这从来就不是问题。我确实反对强类型输入-就像您必须使用反射一样。您是否因为无法进行强类型打字而绝对拒绝使用反射?(如果必须使用反射,则将其像集合一样封装)。
比尔K

3

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

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>>)时,确实减少了一些混乱。


我认为例外情况并非如此。这让我开始思考,但是我95%的肯定是完全不使用泛型(以及更清晰的代码)就能得到完全相同的结果。类型信息是对象的一部分,而不是对象的类型。我很想尝试一下-也许明天我会在工作中和您联系。您的帖子列表,并对5篇帖子投赞成票:)如果不是,您可能要考虑由于泛型的简单可用性导致您使代码复杂化而没有任何优势。
比尔K 2009年

的确,这增加了额外的复杂性。但是,我认为它确实有一些用处。问题是您不能Throwable从具有特定已声明异常的方法主体中抛出普通旧文本。替代方法是编写单独的方法以返回每种异常类型,或者使用返回a的非泛型方法自己进行转换Throwable。前者太冗长且非常无用,而后者将无法从编译器获得任何帮助。通过使用泛型,编译器将自动为您插入正确的强制类型转换。因此,问题是:这值得复杂吗?
jigawot

知道了 因此,它避免了演员阵容的出现……好点。我欠你5
比尔ķ

2

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

这样,您可以执行以下操作:

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

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


如果有选择,我宁愿不投也不愿。
MrBoJangles

强类型化很容易是泛型imho的最佳方面,特别是考虑到允许的编译时类型检查。
ljs

2

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


2

我知道这是一个C#问题,但泛型也用在其他语言中,并且它们的用途/目标非常相似。

从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,这很有用。


2

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

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


我从来没有发现过这样的情况:集合的通用类型会帮助我的代码。我的收藏范围狭窄。这就是为什么我说“可以帮助我吗?”的原因。不过,作为附带的问题,您是说在开始使用泛型之前偶尔会发生这种情况吗?(将错误的对象类型放入集合中)。在我看来,这似乎是没有实际问题的解决方案,但也许我很幸运。
比尔K 2009年

@Bill K:如果代码受到严格控制(即仅由您使用),也许您永远不会遇到这个问题。那很棒!最大的风险在于由多人共同参与的大型项目中。这是一个安全网,是“最佳实践”。
黛咪

@Bill K:这是一个例子。假设您有一个返回ArrayList的方法。假设ArrayList不是通用集合。某人的代码使用此ArrayList并尝试对其项目执行某些操作。唯一的问题是您用BillTypes填充了ArrayList,而使用者正在尝试对ConsumerTypes进行操作。这样可以编译,但是在运行时会崩溃。如果使用泛型集合,则会出现编译器错误,这要好得多。
黛米

@Bill K:我曾与技能水平差异很大的人一起工作。我无法选择与谁共享项目。因此,类型安全性对我来说非常重要。是的,通过将代码转换为使用泛型,我已经发现(并纠正了)错误。
Eddie

1

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

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


1

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

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


1

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


1

使用泛型(尤其是集合/列表)的另一个优点是您可以获得编译时类型检查。当使用通用列表而不是对象列表时,这真的很有用。


1

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

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

相对于

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

作为一个简单的例子。


输入安全性,本身就是一个讨论。也许有人应该问一个问题。
MrBoJangles

是的,但是您在暗示什么?类型安全的重点?
Vin

1

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

这对您有几个好处:

  • 因为编译器对您要执行的操作了解更多,所以它可以省略很多类型转换,因为它已经知道该类型将兼容。

  • 这也可以使您获得有关程序校正的早期反馈。以前在运行时会失败的事情(例如,因为无法将对象强制转换为所需的类型),现在在编译时失败了,您可以在测试部门提交神秘的错误报告之前纠正错误。

  • 编译器可以进行更多优化,例如避免装箱等。


1

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

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

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


1

我在使用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
}

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

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


1

已经提到了诸如“类型安全”和“无强制转换”之类的明显好处,因此也许我可以谈谈其他一些“好处”,希望对我有帮助。

首先,泛型是一个与语言无关的概念,而IMO,如果您同时考虑常规(运行时)多态性,则可能更有意义。

例如,我们从面向对象的设计中了解到的多态性具有运行时概念,即在调用者对象随程序执行时在运行时确定,并根据运行时类型相应地调用相关方法。在泛型中,这个想法有些相似,但是所有事情都是在编译时发生的。这是什么意思,以及您如何利用它?

(让我们坚持使用通用方法来保持紧凑)这意味着您仍然可以在单独的类上使用相同的方法(就像您以前在多态类中所做的一样),但是这次它们由编译器自动生成,取决于设置的类型在编译时。您可以根据编译时给出的类型对方法进行参数化。因此,不必像在运行时多态性(方法重写)中那样为每种类型从头开始编写方法,而应让编译器在编译期间完成工作。这具有明显的优势,因为您无需推断系统中可能使用的所有可能的类型,这无需更改代码即可使其具有更高的可伸缩性。

类的工作方式几乎相同。您对类型进行参数化,并且代码由编译器生成。

一旦有了“编译时间”的概念,就可以使用“有界”类型,并限制可以通过类/方法作为参数化类型传递的内容。因此,您可以控制要传递的内容是有力的,尤其是您的框架正在被其他人使用。

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

现在没有人可以设置MyObject。

另外,您可以在方法参数上“强制”类型约束,这意味着您可以确保两个方法参数都依赖于同一类型。

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

希望所有这些都有意义。



0

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

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
没有泛型,您可以执行以下操作:列出stuffList = getStuff(); for(对象东西:stuffList){((东西)东西.doStuff(); },没有太大区别。
Tom Hawtin-大头钉

没有泛型,我会做stuffList.doAll(); 我说过,我总是有一个包装器类,它正是这样的代码的地方。否则,如何避免将此循环复制到其他地方?您将其放在哪里重复使用?包装器类可能会有某种形式的循环,但是在该类中,由于该类全都与“东西”有关,因此使用类似上面汤姆建议的强制转换for循环是很清楚的/可以自行记录。
比尔K

@Jherico,那真的很有趣。我想知道您是否有这样的感觉,而这正是我不明白的原因,即人的天性中有某种东西会出于某种原因发现任何铸件,并且出于某种原因淫秽并愿意走不自然的长度(例如添加更复杂的语法)来避免这种情况。为什么是“如果必须投放某些东西,您已经迷路了”?
比尔K

@Jherico:根据Sun文档,泛型为编译器提供了将对象强制转换为什么的知识。由于编译器执行的是泛型而不是用户的强制转换,这是否会更改您的语句?
黛咪

我已经将代码从Java 1.5之前的版本转换为泛型,并且在此过程中发现了将错误的对象类型放入集合中的错误。我也属于“如果您必须丢掉您已经迷路了”阵营,因为如果您必须手动丢掉,您将冒ClassCastException的风险。泛型的编译器就看不见你,但一个ClassCastException的几率方式由于类型安全减少。是的,如果没有泛型,则可以使用对象,但这对我来说是一种可怕的代码味道,与“引发异常”相同。
Eddie

0

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


0
  1. 使程序员能够实现泛型算法-通过使用泛型,程序员可以实现可在不同类型的集合上工作,可以自定义并且类型安全且易于阅读的泛型算法。

  2. 编译时进行更严格的类型检查-Java编译器对通用代码进行强类型检查,如果代码违反类型安全性,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到。

  3. 消除演员阵容。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.