'How to use generics in a simple java tree structure?

I'm learning Java generics/inheritance and now I'm trying to implement a Forest structure having two types of trees: coniferous with needles and deciduous with leaves. Trees can grow... It means that if a tree is growing, the trunk is getting higher, branches are added and needles/leaves are added to branches depending on a tree type. Firstly I started with general Tree Class with treeGrow method:

public class Tree<T extends TreeType> {
        protected int id;
        protected String name;
        protected int age;
        protected Trunk trunk;
        protected T type;

        public Tree(int id, String name, int age, Trunk trunk, T type) {
                this.id = id;
                this.name = name;
                this.age = age;
                this.trunk = trunk;
                this.type = type;
        }

        public void treeGrow(){
                this.age ++;
                trunk.treeGrow();
        }

then I implemented trunk which has a set of branches:

public class Trunk{

    private int height;
    private int width;
    private Set<Branch> branches;

    public void treeGrow(){
        trunkGrow();
        branches.forEach(Branch::treeGrow);
        branches.add(new Branch(1, new ArrayList<>()));
    }

    private void trunkGrow(){
        this.width ++;
        this.height += 15;
    }
}

... and a branch class:

public class Branch{
    int branchLength;
    List<TreeType> coates;

    public void treeGrow(){
        branchGrow();
        coates.forEach(TreeType::coatingGrow);
        TreeType leaf = new Leaf(1); 
        coates.add(leaf);
//here I have hardcoded Leaf adding, I want to make a Leaf/Needle choice dependent on a Tree type using T...
    }

    private void branchGrow(){
        this.branchLength++;
    }
}

with Needle/Leaf classes which implement TreeType interface

public class Needle implements TreeType {
    int needleLength;

    public void coatingGrow() {
       this.needleLength++;
    }
}

Question: How should my Branch class know whether Needle or Leaf should be added? I try to use generics, specifying the type of Tree class (extending TreeType interface which is implemented by Needle and Leaf classes), but I think I do not understand it quite right.



Solution 1:[1]

Question: How should my Branch class know whether Needle or Leaf should be added?

This sounds like an ideal situation to use a Supplier<T>.

   import java.util.ArrayList;
   import java.util.List;
   import java.util.Set;
   import java.util.function.Supplier;

   public interface SOQ_20220505_Attempt2
   {
   
      public final class FlatLeaf extends Leaf {
         public FlatLeaf(int length) {
            super(length);
         }
      }
      
      public final class NeedleLeaf extends Leaf {
         public NeedleLeaf(int length) {
            super(length);
         }
      }
   
      public sealed class Leaf{
         private int length;
      
         public Leaf(int length) {
            this.length = length;
         }
      }
      
      public class Branch<L extends Leaf> {
         private int length;
         private List<Branch<L>> subBranches;
         private List<L> leaves;
         private Supplier<L> leafGenerator;
      
         public Branch(int length, List<Branch<L>> subBranches, List<L> leaves, Supplier<L> leafGenerator) {
            this.length = length;
            this.subBranches = subBranches;
            this.leaves = leaves;
            this.leafGenerator = leafGenerator;
         }
      
         public void grow() {
            this.length++;
            this.subBranches.forEach(Branch::grow);
            this.subBranches.add(new Branch<>(1, new ArrayList<>(), new ArrayList<>(), this.leafGenerator));
            this.leaves.add(leafGenerator.get());
         }
      }
      
      public class Trunk<L extends Leaf> {
         private int height;
         private int width;
         private Set<Branch<L>> branches;
         private Supplier<L> leafGenerator;
      
         public Trunk(int height, int width, Set<Branch<L>> branches, Supplier<L> leafGenerator) {
            this.height = height;
            this.width = width;
            this.branches = branches;
            this.leafGenerator = leafGenerator;
         }
      
         public void grow() {
            this.width++;
            this.height += 15;
            this.branches.forEach(Branch::grow);
            this.branches.add(
               new Branch<L>(
                  1, 
                  new ArrayList<>(), 
                  new ArrayList<>(),
                  this.leafGenerator));
         }
      }
   
      public class Tree<L extends Leaf>
      {
         private int id;
         private String name;
         private int age;
         private Trunk<L> trunk;
         private Supplier<L> leafGenerator;
         
         public Tree(int id, String name, int age, Trunk<L> trunk, Supplier<L> leafGenerator) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.trunk = trunk;
            this.leafGenerator = leafGenerator;
         }
         
         public void grow() {
            this.age++;
            this.trunk.grow();
         }
      }
   
   }

