Dependency injection (DI) makes code testable. Some people also think it improves designs, saying things like "your code will be easier to change". I think those arguments are dangerous. They are subjective, and can mistakenly lead people to overuse DI and write four classes when one would do. If you are a low-level framework-hating luddite like me, I think the right way to think about it is that dependency injection is all about making it easy to write unit tests.
Dependency injection means passing dependencies in as parameters (typically to a constructor), rather than creating them directly. This allows unit tests to replace expensive and complicated dependencies like database or web servers. The disadvantage of dependency injection is that objects become more complicated to create, since the caller must first create the dependencies. When used extensively, this means recursively creating a complicated set of objects. Frameworks like Guice and Dagger automate part of this process. You configure the specific types you want, then the framework will create all the dependencies. However, you don't need to use a framework to use dependency injection.
My suggestion: pass objects you need to replace in a unit test into an object's constructor. Good candidates are databases, web requests, or any external state. Bad candidates are simple "value" objects that bundle together related variables, or objects where the unit test would pass in the same thing as the application. If you follow this rule, you are using dependency injection.
I've recently been exposed to Guice and found it very confusing. Part of the problem is that I'm a framework-hating luddite and distrust "magical" code. Guice, unfortunately, "magically" figures out how to create your objects. For example, the Guice Getting Started page creates a service using: RealBillingService billingService = injector.getInstance(RealBillingService.class);
. When is the RealBilling Service created? What dependencies get passed in? Once you understand Guice, you know to look at the Module bindings. However, this is not normal Java code, and hence I consider it magic.
Another reason for my confusion is that the Guice user's guide explains how to use Guice, and not why it is helpful to build applications this way. If you are also a low-level luddite who likes to understand the internals of your tools, the most helpful source I found is do it yourself dependency injection (DIY-DI). Chad Parry explains how dependency injection makes it easier to write tests, and shows how to write it without a framework. Not only is this useful by itself, it helped me understand how and why Guice works the way it does. Another excellent resource is the Guide to Writing Testable Code, written by a group at Google.
Finally, with advice like "@Inject is the new new" and "program to interfaces, not implementations," some people decide to write interfaces and inject absolutely everything, because it makes "better" code. This is when you find classes with 30 lines of scaffolding for 3 real lines of code, and understanding where a method call goes becomes a test of how well you know your editor's navigation and search tools. This overuse is what leads to people to say dependency injection is bad. I like focusing on the tests because it is a version of the "you aren't gonna need it" (YAGNI) design principle. Until you need more than one implementation, don't create an interface. Until you need to replace something in a test, don't inject it. This encourages adding complexity only when you actually need it.