Overloading Dalam Generics
Selagi membaca buku kuno tentang C/C++ jadul, gw terbesit untuk melakukan eksperimen dalam fitur generics yang disediakan dalam beberapa bahasa pemrograman populer yang gw pahami. Gw akan membahas fitur ini dalam 5 bahasa pemrograman yang (setidaknya) gw pahami, yaitu C++, Java, C#, C++/CLI dan VB.NET.
Overview
Overloading adalah fitur suatu bahasa pemrograman yang menerima pendeklarasian dan membedakan method-method yang bernama sama berdasarkan signature dari method tersebut. Signature dari suatu method adalah nama beserta tipe parameter-parameternya.
Generic (pada C++ disebut Template) adalah fitur suatu bahasa pemrograman yang memungkinkan programmer membuat implementasi yang general tanpa dibatasi suatu tipe data tertentu. Sehingga programmer dapat merancang suatu class atau method yang umum dimana suatu tipe data yang digunakan dalam class atau method tersebut dapat ditentukan pada saat penggunaan class atau method tersebut.
Overloading pada dasarnya adalah menghilangkan ambiguitas dari method-method yang bernama sama dengan menambahkan daftar parameter sebagai ‘kata-kunci’ dari suatu method. Sedangkan generic melakukan generalisasi suatu tipe data, dan tentu saja karena tipe data tersebut tidak diketahui saat penulisan class yang generic, tipe data tersebut dianggap ambigu. Nah jadi, jika terdapat method-method yang menggunakan tipe generic pada parameternya, tentu saja method tersebut menjadi ambigu pada saat kompilasi karena satu atau lebih parameter yang bertipe generic tidak dapat ditentukan tipe datanya, dan pada kondisi seperti ini, overloading seharusnya menjadi gagal.
Problem
Apakah Generics dan Overloading dapat berjalan dengan sempurna secara bersamaan?
Problem Case
Bagaimana jika misalkan kita membuat method yang memiliki dua overload dengan parameternya adalah generic dan suatu tipe data A, kemudian kita membuat objek dari class generic itu dengan memasukkan A sebagai parameter genericnya.
[spoiler show=”Kode Dalam C#” hide=”Tutup kode Dalam C#”] [csharp] class Program{
static void Main(string[] args)
{
Test<Suatu> t = new Test<Suatu>();
short a = 4, b = 5;
t.M1(new Suatu());
Console.ReadKey();
}
}
class Suatu
{
}
class Test<T>
{
public void M1(T a)
{
Console.WriteLine("M1(T)");
}
public void M1(Suatu b)
{
Console.WriteLine("M1(Suatu)");
}
}
[/csharp]
[/spoiler]
[spoiler show=”Kode Dalam Java” hide=”Tutup kode Dalam Java”]
Perhatikan bahwa kode dibawah menggunakan tipe data Integer yang berupa class bukan tipe data primitif int. Akan dijelaskan perbedaannya di pembahasan di bawah.
[java]</pre>class Test<T>
{
public void M(T a)
{
System.out.println("M(T)");
}
public void M(Integer b)
{
System.out.println("M(int)");
}
}
public class Tes1
{
public static void main(String… args)
{
Test<Integer> t = new Test<Integer>();
t.M(1);
}
}
[/java]
[/spoiler]
[spoiler show=”Kode Dalam C++” hide=”Tutup kode Dalam C++”]
[cpp]
#include<iostream>
using namespace std;
template<class T>
class Test
{
public:
void M(T &x)
{
cout << "M(T)" << endl;
}
void M(int y)
{
cout << "M(int)" << endl;
}
};
int main()
{
int a = 2;
Test<int> tes;
tes.M(a);
system("pause");
}
[/cpp]
[/spoiler]
[spoiler show=”Kode Dalam C++/CLI” hide=”Tutup kode Dalam C#”]
Keyword-keyword tertentu tidak di-highlight karena SyntaxHighlighter gw nggak ngedukung C++/CLI ๐
using namespace System;
generic<class T>
ref class Test
{
public:
void M(T b)
{
Console::WriteLine(L"M(T)");
}
void M(int b)
{
Console::WriteLine(L"M(int)");
}
};
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
Test<char>^ t = gcnew Test<char>();
t->M(4);
Console::ReadKey();
return 0;
}
[/cpp]
[/spoiler]
[spoiler show=”Kode Dalam VB.NET” hide=”Tutup kode Dalam VB.NET”]
[vbnet]
Module Module1
Sub Main()
Dim test As New Test(Of Integer)
Console.WriteLine("VB")
test.M(1)
Console.ReadKey()
End Sub
Class Test(Of T)
Public Sub M(ByVal b As Integer)
Console.WriteLine("M(T, int)")
End Sub
Public Sub M(ByVal a As T)
Console.WriteLine("M(T, T)")
End Sub
End Class
End Module
[/vbnet]
[/spoiler]
Hasil
Hasil pada seluruh bahasa pemrograman berbeda satu sama lainnya. Khususnya untuk .NET dan yang bukan .NET. Pada C#, C++/CLI, dan VB.NET, program berhasil di-compile dan dijalankan. Uniknya ternyata fungsi overload yang dipanggil adalah fungsi yang bukan generic (M(int) atau M1(Suatu)). Sedangkan pada C++ standar dan Java, program ogah di-compile karena dianggap pemanggilan method tersebut ambiguous untuk tipe generic tersebut.
Sepertinya di .NET, overloading diutamakan untuk fungsi-fungsi non-generic terlebih dahulu, kemudian baru fungsi-fungsi generic. Pada Java dan C++ standar kondisi ini membuat compiler bingung untuk memilih fungsi. Sehingga compiler me-generate error atas pemanggilan fungsi yang ambigu tersebut.
Lucunya, ketika kita menggunakan kode berikut pada .NET (dalam contoh di C#), compiler akan menggenerate error dan pemanggilan tersebut dikatakan ambigu.
[spoiler show=”Kode Dalam C#” hide=”Tutup kode Dalam C#”] [csharp] class Program{
static void Main(string[] args)
{
Test<Suatu, Suatu> t = new Test<Suatu, Suatu>();
short a = 4, b = 5;
t.M1(new Suatu());
Console.ReadKey();
}
}
class Suatu
{
}
class Test<T, U>
{
public void M1(T a)
{
Console.WriteLine("M1(T)");
}
public void M1(U b)
{
Console.WriteLine("M1(U)");
}
}
[/csharp]
[/spoiler]
Pesan error dari masing-masing compiler berbeda. Jika di C#, error yang di-generate adalah ambiguous call. Sedangkan di C++/CLI dikatakan bahwa method anggota telah didefinisikan, sedangkan pada VB.NET mirip dengan C# yaitu overloading resolution failed. Perbedaan antara C# dan VB.NET dengan C++/CLI adalah kode program di C# dan VB.NET masih bisa di-compile dan berjalan ketika tidak ada pemanggilan method yang ambigu tersebut. Namun di C++/CLI, program tersebut tidak dapat di-compile sekalipun tidak ada pemanggilan method yang ambigu tersebut.ย Karakteristik C++/CLI yang unik tersebut agaknya menurunkan karakteristik templating di C++ yang juga akan menolak program yang berstruktur seperti contoh diatas (dua generic diresolusikan ke tipe yang sama, dan akhirnya memiliki dua method ber-signature sama).
Kondisi program seperti contoh diatas akan langsung ditolak mentah-mentah pada Java. Karena Java menerapkan type-erasure pada fitur generic mereka, maka method M(T p1) dan method M(U p1) dianggap memiliki signature yang sama dan tidak boleh hadir dalam program. Error semacam ini sebenarnya lebih baik karena dapat mengeliminasi ambiguitas dalam program yang menggunakan generic, karena ambiguitas atau kerancuan dapat meningkatkan porsi logic error dalam program kita.
Solusi
Hindari penggunaan overloading bersamaan dengan generic, khususnya melakukan overloading dengan format sebagai berikut: (Kode program dalam C#)
1. Melakukan overloading 2 method dengan sama-sama tipe generic
[spoiler show=”Kode Dalam C#” hide=”Tutup kode Dalam C#”] [csharp] class Test<T, U>{
public void M1(T a)
{
Console.WriteLine("M1(T)");
}
public void M1(U b)
{
Console.WriteLine("M1(Suatu)");
}
}
[/csharp]
[/spoiler]
2. Melakukan overloading 2 method salah satunya tipe generic, dan satunya lagi tipe non-generic namun tipe tersebut memiliki kemungkinan untuk di-resolve sebagai pengganti tipe generic pada saat pemanggilan.
[spoiler show=”Kode Dalam C#” hide=”Tutup kode Dalam C#”] [csharp] class Program{
static void Main(string[] args)
{
Test<Suatu> t = new Test<Suatu>();
short a = 4, b = 5;
t.M1(new Suatu());
Console.ReadKey();
}
}
class Suatu
{
}
class Test<T>
{
public void M1(T a)
{
Console.WriteLine("M1(T)");
}
public void M1(Suatu b)
{
Console.WriteLine("M1(Suatu)");
}
}
[/csharp]
[/spoiler]
Berikut saran-saran agar kondisi-kondisi di atas dapat dihindari:
- Buat overloading berdasarkan jumlah parameter, bukan tipe parameter. Karena resolusi berdasarkan jumlah parameter akan lebih fix dan tidak ambigu.
- Jika ada method yang jumlah parameter sama (namun lebih dari satu), buat parameter terakhir dari seluruh method yang saling overload tersebut bukan sebagai tipe generic.
- Selain itu, hindari penggunaan overloading bersamaan dengan generic. ๐
mantepp gil!! ๐ ๐
Hehehe.. Thanks!! ๐