103

So, we've got a guy who likes to write methods that take Objects as parameters, so they can be 'very flexible.' Then, internally, he either does direct casting, reflection or method overloading to handle the different types.

I feel like this is a bad practice, but I can't explain exactly why, except that it makes it more difficult to read. Are there other more concrete reasons why this would be a bad practice? What would those be?


So, some folks have asked for an example. He has an interface defined, something like:

public void construct(Object input, Object output);

And he plans to use these by putting several in a list, so it sort of builds bits and adds them to the output object, like so:

for (ConstructingThing thing : constructingThings)
    thing.construct(input, output);
return output;

Then, in the thing that implements construct, there is a rickety reflection thing that finds the right method that matches the input/output and calls it, passing input/output.

I keep telling him, it works, I understand it works, but so does putting everything into a single class. We're talking about reuse and maintainability, and I think he's actually constraining himself and has less reuse than he thinks. The maintainability, while probably high for him right now, will likely be very low for him and anyone else in the future.

17
  • 66
    If he insists on programming Java as if it were Python or Ruby, he shouldn't be programming in Java.
    – user16764
    Commented May 14, 2013 at 18:00
  • 15
    Personally, I see this as a variant of 'stringly-typed' code - with largely the same downsides. Commented May 14, 2013 at 18:40
  • 3
    even on dynamic type systems, that sounds awful and totally non-maintainable. The idea of polymorphism (be it dynamic, static, class-based, duck-based, whatever) is let different implementations do different things, not to differentiate types and operate differently.
    – Javier
    Commented May 14, 2013 at 19:28
  • 4
    @Mark: I'm betting he's seeing a lot of methods like AppendNumberToString(Object, Object) instead of AppendNumberToString(String, Int). Obviously, if that method is public, there's no information about which parameter is supposed to be provided first. In the second case, the method is self-documenting. Commented May 14, 2013 at 19:48
  • 8
    I usually see people trying to write python as if it was Java, not the other way round...
    – Bakuriu
    Commented May 14, 2013 at 20:43

18 Answers 18

104

From a practical point of view I see these problems:

  • A bloat of possible run type errors -- unless a lot of dynamic type checking which could be avoided with the Java included strong type checker.
  • A lot of unnecessary casts
  • Difficulty understanding what a method does by its signature

From a theoretical point of view I see these problems:

  • A lack of contracts of the interface of a class. If all parameters are of Object type then you aren't declaring anything informative to the client classes.
  • A lack of overloading possibilities
  • The incorrectness of override. You are able to override a method and change its parameter types thus breaking everything which is inheritance related.
4
  • 14
    i misread your second point as "a lot of unnecessary cats". I was going to query which cats would then be necessary but realised none of it made sense.
    – bharal
    Commented May 15, 2013 at 12:55
  • 4
    @bharal, well, if everything is typed as object, a cat is as valid to pass in as anything else.
    – Kyralessa
    Commented Jun 26, 2015 at 20:28
  • Also, completely throw away all useful IDE autocomplete. Rich type information is what allows you to generate / insert correct values very quickly. Not to mention functional interfaces; you can't pass a ()->"do " + stuff() to Object...
    – Ajax
    Commented Oct 4, 2018 at 8:28
  • 2
    @bharal Well, I guess we need an @Meowable type annotation now...
    – varun
    Commented Apr 9, 2019 at 12:17
112

The method violates the Liskov substitution principle.

If a method accepts a parameter of type Object, I should expect that any object I pass to the method will work.

What it sounds like is that only certain subclasses of Object are allowed. The only way to know which types are allowed is to know the details of the method. In other words, the method is less flexible than advertised.

