It is usually admitted that extending implementations of an interface through inheritance is not best practice, and that composition (eg. implementing the interface again from s
The selection is simple based on your requirement.
Note - The below is just a use case . to illustrate the difference.
If you want a list that is not going to keep duplicates and going to do a whole bunch of things very much different from ArrayList then there is no use of extending ArrayList because you are writing everything from scratch.
In the above you should Implement List. But if you are just optimizing an implementation of ArrayList then you should copy paste the whole implementation of ArrayList and follow optimization instead of extending ArrayList. Why because multiple level of implementation makes it difficult for someone tries to sort out things.
Eg: A computer with 4GB Ram as parent and Child is having 8 GB ram. It is bad if parent has 4 GB and new Child has 4 GB to make an 8 GB. Instead of a child with 8 GB RAM implementation.
I would suggest composition in this case. But it will change based on the scenario.