Below, I have reworked your program slightly, and added a runnable example.

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

public interface SOQ_20220505_1
{

    public non-sealed class FlatLeaf extends Leaf {
        public FlatLeaf(int length) {
            super(length);
        }
    }

    public non-sealed class NeedleLeaf extends Leaf {
        public NeedleLeaf(int length) {
            super(length);
        }
    }

    public sealed class Leaf{
        private int length;

        public Leaf(int length) {
            this.length = length;
        }

        public void grow() {
            this.length++;
        }

        @Override
        public String toString() {
            return "\n\t\t\t\tLeaf [length=" + length + "]";
        }
        
    }

    public class Branch<L extends Leaf> {
        private int length;
        private List<Branch<L>> subBranches;
        private List<L> leaves;
        private Supplier<L> leafGenerator;

        public Branch(int length, List<Branch<L>> subBranches, List<L> leaves, Supplier<L> leafGenerator) {
            this.length = length;
            this.subBranches = subBranches;
            this.leaves = leaves;
            this.leafGenerator = leafGenerator;
        }

        public void grow() {
            this.length++;
            this.subBranches.forEach(Branch::grow);
            this.subBranches.add(new Branch<>(1, new ArrayList<>(), new ArrayList<>(), this.leafGenerator));
            this.leaves.add(leafGenerator.get());
            this.leaves.forEach(Leaf::grow);
        }

        @Override
        public String toString() {
            return "\n\t\t\tBranch [length=" + length + ", subBranches=" + subBranches + ", leaves=" + leaves
                    + ", leafGenerator=" + leafGenerator + "]";
        }
        
    }

    public class Trunk<L extends Leaf> {
        private int height;
        private int width;
        private Set<Branch<L>> branches;
        private Supplier<L> leafGenerator;

        public Trunk(int height, int width, Set<Branch<L>> branches, Supplier<L> leafGenerator) {
            this.height = height;
            this.width = width;
            this.branches = branches;
            this.leafGenerator = leafGenerator;
        }

        public Trunk<L> with(Supplier<L> leafGenerator) {
            return new Trunk<>(this.height, this.width, this.branches, leafGenerator);
        }

        public void grow() {
            this.width++;
            this.height += 15;
            this.branches.forEach(Branch::grow);
            this.branches.add(
                    new Branch<L>(
                            1, 
                            new ArrayList<>(), 
                            new ArrayList<>(),
                            this.leafGenerator));
        }

        @Override
        public String toString() {
            return "\n\t\tTrunk [height=" + height + ", width=" + width + ", branches=" + branches + ", leafGenerator="
                    + leafGenerator + "]";
        }
        
    }

    public sealed class Tree<L extends Leaf> permits DeciduousTree, ConiferTree
    {
        private int id;
        private String name;
        private int age;
        private Trunk<L> trunk;

        public Tree(int id, String name, int age, Trunk<L> trunk, Supplier<L> leafGenerator) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.trunk = trunk.with(leafGenerator);
        }

        public static <L extends Leaf> Tree<L> generate(int id, String name, Supplier<L> leafGenerator) {
            return new Tree<L>(id, name, 0, new Trunk<L>(3, 1, new HashSet<>(), leafGenerator), leafGenerator);
        }

        public void grow() {
            this.age++;
            this.trunk.grow();
        }

        @Override
        public String toString() {
            return "\n\tTree [id=" + id + ", name=" + name + ", age=" + age + ", trunk=" + trunk + "]";
        }

    }

    public final class DeciduousTree<L extends FlatLeaf> extends Tree<L> {
        public DeciduousTree(int id, String name, int age, Trunk<L> trunk, Supplier<L> leafGenerator) {
            super(id, name, age, trunk, leafGenerator);
        }
    }

    public final class ConiferTree<L extends NeedleLeaf> extends Tree<L> {
        public ConiferTree(int id, String name, int age, Trunk<L> trunk, Supplier<L> leafGenerator) {
            super(id, name, age, trunk, leafGenerator);
        }
    }

    public static void main(String[] args)
    {

        Tree<FlatLeaf> tree = Tree.generate(1, "Deciduous", (() -> new FlatLeaf(1)));
        System.out.println(tree);
        System.out.println(new ConiferTree<>(2, "Conny", 0, new Trunk<NeedleLeaf>(0, 0, null, null), () -> new NeedleLeaf(1)));

        System.out.println("\n\n\n");
        
        for (int i = 0; i < 3; i++) {
            tree.grow();
            System.out.println(tree);
        }
        
    }

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 davidalayachew