17
  • 19
    Yep - pretty much all the SOLID principles have been drop-kicked out the window here...
    – Robbie Dee
    Commented May 14, 2013 at 19:48
  • 1
    This does not violate any such principle, unless you're prepared to defend the argument that all languages with typeless variables violate Liskov, including languages that Liskov herself was working with when she came up with that.
    – Kaz
    Commented May 14, 2013 at 20:31
  • 5
    @Kaz, what do you mean? A language with weak typing is not typeless and I don't think Liskovs point has anything to do with compiler type checking. If I create a Ruby method called def add_user(user), it should be fairly obvious that you cannot throw a Message instance, Hash or File handler into the method - the method presumably expects a User instance. Whether or not a compiler enforces this is irrelevant. However, if I define def construct(object), I would expect the method to handle every imaginable object... which it obviously does not according to OP. That's a violation.
    – Niels B.
    Commented May 14, 2013 at 20:52
  • 3
    Okay, it seems we don't disagree then. Any method that by, its' signature, claims to accept something it doesn't, is a poorly programmed method that violates Liskovs substitution principle. construct(Object input, Object output) must take accept any subclass of object - meaning every possible parameter except null.
    – Niels B.
    Commented May 14, 2013 at 22:21
  • 3
    This doesn't violate the Liskov substitution principle, unless you're prepared to say that every method that accepts int parameters must accept negatives, and values that are out of range, etc. Also that folks who use interfaces in their public API as part of information hiding and then accept interfaces as arguments but expect them to be their own internal concrete types also violate the principle. My understand is that the principle says that an override of a method must accept at least the same types of input, and result in at most the same types of output. Commented Sep 22, 2013 at 15:22
52

I'd consider the following direct implications:

Readability

I guess working with him is not exactly the most pleasant experience in the world. You never know what the type is going to be in the end or what happens to the parameters. I doubt you appreciate wasting your time with that.

Type Signature

In Java everything is an Object. So what does that method do in the end?

Speed

Reflection, casts, type checks etc all contribute to a slower performance. Why? Because in his opinion it makes things flexible.

Against nature

Java has been strongly typed since birth. Tell him to start writing JavaScript or Python or Ruby if he wants a dynamically typed dialect. His background is probably strongly based on one of them.

Flexibility

To use the same term as the colleague in question, Java has already figured out flexibility long ago. There is no point to look at it with the horse glasses put on by a dynamically typed language.

Interfaces, polymorphism, covariance & contra-variance, generics, inheritance, abstract classes and method overloading are just a few of the things that contribute to the so called "flexibility". Flexibility is given by the fact that it is trivial to specialise virtually anything from any given type to another.(The usual case meaning specialising a direct upper type or lower type).

Documentation

One of the main goals of programming is to write reusable and stable code. How does the documentation of your example look like? What is the learning time for another developer before being able to use that stuff? I would suggest your guy to write self-modifying assembler code and stay responsbile for it.

41

There are a lot of good answers here already. I want to add a secondary thought:

This pattern is most likely a "code smell."

In other words, if you need to pass an Object to your method for "ultimate" flexibility, you most likely have a very poorly defined method. Having strongly typed parameters indicates a "contract" to the caller (I need things that look like "this"). Being unable to specify such a contract most likely means that your method does too much or has a poorly defined scope.

10
  • 9
    This is the coder equivalent of microwaving fish curry. Commented May 14, 2013 at 20:14
  • 3
    @MichaelBlackburn: ??? I don't understand that comment ??? What's so special about microwaving fish curry? Googling for it I only get interesting (if you like fish) recipe's and even video's. Commented May 15, 2013 at 6:22
  • 4
    @MarjanVenema Microwaving a fish curry generates a lot of fish smell very quickly.
    – Jonathan
    Commented May 15, 2013 at 7:07
  • 1
    @Kaz Type casting in no way makes Java "more dynamic". It just hides the types hardcoding inside the method, instead of making types explicit in the method's signature, making the code more brittle and harder to understand. How is that more dynamic, or a good thing?
    – Andres F.
    Commented May 15, 2013 at 13:12
  • 1
    @Kaz Disagreed. Hardcoding the type casting in your method is not like letting a language (such as Python) handle it. Besides, if your method is going to crash and burn because the typecast was incorrect, how is it more "dynamic"? You are subverting Java's compile-time checks, and getting nothing in return.
    – Andres F.
    Commented May 15, 2013 at 17:52
22

It takes away the type checking that the compiler does and can lead to more runtime exceptions when the object is cast to the concrete type. Thus, the end-user will see the unhandled exception, unless the invalid casts are all caught and properly handled.

