Why do you think "/contents/4.json" would call get_index?
Without a route in place, the route will be automatically determined, where the first segment is the controller, the second segment the method, and additional segments are parameters. Or, in the case of a module, the first segment is the module, and everything moves one position.
So, with this URI, the framework tries to find Controller_Contents::get_4(). Which does not exist.
The search will only default to the 'index' method is there is no second segment. Which is not the case here.
I find that dangerous, because that means you'll route all unknown method requests to 'index' too, as the router doesn't know what is and isn't a valid param.
By having to define a route for it, you explicitly define that this is the behaviour your want.
Alternatively, you can add a router() method to the controller. If it exists, it will be called no matter what method was requested. In it, check if the method is a defined action. If so, simply return the call to that action. If not, assume it's a parameter (or validate it if needed) and call the action_index with it.
You can have a peek in the Controller_Rest base class, which does something similar.