Yanıtlar:
Sesimi gürültüye ekleyeceğim ve işleri netleştirmek için bir bıçak alacağım:
List<Person> foo = new List<Person>();
ve sonra derleyici Person
, listede olmayan şeyleri koymanızı engeller .
Perde arkasında C # derleyicisi sadece List<Person>
.NET dll dosyasına koyuyor , ancak çalışma zamanında JIT derleyicisi gider ve yeni bir kod kümesi oluşturur, sanki insanları içermek için özel bir liste sınıfı yazmışsınız gibi ListOfPerson
.
Bunun yararı, onu gerçekten hızlı hale getirmesidir. Döküm ya da başka bir şey yoktur ve dll, bunun bir Listesi olduğu bilgisini içerdiğinden, Person
daha sonra yansıma kullanıldığında ona bakan diğer kodlar Person
nesneler içerdiğini söyleyebilir (böylece intellisense vb.).
Bunun dezavantajı, eski C # 1.0 ve 1.1 kodunun (jenerikler eklemeden önce) bunları yeni anlamamasıdır List<something>
, bu nedenle List
, onlarla birlikte çalışmak için işleri manuel olarak düz eski haline dönüştürmeniz gerekir. C # 2.0 ikili kod geriye doğru uyumlu olmadığı için bu kadar büyük bir sorun değil. Bunun gerçekleşeceği tek zaman, eski C # 1.0 / 1.1 kodunu C # 2.0'a yükseltiyorsanız
ArrayList<Person> foo = new ArrayList<Person>();
Yüzeyde aynı görünüyor ve bir çeşit. Derleyici ayrıca Person
listede olmayan şeyleri koymanızı da önleyecektir .
Aradaki fark, perde arkasında olan şey. C # 'ın aksine, Java gitmez ve özel bir yapı oluşturmaz ListOfPerson
- sadece ArrayList
her zaman Java'da olan düz eski kullanır . Diziden bir şeyler çıkardığınızda, normal Person p = (Person)foo.get(1);
döküm dansının hala yapılması gerekir. Derleyici tuşa basmanızı sağlıyor, ancak hız vuruşu / döküm her zamanki gibi hala gerçekleşiyor.
İnsanlar "Tip Erasure" dan bahsettiklerinde bundan bahsediyorlar. Derleyici, yayınları sizin için ekler ve daha sonra Person
sadece bir liste olmaması anlamına gelir.Object
Bu yaklaşımın yararı, jenerikleri anlamayan eski kodların umrunda olması gerekmemesidir. Hala her zamankiyle aynı eski ile uğraşıyor ArrayList
. Bu Java dünyasında daha önemlidir, çünkü Java 5'i jeneriklerle kullanarak kod derlemeyi ve microsoft'un kasıtlı olarak rahatsız etmemeye karar verdiği eski 1.4 veya önceki JVM'lerde çalışmasını istediler.
Dezavantajı, daha önce bahsettiğim hız hitidir ve ayrıca ListOfPerson
sözde sınıf veya bunun gibi herhangi bir şey olmadığından .class dosyalarına, daha sonra bakılan koda (yansıma ile veya başka bir koleksiyondan çıkarırsanız) dönüştürüldüğünde Object
vb.), herhangi bir şekilde, yalnızca Person
başka bir dizi listesi değil , yalnızca içeren bir liste olması gerektiğini söyleyemez.
std::list<Person>* foo = new std::list<Person>();
C # ve Java jeneriklerine benziyor ve yapması gerektiğini düşündüğünüz şeyi yapacak, ancak sahne arkasında farklı şeyler oluyor.
Cava jenerikleri ile en yaygın olanı, pseudo-classes
sadece java gibi tür bilgilerini atmak yerine özel bir yapı oluşturmasıdır , ancak tamamen farklı bir balık su ısıtıcısıdır.
Hem C # hem de Java, sanal makineler için tasarlanmış çıktılar üretir. Person
İçinde bir sınıfı olan bir kod yazarsanız , her iki durumda da bir Person
sınıfla ilgili bazı bilgiler .dll veya .class dosyasına gider ve JVM / CLR bununla bir şeyler yapar.
C ++ ham x86 ikili kod üretir. Her şey bir nesne değildir ve bir Person
sınıf hakkında bilmesi gereken temel bir sanal makine yoktur . Boks veya kutudan çıkarma yoktur ve işlevlerin sınıflara veya aslında hiçbir şeye ait olması gerekmez.
Bu nedenle, C ++ derleyicisi şablonlarla yapabileceklerinizle ilgili herhangi bir kısıtlama getirmez - temel olarak elle yazabileceğiniz herhangi bir kod, sizin için yazabileceğiniz şablonlar alabilirsiniz.
Bunun en açık örneği bir şeyler eklemektir:
C # ve Java'da, jenerik sistem bir sınıf için hangi yöntemlerin kullanılabilir olduğunu bilmeli ve bunu sanal makineye aktarmalıdır. Bunu söylemenin tek yolu ya gerçek sınıfı kodlamak ya da arabirimler kullanmaktır. Örneğin:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Bu kod C # veya Java'da derlenmez, çünkü türün T
aslında Name () adında bir yöntem sağladığını bilmez . Bunu söylemek zorundasınız - C #'da şöyle:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
Ve sonra addNames'e ilettiğiniz şeylerin IHasName arabirimini uyguladığından emin olmalısınız. Java sözdizimi farklıdır ( <T extends IHasName>
), ancak aynı sorunlardan muzdariptir.
Bu sorunun 'klasik' durumu, bunu yapan bir işlev yazmaya çalışıyor.
string addNames<T>( T first, T second ) { return first + second; }
Aslında bu kodu yazamazsınız, çünkü içindeki yöntemle bir arabirim bildirmenin bir yolu yoktur +
. Kaybettin.
C ++ bu sorunların hiçbirinden muzdarip değildir. Derleyici, türlerin herhangi bir VM'ye geçmesini umursamaz - her iki nesneniz de .Name () işlevine sahipse derlenir. Eğer yapmazlarsa, olmayacak. Basit.
Yani, işte orada :-)
int addNames<T>( T first, T second ) { return first + second; }
C # ile yazamadığınız ifadesine itiraz ediyorum . Genel tür, arabirim yerine bir sınıfla sınırlandırılabilir ve içindeki işleçle bir sınıf bildirmenin bir yolu vardır +
.
C ++ nadiren “jenerikler” terminolojisini kullanır. Bunun yerine, "şablonlar" sözcüğü kullanılır ve daha doğrudur. Şablonlar, genel bir tasarıma ulaşmak için bir tekniği açıklar .
C ++ şablonları, C # ve Java'nın iki ana nedenden dolayı uyguladığından çok farklıdır. İlk neden, C ++ şablonlarının yalnızca derleme zamanı türü bağımsız değişkenlerine değil, aynı zamanda derleme zamanı const değeri bağımsız değişkenlerine de izin vermesidir: şablonlar tamsayılar, hatta işlev imzaları olarak verilebilir. Bu, derleme zamanında oldukça korkak şeyler yapabileceğiniz anlamına gelir, örneğin hesaplamalar:
template <unsigned int N>
struct product {
static unsigned int const VALUE = N * product<N - 1>::VALUE;
};
template <>
struct product<1> {
static unsigned int const VALUE = 1;
};
// Usage:
unsigned int const p5 = product<5>::VALUE;
Bu kod ayrıca C ++ şablonlarının diğer ayırt edici özelliğini, yani şablon uzmanlığını kullanır. Kod product
, bir değer bağımsız değişkeni olan bir sınıf şablonu tanımlar . Ayrıca, bağımsız değişken 1 olarak değerlendirildiğinde kullanılan bu şablon için bir uzmanlık tanımlar. Bu, şablon tanımları üzerinden bir özyineleme tanımlamama olanak tanır. Bunun ilk olarak Andrei Alexandrescu tarafından keşfedildiğine inanıyorum .
Şablon uzmanlığı C ++ için önemlidir çünkü veri yapılarında yapısal farklılıklara izin verir. Şablonlar bir bütün olarak, bir arabirimi türler arasında birleştirmenin bir yoludur. Bununla birlikte, bu istenmesine rağmen, uygulama içinde tüm tiplere eşit muamele edilemez. C ++ şablonları bunu dikkate alır. Bu, OOP'nin sanal yöntemlerin geçersiz kılınmasıyla arayüz ve uygulama arasında yaptığı farkın aynısıdır.
C ++ şablonları algoritmik programlama paradigması için gereklidir. Örneğin, kaplar için hemen hemen tüm algoritmalar kap türünü şablon türü olarak kabul eden ve eşit olarak ele alan işlevler olarak tanımlanır. Aslında, bu doğru değil: C ++ kaplar üzerinde değil, kabın başına ve arkasına işaret eden iki yineleyici tarafından tanımlanan aralıklarda çalışır . Böylece, tüm içerik yineleyiciler tarafından sınırlandırılmıştır: begin <= elements <end.
Kaplar yerine yineleyiciler kullanmak yararlıdır çünkü bir kabın bütünü yerine parçaları üzerinde çalışmasına izin verir.
C ++ 'nın diğer bir ayırt edici özelliği, sınıf şablonları için kısmi uzmanlaşma olasılığıdır . Bu biraz Haskell ve diğer fonksiyonel dillerdeki argümanlarda desen eşleşmesi ile ilgilidir. Örneğin, elemanları depolayan bir sınıfı ele alalım:
template <typename T>
class Store { … }; // (1)
Bu, herhangi bir eleman türü için geçerlidir. Ancak, özel bir numara uygulayarak, işaretçileri diğer türlerden daha etkili bir şekilde depolayabileceğimizi varsayalım. Bunu tüm işaretçi türleri için kısmen uzmanlaştırarak yapabiliriz :
template <typename T>
class Store<T*> { … }; // (2)
Şimdi, bir tür için bir kap şablonu her örneklediğimizde, uygun tanım kullanılır:
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Anders Hejlsberg burada " C #, Java ve C ++ 'da Generics " arasındaki farkları anlattı .
Farklılıkların ne olduğu konusunda zaten çok iyi cevaplar var, bu yüzden biraz farklı bir perspektif vereyim ve nedenini ekleyeyim .
Daha önce açıklandığı gibi, ana fark tip silme , yani Java derleyicisinin genel türleri silmesi ve oluşturulan bayt kodunda bulunmamasıdır. Ancak soru şu: neden bunu kimse yapsın ki? Mantıklı değil! Yoksa öyle mi?
Peki, alternatif nedir? Eğer dilde jeneriği uygulamak yoksa, nerede yapmak bunları uygulamak? Ve cevap: Sanal Makinede. Geriye dönük uyumluluğu bozar.
Tür silme ise, genel istemcileri genel olmayan kitaplıklarla karıştırmanıza olanak tanır. Başka bir deyişle: Java 5'de derlenen kod yine de Java 1.4'e dağıtılabilir.
Ancak Microsoft, jenerikler için geriye dönük uyumluluğu bozmaya karar verdi. Yıllardan bu .NET Jenerik Java Generics "daha iyi" neden.
Tabii ki, Sun aptal ya da korkak değil. "Dışarı çıkmalarının" nedeni Java'nın jenerikleri tanıttıklarında .NET'ten çok daha eski ve yaygın olmasıydı. (Kabaca aynı anda her iki dünyada da tanıtıldı.) Geriye dönük uyumluluğu bozmak büyük bir acı olurdu.
Yine bir başka deyişle: Java, Jenerik bir parçası olan dil (bunlar geçerli demektir sadece diğer dillere değil, Java), .NET içinde parçası oldukları Virtual Machine onlar için geçerli olan araçlar ( hepsi , diller değil sadece C # ve Visual Basic.NET).
Bunu LINQ, lambda ifadeleri, yerel değişken türü çıkarımı, anonim türler ve ifade ağaçları gibi .NET özellikleriyle karşılaştırın: bunların hepsi dil özellikleridir. Bu yüzden VB.NET ve C # arasında küçük farklılıklar vardır: bu özellikler VM'nin bir parçası olsaydı, tüm dillerde aynı olurdu . Ancak CLR değişmedi: .NET 3.5 SP1'de hala .NET 2.0'daki ile aynı. .NET 3.5 derleyicisiyle LINQ kullanan bir C # programı derleyebilir ve .NET 3.5 kitaplıklarını kullanmamanız koşuluyla .NET 2.0 üzerinde çalıştırabilirsiniz. Yani olur değil jenerik ve .NET 1.1 ile çalışır, ancak bunun olacağını Java ve Java 1.4 ile çalışır.
ArrayList<T>
(gizli) statik Class<T>
alana sahip, dahili olarak adlandırılmış yeni bir tür olarak yayılabilir . Genel lib'in yeni sürümü 1.5+ bayt kodla dağıtıldığı sürece 1.4-JVM'lerde çalışabilecektir.
Önceki gönderimin takibi.
Şablonlar, kullanılan IDE'ye bakılmaksızın, C ++ 'nın akıllıca başarısız olmasının ana nedenlerinden biridir. Şablon uzmanlığı nedeniyle, IDE belirli bir üyenin olup olmadığından asla emin olamaz. Düşünmek:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
Şimdi, imleç belirtilen konumda ve IDE'nin o noktada üyelerin a
sahip olup olmadığını söylemesi çok zor . Diğer diller için ayrıştırma basittir, ancak C ++ için önceden biraz değerlendirme yapılması gerekir.
Daha da kötüleşiyor. my_int_type
Sınıf şablonunda da tanımlanmış olsaydı ne olurdu ? Şimdi türü başka bir tür argümanına bağlı olacaktır. Ve burada, derleyiciler bile başarısız oluyor.
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
Biraz düşündükten sonra, bir programcı bu kodun yukarıdakiyle aynı olduğu sonucuna varır: bu nedenle Y<int>::my_type
çözülür int
, bu nedenle b
aynı tür olmalıdır a
, değil mi?
Yanlış. Derleyicinin bu ifadeyi çözmeye çalıştığı noktada, aslında Y<int>::my_type
henüz bilmiyor ! Bu nedenle, bunun bir tür olduğunu bilmiyor. Başka bir şey olabilir, örneğin üye işlevi veya alan. Bu, mevcut davada olmasa da belirsizlikler doğurabilir, bu nedenle derleyici başarısız olur. Bir tür adına atıfta bulunduğumuzu açıkça belirtmeliyiz:
X<typename Y<int>::my_type> b;
Şimdi, kod derleniyor. Bu durumdan belirsizliklerin nasıl ortaya çıktığını görmek için aşağıdaki kodu göz önünde bulundurun:
Y<int>::my_type(123);
Bu kod deyimi mükemmel şekilde geçerlidir ve C ++ 'ya işlev çağrısını yürütmesini söyler Y<int>::my_type
. Ancak, my_type
bir işlev değil, bir tür ise, bu ifade hala geçerli olur ve genellikle bir yapıcı çağırma olan özel bir döküm (işlev stili döküm) gerçekleştirir. Derleyici hangisini kastettiğimizi söyleyemez, bu yüzden burada anlam ayrılmak zorundayız.
Java ve C #, ilk dil sürümlerinden sonra jenerikleri tanıttı. Bununla birlikte, jenerikler tanıtıldığında çekirdek kütüphanelerin nasıl değiştiği konusunda farklılıklar vardır. C # 'ın jenerikleri sadece derleyici büyüsü değildir ve bu nedenle geriye dönük uyumluluğu bozmadan mevcut kütüphane sınıflarını doğrulamak mümkün olmamıştır .
Örneğin, Java mevcut Koleksiyonları Çerçeve edildi tamamen genericised . Java, koleksiyon sınıflarının hem genel hem de eski, genel olmayan bir sürümüne sahip değildir. Bazı açılardan bu çok daha temiz - C # 'da bir koleksiyon kullanmanız gerekiyorsa, genel olmayan versiyona gitmek için gerçekten çok az neden var, ancak bu eski sınıflar yerinde kalır ve manzarayı karıştırır.
Bir diğer önemli fark Java ve C # 'daki Enum sınıflarıdır. Java'nın Enum'u bu şekilde kıvrımlı görünümlü bir tanıma sahiptir:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(Angelika Langer'ın bunun neden böyle olduğuna dair çok açık bir açıklamasına bakın . Temel olarak, Java, bir dizeden Enum değerine tipe güvenli erişim verebileceği anlamına gelir:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
Bunu C # sürümüyle karşılaştırın:
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
Enum, jenerikler dile tanıtılmadan önce C #'da zaten mevcut olduğundan, tanım mevcut kodu bozmadan değiştirilemez. Yani, koleksiyonlar gibi, bu eski durumdaki çekirdek kütüphanelerde kalır.
ArrayList
için List<T>
ve yeni bir ad koydu. Gerçek şu ki, ArrayList<T>
IL kodunda farklı bir derleyici tarafından üretilen sınıf adı olacağından kaynak kodunda bir sınıf varsa , bu nedenle herhangi bir ad çakışması olamaz.
11 ay geç, ama bu soru bazı Java Wildcard şeyler için hazır olduğunu düşünüyorum.
Bu, Java'nın sözdizimsel bir özelliğidir. Bir yönteminiz olduğunu varsayalım:
public <T> void Foo(Collection<T> thing)
Ve varsayalım ki yöntem gövdesindeki T tipine bakmanıza gerek yok. T adını söylüyorsunuz ve sonra sadece bir kez kullanıyorsunuz, neden bunun için bir isim düşünmelisiniz? Bunun yerine şunları yazabilirsiniz:
public void Foo(Collection<?> thing)
Soru işareti derleyiciden, o noktada yalnızca bir kez görünmesi gereken normal bir adlandırılmış tür parametresi bildirdiğinizi iddia etmesini ister.
Joker karakterlerle, adlandırılmış bir type parametresiyle de yapamayacağınız hiçbir şey yoktur (bu şeyler her zaman C ++ ve C # 'da nasıl yapılır).
class Foo<T extends List<?>>
ve kullanın Foo<StringList>
ancak C # 'da bu ekstra tür parametresini eklemeniz gerekir: class Foo<T, T2> where T : IList<T2>
ve clunky kullanın Foo<StringList, String>
.
Wikipedia, hem Java / C # generics hem de Java generics / C ++ şablonlarını karşılaştıran harika yazılar içeriyor . Jenerik ana makale biraz darmadağın görünüyor ama içinde bazı iyi bilgi var.
En büyük şikayet tip silme. Bunda jenerikler çalışma zamanında zorunlu kılınmaz. İşte konuyla ilgili bazı Sun belgelerine bir bağlantı .
Jenerikler tür silinmesine göre uygulanır: genel tür bilgileri yalnızca derleme zamanında bulunur ve daha sonra derleyici tarafından silinir.
C ++ şablonları, derleme zamanında değerlendirildikleri ve uzmanlaşmayı destekledikleri için C # ve Java muadillerinden çok daha güçlüdür. Bu, Şablon Meta-Programlamaya izin verir ve C ++ derleyicisini bir Turing makinesine eşdeğer hale getirir (yani derleme işlemi sırasında bir Turing makinesiyle hesaplanabilir herhangi bir şeyi hesaplayabilirsiniz).
Java'da, jenerikler yalnızca derleyici düzeyindedir, bu nedenle şunları elde edersiniz:
a = new ArrayList<String>()
a.getClass() => ArrayList
'A' türünün dize listesi değil, bir dizi listesi olduğunu unutmayın. Yani bir muz listesinin türü bir maymun listesine eşit olacaktır.
Tabiri caizse.
Görünüşe göre, diğer çok ilginç tekliflerin yanı sıra, jenerikleri geliştirmek ve geriye dönük uyumluluğu kırmakla ilgili bir tane var:
Şu anda, jenerikler silme kullanılarak uygulanmaktadır, yani jenerik tip bilgisi çalışma zamanında mevcut değildir, bu da bir tür kodun yazılmasını zorlaştırır. Jenerikler, eski jenerik olmayan kodlarla geriye dönük uyumluluğu desteklemek için bu şekilde uygulandı. Birleştirilmiş jenerikler, çalışma sırasında genel tür bilgisini kullanılabilir hale getirir ve eski jenerik olmayan kodu bozar. Ancak Neal Gafter, geriye dönük uyumluluğu bozmamak için türleri yalnızca belirtildiyse yeniden kullanılabilir hale getirmeyi önerdi.
Not: Yorum yapmak için yeterli noktam yok, bu yüzden bunu uygun cevaba bir yorum olarak taşımaktan çekinmeyin.
Nereden geldiğini asla anlamadığım popüler inanışın aksine, .net geriye dönük uyumluluğu bozmadan gerçek jenerikleri uyguladı ve bunun için açık bir çaba harcadılar. Genel olmayan .net 1.0 kodunuzu, yalnızca .net 2.0'da kullanılacak jeneriklere dönüştürmeniz gerekmez. Hem genel hem de genel olmayan listeler, 4.0'a kadar bile .Net framework 2.0'da, geriye dönük uyumluluk nedeninden başka hiçbir şey olmadan hala kullanılabilir. Bu nedenle, hala genel olmayan ArrayList kullanan eski kodlar çalışmaya devam eder ve önceki ile aynı ArrayList sınıfını kullanır. Geri kod uyumluluğu her zaman 1.0'dan bugüne kadar korunur ... Yani .net 4.0'da bile, bunu seçerseniz, hala 1.0 BCL'den herhangi bir jenerik olmayan sınıfı kullanma seçeneğini seçmeniz gerekir.
Bu yüzden java'nın gerçek jenerikleri desteklemek için geriye dönük uyumluluğu kırması gerektiğini düşünmüyorum.
ArrayList<Foo>
bir ArrayList
örneğiyle doldurması gereken daha eski bir yönteme geçmek istediğini varsayalım Foo
. Eğer bir ArrayList<foo>
değil ise ArrayList
, bu nasıl çalışır?