generics有什么好处,为什么要使用它们?

我想我会提供这个垒球给任何人想要打到公园外面的人。 generics是什么,generics的优点是什么,为什么,在哪里,我应该如何使用它们? 请保持相当基本。 谢谢。

  • 允许您编写types安全的代码/使用库方法,即List <string>保证是一个string列表。
  • 由于使用了generics,编译器可以对types安全的代码执行编译时检查,也就是说,你是否试图将一个int放入string列表中? 使用ArrayList会导致这是一个不太透明的运行时错误。
  • 比使用对象更快,因为它可以避免装箱/拆箱(其中.net必须将值types转换为引用types,反之亦然 ),或者将对象转换为所需的引用types。
  • 允许您编写适用于具有相同基本行为的多种types的代码,即Dictionary <string,int>使用与Dictionary <DateTime,double>相同的基础代码。 使用generics,框架团队只需编写一段代码即可达到上述两个优点。

我真的很讨厌重复自己。 我讨厌比我更频繁地input相同的东西。 我不喜欢重复多次,略有不同。

而不是创build:

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

我可以创build一个可重用的类(如果您因为某些原因不想使用原始集合)

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

我现在是3倍更有效率,我只需要保持一个副本。 为什么不想维护更less的代码?

对于非集合类(如Callable<T>或必须与其他类进行交互的Reference<T>如此。 你真的想扩展Callable<T>Future<T>和其他所有关联的类来创buildtypes安全的版本吗?

我不。

不需要强制转换是Javagenerics最大的优点之一 ,因为它将在编译时执行types检查。 这将减less在运行时抛出ClassCastException的可能性,并可能导致更强大的代码。

但是我怀疑你完全知道这一点。

每次我看generics,都让我头疼。 我发现Java的最好的部分是简单和最小的语法和generics不简单,并添加了大量的新语法。

起初,我也没有看到generics的好处。 我开始从1.4语法学习Java(尽pipe当时Java 5已经出来了),当我遇到generics时,我觉得这是更多的代码编写,我真的不明白其好处。

现代的IDE使编写generics的代码更容易。

大多数现代,体面的IDE都足够聪明,可以帮助用generics编写代码,特别是在代码完成时。

下面是一个使用HashMap创buildMap<String, Integer>的例子。 我必须input的代码是:

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

实际上,为了创build一个新的HashMap ,键入的内容非常多。 然而,实际上,在Eclipse知道我需要什么之前,我只需要input这些内容即可:

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

的确,我确实需要从候选列表中selectHashMap ,但基本上IDE知道要添加什么,包括genericstypes。 使用正确的工具,使用generics不是太糟糕。

另外,由于types是已知的,所以当从generics集合中检索元素时,IDE将如同该对象已经是其声明types的对象那样工作 – 不需要为IDE投射以便知道该对象的types是。

generics的一个关键优势来自它与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
  • 自动装箱和拆箱
  • 为每个循环

首先,基元的generics和自动装箱允许以下几行:

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

整数10被自动装入一个Integer值为10 。 (和42相同)。 然后这个Integer被扔进已知保持IntegerSet中。 试图抛出一个String会导致编译错误。

接下来,对于每个循环都采取所有这三个:

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

首先,在for-each循环中使用包含IntegerSet 。 每个元素被声明为一个int并且Integer被拆箱回原始的int 。 而且这个拆箱的事实是已知的,因为generics被用来指定Set中有Integer

generics可以成为Java 5中引入的新特性的粘合剂,它使得编码更简单,更安全。 而且大多数IDE都足够聪明,可以帮助你提供很好的build议,所以一般来说,input的内容不会太多。

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

编辑 – 没有generics的例子

以下是不使用generics的上述Set示例。 这是可能的,但并不令人愉快:

 Set set = new HashSet(); set.add(10); set.add(42); int total = 0; for (Object o : set) { total += (Integer)o; } 

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

使用非generics集合时,input到集合中的types是Objecttypes的Object 。 因此,在这个例子中, Object是被add到集合中的东西。

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

在上面的行中,自动装箱正在进行中 – 原始的int1042被自动装入Integer对象中,这些对象被添加到Set 。 但是请记住, Integer对象是作为Object来处理的,因为没有types信息来帮助编译器知道Set应该指定什么types。

 for (Object o : set) { 

这是至关重要的部分。 for-each循环工作的原因是因为Set实现了Iterable接口,它返回一个带有types信息的Iterator (如果存在的话)。 ( Iterator<T> ,就是)

但是,由于没有types信息, Set将返回一个Iterator ,它将返回Set as Object的值,这就是为什么在for-each循环中检索的元素必须Objecttypes的原因。

既然Object是从Set获取的,那么它需要手动强制转换为Integer来执行加法:

  total += (Integer)o; 

在这里,一个types转换是从一个Object执行到一个Integer 。 在这种情况下,我们知道这将始终有效,但是手动types转换总是让我觉得这是脆弱的代码,如果在其他地方做了小的改动,可能会损坏代码。 (我觉得每一个types转换都是一个ClassCastException等待发生,但是我离题了…)

Integer现在被拆箱成一个int并允许执行添加到intvariables。

我希望我可以说明,Java 5的新特性可以用于非generics代码,但它不像generics一样编写代码简洁直接。 而且,在我看来,为了充分利用Java 5的新特性,我们应该研究generics,如果至less可以进行编译时检查,以防止无效的types转换在运行时抛出exception。

如果要在1.5发布之前searchJava bug数据库,则会发现NullPointerException错误比ClassCastException多七倍。 所以看起来好像没有什么特别的地方可以findbug,或者至less是在一些冒烟testing之后仍然存在的bug。

对我来说,generics的巨大优势在于它们在代码中logging重要的types信息。 如果我不想在代码中loggingtypes信息,那么我会使用dynamictypes语言,或者至less使用一种更隐式types推断的语言。

保持一个对象的集合本身并不是一个糟糕的风格(但常见的风格是有效地忽略封装)。 这取决于你在做什么。 将集合传递给“algorithm”比使用generics更容易检查(在编译时或之前)。

Java中的generics促进了参数多态性 。 通过types参数,可以将parameter passing给types。 就像String foo(String s)这样的方法模拟一些行为,不仅仅是针对特定的string,而是针对任何strings ,所以像List<T>这样的types会模拟一些行为,而不仅仅是针对特定的types,而是针对任何typesList<T>表示对于任何typesT ,都有一个Listtypes的元素为T s的types 。 所以List实际上是一个types构造函数 。 它将一个types作为参数,并构造另一个types作为结果。

这里有几个我每天使用的genericstypes的例子。 首先,一个非常有用的通用接口:

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

这个接口说有两种types, AB ,有一个函数(叫做f ),它带一个A并返回一个B 当你实现这个接口时, AB可以是你想要的任何types,只要你提供一个函数f来取得前者并返回后者。 以下是接口的一个示例实现:

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

在generics之前,通过使用extends关键字进行子类化 ,实现了多态性。 有了generics,我们实际上可以废除子类化,而使用参数化多态。 例如,考虑用于计算任何types的散列码的参数化(通用)类。 而不是重写Object.hashCode(),我们将使用这样的generics类:

 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); } } 

