When developing in teams with a large number of people, the processing that you want to do in common comes out. For example, character string operations and date operations are used in various scenes, but in that case, I think that you will use a convenient library or create a class with useful methods.
If it is a process for a specific class, it can be created to some extent easily, but when developing with a large number of people, there is a request to perform common process for various classes.
Something that is convenient to use at that time is "generics" or "functional interface". Even if you know about these two things, it may be difficult for some people to understand how to actually use them.
So, this time, I would like to introduce how to actually use it while following the process of actually creating a convenient method.
By passing it like a parameter to the definition of a data type, a program with a similar structure can support multiple data types.
For example, up to JDK1.4, when dealing with List, there was no type specification and anything could be entered as an Object.
List list = new ArrayList();
list.add("string");
list.add(Integer.valueOf(100));
If the type is not restricted and you want to enter only characters, the person who creates the program has to be careful. After the generics came out, it became possible to specify the type, and it became possible to write as follows.
List<String> list = new ArrayList<>();
list.add("string1");
list.add("string2");
Looking at the Java API specifications, they are listed as ** List \ <E > ** and ** ArrayList \ <E > **, respectively. This ** E ** part is called generics, and any type can be set for a program with the same structure.
A simple representation of a functional interface is an interface that allows you to assign "method references" and "lambda expressions" introduced in Java 8.
For example, getters and setters that are often created in Java are represented by functional interfaces as follows.
Method | interface | Method |
---|---|---|
getter | Supplier<T> | T get​() |
setter | Consumer<T> | accept(T t) |
Supplier is an interface that has no arguments and returns an arbitrary type. Similar companions include IntSupplier, BooleanSupplier, etc., which return primitives rather than arbitrary ones.
Consumer is an interface that takes arguments and has no return value. There are also IntConsumer, DoubleConsumer, etc. that pass primitives to similar peers rather than arbitrarily.
For those who have never used generics or functional interfaces, the above explanation may be difficult. Therefore, I will explain how to use it easily while actually creating a convenient method.
Let's say you have created a student class. Suppose a student class has a student ID number, student name, and age, and setters and getters are defined.
public class Student {
/**Student number*/
private String code;
/**Student name*/
private String name;
/**age*/
private int age;
//... setter,getter abbreviation...
}
To prepare the unit test data, create 3 students as test data and pack them in the List.
final Student student1 = new Student();
student1.setCode("S01001");
student1.setName("Yamada Taro");
student1.setAge(20);
final Student student2 = new Student();
student1.setCode("S02001");
student1.setName("Jiro Yamada");
student1.setAge(19);
final Student student3 = new Student();
student1.setCode("S03001");
student1.setName("Saburo Yamada");
student1.setAge(18);
final List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
students.add(student3);
If it's for 3 people, I can't see it yet, but if this number increases to 10 or 20, it will be a big deal.
First, I created a method that simplifies the creation of List.
public <T> List<T> createInstanceList(Supplier<T> supplier, int size) {
return IntStream.range(0, size)
.mapToObj(i -> supplier.get())
.collect(toList());
}
I will explain this program little by little.
public <T> List<T> createInstanceList(Supplier<T> supplier, int size)
First of all, regarding the method declaration, \ <T > and generics are defined. Since it uses generics, it has become a method that can be used for any class.
Since the return type is ** List \ <T > **, a List of any type will be returned.
Since the first argument is declared ** Supplier \ <T > **, you can receive an interface just to return an arbitrary type as mentioned in the introduction of generics.
The first argument is ** int **, which allows you to specify the size of the List.
IntStream.range(0, size)
Next is the value to be returned. First, the stream is created with ** IntStream.range (0, size) **. It repeats from 0 to size -1 because it uses range. If size is 3, the numbers 0, 1, 2 are repeated.
.mapToObj(i -> supplier.get())
I've just passed 0, 1, 2 from IntStream, but I'm ignoring it and using ** supplier.get () **. This calls the get method of the function interface passed as an argument and returns the result.
.collect(toList());
Finally, the number of repetitions of the number (3 times in the example) returns the value received from the functional interface in a List.
Since there are some parts that are difficult to explain, let's change the first code to see how it changes when actually using this method.
final List<Student> students = createInstanceList(Student::new, 3);
students.get(0).setCode("S01001");
students.get(0).setName("Yamada Taro");
students.get(0).setAge(20);
students.get(1).setCode("S02001");
students.get(1).setName("Jiro Yamada");
students.get(1).setAge(19);
students.get(2).setCode("S03001");
students.get(2).setName("Saburo Yamada");
students.get(2).setAge(18);
The student instantiation and set to List are gone.
final List<Student> students = createInstanceList(Student::new, 3);
This first line creates a List and an internal instance at the same time. ** Student :: new ** is said to be a ** constructor reference ** and returns a ** functional interface ** that returns the result of new Student ().
The constructor reference will be treated as a Supplier
I created the method earlier and the code is shorter, but setting the value is still complicated. Therefore, I will write the following code.
public <T, U> void setValues(List<T> obj, BiConsumer<T, U> biConsumer, U... values) {
for (int i = 0; i < obj.size(); i++) {
biConsumer.accept(obj.get(i), values[i]);
}
}
I will explain this program little by little.
public <T, U> void setValues(List<T> obj, BiConsumer<T, U> biConsumer, U... values)
First of all, regarding the method declaration, ** \ <T, U > ** and generics are defined. Unlike the first method, it handles two arbitrary types.
Since the return type is ** void **, no value is returned.
Since the first argument is ** List
The second argument is ** BiConsumer <T, U> biConsumer **. This will be explained later.
The third argument is ** U ... values **, which allows you to pass variable arguments. With this argument, you can make the value you want to set for the objects in the List variable.
Let's change the code to see how it changes when we actually use this method.
final List<Student> students = createInstanceList(Student::new, 3);
setValues(students, Student::setCode, "S01001", "S02001", "S03001");
setValues(students, Student::setName, "Yamada Taro", "Jiro Yamada", "Saburo Yamada");
setValues(students, Student::setAge, 20, 19, 18);
The code is much shorter and the outlook is better.
I want to set a value for the first argument ** I have a list of students **.
We are passing ** setter's method reference ** as the second argument. This method reference is passed as BiConsumer \ <T, U > for the method we just created and is called by biConsumer.accept (...).
Since the third and subsequent arguments are ** variable arguments **, list the values you want to set.
I have one question here. The setter method should normally use ** Consumer \ <T > ** as it has one argument and no return value. Now, I will briefly explain why we use a functional interface called BiConsumer that takes two arguments.
For example, if you use Conumer \ <T > as an argument, you can create the following program.
public <T> void setValue(Consumer<T> consumer, T value) {
consumer.accept(value);
}
Here's how to use it.
Student student = new Student();
setValue(student::setCode, "S01001");
The differences from using BiConsumer are as follows.
interface | Method reference |
---|---|
BiConsumer<T, U> | Student::setCode |
Consumer<T> | student::setCode |
When using BiConsumer, the method reference of the class is used, and when using Consumer, ** the method reference of the instance ** is used.
It doesn't make sense to pass a method reference for each instance inside the List because we want to set the value for each instance in the List this time. Therefore, I pass ** class method reference ** and call setter for each instance in List.
The following two methods were created this time.
public <T> List<T> createInstanceList(Supplier<T> supplier, int size) {
return IntStream.range(0, size)
.mapToObj(i -> supplier.get())
.collect(toList());
}
public <T, U> void setValues(List<T> obj, BiConsumer<T, U> biConsumer, U... values) {
for (int i = 0; i < obj.size(); i++) {
biConsumer.accept(obj.get(i), values[i]);
}
}
I usually work on development with many students. Since there are large differences in skills, it is essential to be able to write similar programs as much as possible and to create convenient methods to improve development efficiency.
You can quickly create simple and convenient methods, but the more general you try to create, the more convenient you will be if you know the generics and functional interfaces introduced here.
Recommended Posts