我以为我会把这个垒球提供给任何想要从公园里击出的人。什么是泛型,泛型的优点是什么,为什么,在哪里,我应该如何使用它们?请保持相当基本。感谢。
答案 0 :(得分:120)
答案 1 :(得分:45)
我真的很想重复自己。我讨厌经常输入相同的东西。我不喜欢多次重复处理,但略有不同。
而不是创建:
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>
以及所有其他关联类来创建类型安全的版本吗?
我没有。
答案 2 :(得分:20)
答案 3 :(得分:15)
如果您在1.5发布之前搜索Java错误数据库,那么NullPointerException
发现的错误比ClassCastException
多七倍。所以它似乎不是一个很好的功能来发现错误,或者至少在经过一些烟雾测试后仍然存在的错误。
对我而言,泛型的巨大优势在于它们在代码重要类型信息中记录。如果我不希望代码中记录的类型信息,那么我将使用动态类型语言,或者至少使用具有更隐式类型推断的语言。
保持对象的集合本身并不是一种糟糕的风格(但是通常的风格是有效地忽略封装)。这取决于你在做什么。将集合传递给“算法”稍微容易用泛型检查(在编译时或编译时)。
答案 4 :(得分:10)
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
并返回{ {1}}。当您实现此界面时,B
和A
可以是您想要的任何类型,只要您提供的函数B
可以使用前者和返回后者。以下是界面的示例实现:
f
在泛型之前,使用F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
关键字通过子类化实现多态性。使用泛型,我们实际上可以取消子类化并使用参数多态。例如,考虑用于计算任何类型的哈希码的参数化(通用)类。我们不会覆盖Object.hashCode(),而是使用这样的泛型类:
extends
这比使用继承更灵活,因为我们可以保持使用组合和参数多态的主题,而不会锁定脆弱的层次结构。
但是,Java的泛型并不完美。例如,您可以抽象类型,但不能抽象类型构造函数。也就是说,您可以说“对于任何类型T”,但您不能说“对于任何类型T采用类型参数A”。I wrote an article about these limits of Java generics, here.
泛型的一个巨大胜利是它们让你避免子类化。子类化倾向于导致易于扩展的脆弱类层次结构,以及在不查看整个层次结构的情况下难以单独理解的类。
在泛型之前,您可能会将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);
}
}
等类扩展为Widget
,FooWidget
和BarWidget
,而使用泛型,您可以拥有一个通用类{{1}在其构造函数中使用BazWidget
,Widget<A>
或Foo
来为您提供Bar
,Baz
和Widget<Foo>
。
答案 5 :(得分:8)
泛型可以避免拳击和拆箱的性能损失。基本上,请查看ArrayList vs List&lt; T&gt;。两者都做同样的核心事情,但List&lt; T&gt;将会更快,因为您不必对象/来自对象。
答案 6 :(得分:5)
泛型的最大好处是代码重用。假设您有很多业务对象,并且您将为每个实体编写非常类似的代码来执行相同的操作。 (I.E Linq对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方法中给定不同类型的相同服务的重用。真的优雅!
在你的作品中使用泛型还有很多其他很好的理由,这是我最喜欢的。
答案 7 :(得分: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()函数。由于hte ArrayList中填充了Objects,因此必须先抛出它们才能调用bar。但由于Foo的通用列表只能包含Foos,你可以直接在那些上调用Bar()。
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
答案 8 :(得分:5)
键入的集合 - 即使您不想使用它们,您也可能需要从其他库中处理它们,其他来源。
在类创建中进行通用输入:
公共课Foo&lt; T&GT; { public T get()...
避免施法 - 我总是不喜欢
之类的东西新比较者{ public int compareTo(Object o){ if(o instanceof classIcareAbout)...
你实际上在检查一个条件是否应该存在,因为接口是用对象表示的。
我对仿制药的初步反应与你的相似 - “太乱,太复杂”。我的经验是,在使用它们之后你会习惯它们,没有它们的代码感觉不那么明确,而且不太舒服。除此之外,java世界的其余部分使用它们,所以你最终必须使用该程序,对吗?
答案 9 :(得分:5)
我只是喜欢它们,因为它们为您提供了一种快速定义自定义类型的方法(因为我还是使用它们)。
因此,例如,不是定义由字符串和整数组成的结构,而是必须实现一组关于如何访问这些结构的数组等的对象和方法,您可以只创建一个字典
Dictionary<int, string> dictionary = new Dictionary<int, string>();
编译器/ IDE完成了其余的繁重任务。特别是字典允许您使用第一种类型作为键(没有重复值)。
答案 10 :(得分:4)
你有没有写过一个方法(或类),其中方法/类的关键概念没有紧密绑定到参数/实例变量的特定数据类型(想想链表,最大/最小函数) ,二元搜索等。)。
你有没有希望你可以重复使用algorthm /代码而不需要使用cut-n-paste重用或者破坏强类型(例如我想要一个List
的字符串,而不是List
我希望的东西是字符串!)?
这就是为什么你应该 想要 来使用泛型(或更好的东西)。
答案 11 :(得分: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>>
)时,它确实减少了一些混乱。
答案 12 :(得分:2)
我知道这是一个C#问题,但generics也用于其他语言,它们的用途/目标非常相似。
自Java 1.5以来,Java集合使用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,这很有用。
答案 13 :(得分:2)
从Sun Java文档中,回应“我为什么要使用泛型?”:
“泛型提供了一种方法,您可以将集合的类型传递给编译器,以便可以检查它。一旦编译器知道集合的元素类型,编译器就可以检查您是否一直使用集合并且可以在从集合中取出的值上插入正确的强制转换...使用泛型的代码更清晰,更安全.... 编译器可以在编译时验证在运行时不违反类型约束< / strong> [强调我的]。由于程序在没有警告的情况下编译,我们可以肯定地说它不会在运行时抛出ClassCastException。使用泛型的净效果,特别是在大型程序中,提高了可读性和稳健性。[强调我的]“
答案 14 :(得分:2)
jvm无论如何都会强制转换......它隐式地创建了将泛型类型视为“对象”的代码,并为所需的实例化创建了强制转换。 Java泛型只是语法糖。
答案 15 :(得分:2)
正如Mitchel指出的那样,主要优势是强类型,无需定义多个类。
通过这种方式,您可以执行以下操作:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
如果没有泛型,你必须将blah [0]转换为正确的类型才能访问它的函数。
答案 16 :(得分:1)
总之,泛型允许您更准确地指定您打算做什么(更强的打字)。
这对您有几个好处:
因为编译器更了解你想要做什么,所以它允许你省略很多类型转换,因为它已经知道类型是兼容的。
这也可以提前获得有关程序正确性的反馈。之前在运行时失败的事情(例如因为对象无法以所需类型进行转换)现在在编译时失败,您可以在测试部门提交密码错误报告之前修复错误。
编译器可以进行更多优化,例如避免装箱等。
答案 17 :(得分:1)
已经提到了诸如“类型安全”和“无强制转换”之类的显而易见的好处,所以也许我可以谈谈其他一些“好处”,希望对我有帮助。
首先,泛型是一个与语言无关的概念,而IMO,如果您同时考虑常规(运行时)多态性,则可能更有意义。
例如,我们从面向对象设计中了解到的多态性具有运行时概念,在该概念中,随着程序执行的进行,将在运行时确定调用者对象,并根据运行时类型相应地调用相关方法。在泛型中,这个想法有些相似,但是所有事情都是在编译时发生的。这是什么意思,以及您如何利用它?
(让我们坚持使用通用方法来保持紧凑)这意味着您仍然可以在单独的类上使用相同的方法(就像您以前在多态类中所做的一样),但是这次它们由编译器自动生成,具体取决于在编译时设置的类型。您可以根据编译时给出的类型对方法进行参数化。因此,您不必像在运行时多态(方法覆盖)中从头开始为每种类型编写方法那样,而是让编译器在编译期间完成工作。这具有明显的优势,因为您无需推断系统中可能使用的所有可能的类型,这使得无需更改代码即可具有更大的可伸缩性。
类的工作方式几乎相同。您可以对类型进行参数化,并且代码是由编译器生成的。
一旦有了“编译时间”的概念,就可以使用“有界”类型,并限制可以通过类/方法作为参数化类型传递的内容。因此,您可以控制要传递的内容是有力的,尤其是您的框架正在被其他人使用。
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
除了MyObject之外,没有人可以设置其他东西。
此外,您可以在方法参数上“强制”类型约束,这意味着您可以确保两个方法参数都依赖于同一类型。
public <T extends MyObject> foo(T t1, T t2){
...
}
希望所有这些都是有道理的。
答案 18 :(得分:1)
泛型允许您对应该能够容纳任何对象的对象和数据结构使用强类型。从通用结构(装箱/拆箱)中检索对象时,它还消除了繁琐且昂贵的类型转换。
使用两者的一个例子是链表。如果链表类只能使用对象Foo,它会有什么用?要实现可以处理任何类型对象的链接列表,如果希望列表只包含一种类型的对象,则链接列表和假设节点内部类中的节点必须是通用的。
答案 19 :(得分:1)
如果您的集合包含值类型,则在插入集合时,它们不需要对对象进行装箱/取消装箱,因此您的性能会大幅提升。像resharper这样的酷附加组件可以为你生成更多代码,比如foreach循环。
答案 20 :(得分: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
}
我的小框架更健壮(包括过滤,延迟加载,搜索等)。我刚刚在这里简化了一个例子
我,就像史蒂夫和你一样,在开头说“太麻烦和复杂”但现在我看到了它的优点
答案 21 :(得分:1)
使用Generics(特别是使用Collections / Lists)的另一个好处是可以获得编译时间类型检查。当使用通用列表而不是对象列表时,这非常有用。
答案 22 :(得分:1)
泛型允许您创建强类型的对象,但您不必定义特定类型。我认为最有用的例子是List和类似的类。
使用通用列表,你可以拥有一个列表列表列表,你可以随时引用强类型,你不必转换或类似于你使用数组或标准列表。
答案 23 :(得分:1)
要添加/扩展的几件事(从.NET的角度来讲):
通用类型允许您创建基于角色的类和接口。这已经用更基本的术语说过了,但是我发现你开始使用以类型无关的方式实现的类来设计代码 - 这会产生高度可重用的代码。
关于方法的通用参数可以做同样的事情,但它们也有助于将“告诉不要问”原则应用于投射,即“给我我想要的东西,如果你不能,你告诉我为什么”
答案 24 :(得分:1)
最主要的原因是他们提供类型安全
List<Customer> custCollection = new List<Customer>;
而不是,
object[] custCollection = new object[] { cust1, cust2 };
作为一个简单的例子。
答案 25 :(得分:0)
使用泛型集合非常简单干净。即使你在其他任何地方都在使用它,收藏品的收益对我来说也是一种胜利。
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
vs
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();
}
仅凭这一点值得泛型的边际“成本”,你不必成为一名通用的大师就可以使用它并获得价值。
答案 26 :(得分:0)
泛型还使您能够创建更多可重用的对象/方法,同时仍提供特定于类型的支持。在某些情况下,您还可以获得很多性能。我不知道Java Generics的完整规范,但是在.NET中我可以在Type参数上指定约束,比如实现接口,构造函数和派生。
答案 27 :(得分:0)
我曾经谈过这个话题。您可以在http://www.adventuresinsoftware.com/generics/找到我的幻灯片,代码和录音。
答案 28 :(得分:0)
使程序员能够实现泛型算法-程序员可以通过使用泛型来实现适用于不同类型的集合,可以自定义并且类型安全且易于阅读的泛型算法。
在编译时进行更强的类型检查-Java编译器对通用代码进行强类型检查,如果代码违反类型安全性,则发出错误。修复编译时错误比修复运行时错误更容易,后者可能很难找到。
消除演员表。