1
  • 2
    This... He is throwing away static type checking. It means that the program will fail at run time rather than compile time. That's a bad thing. The language has types. Use them. To ignore that is like calling all your variables var1, var2, var3 etc. Sure it will work but it's not a good idea
    – JohnB
    Commented May 15, 2013 at 7:35
16

I agree with the other responses that this is generally bad practice.

There is one specific case where I find passing an Object may be superior: when you are eventually dealing with Strings, e.g. if you are creating XML or JSON. In that case, instead of something like:

public void addAttribute(String s) { 
  //... add s to the XML
}

I prefer:

public void addAttribute(Object o) {
   String s = String.valueOf(o);  // perhaps check for null explicitly
   // now add s to the XML
}

This saves endless String.valueOf's in the calling code. Also, it works nicely with vararg's. And, with autoboxing, you can pass ints, floats, etc... Since all Objects have a toString(), this, arguably, does not violate Liskov etc...

4
  • 4
    +1 for showing by example one of the few times to do this - thus being the exception that proves the rule.
    – fluffy
    Commented May 14, 2013 at 21:01
  • 'Prove' in the original meaning of the word, meaning 'test'.
    – Alan B
    Commented May 15, 2013 at 8:18
  • This raise more questions than it answers. Why are you making an API to manipulate XML? Why it allows anything to get in, unconstrained? Commented Sep 23, 2013 at 15:32
  • 1
    I'd call this an API enhancement, which, underneath, probably calls some more standard XML package. Not sure I fully understand your question.
    – user949300
    Commented Nov 14, 2013 at 4:37
5

yes. java is supposed to be strictly typecasted . But having Object as parameter will virtually break this rule. Moreover, it will make your program more bug prone.

And that's the one of the top reasons, why generics were introduced.

0
5

Being flexible and able to handle multiple types of data is exactly what method overloading and interfaces were created for. Java already has the features that make handling these situations simpler while guarenteeing compile time safety via type checking.

Also, if it is impossible to narrowly define what data the method operates on, then that method may be doing too much and needs to be decomposed into smaller methods.

I like a rule of thumb I've read about (in Code Complete I think). The rule states that you should be able to tell what a function or method does just by reading its name. If it is really difficult to clearly name a function\method using that idea (or have to use And in the name) then the function/method is really doing too much.

2

First, if you pass the parameters as the concrete object, any error of using the wrong object (instance of a non expected class) will be detected in compile time, so will be faster to fix. In the other case, the error will be shown at runtime and will be harder to detect and fix.

Second, the code of checking the real class of the instance, reflection, etc, will be slower than a direct call

2

The rule that I follow is... parameter definitions should be as specific as needed by the method, and no more and no less.

If all I need is to iterate an enumerable, the param is defined as an enumerable, not an array or a list. This allows any type of enumerable to be used, avoiding converting to a specific type.

I also will not define it as an object because I need an enumerable. Any object that is not an enumerable will not work in this method. It's confusing to read a method signature and not know what it means, and only run-time testing can find the error.

2

What he does is throwing away all advantages of having a static type system.

Simply: It's much less clear to what such methods do, both to a compiler and to people reading the code.

One of the biggest reasons for a static type system is to have compile-time guarantees about what a program does. The compiler can check that a piece of code is used correctly and if not, issue a compiler-time error. Methods like you're colleague is writing are much more prone to run-time errors, because compiler-time checks are missing. Be prepared run-time errors and for writing a lot more tests.

Another big problem is that it's not at all clear what a method does. Reading such a code, maintaining it, or using it is a big pain.

Java added generics to give stronger compile-time guarantees about code, Scala is going even further with its functional approach, and sophisticated type system with covariant and contra-variant types. Your colleague is going backwards against this line of development, throwing away all its advantages.

If he used properly typed, overloaded methods instead, he (and you all) would gain:

  • Compiler time checking for correct usage of those methods.
  • Clear indication what combinations of arguments are allowed and what aren't.
  • The possibility to properly and separately document each of these combinations.
  • Code readability and maintainability.
  • It would help him to split his methods into smaller parts, which are easier to debug, test and maintain. (See also How to convince your fellow developer to write short methods?.)
