Just a little "point of view" ( I had a hard time with the idea of coupling early on...)
changing the variable from default to private is more about encapsulation than coupling because another class in the same package can have direct access to the variable and THAT is, as I say, an encapsulation problem!
The way you might want to think about the second issue: the quacker() method in class Duck has the following signature:
so you can't ever pass it anything BUT a Main. This is a) inflexible and b) it makes Duck class highly dependent on the existence of the Main class.
Now, how do you decrease that coupling? That is where the magic of the "interface" comes in to play.
What if you created and interface called "Avian" and, in that interface you specified the signatures of some behaviors (like flaps, flies, chirps.... things that all avians do) and then made Duck implement Avian. But, at the same time, you could make a Sparrow that implements Avian, an Ostrich, a seagull, a parakeet..... whatever you want.
Now, alter quacker to the following signature:
Now, quacker no longer has to have a "concrete class" Main object.... it can accept any implementer of Avian. This makes quacker() dependent on the interface type NOT the concrete type Main. That makes the code much more flexible.
I realize that this might be a little confusing because your example class IS Duck and so you might think that...."why would I want anything BUT a duck?" But try to separate this specific example from the issue of coupling that we are discussing.
If I had a method that was designed to implement a logging class called PrintLogger. Let's say that I have another class LogSetup that has a method that is designed to set a log class and it has a signature like this:
The only thing that you can ever pass to setLogger() is a PrintLogger.
Let's change that a little...
Let's design an interface called Loggable. It might have method signatures like logFine(), logInfo(), logFatal()...just to make an example.
Then we change PrintLogger class to implement Loggable
now, you can change LogSetup to look like this:
now, you can implement a FileLogger, a NetworkLogger, a TapeDriveLogger, a ScreenLogger that all implement the Loggable
interface and you can pass any of them to the setLogger method, not JUST the PrintLogger.
This is much more flexible design and you can, if you care to add and remove logger types without breaking the LogSetup code.
So, cutting it down to the essentials, any time you specify the use of a concrete class and pass concrete classes around it increases coupling.
When you design interface types, then design concrete classes that implement the interfaces, then have your code specify and pass by interface
type rather than concrete type, this reduces coupling.
I hope that this will give you some help in understanding.