One "real world" example that's pretty straightforward to understand is the java.io.InputStream class and it's children. This is a decent example of polymorphism: if you write your code to understand how to use InputStream, it doesn't matter how the underlying class works, as long as it conforms to the contract imposed by InputStream. So, you can have a method in some class
public void dump(InputStream in) throws IOException {
int b;
while((b = in.read()) >= 0) {
System.out.println(b);
}
}
This method doesn't care where the data comes from.
Now, if you want to use the dump method with data from a file, you can call
dump(new FileInputStream("file"));
or, if you want to use dump with data from a socket,
dump(socket.getInputStream());
or, if you have a byte array, you can call
dump(new ByteArrayInputStream(theArray));
There are implementations if InputStream that wrap other InputStreams. For example, SequenceInputStream lets you glom multiple InputStreams into one:
dump(new SequenceInputStream(new FileInputStream("file1"),
new FileInputStream("file2"));
If you want to create your own, custom InputStream, you can extend the InputStream class, and override the int read() method:
public class ZerosInputStream extends InputStream {
protected int howManyZeros;
protected int index = 0;
public ZerosInputStream(int howManyZeros) {
this.howManyZeros = howManyZeros;
}
@Override
public int read() throws IOException {
if(index < howManyZeros) {
index++;
return 0;
} else {
return -1;
}
}
Then you can use that in your dump call:
dump(new ZerosInputStream(500));