public class LinkedList<T> {
    private T data;
    private LinkedList<T> prevNode, nextNode;

    /*
     * Constructs a new element
     * @param data, data of object
     * @param node, previous node
     */
    public LinkedList(T data, LinkedList<T> node) {
        this.setData(data);
        this.setPrevNode(node);
        this.setNextNode(null);
    }

    /*
     * Clone an object,
     * @param node  object to clone
     */
    public LinkedList(LinkedList<T> node) {
        this.setData(node.data);
        this.setPrevNode(node.prevNode);
        this.setNextNode(node.nextNode);
    }

    /*
     * Setter for T data in DoubleLinkedNode object
     * @param  data, update data of object
     */
    public void setData(T data) {
        this.data = data;
    }

    /*
     * Returns T data for this element
     * @return  data associated with object
     */
    public T getData() {
        return this.data;
    }

    /*
     * Setter for prevNode in DoubleLinkedNode object
     * @param node, prevNode to current Object
     */
    public void setPrevNode(LinkedList<T> node) {
        this.prevNode = node;
    }

    /*
     * Setter for nextNode in DoubleLinkedNode object
     * @param node, nextNode to current Object
     */
    public void setNextNode(LinkedList<T> node) {
        this.nextNode = node;
    }


    /*
     * Returns reference to previous object in list
     * @return the previous object in the list
     */
    public LinkedList<T> getPrevious() {
        return this.prevNode;
    }

    /*
     * Returns reference to next object in list
     * @return  the next object in the list
     */
    public LinkedList<T> getNext() {
        return this.nextNode;
    }

}

public class Stack<T> {

    private LinkedList<T> upper;
    private int size;

    // constructor initiates null LinkedList<T> object + set size to 0
    public Stack() {
        this.upper = null;
        this.size = 0;
    }

    // push method for a new element to the upper value
    public void push(T data) {
        LinkedList<T> newNode = new LinkedList<T>(data, this.upper);
        this.upper = newNode;
        this.size++;
    }

    // peek method, return upper
    public T peek() {
        // try/catch to either return upper or print message if upper doesn't exist
        try {
            return this.upper.getData();
        } catch (NullPointerException e) {
            System.out.println("No upper element, empty stack!");
            return null;
        }
    }

    // pop method, return upper and remove 
    public T pop() {
        // try/catch to either return + pop upper or print message if upper doesn't exist
        try {
            T data = this.upper.getData();
            this.upper = this.upper.getPrevious();
            this.size--;
            return data;
        } catch (NullPointerException e) {
            System.out.println("No upper element, empty stack!");
            return null;
        }
    }

    // get size method
    public int size() {
        return this.size;
    }

    // isEmpty method, compare size to 0
    public boolean isEmpty() {
        return this.size == 0;
    }

    // toString method, from top to bottom
    public String toString() {
        String x = "[ ";
        LinkedList<T> currentNode = upper;
        // gets upper node, then keeps going down to previous until previous is null
        while (currentNode != null) {
            x += currentNode.getData();
            currentNode = currentNode.getPrevious();
            if (currentNode != null) {
                x += ", ";
            }
        }
        x += " ]";
        return x;
    }

    
}

public class Tester {
    public static void main(String[] args) {
        // test stack with Integer wrapper class
        Stack<Integer> x1 = new Stack<Integer>();
        x1.push(1);
        x1.push(2);
        x1.push(3);
        x1.push(4);
        x1.push(5);
        System.out.println(x1.toString());
        System.out.println(x1.peek());
        System.out.println(x1.pop());
        System.out.println(x1.peek());
        System.out.println(x1.size());
        System.out.println(x1.isEmpty());

        // test stack with String class
        Stack<String> x2 = new Stack<String>();
        x2.push("Porsche");
        x2.push("Lamborghini");
        x2.push("Ferrari");
        x2.push("Pagani");
        x2.push("Bugatti");
        System.out.println(x2.toString());
        System.out.println(x2.peek());
        System.out.println(x2.pop());
        System.out.println(x2.peek());
        System.out.println(x2.size());
        System.out.println(x2.isEmpty());

    }
}

Tester.main(null);
[ 5, 4, 3, 2, 1 ]
5
5
4
4
false
[ Bugatti, Pagani, Ferrari, Lamborghini, Porsche ]
Bugatti
Bugatti
Pagani
4
false

Merge two stacks by pushing both until one is empty and then pushing the rest of the other

public class StackMerger<T> {
    private final Stack<T> x1;
    private final Stack<T> x2;
    private Stack<T> x3;
    
    // constructor for Stackmerger
    public StackMerger(Stack<T> x1, Stack<T> x2) {
        this.x1 = x1;
        this.x2 = x2;
        this.merge(x1, x2);
    }
    
    public void merge(Stack<T> x1, Stack<T> x2) {
        Stack<T> mergedStack = new Stack<T>();
        // if both stacks are not empty, 
        while (!x1.isEmpty() && !x2.isEmpty()) {
            mergedStack.push(x1.pop());
            mergedStack.push(x2.pop());
        }
        // if x1 is empty, pop from x2 and push to mergedStack
        while (!x2.isEmpty()) {
            mergedStack.push(x2.pop());
        }
        // if x2 is empty, pop from x1 and push to mergedStack
        while (!x1.isEmpty()) {
            mergedStack.push(x1.pop());
        }
        x3 = mergedStack;
    }

    // toString method using Stack<T> toString
    public String toString() {
        return x3.toString();
    }
}

public class TesterTwo {
    public static void main(String[] args) {
        // test stack with Integer wrapper class
        Stack<Integer> x1 = new Stack<Integer>();
        x1.push(2);
        x1.push(4);
        x1.push(6);
        x1.push(8);
        x1.push(10);
        System.out.println("\033[1m"+"String 1: "+"\033[0m" + x1.toString());

        Stack<Integer> x2 = new Stack<Integer>();
        x2.push(1);
        x2.push(3);
        x2.push(5);
        x2.push(7);
        x2.push(9);
        System.out.println("\033[1m"+"String 2: "+"\033[0m" + x2.toString());

        StackMerger<Integer> xy1 = new StackMerger<Integer>(x1, x2);
        System.out.println("\033[1m"+"Combined String 1: "+"\033[0m" + xy1.toString());

        // test stack with String class
        Stack<String> x3 = new Stack<String>();
        x3.push("Porsche");
        x3.push("Lamborghini");
        x3.push("Ferrari");
        System.out.println("\033[1m"+"String 3: "+"\033[0m" + x3.toString());
        
        Stack<String> x4 = new Stack<String>();
        x4.push("Pagani");
        x4.push("Bugatti");
        System.out.println("\033[1m"+"String 4: "+"\033[0m" + x4.toString());

        StackMerger<String> xy2 = new StackMerger<String>(x3, x4);
        System.out.println("\033[1m"+"Combined String 2: "+"\033[0m" + xy2.toString());
    }
}

TesterTwo.main(null);
String 1: [ 10, 8, 6, 4, 2 ]
String 2: [ 9, 7, 5, 3, 1 ]
Combined String 1: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
String 3: [ Ferrari, Lamborghini, Porsche ]
String 4: [ Bugatti, Pagani ]
Combined String 2: [ Porsche, Pagani, Lamborghini, Bugatti, Ferrari ]