11
  • 1
    @Kaz Yes, I respect their arguments, but Java is then a very bad choice for such an approach.
    – Petr
    Commented May 14, 2013 at 21:48
  • 2
    @Kaz: I think everyone can agree that a static type system has some advantages. Not everyone agrees on whether these advantages outweigh the advantages of the opposite approach; but this developer isn't getting the advantages of the opposite approach. (No ducktyping, no implicit typing, no terseness.) He's sacrificing all of the benefits of static typing, while shedding almost none of the costs.
    – ruakh
    Commented May 15, 2013 at 2:54
  • 1
    @Kaz That's absurd. If you program in Java, follow Java conventions. If you program in Python, follow Python's. And not all of us think the arguments for dynamic type systems are "solid".
    – Andres F.
    Commented May 15, 2013 at 12:06
  • 1
    @Kaz If you want to write experimental code against the conventions and assumptions of a language, by all means do it... on your own. If working with a team, follow the conventions. It's as simple as that. Also you are turning this into a debate about creativity, which is absurd. If you use a hammer as a hammer, instead of as a screwdriver, you are not being any less creative; you are using a tool for its best job.
    – Andres F.
    Commented May 16, 2013 at 12:14
  • 2
    @Kaz But Java is not a dynamic language, and if you use Object, you -- the programmer -- are later forced to do an explicit conversion when you want to use the value. Therefore, in Java, it's a bad idea to use Object. Not in Python, not in whatever dynamic language you're thinking of, but in Java, which is what this question was about.
    – Andres F.
    Commented Sep 20, 2013 at 18:52
2

I actually had to work on a package like that once, it was a GUI package--the designer said he came from a Pascal background and thought passing around nothing but a base class everywhere was an awesome idea.

In 20 years, working with this toolkit was one of the most annoying experiences of my career.

Until you have to work with code like that you don't notice how much you rely on the data types in your parameters to guide you. If I said I had a method "connect(Object dest)", what does it mean? Even if you see "connect(Object website)" it's not much help, but "connect(String website)" or "connect(URL website)" you don't even have to think about it.

You might think "connect(Object website)" is a GREAT idea because it can take a string or a URL, but that just means I have to guess which cases the programmer decided to support and which he didn't. At best it moves most of the trial and error part of development from coding time to compile time, at worst it it make the whole development process muddy and annoying.

I (And most Java programmers, I believe) constantly figure out API call chains by examining a group of parameters to constructors to see which constructor to use, then how to build the objects you need to pass until you get to objects you have or can make--it's like putting a puzzle together instead of searching crappy documentation for incomplete examples (my pre-java experience with C).

Although building code like a puzzle seems ad-hock, it works amazingly well--all the time.

I know this has been answered, but this was an annoying enough experience that I really felt motivated to make sure as few people as possible need to go through it again.

4
  • The fix for that is "connect_httpd(Object url)". Look, countless programmers Get Stuff Done in languages that lack declarations: static languages with type inference, as well as dynamic languages. The anecdotal evidence of millions trounces the anecdotal evidence of one. You're arguing that since names of functions and arguments are badly chosen, and will stay that way, we should have declared types. Well, what's to stop me from having uselessly named types too that are not Object? "blurch(Widget foo)" Can't you understand? The function blurches the Widget! It's typed an everything!
    – Kaz
    Commented May 15, 2013 at 16:38
  • I've worked in Ruby and Groovy. Although I like coding small tasks in them, they are horrific when dealing with a poorly documented library or integrating with code from an adjacent team (that you aren't in contact with). Not enough information in the code is the reason. People get stuff done in Basic, Fortran, Cobol and machine language too--all have their place, many have advantages and disadvantages, but programmers Get Stuff Done isn't a good argument to chose any of them for a given task.
    – Bill K
    Commented May 15, 2013 at 17:12
  • "They are horrific" is not a fact, just your anecodtal experience. People get stuff done in Fortran and machine language, but beople get stuff done faster and easier in dynamic languages. Some of the things you can do in Ruby in minutes would you months in Java.
    – Kaz
    Commented May 15, 2013 at 17:24
  • People do small things quicker in Ruby and dynamic languages--say twitter. Large things not so much (say twitter). There are appropriate tools for every job, Ruby isn't the best answer for them all--are you suggesting it is?
    – Bill K
    Commented May 15, 2013 at 17:29
