Eh, don't read more into an argument than is really called for.

The crux of the above argument is usually something like:

In most nontrivial programs you'll wind up with a lot of "sophisticated" code that performs some task but has a ton of parameters and behaviors it'd be at least theoretically useful to be able to tune or override at some specific point-of-use of said code.

Sometimes the right decision is to just hard-code in all the non-essential parameters to sensible defaults, and do only what you need; in places where this approach is warranted ("keep it simple, and only do what you need to"), Java is as annoying to use as ever but doesn't do much to shoot you in the foot beyond being more verbose than it needs to be.

It's also sometimes the case that the right decision is to make the code as fully-articulated-as-possible: as many parameters as possible are tunable, and as many behaviors as possible can be overridden. We'll call this the "complicated" scenario.

In the "complicated" scenario, you have at least the following choices for architecture:

- approach 1: a tower of interfaces and base classes; each interface+base class combination augments or extends some of the behavior or adds or removes points-of-articulation from the layer beneath it. Once you've built the tower, whenever you get to a point where you need to call on this functionality you pick the appropriate item from the tower, subclass+augment its behavior further (if necessary), and go to town.

- approach 2: similar to 1, but make heavy use of reflection (at least at certain points) to try and achieve some level of genericity (in the core methods and algorithms) and to make the implementation less dependent (at time of coding) on the specifics of any particular interface implementation.

- approach 3: have methods that're like void openConnection(IConnectionSpecification connectionSpecification, Map<String,Object> options), and then be smart about how you extract options from the options Map.

approach 1 and approach 2 have their problems but fit well into the tools Java gives you. Approach 1 is more suited for writing code that other people will use (ie, call into), whereas approach 2 is more suited for writing code that other people's code will be used in (ie, called) (yes, that's a bit vague, but it's good enough for today).

Approach 3 is common in the scripting languages, because it allows for a lot of ad-hoc extension / parameter setting without excessively cluttering the interface.

Approach 3 isn't impossible in Java, but the lack of literal syntax for eg maps and lists makes it unwieldy. If it were possible to write something like:

- ConnectionBroker.openConnection(cSpec, {"port":80,"username":"username","password":"password"});

you'd see a lot more use of Approach 3 in Java. As-is, you'd have to explicitly construct a Map of the right type, then one-item-and-line-at-a-time populate it with keys and values, and so on, at which point (especially if used often and in a big project) you're better off coding up an IConnnectionOptions interface, etc., and going from there.

So it's less: lack of data structures == bad design.

It's more: lack of literal syntax for the basic data structures means that it's a royal pain to do certain ad-hoc friendly tasks in an ad-hoc way. Assuming laziness (always a good assumption), given an idiomatic way to handle cases with lots of potential parameters most programmers would handle things the ad-hoc way, and save the "over-engineered" tower-of-interfaces for the situations which are actually necessary (eg: real-honest-enterprisey-software).