我以为我会把这个垒球提供给任何想从公园里踢出去的人。什么是泛型,泛型的优点是什么,为什么,在哪里,应该如何使用它们?请保持基本。谢谢。
Answers:
我真的很讨厌重复自己。我讨厌经常输入相同的东西。我不喜欢在有些许差异的情况下多次重述。
而不是创建:
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>
其他所有关联的类来创建类型安全的版本?
我不。
无需类型转换是Java泛型的最大优点之一,因为它将在编译时执行类型检查。这将减少ClassCastException
s在运行时抛出的可能性,并可以导致更健壮的代码。
但是我怀疑您完全意识到这一点。
每次查看泛型时,都会感到头痛。我发现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
包含的Integer
s在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);
在上述各行中,自动装箱正在起作用-原始int
值10
并将42
其自动装箱到Integer
对象中,然后将其添加到中Set
。但是,请记住,Integer
对象是作为Object
s处理的,因为没有类型信息可以帮助编译器知道Set
应该使用的类型。
for (Object o : set) {
这是至关重要的部分。for-each循环起作用的原因是因为该Set
实现实现了Iterable
接口Iterator
,如果存在则返回带有类型信息的。(Iterator<T>
)。
但是,由于没有类型信息,因此Set
will将返回an Iterator
,后者将返回Set
as Object
s中的值,这就是为什么在for-each循环中检索的元素必须为type的原因Object
。
现在Object
已从中检索到Set
,需要将其强制转换为Integer
手动以执行添加:
total += (Integer)o;
在这里,类型转换是从Object
到进行的Integer
。在这种情况下,我们知道这将始终有效,但是手动类型转换始终使我感到这是易碎的代码,如果在其他位置进行较小的更改,则可能会损坏代码。(我觉得每种类型的转换都在ClassCastException
等待发生,但是我离题了……)
在Integer
现在拆箱成int
并允许执行加法入int
变量total
。
我希望我能举例说明Java 5的新功能可以与非泛型代码一起使用,但是它不像使用泛型编写代码那样简洁明了。而且,我认为,要充分利用Java 5的新功能,应该研究泛型,至少要允许进行编译时检查以防止无效的类型转换在运行时引发异常。
如果你要搜索的Java bug数据库1.5发布之前,你会发现七倍的错误与NullPointerException
比ClassCastException
。因此,查找错误或至少经过一点烟雾测试后仍然存在的错误似乎不是一个很棒的功能。
对我而言,泛型的巨大优势在于它们可以在代码中记录重要的类型信息。如果我不希望将类型信息记录在代码中,那么我将使用动态类型的语言,或者至少使用具有更多隐式类型推断的语言。
保持对象的集合本身不是坏样式(但是,常见的样式是有效地忽略封装)。而是取决于您在做什么。将泛型传递给“算法”稍微容易一点(在编译时或编译时)。
Java中的泛型促进了参数多态性。通过类型参数,可以将参数传递给类型。就像方法为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
and 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”。
泛型的一个巨大胜利就是它们可以避免子类化。子类化往往会导致脆弱的类层次结构难以扩展,并且难以在不查看整个层次结构的情况下单独理解类。
仿制药之前Wereas你可能有类,如Widget
延长了FooWidget
,BarWidget
和BazWidget
,与仿制药可以有一个通用类Widget<A>
,需要一个Foo
,Bar
或Baz
在其构造给你Widget<Foo>
,Widget<Bar>
和Widget<Baz>
。
泛型避免了装箱和拆箱对性能的影响。基本上,请看ArrayList vs List <T>。两者都具有相同的核心功能,但是List <T>会快很多,因为您不必对对象进行装箱。
泛型的最大好处是代码重用。假设您有许多业务对象,并且您将为每个实体编写非常相似的代码以执行相同的操作。(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方法中,如果给定了不同的类型,则相同服务的重用。真正的优雅!
在您的工作中使用泛型还有很多其他重要原因,这是我的最爱。
类型化的集合-即使您不想使用它们,也可能不得不从其他库或其他来源来处理它们。
类创建中的泛型键入:
公共类Foo <T> {public T get()...
避免强制转换-我一直不喜欢这样的事情
新的Comparator {public int compareTo(Object o){if(o classofIcareAbout)...
实际上,您在检查仅应存在的条件的地方,因为接口是以对象表示的。
我对仿制药的最初反应与您的相似-“太混乱了,太复杂了”。我的经验是,使用它们一段时间后,您会习惯它们,没有它们的代码感觉不太清晰,也不太舒服。除此之外,其余的Java世界都使用它们,因此您最终将不得不使用该程序,对吗?
举一个很好的例子。假设您有一个名为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();
}
您是否从未编写过方法(或类),其中方法/类的关键概念未紧密绑定到参数/实例变量的特定数据类型(请考虑链接列表,max / min函数,二进制搜索)等)。
您是否从未希望过可以重用算法/代码,而不必求助于n-paste重用或损害强类型(例如,我想要一个List
Strings,而不是List
我希望是String的东西!)?
这就是为什么你应该要使用泛型(或更好的东西)。
不要忘记,泛型不仅被类使用,它们也可以被方法使用。例如,使用以下代码段:
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>>
)时,确实减少了一些混乱。
Throwable
从具有特定已声明异常的方法主体中抛出普通旧文本。替代方法是编写单独的方法以返回每种异常类型,或者使用返回a的非泛型方法自己进行转换Throwable
。前者太冗长且非常无用,而后者将无法从编译器获得任何帮助。通过使用泛型,编译器将自动为您插入正确的强制类型转换。因此,问题是:这值得复杂吗?
正如Mitchel指出的那样,主要优点是无需定义多个类即可进行强类型化。
这样,您可以执行以下操作:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
如果没有泛型,则必须将blah [0]强制转换为正确的类型才能访问其功能。
无论如何,jvm都进行强制转换...它隐式创建将通用类型视为“对象”的代码,并针对所需的实例创建强制转换。Java泛型只是语法糖。
我知道这是一个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,这很有用。
从Sun Java文档中,响应“我为什么要使用泛型?”:
“泛型为您提供了一种将集合类型与编译器进行通信的方式,以便可以对其进行检查。一旦编译器知道集合的元素类型,编译器便可以检查您是否一致地使用了该集合并可以插入正确地对从集合中取出的值进行强制转换...使用泛型的代码更清晰,更安全....编译器可以在编译时验证运行时没有违反类型约束[强调我的]。程序编译时没有警告,我们可以肯定地说它不会在运行时引发ClassCastException。使用泛型的最终效果是提高可读性和健壮性,尤其是在大型程序中。
最主要的原因是它们提供了类型安全性
List<Customer> custCollection = new List<Customer>;
相对于
object[] custCollection = new object[] { cust1, cust2 };
作为一个简单的例子。
我在使用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
}
我的小框架更加健壮(具有过滤,延迟加载,搜索之类的功能)。我只是在这里简化了一个例子
我像史蒂夫和您一样,在一开始就说“太凌乱和复杂”,但现在我看到了它的优势
已经提到了诸如“类型安全”和“无强制转换”之类的明显好处,因此也许我可以谈谈其他一些“好处”,希望对我有帮助。
首先,泛型是一个与语言无关的概念,而IMO,如果您同时考虑常规(运行时)多态性,则可能更有意义。
例如,我们从面向对象的设计中了解到的多态性具有运行时概念,即在调用者对象随程序执行时在运行时确定,并根据运行时类型相应地调用相关方法。在泛型中,这个想法有些相似,但是所有事情都是在编译时发生的。这是什么意思,以及您如何利用它?
(让我们坚持使用通用方法来保持紧凑)这意味着您仍然可以在单独的类上使用相同的方法(就像您以前在多态类中所做的一样),但是这次它们由编译器自动生成,取决于设置的类型在编译时。您可以根据编译时给出的类型对方法进行参数化。因此,不必像在运行时多态性(方法重写)中那样为每种类型从头开始编写方法,而应让编译器在编译期间完成工作。这具有明显的优势,因为您无需推断系统中可能使用的所有可能的类型,这无需更改代码即可使其具有更高的可伸缩性。
类的工作方式几乎相同。您对类型进行参数化,并且代码由编译器生成。
一旦有了“编译时间”的概念,就可以使用“有界”类型,并限制可以通过类/方法作为参数化类型传递的内容。因此,您可以控制要传递的内容是有力的,尤其是您的框架正在被其他人使用。
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
现在没有人可以设置MyObject。
另外,您可以在方法参数上“强制”类型约束,这意味着您可以确保两个方法参数都依赖于同一类型。
public <T extends MyObject> foo(T t1, T t2){
...
}
希望所有这些都有意义。
我曾经就这个话题发表过演讲。您可以在http://www.adventuresinsoftware.com/generics/上找到我的幻灯片,代码和音频记录。
使用泛型进行收集非常简单明了。即使您在其他任何地方都花钱,从收藏中获得的收益对我来说也是一个胜利。
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();
}
仅凭这一点就值得仿制药的边际“成本”,而且您不必成为仿制药的专家就可以使用它并获得价值。
泛型还使您能够创建更多可重用的对象/方法,同时仍提供特定于类型的支持。在某些情况下,您还会获得很多性能。我不了解Java泛型的完整规范,但是在.NET中,我可以在Type参数上指定约束,例如,实现接口,构造函数和派生。