1

To give something a type give:

  1. Users an interface to code to with a semantic description.
  2. Compilers an interface to parse from to allow their optimizers to increase code execution time or shrink code size more easily.
  3. Early(static) binding which catches code errors early in the development cycle.

Using just an object conveys almost no information, thus removing all of these features that were put into the language for the reasons listed above.

Also, the type system was designed for flexibility with its is-a relationships allowing for all of these conditions. So use it to your advantage. Don't waste it by making unmaintainable code.

There may be a particular reason to do something like this at a particular time, but to do this it this generically all the time is poor practice. If you are doing it, get others to look over the problem and see if it can be done another way. In the majority of cases (99.999%) it can be.

In that 0.001% that it can't be, at least you are all on board and know what is happening (though you probably just need some fresh eyes on the problem).

1

Yes, this is poor practice because it makes no guarentee that the method will be able to do what it needs to for any particular input passed in. The goal of making a method that is widely useable is not a bad goal, but there are better ways to accomplish it.

For this particular case, it sounds like a perfect situation for the use of interfaces. An interface would ensure that objects being passed to the method provide the needed information and allows the casting to be avoided. There are some situations where this won't work well (like if it has to work with either sealed or base classes that you can't add an interface implementation to), but overall a method should only take input that is valid for it as parameters.

0

Great in-depth answers so far, I really have to say that before even getting into high level philosophy and methodology, they are being lazy, short and simple.

They are not providing good information in their code for others to maintain and understand, and they are going against the design of the programming language at hand. I love PERL, I love Objective-C. I do not however make efforts to bend Java, C#, or C++ for that matter, toward my preferences at the expense of having maintainable, readable, and efficient code.

Yes, if they need to change how a function works, or what the inputs are, they saves a few line changes here and a few refactoring steps. However, the cost to others having to maintain the code is greater, not to mention most modern IDEs such as eclipse and Xcode can handle doing the refactoring automatically with a simple right click command. Furthermore, as others have mentioned, the processing cost is increased with each cast, with each unknown object type that's passed, and I imagine basic built in speedups that runtime would perform by knowing the proper classes are also thrown out the window.

Syntax sugar is always better than salt for the developer, but a balanced diet of both ensures the health of the project and others involved (customer, application, other developers, etc.)

-2

There is no problem with this. In languages that do not have typed variables, this is what happens in every function. Formal parameters are just values, and the type is in the object that is passed, not in the formal parameter.

People Get Stuff Done (TM) in Lisp, Python, Ruby, ...

Though you benefit less from compile time type checking, you also suffer less from compile time type checking.

The real solution is to use some dynamic language that targets the Java virtual machine, instead of writing ugly Java code in which every third identifier is the word Object.

I does make sense to have a completely generic function called construct which takes arbitrary objects.

I think I would enjoy collaborating with this programmer. It would take some of the sting out of using a bletcherous programming language.

Note that this type of code occurs in C in the implementation of the run-time support for dynamic languages implemented in C. E.g. you might see something like this in a Lisp implemented in C:

/* Map each object in a list through a function, in order to produce
   a list of the return values. */

val mapcar(val function, val list)
{
   val iter = car(list);
   val out = nil;  /* nil is really a null pointer */

   for (iter = car(list); iter; iter = cdr(iter))
      push(funcall_1(function, car(iter)), out);  /* push is a C macro */

   return nreverse(out); /* destructively reverse list */
}

The val type is actually something like:

typedef struct object *val;

and struct object is a complicated structure that overlays various kinds of type information for various kinds of objects.

I developed a very nice little programming language, whose internals are all done in this style, which I deliberately kept as clean as possible: more so than in the internals of some other language implementation which take a similar approach.

In this style, amazing things are possible in a small amount of C code.

