Odpověď máš tady: https://stackoverflow.com/questions/11759414/java-how-to-load-different-versions-of-the-same-class
Croninovy příspěvky ohledně reflection si vymaž z hlavy, nedávají smysl.
Reflection je potřeba v situaci, kdy musíte použít tu třídu jako typ. Pokud proměnnou nebo parametr metody nadefinujete jako třídu ve verzi 1, pak do ní nejde vložit reference na třídu ve verzi 2. Musíte pak najít společného předka, kupříkladu Object, a na metody použít reflection.
V oblastech, kde se tohle "jedna třída ve více verzích" používá, se to ale obchází tím, že se použije interface. Ten může být nahraný default class loaderem a dokud ho obě verze používají, tak není žádný problém. Do proměnné/parametru typu interface se dají beztrestně použít obě verze té třídy.
Jo a pokud někoho zajímá, kde se to používá (spíše používalo) tak to byly systémy, kde se daly měnit zdrojové kódy za běhu. Kupříkladu Tomcat to možná dosud používá k tomu, aby uměl spustit novou verzi aplikace i bez restartu. Upravil jste třídu a aplikaci řekl, že je nová verze. Ta si ji pak natáhla novým classloaderem a nové instance pak byly už upravené. Samo o sobě to noční můra nebyla, protože se pracovalo s interface. Co už noční můra byla to byl marshalling. Jestli je u moderních VM nějaký pohodlnější mechanismus, to už nevím.
Aha, OK, díky za opravu a vysvětlení.
Pro zajímavost, kdysi jsem dělal ve Smalltalku. Tam se zcela běžně dělají úpravy tříd za běhu, je ta to nejsamozřejmější věc na světě, protože celé IDE je vlastně napsané samo v sobě a běží samo v sobě a vyvíjí se tím, že to IDE v tom IDE člověk za běhu edituje. Může vám to připadat hrozné, ale v praxi je to neuvěřitelně praktické.
Změny tříd jsou v zásadě dvojí:
1) Buď se mění pouze metody (ať už třídní nebo instanční), to vlastně neznamená změnu třídy jako takové. Reference na instanci té třídy dále fungují, a nové metody jsou okamžitě k dispozici (takže mám třeba otevřené okno debuggeru, které mi otevřel failnutý assert v testu, dostepoval jsem se k podezřelé metodě, tak tu metodu hnedka upravím aniž bych debugger zavíral, zkusím z debuggeru zavolat metodu znova, funguje to, hotovo. Edit-compile-run je překonaný mýtus :-)
2) Upravím definici třídy, což v praxi skoro vždy znamená, že jsem přidal nebo ubral instanční proměnné. To už je problém, vzniká mi nová třída. Stane se to, že např. třída Foo se přejmenuje na ObsoleteFoo, existující reference na Foo teď ukazují na ObsoleteFoo, a vytváření nových instancí už dělá nová Foo-čka. Protože si s nejvyšší pravděpodobností Foo a ObsoleteFoo budou dost podobné, většinou nevadí, že v běžící vyvíjené aplikaci je někde Foo a někde ObsoleteFoo, a když chci mít jistotu, tak aplikaci pustím znovu...
Tohle všechno by v principu mohl hojně používat třeba Python nebo Ruby, to jsou jazyky které podle mě (na designové úrovni) nepřinášejí oproti Smalltalku nic navíc, jen spoustu věcí dělají hůře a pomaleji. U jazyků se statickými typy už by to bylo komplikovanější.