Building on BurntPizza's reply, there are many concepts and structures that make a good API. People spend there lives designing good APIs.
Although that may not be the abstract answer you're looking for.
If you have time, pick up Joshua Bloch's "Effective Java" (2nd ed.), awesome, concise advice on overriding, overloading, abstract classes, interfaces, access modifiers, encapsulation, cohesion, etc. I've read through almost twice now and still learning stuff.
My personnel advice...
Make classes that are often used as parameters immutable (java's String class is a good example). A lot of methods in the Java API take strings and it's a good thing that Strings are immutable.
A example of this failing is the getDimensions() method in most Swing GUI classes. Dimensions is a mutable class with publicly accessible .x and .y variables.
This slip on the part of Java's designers has caused headaches for developers the world over and has caused some performance problem's for Swing since every Swing class must make defensive copies of Dimensions objects when using them (Effective Java, p.235).
Higher level logic/processing classes (for example Scanner, Matcher, OutputWriter) that you don't envision being passed around as parameters, can have stateful methods, but should avoid it.
For example, a rendering class that requires
startBatch() and
endBatch() to be called before and after a group of
render(Renderable r) calls is not really a good design. It is easy to forget to call the methods in the right order which can end up trading valuable development time for debugging time.
For example, the class mentioned above could be consolidated to provide a
renderBatch(Collection<Renderable> r) or
renderBatch(Renderable[] r) method which removes the chance for the programmer to make an error when using the class.
This type of programming removes possible runtime errors and forces the programmer to program correctly and if the programmer makes a mistake, the mistake becomes a compile time error rather than a runtime error. In one type of perfect programming world programmers could/would write code which would be impossible to compile with bugs. In other words, null checking, bounds checking, error handling, error recovery, correct object state, etc. would all be part of the language/object syntax itself.
Such an idea is the extreme boundary of moving runtime errors to compile time errors, but in small doses the idea greatly improves API design