8
  • 1
    -1 Except in the OP it's made clear the programmer is not doing a form of duck typing, but in some cases actually casting to the "correct" type. This isn't at all a recommended practice in languages with dynamic typing such as Python, Ruby, etc. Instead, it's a deep misunderstanding of how OOP works, regardless of type system.
    – Andres F.
    Commented May 15, 2013 at 12:04
  • @AndresF Can you use an Object in all the necessary ways without obtaining a more specifically typed reference to it? Do Python and Ruby have the equivalent idea: that a variable is reference to a base, and has to be cast so that you can invoke the subclass operations?
    – Kaz
    Commented May 15, 2013 at 16:05
  • I don't think you have to explicitly cast in Python or Ruby (that runs contrary to their philosophies, as far as I can tell). E.g. you are encouraged to use Duck Typing in Python; if you are casting, you are doing it wrong. And in Java, if you receive an Object but then cast it to a more appropriate subtype you are also doing it wrong! You are subverting Java's static type checks to gain nothing in return; you are essentially saying "I know how this will be used, but I don't want to let the compiler know. Just because."
    – Andres F.
    Commented May 15, 2013 at 17:56
  • Python casting is the conversion of a value to another type: essentially a construction of a new value based on another one. For instance an integer can be cast to boolean but what that means is that a boolean value is constructed which is true for a nonzero integer, false otherwise. This is not the same thing as casting static references in order to gain access to the full type of an object. I don't think Python has anything like Java references so to say that it's not a recommended practice is misleading; it's an impossible practice.
    – Kaz
    Commented May 15, 2013 at 18:28
  • But, of course, inside the Python interpreter, casting has to take place so that objects can be operated upon properly. If some object comes into a string function, the interpreter has to validate that it's a string, and then cast the pointer to the right type to work with it as a string object. Does that mean that the developers of Python do not understand how OOP works?
    – Kaz
    Commented May 15, 2013 at 18:31
-3

Assume for a minute that "Object guy" has reason for using a generic "loosely" typed object. We do not know his reasons, but it reminds me of the value of using inheritance and Generics (Templates). In the off chance that Object-guy is actually trying to make sense, this is the sense he might be trying to make:

I always base my objects on a single base object. This base object handles additional duties like exception handling, logging, profiling, and garbage collection.

I always base mathematical objects (those that might support mathematical operators) on a single base class. This helps provides utilities to handle exceptions in the event a derived class does not handle a certain operation. For example, using a Taylor series in the event an explicit representation for a function is not available.

So I do not know Object-guys reasons are: perhaps they are just because he like dynamic typing offered by other languages. But perhaps he is trying to appreciate the value of inheritance.

He may also want to explore Generics (Templates). I used them in C++ to define two, three, and four dimensional images, where the pixels can be bytes, ints, floats, complex, or spectra. I had to do a fair bit of casting, but the pay-off was significant in terms of separating the geometry of images from the computation of pixels.

1
  • 1
    The value of inheritance is not "appreciated" by type casting. That's throwing OOP and polymorphism completely out the window (regardless of type system!).
    – Andres F.
    Commented May 15, 2013 at 12:08
-5

Don't forget that this is a good pattern for languages like JavaScript which don't support passing parameters by name. Consider a function

function dialog( message, cancelButton, okText, notOkText ) { ... } 

which has to be called like

dialog( "...", true, "...", "..." );

When you study the client code, it's not easy to remember the meaning of, say, the third parameter of the call. The readability of the client code suffers. Therefore, it is better to declare it with a param object

function dialog( options ) { ... }

which enables you to call it with named parameters:

dialog({ 
  message : "...",
  cancelButton: true,
  okText: "...",
  notOkText: "..."
  })

Principle: We should reduce the number of parameters of a function (zero is excellent, one is frequent, two and more should be rare)!

6
  • 11
    Java is to JavaScript as car is to carpet. The question is about Java. How does any of this answer apply to the question?
    – user16764
    Commented May 14, 2013 at 20:34
  • 1
    I LOOOOVE the car to carpet analogy :D
    – SinisterMJ
    Commented May 14, 2013 at 22:57
  • @AntonRoth It's not mine.
    – user16764
    Commented May 14, 2013 at 23:44
  • In Java the direct equivalent would be dialog( Map<String, ?> options ), not dialog( Object options ) so this is not what OP's code was trying to do.
    – Esailija
    Commented May 15, 2013 at 9:44
  • Java and JavaScript are completely unrelated, so this answer is really bad.
    – Andres F.
    Commented May 15, 2013 at 12:07

Not the answer you're looking for? Browse other questions tagged or ask your own question.