It has to do with extensibility, reuse and avoiding duplication.
For one built in tasks can be extended like:
task CopyAndThen(type: Copy) {
doFirst {
println "this is before the actual copy"
}
doLast {
println "this is after the actual copy"
}
}
The second common scenario that comes to mind is with multi project builds, where you can have a task definition at the top of the project with common behavior:
allprojects {
task myTask_a {
doFirst {...}
}
}
And then the specific projects can extend that.
The task essentially has a list of the Closures that needs to be run and the choice of doFirst
or doLast
controls to which end of the list the insert goes to.