There is a huge difference between Arrays and Generics: the former are covariant while the latter are invariant. How and why does they differ?
If a reference has a covariant type then it refers to an object of it's type or a subtype of it's type.
That means if Sub is a subtype of Super, then Sub[] is a subtype of Super[], as arrays are by definition covariant.
One might think this is a feature, but no, it is dangerous to use. Remember: a bag of apples is not a bag of fruits, even when an apple is indeed a fruit. Or else you could put a banana into the bag, that is obviously no an apple. A bag of something is not a bag of anything, even when something is a kind of anything. Exactly this is violated by covariant arrays:
Assume the following Java code:
Object[] objectArray = new Long[1]; // reference to a BagOfAnything = reference to a BagOfSomething objectArray[0] = "lol"; // Throws ArrayStoreException, you can't put SomethingElse in a bag of Something
The problem is, this should not compile as it is not typesafe. But it does, as arrays are covariant and because on some rare occasions this cast is required (where the logic of the code ensures type safety). Instead it fails at runtime, possibly on a presentation for your Most Important Customer.
This is why C++ prohibits the implicit conversion from Derived** to Base** but permits the conversion from Derived* to Base*.
Otherwise you could park an instance of NuclearSubmarine into an array of Car when the array is first referenced as an array of Vehicle. C++ does no runtime type checking! After storing nuclearSubmarine in cars (through vehicles), you could extract the very same nuclearSubmarine as a Car object from the cars array. There would be no warning, no error, no segfault. And for example if both Car and NuclearSubmarine has a parameterless member function in the first place, I coul invoke it without any problems. I wonder what would happen if Car has openGasCap() and NuclearSubmarine has fireNuclearMissiles().
Stand alone objects are safe covariants, because it is guaranteed that all subtypes of a type have at least the capabilities of the supertype (the interface of a supertype is always a subset of the interface on any subtype). Therefore it is safe to refer to an instance of a subtype as an instance of the supertype.
But arrays aren't classes. Assume array would be a class and operator[] is represented by the member function at().
It couldn't be legal to convert an array or Car objects to an array of Vehicle objects, as the former has a member function at() returning a Car object while the latter has a member function at() returning a Vehicle object. The conversion would broaden the type of the return value, and this is obviously illegal: if you expect something as a return value, it is not OK to return anything.
Generics are typesafe because their type is invariant. A List<Type1> is neither a subtype nor a supertype of List<Type2> for every Type1 and Type2. At compile time the type of List<Type1> equals to List<Type1>, but at runtime this comparison is not possible, as generic type information can (and is) discarded. This is because with generics the compiler is able to ensure type safety so it can discard generic type information and some type checking can be skipped at runtime. So thus for every type Type1, every instance of List<Type1> is an instance of List. Raw types are discouraged, and you can use wildcard types to refer to generics of unknown types. But as generic type information is not available at runtime you can't use the class literal/type token (Type.class).
But it is illegal to create an array of a generic type, a parametrized type, or a type parameter. None of these array creation expressions are legal: new List<E>[], new List<String>[], new E[] (where E is formal generic type). All will result in generic array creation errors at compile time. If allowed, as arrays are not typesafe generics would break the promise of compiler checked type safety.
// Why generic array creation is illegal - won't compile! List<String>[] stringLists = new List<String>[1]; // compiler error List<Integer> intList = Arrays.asList(42); Object[] objects = stringLists; objects[0] = intList; String s = stringLists[0].get(0); // ClassCastException here at runtime
It is important to note that these restrictions apply to creation expressions (new) only. You can have an array of a formal type parameter (E[]), you just have to create it as an instance of an unparametrized type and cast it like this:
Stack.java:8: warning: [unchecked] unchecked cast
found : Object[], required: E[]
elements = (E[]) new Object[size];
^
Do this only if you are 100% sure that it is type safe what you are doing. In this case always document this fact, document it's proof and documents the premises. Then you can use the @SuppressWarnings("unchecked") annotation to hide the warning.
Type parameters (a.k.a. templates in C++) can be used to do crazy things. It is not just a feature improving type safety by making more checks possible at compile time, but it also extends the ways a programmer can express parametrization of classes. In the following example I demonstrate an algorithm that implements a heapsort, but expects three type parameters:
The type parameters are required to instantiate them (as a class or function) at runtime. Of course I could use some generic interface (a class with virtual functions). Using templates instead is more advanced because the compiler can check at compile time, whether the supplied arguments are of the correct type or not. It makes also possible for supplying the most general default values: the compiler can automatically guess the operator< for a type having one.
Here is the header heapsortplus.h:
1 2 3 4 56 7 8 9 1011 12 13 14 1516 17 18 19 2021 22 23 24 2526 27 28 29 3031 32 33 34 3536 37 38 39 4041 42 43 44 4546 47 48 49 5051 52 53 54 5556 57 58 59 6061 62 63 64 6566 67 68 69 7071 72 73 74 7576 77 78 79 8081 82 83 84 8586 87 88 89 9091 92 93 94 9596 97 98 99 100101 102 103 104 105106 107 108 109 110111 112 113 114 115116 117 118 119 120121 122 123 124 125126 127 128 129 |
/** @file heapsortplus.h Templateable sorting functions for C++. * * With the heapsortplus function you can sort an arbitrary (random access) iterable * set of elements. It uses the well known (and fast) heapsort algorithm for sorting. * You can specify the ordering operator. * You can also specify a permutation (bijective relation) of the elements the iterator * iterates on, so even with a standard ordering operator you can achieve special * sorting results. In other ways this is an index function used by the sorter. For example * you can order the elements in a way, that the biggest elements are sorted to the * centre, and the smaller ones to the left and right in descending order. There is no * ordering relation that would result in this ordering. */ #ifndef heapsortplus_h #define heapsortplus_h /** heapsort's shiftdown function */ template <class RandomAccessIterator, class StrictWeakOrdering, class Indexer> void shiftdown(RandomAccessIterator i,unsigned long int from, unsigned long int end, const StrictWeakOrdering& less,const Indexer& idx) { unsigned long int child, root = from; while ( root * 2 + 1 <= end ) { child = root * 2 + 1; if (child < end && less(i[idx(child+1)],i[idx(child)])) ++child; if (less(i[idx(child)],i[idx(root)])) { std::swap(i[idx(child)],i[idx(root)]); root=child; } else return; } } /** initializes the heap */ template <class RandomAccessIterator, class StrictWeakOrdering, class Indexer> void heapify(RandomAccessIterator first,unsigned long int count, const StrictWeakOrdering& less,const Indexer& idx) { unsigned long int from = count / 2 - 1 + count % 2; shiftdown(first, from, count - 1,less,idx); while (from>0) { --from; shiftdown(first, from, count - 1,less,idx); } } /** the sorter */template <class RandomAccessIterator,class StrictWeakOrdering,class Indexer> void heapsortplus(RandomAccessIterator first,RandomAccessIterator end, const StrictWeakOrdering& less,const Indexer& idx) { if (end-first<2) return; unsigned long int count=end-first; unsigned long int last=count-1; heapify(first,count,less,idx); while (last > 0) { std::swap(first[idx(0)],first[idx(last)]); --last; shiftdown(first,0,last,less,idx); } } /** index relation to retain the ordering of the sort*/ template <class T, class D> class minsorter { protected: T first,length; public: minsorter(D f, D l) { first=f; length=l; } inline T operator()(T x) const { return x; } }; /** index relation to reverse the ordering of the sort */ template <class T, class D> class maxsorter { protected: D first,length; public: maxsorter(D f, D l) { first=f; length=l; } inline T operator()(T x) const { return length-x-1; } }; /** index relation impossible to substitute with an ordering */template <class T, class D> class lilacsorter { protected: D first,length; public: lilacsorter(D f, D l) { first=f; length=l; } inline T operator()(T x) const { return (length/2)-((x-first)%2==0?-1:1)*(x/2)-((x-first)%2==0?0:1); } }; /** use the default lilacsorter */ template <class RandomAccessIterator, class StrictWeakOrdering> void heapsortplus(RandomAccessIterator first,RandomAccessIterator end, const StrictWeakOrdering& less) { heapsortplus(first,end,less, lilacsorter< typename std::iterator_traits<RandomAccessIterator>::value_type, typename std::iterator_traits<RandomAccessIterator>::difference_type >(0,end-first)); } /** let the compiler guess the default ordering */ template <class RandomAccessIterator> void heapsortplus(RandomAccessIterator first,RandomAccessIterator end) { heapsortplus(first,end,std::less<typename RandomAccessIterator::value_type>()); } #endif //heapsortplus_h |
Here are some use cases:
1 2 3 4 56 7 8 9 1011 12 13 14 1516 17 18 19 2021 22 23 24 2526 27 28 29 3031 32 33 34 3536 37 38 39 4041 42 43 44 4546 47 48 49 5051 52 53 54 5556 57 58 |
#include <iostream> #include "heapsortplus.h" #include <vector> using namespace std; // inverse sorting function template<class T> bool inverseless(T a,T b) { return !less<T>()(a,b);} // special sorting fuction bool myless(const int& a,const int& b) { return (a%2==0^b%2==0?b%2==0:a<b);} int main() { srand(time(NULL)); vector<int> v(23); for (vector<int>::iterator i=v.begin();i!=v.end();++i) *i=random()%100; cout << "Original : "; // 93 50 50 36 59 68 77 20 80 36 24 29 75 16 21 34 8 1 48 72 88 33 89 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; heapsortplus(v.begin(),v.end()); cout << endl << "Lilacsort : "; // 8 20 24 33 36 48 50 68 75 80 89 93 88 77 72 59 50 36 34 29 21 16 1 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; heapsortplus(v.begin(),v.end(),inverseless<int>); cout << endl << "InverseLilacsort : "; // 89 80 75 68 50 48 36 33 24 20 8 1 16 21 29 34 36 50 59 72 77 88 93 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; heapsortplus(v.begin(),v.end(),std::less<int>(),minsorter<int,int>(0,v.size())); cout << endl << "Minsort : "; // 93 89 88 80 77 75 72 68 59 50 50 48 36 36 34 33 29 24 21 20 16 8 1 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; heapsortplus(v.begin(),v.end(),std::less<int>(),maxsorter<int,int>(0,v.size())); cout << endl << "Maxsort : "; // 1 8 16 20 21 24 29 33 34 36 36 48 50 50 59 68 72 75 77 80 88 89 93 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; heapsortplus(v.begin(),v.end(),myless,maxsorter<int,int>(0,v.size())); cout << endl << "My less's maxsort: "; // 1 21 29 33 59 75 77 89 93 8 16 20 24 34 36 36 48 50 50 68 72 80 88 for (vector<int>::iterator i=v.begin();i!=v.end();++i) cout << *i << " "; cout << endl; return 0; } |
Although overloading an inherited function implies that the (now) overloaded function of the base class will not be visible anymore in the subclass, by using the using keyword, you can make it accessible again:
#include <iostream>
#include <string>
using namespace std;
class A {
public:
void x() { cerr <<"x"; }
};
class B : public A {
public:
void x(string s) { cerr <<s; }
using A::x;
};
int main() {
B b;
b.x(); // without using A::x in B's declaration, this line would fail to compile
}