这比使用inheritance更加灵活,因为我们可以保留使用组合和参数多态的主题,而不会locking脆弱的层次结构。

Java的generics并不完美。 例如,您可以抽象types,但不能抽象types构造函数。 也就是说,你可以说“对于任何types的T”,但是你不能说“对于任何types参数为A的types”。

我在这里写了一篇关于Javagenerics的这些限制的文章。

generics的一个巨大的胜利是,他们让你避免子类化。 子类化往往会导致难以扩展的脆弱的类层次结构,而在不考虑整个层次结构的情况下难以单独理解的类。

在generics之前,你可能会有像Widget这样的类扩展FooWidgetBarWidgetBazWidget ,generics可以有一个generics类Widget<A> ,在其构造函数中使用FooBar或者Baz来给你Widget<Foo> Widget<Bar>Widget<Baz>

generics避免了拳击和拆箱的性能打击。 基本上看ArrayList vs List <T>。 两者都做同样的核心事情,但List <T>会快很多,因为您不必对/从对象框。

  • 键入的集合 – 即使你不想使用它们,你也可能不得不从其他库和其他来源处理它们。

  • 类创build中的通用types:

    public class Foo <T> {public T get()…

  • 避免投射 – 我一直不喜欢的东西

    新的比较器{公众int比较(对象o){如果(o instanceof classIcareAbout)…

在那里你基本上检查一个应该只存在的条件,因为接口是用对象来表示的。

我对generics的最初反应与您的类似 – “太混乱,太复杂”。 我的经验是,在使用它们之后,你习惯了它们,没有它们的代码感觉不太清楚,不太舒服。 除此之外,Java世界的其他部分使用它们,所以你最终必须得到这个程序,对吧?

举一个很好的例子。 想象一下你有一个叫Foo的class级

 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将会添加它,但是generics列表Foo将会引发exception。

例二。

现在你有两个数组列表,你想调用每个Bar()函数。 由于hte ArrayList填充了对象,所以在调用它之前,必须先对它们进行强制转换。 但是由于Foo的generics列表只能包含Foos,所以可以直接调用Bar()。

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

我只是喜欢他们,因为他们给你一个快速的方式来定义一个自定义types(正如我使用它们)。

因此,例如,而不是定义一个由一个string和一个整数组成的结构,然后必须实现一整套对象和方法来访问这些结构的数组等等,您可以将一个字典

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

编译器/ IDE完成剩下的繁重工作。 一个字典特别让你使用第一个types作为一个键(没有重复的值)。

generics的最大好处是代码重用。 比方说,你有很多的业务对象,你会写每个实体非常相似的代码来执行相同的操作。 (IE Linq到SQL操作)。

使用generics,您可以创build一个类,该类可以从给定基类inheritance的任何types进行操作,或者实现给定的接口,如下所示:

 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方法中给定不同types的同一服务的重用。 真高雅!

使用仿制药还有很多其他的好处,这是我的最爱。

你没有写过方法(或类)的方法/类的关键概念没有紧密绑定到参数/实例variables的特定数据types(思考链接列表,最大/最小function,二进制search等)。

你不希望你可以重用algorthm /代码,而不是诉诸于剪切粘贴重用或危及强types(例如我想要一个stringList ,而不是我希望是string的List !)?

这就是为什么你应该使用generics(或更好的)。

正如Mitchel所指出的,主要优势在于无需定义多个类即可进行强types化。

这样你可以做这样的事情:

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

没有generics,你必须将blah [0]转换为正确的types来访问它的函数。

我知道这是一个C#的问题,但generics也用于其他语言,他们的使用/目标非常相似。

Java集合自Java 1.5以来使用generics 。 所以,使用它们的好地方是当你创build你自己的类似收集的对象时。

我几乎在任何地方看到的例子都是一个Pair类,它包含两个对象,但是需要以通用的方式处理这些对象。

 class Pair<F, S> { public final F first; public final S second; public Pair(F f, S s) { first = f; second = s; } } 

每当你使用这个Pair类时,你可以指定你希望处理哪种types的对象,并且在编译时显示任何types转换问题,而不是运行时。

generics也可以用关键字“super”和“extends”定义边界。 例如,如果你想处理一个genericstypes,但你想确保它扩展了一个名为Foo的类(它有一个setTitle方法):

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

虽然它本身不是很有趣,但知道每当处理一个FooManager时,知道它将处理MyClasstypes,并且MyClass扩展Foo是有用的。

从Sun Java文档中,为了回应“为什么我应该使用generics?”:

“generics为您提供了一种将集合的types传递给编译器的方法,以便可以对其进行检查。一旦编译器知道集合的元素types,编译器就可以检查您是否一直使用集合,并可以插入从集合中取出正确的值…使用generics的代码更清晰和更安全…. 编译器可以在编译时validationtypes约束在运行时没有被违反 [我的重点]因为程序在没有警告的情况下编译,我们可以肯定地说它不会在运行时抛出一个ClassCastExceptionexception,特别是在大型程序中使用generics的效果是提高了可读性和健壮性

不要忘记generics不只是被类使用,它们也可以被方法使用。 例如,采取以下片段:

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

这很简单,但可以非常优雅地使用。 好的是,这个方法返回给定的东西。 这有助于在处理需要重新抛回给调用者的exception时:

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

关键是你不要通过传递方法来丢失types。 你可以抛出正确types的exception,而不是只是一个Throwable ,这将是所有你可以做的,没有generics。

这只是generics方法的一个简单例子。 用generics方法可以做很多其他的事情。 在我看来,最酷的是types与generics的推理。 以下面的例子(取自Josh Bloch的Effective Java 2nd Edition):

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

这并没有太多的工作,但是当genericstypes很长(或者是嵌套的,即Map<String, List<String>> )时,它确实减less了一些混乱。

generics允许您创build强types的对象,但您不必定义特定的types。 我认为最好的例子是List和类似的类。

使用通用列表,你可以有一个列表列表,无论你想要什么,你总是可以引用强大的input,你不必转换或任何像你会用数组或标准列表。

generics让你使用强types的对象和数据结构,应该能够容纳任何对象。 它还消除了从普通结构中检索对象时的单调乏味和昂贵的types转换(装箱/拆箱)。

一个使用这两个例子是一个链表。 如果链表只能使用Foo对象,那链表类会有什么好处呢? 为了实现可以处理任何types的对象的链表,如果希望列表只包含一种types的对象,则假设的节点内部类中的链表和节点必须是通用的。

如果您的集合包含值types,则在插入到集合中时不需要对对象进行装箱/取消装箱,因此性能会显着提高。 像resharper这样的酷插件可以为你生成更多的代码,比如foreach循环。

无论如何,jvm都会投射出来……它隐含地创build了将genericstypes视为“Object”并创build强制转换为所需实例的代码。 Javagenerics只是语法糖。

使用generics的另一个优点(特别是集合/列表)的另一个好处是你得到编译时间types检查。 当使用通用列表而不是对象列表时,这非常有用。

单一的最大的原因是他们提供types安全

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

而不是,

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

作为一个简单的例子。

总之,generics允许你更精确地指定你打算做什么(更强的打字)。

This has several benefits for you:

  • Because the compiler knows more about what you want to do, it allows you to omit a lot of type-casting because it already knows that the type will be compatible.

  • This also gets you earlier feedback about the correctnes of your program. Things that previously would have failed at runtime (eg because an object couldn't be casted in the desired type), now fail at compile-time and you can fix the mistake before your testing-department files a cryptical bug report.

  • The compiler can do more optimizations, like avoiding boxing, etc.

A couple of things to add/expand on (speaking from the .NET point of view):

Generic types allow you to create role-based classes and interfaces. This has been said already in more basic terms, but I find you start to design your code with classes which are implemented in a type-agnostic way – which results in highly reusable code.

Generic arguments on methods can do the same thing, but they also help apply the "Tell Don't Ask" principle to casting, ie "give me what I want, and if you can't, you tell me why".

I use them for example in a GenericDao implemented with SpringORM and Hibernate which look like this

 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 } })); } . . . } 

By using generics my implementations of this DAOs force the developer to pass them just the entities they are designed for by just subclassing the 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 } 

My little framework is more robust (have things like filtering, lazy-loading, searching). I just simplified here to give you an example

I, like Steve and you, said at the beginning "Too messy and complicated" but now I see its advantages

I once gave a talk on this topic. You can find my slides, code, and audio recording at http://www.adventuresinsoftware.com/generics/ .

Using generics for collections is just simple and clean. Even if you punt on it everywhere else, the gain from the collections is a win to me.

 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(); } 

That alone is worth the marginal "cost" of generics, and you don't have to be a generic Guru to use this and get value.

Generics also give you the ability to create more reusable objects/methods while still providing type specific support. You also gain a lot of performance in some cases. I don't know the full spec on the Java Generics, but in .NET I can specify constraints on the Type parameter, like Implements a Interface, Constructor , and Derivation.