0

I'm trying to figure out Generics. Let's say I have a Person class, and I want to write a list of persons to a file. How to make the method accept any list and if the list is List of Person, the method will be able to write all its fields to a file.

class PERSON { String name; String surname; public PERSON(String name, String surname) { this.name = name; this.surname = surname; } } class Lesson7 { public <T> void writeToFile(List<T> list) throws Exception{ Path path = Path.of("C:\\Users\\Professional\\IdeaProjects\\Lesson\\src\\FileWr"); String lineFirst = "# Name Surname"; Files.write(path, lineFirst.getBytes(), StandardOpenOption.APPEND); List<String> rows = new ArrayList<>(); int num = 1; for (T t: list) { String line = String.valueOf(num++) + "." + " " + t.name + " " + t.surname + System.lineSeparator(); } Files.write(Path.of("FileWr"), rows); } public static void main(String[] args) throws Exception { PERSON p1 = new PERSON("Christian", "Bale"); PERSON p2 = new PERSON("Leo", "Dicaprio"); List<PERSON> list1 = List.of(p1, p2); Lesson7 l7 = new Lesson7(); l7.writeToFile(list1); } } 
9
  • 5
    What happens in the case, when T isn't a Person? However I think this is not how you want to use generics in general. As the name implies, generics work with any kind of object you use it with. So I suggest you, that you take a look at generic constraints (e.g. the where keyword in this case) and that you add an interface Writeable e.g. which adds a toLine method or something. Then you can constraint T to implement this interface and simply call the toLine method on any object which is passed to writeToFile Commented Jun 4, 2023 at 11:59
  • 1
    away from the generic topic , since you have the list object , stream the list and collect objects which are instances of Person class Commented Jun 4, 2023 at 12:07
  • docs.oracle.com/javase/tutorial/java/generics/index.html Commented Jun 4, 2023 at 12:22
  • 1
    How to make the method accept any list and if the list is List of Person, the method will be able to write all its fields to a file. To interpret your requirement exactly, you will probably want to type-check it: if T is of type Person (don't use all caps for class names) then you will know what its fields are and can use them in the write. Otherwise, you don't know, so you can call T.toString() instead and write using that. Were you to want to be writing them the same in all cases, you would have to use reflection Commented Jun 4, 2023 at 13:14
  • 2
    You could provide your "Person" class with a proper toString implementation and then write the string that this creates to a file. The toString method is available in all Java objects, so you would not need to handle different classes differently, but the results in the file of course would vary by the implementation of toString in the individual classes. Commented Jun 4, 2023 at 22:54

2 Answers 2

0

I tried to solve it using the toString method and reflection. If you don't get hung up on the PERSON class, it turns out you can try to put any other class that has a toString method. probably I can add a check for the existence of the toString method, if it exists, then fill in the List rows

public <T> void writeToFile(List<T> list) throws Exception{ Path path = Path.of("FileWr"); String lineFirst = "# Name Surname" + System.lineSeparator(); Files.write(path,lineFirst.getBytes(), StandardOpenOption.APPEND); Class<? extends T> clazz = (Class<? extends T>) list.get(0).getClass(); Method method = clazz.getMethod("toString"); List<String> rows = new ArrayList<>(); int num = 1; for (T t: list) { String line = String.valueOf(num++) + method.invoke(t); rows.add(line); } Files.write(path,rows, StandardOpenOption.APPEND); } 
Sign up to request clarification or add additional context in comments.

1 Comment

You don't need to access .toString via reflection. It is a method of java.lang.Object so all java classes have some implementation of it, you can call it on any object in Java! I would just add an instanceof check to be sure you only call it on instances of your PERSON class, but no cast necessary. Or you use generics boundaries, will add an answer for that.
0

Based on the OPs own answer I want to add this variant that uses Java generics boundaries to ensure the method is only called on lists with suitable members:

public <T extends PERSON> void writeToFile(Collection<T> collection) throws Exception { Path path = Path.of("FileWr"); String lineFirst = "# Name Surname" + System.lineSeparator(); Files.writeString(path, lineFirst, StandardOpenOption.CREATE); int row = 1; for (PERSON person : collection) { String line = String.valueOf(row) + person.toString() + System.lineSeparator(); Files.writeString(path, line, StandardOpenOption.APPEND); row++; } } 

I modified a few places of the original method:

  • used the generic boundary T extends PERSON to ensure all list members are assignable to a PERSON variable
  • used a Collection instead of a List for the input parameter as this method does not need to know the type of collection, no specific methods of the List interface are used
  • used Files.writeString for the heading to ensure proper encoding
  • used the StandardOpenOption.CREATE for writing the header to ensure the file is not appended over and over again with the header and values, but looks "fresh" after every run
  • made use of the boundary to handle the values of the collection inside the loop as real PERSON instances. This would allow to use any special method of PERSON instead of toString if this is required.
  • used immediate Files.writeString inside the loop instead of creating an in-memory list of lines to prevent OutOfMemoryErrors and get the code more compact. This is not optimized for speed as the multiple operations to open and close the file will stress the OS but optimization is secondary here.
  • incremented the row counter in a separate instruction to increase readability. If it were for optimization I would initialize it with 0 and use inline ++row instead.

1 Comment

If you don't want to use generics boundaries, you can still go with public <T> void ... but have to use this for loop then: for (T person: collection). The call of the toString method does not change!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.