I read everything on the Go Fish page before starting this implementation -- except for the part about Playing Cards. So no code from that was reused here, although there may be strong similarities. The class architecture and names , as you might have noticed, were stolen from the Ruby version (GoFish was Deck until my storing all the classes in one GoFish file stopped that). Although I didn't look at the Java Playing Cards until after most of my coding was complete, as I said, I have a feeling my using enum was not an original thought, though I don't know where it came from.

Go Fish/Java is part of Go Fish. You may find other members of Go Fish at Category:Go Fish.
Works with: JDK version 1.5+

As I say in the comments, this implementation was made possible by the JCF and Java's enums. I don't know how I could have done it without them.

<lang Java>import java.util.ArrayList; //Java's Collection Framework is really what is import java.util.Scanner; //powering the whole game: don't underestimate it. import java.util.Random;

public class GoFish {

   static final Random rng = new Random();
   static private ArrayList<Card> cards;
   static public Player[] Players;
   public static Card draw()

{ return cards.remove(rng.nextInt(cards.size())); }

public static int deckSize() { return cards.size();

   }
   public static void main(String[] args)
   {
       cards = new ArrayList<Card>();
       for(int i=0;i<4;i++)
           for(Card c: Card.values())
               cards.add(c);
       Player h = new HumanPlayer();
       Player ai = new AIPlayer();
       Players = new Player[] {h, ai};
       while(Players[0].getNumBooks() + Players[1].getNumBooks() < 13)
       {
           Players[0].haveTurn();
           System.out.println("----------");
           Players[1].haveTurn();
           System.out.println("----------");
       }
       int yScore = Players[0].getNumBooks(); int aiScore = Players[1].getNumBooks();
       if (yScore > aiScore)
           System.out.println("Congratulations, you win "+ yScore + " to "+ aiScore +"!");
       else if (aiScore > yScore)
           System.out.println("The terrible AI beat you "+ yScore + " to "+ aiScore +"...");
       else
           System.out.println("It's a tie at "+yScore+" each!");
   }

}

enum Card //Java's enums made this possible! {

   ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING;

}

abstract class Player {

   protected ArrayList<Card> hand = new ArrayList<Card>();
   private int numBooks;
   public Player()
   {
       for(int i=0;i<8;i++)
           fish();
   }
   public boolean hasGiven(Card cType)
   {
       return hand.contains(cType);
   }
   public ArrayList<Card> giveAll(Card cType)
   {
       ArrayList<Card> x = new ArrayList<Card>(); //Complicated because simply taking the cards as they
       for(int i=0;i<hand.size();i++)            //are found would mess up the traversing of the hand
           if (hand.get(i) == cType)
             x.add(hand.get(i));
       for(int c=0;c<x.size();c++)
           hand.remove(cType);
       return x;
   }
   protected boolean askFor(Card cType)
   {
       int tmp = 0;
       if (this instanceof HumanPlayer)
           tmp = 1;
       Player other = GoFish.Players[tmp];
       //Used for the computer's strategy//
       if (tmp==1)
           ((AIPlayer) other).queries.add(cType);
       //                               //
       if (other.hasGiven(cType))
       {
           for(Card c: other.giveAll(cType))
               hand.add(c);
           return true;
       }
       else
       {
           return false;
       }
   }
   protected void fish()

{ if (GoFish.deckSize() > 0) hand.add(GoFish.draw()); else System.out.println("But that's impossible since the deck is empty.");

   }
   public int getNumBooks()
   {
       return numBooks;
   }
   protected Card checkForBooks()
   {
       for(Card c: hand) //Not very elegant!
       {
           int num = 0;
           for(Card d: hand)
             if (c == d)
                 num++;
           if (num == 4)
           {
               for(int i=0;i<4;i++)
                   hand.remove(c);
               numBooks++;
               return c;
           }
       }
       return null;


   }
   public abstract void haveTurn();

}

class HumanPlayer extends Player {

   public void haveTurn()
   {
       Scanner scn = new Scanner(System.in);
       boolean playing = true;
       do{
           Card book = checkForBooks();
           if(book != null)
               System.out.println("You got a book of " + book + "s!");
           if (hand.size() == 0)
           {
               System.out.print("Your hand is empty, you must "); //"Go fish!"
               break;
           }
           else
           {
               System.out.print("Your hand:");
               for(Card c: hand)
                   System.out.print(c + " ");
               System.out.println();
           }
           System.out.println("Ask opponent for what card?");
           Card req;
           try{
               req = Card.valueOf(scn.next().toUpperCase());
           }
           catch(IllegalArgumentException e){ //If what you said is not in Card
               System.out.println("Card not present in this deck. Try again:");
               continue;
           }
           if(!hand.contains(req))
           {
               System.out.println("You may not ask for a card you have none of. Try again:");
               continue;
           }
           System.out.println("You ask for a " + req);
           playing = askFor(req); //If you get card(s), askFor returns true and loops
       } while(playing);
       System.out.println("Go fish!");
       fish();
   }

}

class AIPlayer extends Player {

   public ArrayList<Card> queries = new ArrayList<Card>();
   private int age = 0;
   public void haveTurn()
   {
       boolean playing;
       do{
           Card book = checkForBooks();
           if(book != null)
               System.out.println("Your opponent got a book of " + book + "s...");
           if (hand.size() == 0)
           {
               System.out.print("Your opponent's hand is empty.");
               break;
           }
           Card req = aiMagic();
           System.out.println("Your opponent asks for cards by the name of " + req);
           playing = askFor(req);
           age++;
       } while(playing);
       System.out.println("Your opponent goes fishing.");
       fish();
   }

//The AI's strategy is to first ask for things you asked for, since you have those things. //Failing that, it chooses at random.

   private Card aiMagic()
   {
       if (age>2)
       {
           queries.remove(queries.size()-1); //gets rid of oldest entry so it won't 
           age=0;                           //get stuck asking for the same thing
       }
       for(int i=queries.size()-1; i>-1; i--) //(maybe a queue would have been better?)
           if (hand.contains(queries.get(i)))
           {
               return queries.remove(i); //once it extracts a card from you, it will stop
           }                            //asking for that card, until YOU ask for it again.
       return hand.get(GoFish.rng.nextInt(hand.size()));
   }

}</lang>