On ne parle pas vraiment de "pointeurs" en Java.
Il y a des types simples (nombre, caractère, booléen), où la variable contient une valeur directement (3,"a",true par exemple) et des types objet (tous les autres), où la variable contient une référence vers un objet.
Par exemple, avec un type primitif, si tu fais quelque chose comme
int a = 5;
int b = a;
Tu copies la valeur de a (qui est 5) vers b, donc a et b sont deux variables indépendantes qui valent toutes les deux 5.
Mais avec un type objet, quand tu fais
a = new DataPtr();
b = a;
new DataPtr() crée un objet de type DataPtr et sa référence est enregistrée dans a. Ensuite, b = a; copie cette référence dans b, et donc a et b contiennent donc tous les deux une référence vers le même objet.
Donc techniquement toute variable de type objet est une sorte de "pointeur" vers un objet.
En ce qui concerne ton code, il construit une liste chaînée, où chaque maillon est un objet de type DataPtr, qui contient une valeur propre (le membre Data) et une référence vers le maillon suivant, le membre ptr.
Quand le code fait
DataPtr maillon2 = new DataPtr();
maillon1.ptr=maillon2;
Il crée un nouveau maillon et met sa référence dans le membre ptr de maillon1.
Quand il fait
DataPtr maillon3 = new DataPtr();
maillon2.ptr=maillon3;
Il ajoute un troisième maillon et met sa référence dans maillon2. On se retrouve donc avec une suite de maillons (objets DataPtr) où chaque maillon contient une référence vers le maillon suivant, ce qui constitue une sorte de chaîne qu'on peut parcourir en remontant de maillon en maillon grâce aux références.
La "tête" de la chaîne est le dernier maillon, on la trouve en remontant la chaîne jusqu'à trouver un maillon qui n'a pas de maillon suivant, donc où son membre ptr est null.