我以为我会把这个垒球提供给任何想要从公园里击出的人.什么是泛型,泛型的优点是什么,为什么,在哪里,我应该如何使用它们?请保持相当基本.谢谢.
允许您编写类型安全的代码/使用库方法,即List
由于使用了泛型,编译器可以对代码执行编译时检查以确保类型安全,即您是否尝试将int放入该字符串列表中?使用ArrayList会导致运行时错误不太透明.
比使用对象更快,因为它避免装箱/拆箱(其中.net必须将值类型转换为引用类型,反之亦然)或从对象转换为所需的引用类型.
允许您编写适用于具有相同基础行为的许多类型的代码,即Dictionary
我真的不想重复自己.我讨厌经常输入相同的东西.我不喜欢多次重复一些事情而略有不同.
而不是创建:
class MyObjectList { MyObject get(int index) {...} } class MyOtherObjectList { MyOtherObject get(int index) {...} } class AnotherObjectList { AnotherObject get(int index) {...} }
我可以构建一个可重用的类...(在你不想因为某些原因使用原始集合的情况下)
class MyList{ T get(int index) { ... } }
我现在效率提高了3倍,我只需保留一份.为什么你不想保持更少的代码?
对于非集合类(例如a Callable
或Reference
必须与其他类交互)也是如此.你真的想扩展Callable
和 Future
其他所有相关的类来创建类型安全的版本吗?
我不.
不需要进行类型转换是Java泛型的最大优势之一,因为它将在编译时执行类型检查.这将减少ClassCastException
可以在运行时抛出的s 的可能性,并且可以导致更强大的代码.
但我怀疑你完全清楚这一点.
我每次看泛色都会让我头疼.我发现Java最好的部分是它的简单性和最小的语法和泛型并不简单,并添加了大量的新语法.
起初,我也没有看到仿制药的好处.我开始从1.4语法学习Java(尽管当时Java 5已经出来了),当我遇到泛型时,我觉得编写的代码更多,我真的不明白它的好处.
现代IDE使得使用泛型编写代码变得更容易.
大多数现代的,不错的IDE都足够聪明,可以协助使用泛型编写代码,尤其是代码完成时.
这是一个Map
用a 做的例子HashMap
.我必须输入的代码是:
Mapm = new HashMap ();
事实上,只需要打造一个新的东西就可以了HashMap
.但是,实际上,在Eclipse知道我需要的东西之前,我只需要输入这么多内容:
Map
Ctrl+Space
没错,我确实需要HashMap
从候选列表中进行选择,但基本上IDE知道要添加什么,包括泛型类型.使用正确的工具,使用泛型也不算太糟糕.
此外,由于类型是已知的,因此从通用集合中检索元素时,IDE将表现为该对象已经是其声明类型的对象 - 不需要为IDE转换以了解对象的类型是.
泛型的一个关键优势来自于它与Java 5新功能的良好配合.这是一个将整数投入a Set
并计算其总数的示例:
Setset = new HashSet (); set.add(10); set.add(42); int total = 0; for (int i : set) { total += i; }
在那段代码中,有三个新的Java 5特性:
泛型
自动装箱和拆箱
For-each循环
首先,基元的泛型和自动装箱允许以下行:
set.add(10); set.add(42);
整数10
被自动装箱到一个Integer
值为10
.(同样如此42
).然后将Integer
其扔进Set
已知Integer
的东西中.试图抛出一个String
会导致编译错误.
接下来,对于for-each循环,需要以下三个:
for (int i : set) { total += i; }
首先,Set
包含Integer
s用于for-each循环.每个元素都声明为a,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
.因此,在这个例子中,a Object
是被add
编入集合的内容.
set.add(10); set.add(42);
另外,在上述线,自动装箱在起作用-原始int
值10
和42
正在autoboxed到Integer
对象,它们被添加到Set
.但是,请记住,Integer
对象是作为Object
s 处理的,因为没有类型信息可以帮助编译器知道Set
应该期望的类型.
for (Object o : set) {
这是至关重要的部分.for-each循环工作的原因是因为Set
实现了Iterable
接口,该接口返回Iterator
带有类型信息(如果存在).(Iterator
是的.)
但是,由于没有类型信息,Set
将返回一个Iterator
将返回Set
as中Object
的值,这就是为什么在for-each循环中检索的元素必须是类型的原因Object
.
现在Object
从中检索Set
,需要Integer
手动转换为执行添加:
total += (Integer)o;
这里,从a Object
到a执行类型转换Integer
.在这种情况下,我们知道这将始终有效,但手动类型转换总是让我觉得它是脆弱的代码,如果在其他地方进行微小的更改可能会被损坏.(我觉得每个类型都在ClassCastException
等待发生,但我离题了...)
在Integer
现在拆箱成int
并允许执行加法入int
变量total
.
我希望我可以说明Java 5的新功能可以与非泛型代码一起使用,但它并不像使用泛型编写代码那样干净和直接.而且,在我看来,为了充分利用Java 5中的新功能,人们应该研究泛型,如果至少允许编译时检查以防止无效的类型转换在运行时抛出异常.
如果你要搜索的Java bug数据库1.5发布之前,你会发现七倍的错误与NullPointerException
比ClassCastException
.所以它似乎不是一个很好的功能,找到错误,或至少在一些烟雾测试后仍然存在的错误.
对我来说,泛型的巨大优势在于它们在代码中记录了重要的类型信息.如果我不想在代码中记录该类型信息,那么我将使用动态类型语言,或者至少使用具有更隐式类型推断的语言.
保持对象的集合本身并不是一种糟糕的风格(但是通常的风格是有效地忽略封装).这取决于你在做什么.将集合传递给"算法"稍微容易用泛型检查(在编译时或编译时).
Java中的泛型有助于参数多态性.通过类型参数,您可以将参数传递给类型.就像一个方法,比如String foo(String s)
模拟某些行为,不仅仅是针对特定的字符串,而是针对任何字符串s
,所以类似于List
某种行为的类型,不仅针对特定类型,而且适用于任何类型.List
说对于任何类型T
,都有一种List
元素是T
s.所以List
实际上是一个类型构造函数.它将一个类型作为参数,并构造另一个类型作为结果.
以下是我每天使用的泛型类型的几个示例.首先,一个非常有用的通用接口:
public interface F { public B f(A a); }
这个接口表示对于某些两种类型,A
并且B
,有一个函数(被调用f
)接受A
并返回一个B
.当你实现这个接口,A
并且B
可以是任何你想要的类型,只要你提供一个函数f
,是以前者,并返回后者.这是接口的示例实现:
FintToString = new F () { public String f(int i) { return String.valueOf(i); } }
在泛型之前,通过使用关键字进行子类化来实现多态性extends
.使用泛型,我们实际上可以取消子类化并使用参数多态.例如,考虑用于计算任何类型的哈希码的参数化(通用)类.而不是重写Object.hashCode(),我们将使用这样的泛型类:
public final class Hash { private final F hashFunction; public Hash(final F f) { this.hashFunction = f; } public int hash(A a) { return hashFunction.f(a); } }
这比使用继承更灵活,因为我们可以保持使用组合和参数多态的主题,而不会锁定脆弱的层次结构.
Java的泛型并不完美.例如,您可以抽象类型,但不能抽象类型构造函数.也就是说,您可以说"对于任何类型T",但您不能说"对于任何类型T采用类型参数A".
我在这里写了一篇关于Java泛型的这些限制的文章.
泛型的一个巨大的胜利是它们让你避免子类化.子类化倾向于导致易于扩展的脆弱类层次结构,以及在不查看整个层次结构的情况下难以单独理解的类.
Wereas仿制药之前,你可能有类,如Widget
延长了FooWidget
,BarWidget
和BazWidget
,与仿制药可以有一个通用类Widget
,需要一个Foo
,Bar
或Baz
在其构造给你Widget
,Widget
和Widget
.
泛型可以避免拳击和拆箱的性能损失.基本上,请查看ArrayList与List
我只是喜欢它们,因为它们为您提供了一种快速定义自定义类型的方法(因为我仍然使用它们).
因此,例如,不是定义由字符串和整数组成的结构,而是必须实现关于如何访问这些结构的数组等的整套对象和方法,您可以只创建一个字典
Dictionarydictionary = new Dictionary ();
编译器/ IDE完成了其余的繁重工作.特别是字典允许您使用第一种类型作为键(没有重复值).
泛型的最大好处是代码重用.假设您有很多业务对象,并且您将为每个实体编写非常类似的代码来执行相同的操作.(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 DataServicewhere ENTITY : class, IEntity, new() where DATACONTEXT : DataContext, new() { public void Create(List entities) { using (DATACONTEXT db = new DATACONTEXT()) { Table table = db.GetTable (); foreach (ENTITY entity in entities) table.InsertOnSubmit (entity); db.SubmitChanges(); } } } public class MyTest { public void DoSomething() { var dataService = new DataService (); dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 }); var otherDataService = new DataService (); otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" }); } }
注意在上面的DoSomething方法中给定不同类型的相同服务的重用.真的很优雅!
在您的工作中使用泛型还有很多其他很好的理由,这是我的最爱.
键入的集合 - 即使您不想使用它们,您也可能需要从其他库中处理它们,其他来源.
类创建中的通用类型:
公共课Foo
避免施法 - 我一直不喜欢像
new Comparator {public int compareTo(Object o){if(o instanceof classIcareAbout)...
你实际上在检查一个应该只存在的条件,因为接口用对象表示.
我对仿制药的最初反应与你的相似 - "太乱,太复杂".我的经验是,在使用它们之后你会习惯它们,没有它们的代码感觉不那么明确,而且不太舒服.除此之外,java世界的其余部分使用它们,所以你最终必须得到程序,对吧?
举个好例子.想象一下,你有一个名为Foo的课程
public class Foo { public string Bar() { return "Bar"; } }
示例1 现在您想拥有一个Foo对象的集合.您有两个选项,LIst或ArrayList,它们都以类似的方式工作.
Arraylist al = new ArrayList(); Listfl = new List (); //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(); }