泛型有啥好处,为啥要使用它们?
Posted
技术标签:
【中文标题】泛型有啥好处,为啥要使用它们?【英文标题】:What is cool about generics, why use them?泛型有什么好处,为什么要使用它们? 【发布时间】:2010-09-09 19:25:31 【问题描述】:我想我会把这个垒球提供给任何想把它打出公园的人。什么是泛型,泛型的优点是什么,为什么,在哪里,如何使用它们?请保持相当基本。谢谢。
【问题讨论】:
***.com/questions/77632/… 的副本。但简单的回答,即使集合不是您 API 的一部分,我也不喜欢在内部实现中进行不必要的强制转换。 这个问题很相似,但我认为接受的答案不能回答这个问题。 也结帐***.com/questions/520527 @MatthewFlaschen 很奇怪,你的副本指向这个问题......矩阵出现故障! 正如@Ijs 提到的 -write code which is applicable to many types with the same underlying behavior
【参考方案1】:
允许您编写代码/使用类型安全的库方法,即保证 List【讨论】:
啊,非常好,我确实喜欢项目符号列表。我认为这是迄今为止最完整但最简洁的答案。 整个解释都在“集合”上下文中。我正在寻找更广泛的外观。有什么想法吗? 好答案我只想添加 2 个其他优点,除 1 之外,泛型限制还有 2 个好处 - 您可以在泛型类型中使用受约束类型的属性(例如,其中 T : IComparable 提供使用 CompareTo ) 2- 写代码的人在你知道后会做什么【参考方案2】:我真的很讨厌重复自己。我讨厌比我必须更频繁地输入相同的东西。我不喜欢多次重复有细微差别的事情。
而不是创建:
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>
以及所有其他关联的类来创建类型安全的版本吗?
我没有。
【讨论】:
我不确定我是否理解。我不是建议你制作“MyObject”包装器,那太可怕了。我建议如果您的对象集合是 Cars 的集合,那么您编写一个 Garage 对象。它不会有 get(car),它会有像 buyFuel(100) 这样的方法,可以购买 100 美元的燃料并将其分配给最需要它的汽车。我说的是真正的商务课程,而不仅仅是“包装”。例如,您几乎不应该在集合之外获取(汽车)或循环它们——您不会向对象询问它的成员,而是要求它为您执行操作。 即使在你的车库里,你也应该有类型安全。所以要么你有 List不需要类型转换是 Java 泛型的最大优势之一,因为它会在编译时执行类型检查。这将减少ClassCastException
s 在运行时被抛出的可能性,并且可以产生更健壮的代码。
但我怀疑你完全知道这一点。
每次我查看泛型时,它都会给出 我头疼。我发现最好的部分 Java 是它的简单性和最小化 语法和泛型并不简单 添加大量新的 句法。
起初,我也没有看到泛型的好处。我从 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+空格
没错,我确实需要从候选列表中选择 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 特性:
Generics Autoboxing and unboxing For-each loop首先,泛型和基元的自动装箱允许以下行:
set.add(10);
set.add(42);
整数10
被自动装箱成一个Integer
,其值为10
。 (42
也一样)。然后Integer
被扔进已知持有Integer
s 的Set
。尝试抛出 String
会导致编译错误。
接下来,for-each 循环采用所有这三个:
for (int i : set)
total += i;
首先,包含Integer
s 的Set
用于for-each 循环。每个元素都被声明为int
,这是允许的,因为Integer
被拆箱回原语int
。并且这种拆箱发生的事实是众所周知的,因为泛型用于指定 Integer
s 保存在 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;
(注意:以上代码会在编译时产生未经检查的转换警告。)
使用非泛型集合时,输入到集合中的类型是Object
类型的对象。因此,在本例中,Object
就是 add
编入集合中的内容。
set.add(10);
set.add(42);
在上面几行中,自动装箱正在发挥作用——原始 int
值 10
和 42
被自动装箱到 Integer
对象中,这些对象被添加到 Set
中。但是,请记住,Integer
对象被作为Object
s 处理,因为没有类型信息可以帮助编译器知道Set
应该期望什么类型。
for (Object o : set)
这是至关重要的部分。 for-each 循环起作用的原因是Set
实现了Iterable
接口,该接口返回带有类型信息的Iterator
(如果存在)。 (即Iterator<T>
。)
但是,由于没有类型信息,Set
将返回一个Iterator
,它将Set
中的值返回为Object
s,这就是为什么在 for-每个循环必须为Object
类型。
现在Object
是从Set
中检索到的,需要手动将其转换为Integer
以执行添加:
total += (Integer)o;
在这里,从Object
到Integer
执行类型转换。在这种情况下,我们知道这将始终有效,但是手动类型转换总是让我觉得它是脆弱的代码,如果在其他地方进行微小的更改可能会损坏。 (我觉得每个类型转换都是一个等待发生的ClassCastException
,但我离题了......)
Integer
现在被拆箱成int
并允许执行添加到int
变量total
。
我希望我可以说明 Java 5 的新特性可以与非泛型代码一起使用,但它不像使用泛型编写代码那样干净和直接。而且,在我看来,要充分利用 Java 5 中的新特性,应该研究泛型,至少允许编译时检查以防止无效类型转换在运行时引发异常。
【讨论】:
与增强的 for 循环的集成非常好(尽管我认为该死的循环被破坏了,因为我不能对非泛型集合使用完全相同的语法——它应该只使用cast/autobox to int,它有它需要的所有信息!),但你是对的,IDE 中的帮助可能很好。我仍然不知道这是否足以证明额外的语法是合理的,但至少这是我可以从中受益的(这回答了我的问题)。 @Bill K:我添加了一个使用 Java 5 特性而不使用泛型的示例。 这是一个很好的观点,我没有使用过(我提到我已经被卡住了,在 1.4 和早期的几年里)。我同意这是有优势的,但我得出的结论是 A)它们往往对不正确使用 OO 的人非常有利,而对那些使用 OO 的人来说用处不大,以及 B)你的优势上面列出了一些优点,但几乎不足以证明即使是很小的语法更改也是合理的,更不用说真正理解 Java 中实现的泛型所需的大更改了。但至少有优势。【参考方案4】:如果您在 1.5 发布之前搜索 Java 错误数据库,您会发现 NullPointerException
的错误是 ClassCastException
的七倍。因此,查找错误,或者至少在经过少量冒烟测试后仍然存在的错误似乎并不是一个很棒的功能。
对我来说,泛型的巨大优势在于它们在代码中记录了重要的类型信息。如果我不想在代码中记录类型信息,那么我会使用动态类型语言,或者至少是一种具有更隐式类型推断的语言。
将对象的集合保留给自己并不是一种糟糕的风格(但常见的风格是有效地忽略封装)。这取决于你在做什么。将集合传递给“算法”使用泛型更容易检查(在编译时或之前)。
【讨论】:
不仅记录在案(这本身就是一个巨大的胜利),而且由编译器强制执行。 好点,但这不也是通过将集合包含在定义“此类对象的集合”的类中来实现的吗? James:是的,这就是它被记录在代码中的重点。 我想我不知道有哪些优秀的库将集合用于“算法”(可能暗示您正在谈论“函数”,这让我相信它们应该是集合对象中的一种方法)。我认为 JDK 几乎总是提取一个漂亮、干净的数组,它是数据的副本,这样它就不会被弄乱——至少经常是这样。 另外,没有一个对象来准确描述集合的使用方式并限制它使用更好的代码内文档形式吗?我发现泛型中给出的信息在好的代码中是非常多余的。【参考方案5】:Java 中的泛型有助于parametric polymorphism。通过类型参数,您可以将参数传递给类型。就像String foo(String s)
这样的方法模拟一些行为,不仅针对特定字符串,而且针对任何字符串s
,所以像List<T>
这样的类型模拟一些行为,不仅针对特定类型,而是为任何类型。 List<T>
表示对于任何类型T
,都有一个类型为List
,其元素是T
s。所以List
实际上是一个类型构造函数。它将一个类型作为参数并构造另一个类型作为结果。
这里有几个我每天使用的泛型类型的例子。首先,一个非常有用的通用接口:
public interface F<A, B>
public B f(A a);
这个接口表示对于某些两种类型,A
和B
,有一个函数(称为f
)接受一个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”。
I wrote an article about these limits of Java generics, here.
泛型的一个巨大优势是它们可以让您避免子类化。子类化往往会导致难以扩展的脆弱类层次结构,以及不查看整个层次结构就难以单独理解的类。
在泛型之前,您可能有 FooWidget
、BarWidget
和 BazWidget
扩展的类,如 Widget
,使用泛型,您可以有一个泛型类 Widget<A>
,它采用 Foo
、@987654349 @ 或 Baz
在其构造函数中为您提供 Widget<Foo>
、Widget<Bar>
和 Widget<Baz>
。
【讨论】:
如果 Java 没有接口,那么子类化将是一个很好的观点。让 FooWidget、BarWidtet 和 BazWidget 都实现 Widget 不是正确的吗?不过,我可能错了。但如果我错了,这是一个非常好的观点。 为什么需要动态泛型类来计算哈希值?带有适当参数的静态函数不会更有效地工作吗?【参考方案6】:泛型避免了装箱和拆箱对性能的影响。基本上,看看 ArrayList 与 List
【讨论】:
这就是它的本质,不是吗?不错。 不完全是;它们对于类型安全很有用 + 也避免了引用类型的强制转换,而根本没有装箱/拆箱。 所以我猜接下来的问题是,“我为什么要使用 ArrayList?”而且,冒着回答的风险,我会说 ArrayLists 很好,只要你不希望对它们的值进行太多的装箱和拆箱。评论? 不,它们在 .net 2 中已经失效;仅对向后兼容有用。 ArrayList 速度较慢,类型不安全,需要装箱/拆箱或强制转换。 如果您不存储值类型(例如整数或结构),则使用 ArrayList 时没有装箱 - 类已经是“对象”。由于安全类型,使用 List泛型的最大好处是代码重用。假设您有很多业务对象,并且您将为每个实体编写非常相似的代码来执行相同的操作。 (I.E 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 方法中给定不同类型的相同服务的重用。真优雅!
在你的工作中使用泛型还有很多其他重要的理由,这是我最喜欢的。
【讨论】:
【参考方案8】:我只是喜欢它们,因为它们为您提供了一种快速定义自定义类型的方法(因为我仍然使用它们)。
因此,例如,您不必定义由字符串和整数组成的结构,然后必须实现一整套对象和方法,以了解如何访问这些结构的数组等等,您可以只创建一个 Dictionary
Dictionary<int, string> dictionary = new Dictionary<int, string>();
编译器/IDE 会完成其余的繁重工作。尤其是 Dictionary 允许您使用第一种类型作为键(无重复值)。
【讨论】:
【参考方案9】:类型化集合 - 即使您不想使用它们,您也可能不得不从其他库、其他来源处理它们。
类创建中的通用类型:
公共类 Foo
避免选角——我一直不喜欢像
这样的东西新的比较器 公共 int compareTo(对象 o) if (o instanceof classIcareAbout)...
您实际上是在检查一个只应该存在的条件,因为接口是用对象表示的。
我对泛型的最初反应与你的相似——“太混乱、太复杂”。我的经验是,在使用它们一段时间后,你会习惯它们,没有它们的代码感觉不太明确,而且不太舒服。除此之外,java 世界的其他地方都在使用它们,所以你最终将不得不使用这个程序,对吧?
【讨论】:
准确地避免强制转换:根据 Sun 文档,会发生强制转换,但它是由编译器完成的。您只需避免在代码中编写强制转换。 也许,我似乎被困在一长串不愿超过 1.4 的公司中,所以谁知道呢,我可能会在达到 5 之前退休。我最近也一直在想,分叉一个“SimpleJava”可能是一个有趣的概念——Java 将不断添加功能,而对于我见过的很多代码来说,这不是一件健康的事情。我已经和那些不会编写循环代码的人打交道了——我不想看到他们试图将泛型注入到他们毫无意义的展开循环中。 @Bill K:很少有人需要使用泛型的全部力量。仅当您编写本身是泛型的类时才需要这样做。【参考方案10】:举一个很好的例子。想象一下,你有一个名为 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 的 Generic List 会导致抛出异常。
示例二。
现在您有了两个数组列表,并且您想在每个列表上调用 Bar() 函数。由于 hte ArrayList 中充满了对象,因此您必须先转换它们,然后才能调用 bar。但是由于 Foo 的 Generic List 只能包含 Foos,因此您可以直接在它们上调用 Bar()。
foreach(object o in al)
Foo f = (Foo)o;
f.Bar();
foreach(Foo f in fl)
f.Bar();
【讨论】:
我猜你在阅读问题之前已经回答了。这两个案例对我没有多大帮助(有问题描述)。还有其他一些有用的情况吗?【参考方案11】:您是否曾经编写过一个方法(或一个类),其中方法/类的关键概念没有与参数/实例变量的特定数据类型紧密绑定(想想链表、最大/最小函数、二分查找等)。
你有没有希望你可以重用算法/代码而不诉诸剪切-粘贴重用或损害强类型(例如,我想要 List
的字符串,而不是 List
的东西我 hope 是字符串!)?
这就是为什么您应该想要使用泛型(或更好的东西)。
【讨论】:
我从来没有为这种东西复制/粘贴重用(不确定这会如何发生?)您实现了一个适合您需求的接口,并使用该接口。您无法做到这一点的罕见情况是当您编写纯实用程序(例如集合)时,而对于那些(如我在问题中描述的)来说,它真的从来都不是问题。我确实妥协了强类型——就像你必须使用反射一样。你绝对拒绝使用反射,因为你不能有强类型? (如果非要使用反射,我只是像集合一样封装)。【参考方案12】:不要忘记泛型不仅仅被类使用,它们也可以被方法使用。比如下面的sn-p:
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 个帖子投票 :) 如果不是这样,您可能要考虑泛型的简单可用性导致您使代码复杂化而没有任何优势。 这确实增加了额外的复杂性。但是,我认为它确实有一些用处。问题是您不能从具有特定声明异常的方法体内抛出一个普通的旧Throwable
。另一种方法是编写单独的方法来返回每种异常类型,或者使用返回Throwable
的非泛型方法自己进行转换。前者过于冗长且毫无用处,后者不会从编译器中获得任何帮助。通过使用泛型,编译器会自动为您插入正确的转换。所以,问题是:这值得复杂吗?
哦,我明白了。所以它避免了演员出来......好点。我欠你 5 个。【参考方案13】:
正如 Mitchel 指出的那样,主要优势是无需定义多个类的强类型化。
通过这种方式,您可以执行以下操作:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
如果没有泛型,您必须将 blah[0] 转换为正确的类型才能访问其功能。
【讨论】:
如果可以选择,我宁愿不投也不愿投。 强类型很容易成为泛型恕我直言的最佳方面,尤其是考虑到允许的编译时类型检查。【参考方案14】:无论如何,jvm 都会强制转换...它隐式创建将泛型类型视为“对象”的代码,并创建对所需实例的强制转换。 Java 泛型只是语法糖。
【讨论】:
【参考方案15】:我知道这是一个 C# 问题,但 generics 也用于其他语言,它们的用途/目标非常相似。
Java 集合从 Java 1.5 开始使用 generics。因此,使用它们的好地方是当您创建自己的类似集合的对象时。
我几乎在任何地方都能看到的一个例子是 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,这很有用。
【讨论】:
【参考方案16】:来自 Sun Java 文档,以回应“我为什么要使用泛型?”:
"泛型为您提供了一种将集合的类型传达给编译器的方法,以便对其进行检查。一旦编译器知道集合的元素类型,编译器就可以检查您是否一致地使用了该集合并且可以在从集合中取出的值上插入正确的强制转换...使用泛型的代码更清晰、更安全....编译器可以在编译时验证在运行时没有违反类型约束 [强调我的]。因为程序编译时没有警告,我们可以肯定地说它不会在运行时抛出 ClassCastException。使用泛型的净效果,特别是在大型程序中,提高了可读性和稳健性。[强调我的]"
【讨论】:
我从未发现集合的通用类型对我的代码有帮助的情况。我的收藏范围很窄。这就是为什么我说“它可以帮助我吗?”。不过,作为一个附带问题,您是说在您开始使用泛型之前,这种情况偶尔会发生在您身上吗? (将错误的对象类型放入您的集合中)。在我看来,这似乎是一个没有真正问题的解决方案,但也许我只是幸运。 @Bill K:如果代码受到严格控制(即,仅由您使用),也许您永远不会遇到这个问题。那太棒了!最大的风险在于多人参与的大型项目,IMO。这是一个安全网,也是一个“最佳实践”。 @Bill K:这是一个例子。假设您有一个返回 ArrayList 的方法。假设 ArrayList 不是通用集合。某人的代码采用此 ArrayList 并尝试对其项执行一些操作。唯一的问题是您用 BillTypes 填充了 ArrayList,而消费者正在尝试对 ConsumerTypes 进行操作。这可以编译,但在运行时会爆炸。如果你使用泛型集合,你会遇到编译器错误,这更容易处理。 @Bill K:我曾与技能水平参差不齐的人共事。我没有选择与谁共享项目的奢侈。因此,类型安全对我来说非常重要。是的,我通过将代码转换为使用泛型发现(并纠正了)错误。【参考方案17】:泛型允许您创建强类型的对象,但您不必定义特定类型。我认为最有用的例子是 List 和类似的类。
使用通用列表,您可以拥有任何您想要的列表列表,并且您始终可以引用强类型,您不必转换或像使用数组或标准列表那样做任何事情。
【讨论】:
【参考方案18】:泛型允许您对应该能够容纳任何对象的对象和数据结构使用强类型。在从通用结构(装箱/拆箱)中检索对象时,它还消除了繁琐且昂贵的类型转换。
使用这两种方法的一个例子是链表。如果链表类只能使用对象 Foo,它会有什么好处?要实现可以处理任何类型对象的链表,如果您希望列表仅包含一种类型的对象,则链表和假设节点内部类中的节点必须是通用的。
【讨论】:
【参考方案19】:如果您的集合包含值类型,则它们在插入集合时不需要对对象进行装箱/取消装箱,因此您的性能会显着提高。 resharper 等很酷的插件可以为您生成更多代码,例如 foreach 循环。
【讨论】:
【参考方案20】:使用泛型(尤其是集合/列表)的另一个优点是您可以获得编译时类型检查。这在使用通用列表而不是对象列表时非常有用。
【讨论】:
【参考方案21】:最重要的一个原因是它们提供了类型安全
List<Customer> custCollection = new List<Customer>;
相反,
object[] custCollection = new object[] cust1, cust2 ;
作为一个简单的例子。
【讨论】:
类型安全,本身就是一个讨论。也许有人应该问一个问题。 是的,但你在暗示什么?类型安全的重点?【参考方案22】:总之,泛型允许您更精确地指定您打算做什么(更强的类型化)。
这对您有几个好处:
因为编译器更了解你想要做什么,它允许你省略很多类型转换,因为它已经知道类型是兼容的。
这还可以让您更早地获得有关程序正确性的反馈。以前在运行时会失败的事情(例如,因为无法将对象转换为所需的类型),现在在编译时会失败,您可以在测试部门提交神秘的错误报告之前修复错误。
编译器可以做更多的优化,比如避免装箱等
【讨论】:
【参考方案23】:需要添加/扩展的几件事(从 .NET 的角度来看):
泛型类型允许您创建基于角色的类和接口。这已经在更基本的术语中说过,但我发现您开始使用以与类型无关的方式实现的类来设计代码 - 这会产生高度可重用的代码。
方法上的通用参数可以做同样的事情,但它们也有助于将“告诉不要问”原则应用于强制转换,即“给我我想要的,如果你不能,你告诉我为什么” .
【讨论】:
【参考方案24】:我在一个用 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
我的小框架更健壮(有过滤、延迟加载、搜索等功能)。我这里只是简化了给大家举个例子
我和史蒂夫和你一样,一开始就说“太杂乱了”,但现在我看到了它的优点
【讨论】:
【参考方案25】:已经提到了诸如“类型安全”和“无强制转换”之类的明显好处,所以也许我可以谈谈其他一些“好处”,希望对您有所帮助。
首先,泛型是一个独立于语言的概念,而且,IMO,如果您同时考虑常规(运行时)多态性,它可能更有意义。
例如,我们从面向对象设计中知道的多态性有一个运行时概念,其中调用者对象在运行时随着程序执行的进行而被计算出来,并根据运行时类型相应地调用相关方法。在泛型中,这个想法有点相似,但一切都发生在编译时。这是什么意思以及你如何使用它?
(让我们坚持使用泛型方法以保持其紧凑性)这意味着您仍然可以在不同的类上使用相同的方法(就像您之前在多态类中所做的那样)但是这次它们是由编译器自动生成的,取决于在编译时设置的类型。你在编译时给你的类型参数化你的方法。因此,不像在运行时多态性(方法覆盖)中所做的那样,为每个单一类型从头开始编写方法,而是让编译器在编译期间完成工作。这有一个明显的优势,因为您不需要推断系统中可能使用的所有可能类型,这使得它在不更改代码的情况下更具可扩展性。
类的工作方式几乎相同。您将类型参数化,代码由编译器生成。
一旦您有了“编译时间”的概念,您就可以使用“有界”类型,并限制可以通过类/方法作为参数化类型传递的内容。因此,您可以控制要传递的内容,这是一个强大的东西,尤其是您有一个被其他人使用的框架。
public interface Foo<T extends MyObject> extends Hoo<T>
...
现在除了MyObject之外没有人可以设置。
此外,您可以对方法参数“强制”类型约束,这意味着您可以确保两个方法参数都依赖于相同的类型。
public <T extends MyObject> foo(T t1, T t2)
...
希望所有这些都有意义。
【讨论】:
【参考方案26】:我曾经就这个话题发表过演讲。你可以在http://www.adventuresinsoftware.com/generics/找到我的幻灯片、代码和录音。
【讨论】:
【参考方案27】:对集合使用泛型非常简单明了。即使您在其他任何地方都使用它,从收藏中获得的收益对我来说也是一种胜利。
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();
仅此一项就值得仿制药的边际“成本”,而且您不必成为仿制药大师即可使用它并获得价值。
【讨论】:
没有泛型你可以这样做: List stuffList = getStuff(); for(Object stuff : stuffList) ((Stuff)stuff).doStuff(); ,这并没有太大的不同。 没有泛型我做 stuffList.doAll();我说我总是有一个包装类,它正是这样的代码的地方。否则你如何避免在别处复制这个循环?你把它放在哪里以供重复使用?包装类可能会有某种循环,但在那个类中,因为该类都是关于“东西”的,所以使用上面提到的 Tom 建议的强制循环是非常清晰/自我记录的。 @Jherico,这真的很有趣。我想知道你有这种感觉是否有原因,如果那是我不理解的——在人们的天性中,有某种东西会发现任何出于某种原因的演员都是淫秽的,并且愿意不自然地去做(比如添加更复杂的语法)来避免它。为什么“如果你必须施放东西,你就已经输了”? @Jherico:根据 Sun 文档,泛型为编译器提供了将对象转换为什么的知识。由于编译器为泛型而不是用户进行强制转换,这是否会改变您的语句? 我已将 Java 1.5 之前的代码转换为泛型,在此过程中发现了错误对象类型被放入集合的错误。我也落入了“如果你必须施放你就输了”的阵营,因为如果你必须手动施放,就会冒着 ClassCastException 的风险。使用泛型,编译器会为您隐形,但由于类型安全,ClassCastException 的可能性会降低方式。是的,没有泛型你可以使用 Object,但是这对我来说是一种可怕的代码味道,就像“抛出异常”一样。【参考方案28】:泛型还使您能够创建更多可重用的对象/方法,同时仍提供特定于类型的支持。在某些情况下,您还会获得很多性能。我不知道 Java 泛型的完整规范,但在 .NET 中,我可以对 Type 参数指定约束,例如实现接口、构造函数和派生。
【讨论】:
【参考方案29】:使程序员能够实现泛型算法 - 通过使用泛型,程序员可以实现适用于不同类型集合、可以自定义、类型安全且更易于阅读的泛型算法。
编译时更强的类型检查 - Java 编译器对泛型代码应用强类型检查并在代码违反类型安全时发出错误。修复编译时错误比修复运行时错误更容易,后者很难找到。
消除演员表。
【讨论】:
以上是关于泛型有啥好处,为啥要使用它们?的主要内容,如果未能解决你的问题,请参考以下文章