One place where Objective-C and Java are very similar is the core object model. Both use a very close copy of the Smalltalk object model, although Java tries to confuse people by describing it in Simula terminology.
Objects are instances of classes in both languages, and classes have at most one superclass. In Java, every object is a subclass of Object. In Objective-C, there are no such requirements. There is an Object class, but most classes in Cocoa inherit from NSObject instead. You can create new root classes, although it's not advisable.
Because you can have multiple base classes, Objective-C introduces the id type to represent a pointer to some kind of object. You can implicitly cast between any object type and id.
The type systems are also similar, but with some important differences. Both are type-checked at compile time and have runtime type enforcement. That's about where the similarities end.
The difference can be seen in the terminology used to describe how you interact with objects. Objective-C uses the Smalltalk terminology, talking about sending messages. Java talks about calling methods. The distinction is quite subtle. Sending a message in Objective-C usually results in a method being called.
The big difference is that an object chooses how it handles a message, while a method call is seen as a more static thing. Java actually does method lookup dynamically, but it tries to hide this from the programmer. In Java, when you call a method, you must have a reference to an object of a type that implements this method. You can fake this with explicit casts, but the language tries to discourage you from doing so.
In Objective-C, you can send any message to any object. The type checker in the compiler will give you a warning if it doesn't think the object understands the message, but you are free to ignore it. If the message receiver is typed as id, then it can receive any message.
This is very flexible and is also the cause of one of the most difficult to debug problems in Objective-C. Methods are identified by name, not by name and type. If you define two methods with the same name and different argument types, then you may not get any errors[md]especially if they are defined in different libraries. When you send a message to an object, the compiler constructs a call frame based on the types for the method definition that it can see.
Methods in Objective-C are compiled down to the equivalent of C functions with two hidden arguments: the receiver and the selector (an abstract representation of the method name). These are called just like any C function. If you define a method taking a structure as an argument, for example, and then call it with an object as an argument, then the call frame will not have the structure that the callee expects. The result is stack corruption. Because the stack is corrupted, your debugger probably won't be able to tell you exactly what broke.
Objective-C uses a small runtime library to implement dynamic behavior, rather than a complete virtual machine. Apple maintains one and the GNU project maintains another. In the GNU runtime, selectors have type information associated with them, so it's possible to track this kind of problem. In the Apple runtime, they are just names so it is not.
To avoid this problem, Objective-C methods usually have long and descriptive names, including type information.