preface

Due to personality, the author is very difficult to sink down and serious system study, always like to skills, for some strange her very like design code, a code of details, so I know the share tips of how to write the least code, if you have a better solution, welcome to leave a message in the comments section, the plan is very good, plus I WeChat, Send you a cup of milk tea in winter

Java: I want to return multiple return values

Show the multiple return values of Go:

package main
import "fmt"

// Return X + Y and X * Y
func Computer(X, Y int) (int.int) {
    return X + Y, X * Y
}
Copy the code

As we all know, Java only supports a single return value. In general, if we need to return multiple objects, we will either choose a container or create a new class based on the code semantics to package what we want.

Is there a problem with that? No problem, of course, but the flaw is that you can create semantically insignificant intermediate classes that have to exist. I personally talk a lot about this type of code, so how do you solve this problem?

The first step is to recognize that the solution must meet several requirements:

  • Code reuse
  • Semantic clarity
  • security

In this case, we can use generics to meet the requirements of reuse and clear semantics, and use intermediate classes to meet the requirements of code security, as follows:

public class MultipleTwoReturn<A.B> {
    /** First return value **/
    private final A first;

    /** The second return value **/
    private final B second;

    public MultipleTwoReturn(A first, B second) {
        this.first = first;
        this.second = second;
    }

    // Omit the Get method
}
Copy the code

At the same time, we can rely on inheritance to have the utility class extend more parameters:

public class MultipleThreeReturn<A.B.C> extends MultipleTwoReturn<A.B> {

    /** The third return value **/
    private final C third;

    public MultipleThreeReturn(A first, B second, C third) {
        super(first, second);
        this.third = third; }}Copy the code

The test class:

public class MultipleApp {

    public static void main(String[] args) {
        MultipleTwoReturn<Integer, String> returnTest = MultipleApp.getReturnTest();
        System.out.println(returnTest.getFirst());
        System.out.println(returnTest.getSecond());
    }

    private static MultipleTwoReturn<Integer, String> getReturnTest(a) {
        MultipleTwoReturn<Integer, String> demo = new MultipleTwoReturn<>(0."Kerwin Demo.");
        returndemo; }}Copy the code

Still a common object in nature, but with generics added power! Because generic constraints are enforced in the method definition, the semantics are very clear, and the semantic-free intermediate classes mentioned above are completely eliminated, although some necessary assembly classes with business meaning are not recommended.

Generics: I want to new an object

Did you think that when you started learning Java generics? I want to use it as a generic constraint, but I need a new T, but Java doesn’t come out with new 😂

A long time ago, I was writing a generic Java crawler interface, which had a feature in it that if you pass in the target web page, you can get the beans designed for different web pages, something like this:

public interface SpiderBeansHandle<T> {
    /** Get Url **/
    String getUrl(a);

    /** Get Cookie **/
    String getCookie(a);

    /** Get CSS selector **/
    String getSelector(a);
    
    / /...
 }
Copy the code

The key point in the middle is how to get the Bean. At that time, I only had one idea: new a T

Turns out, I was naive 🙄

But in other words, since new doesn’t come out, I’m just going to go back, and I have my code

public interface SpiderBeansHandle<T> {

    /** * Get Url */
    String getUrl(a);

    /** * Get Cookie */
    String getCookie(a);

    /*** * To obtain the CSS selector */
    String getSelector(a);

    /*** * Parse Element *@param element  element
     */
    T parseElement(Element element);

    /***
     * Get Beans
     * @param* handle | Bean object handle object@param<T> Bean type *@return        List<Beans>
     */
    static <T> List<T> getBeans(SpiderBeansHandle<T> handle) {
        List<T> list = new ArrayList<>();
        List<Element> elements = SpiderUtils.getElementWithCookie(handle.getUrl(), handle.getSelector(), handle.getCookie());
        for (Element element : elements) {
            T bean = handle.parseElement(element);
            if(bean ! =null) { list.add(bean); }}returnlist; }}Copy the code

The key step is to:

/*** * Parse Element *@param element  element
 */
T parseElement(Element element);
Copy the code

So what does this little trick do? Does it look like a deformable form of a design pattern? That’s right! There is only one truth: the template method pattern

I just mentioned that I need a generic interface for handling crawlers, because a simple crawler does nothing more than take a URL and request it, parse the details and encapsulate them in its own Bean, and then get a list. So when developing business code, there must be some scenarios and requirements that are highly consistent. Using this design solution can greatly reduce duplication of code

Method: What do you really want?

Have we ever had this problem when we were writing code? Write a tool class method, but the function is too single, although the single principle is ok, but a small logic to write a bunch of methods, always feel frustrated, how to solve it?

Java8 provides functional programming to help us solve this problem to some extent, such as:

// Write a utility class method that gets a list of files and determines if they end in TXT
public static File getFileWithTxt(String path) throws IOException {
    File file = new File(path);
    if(! file.exists()) {throw new IOException("File is not exist.");
    }

    if (file.getName().endsWith(".txt")) {
        return file;
    }

    return null;
}
Copy the code

An old hand would pass in.txt, but what should I do when I need to determine the size of a file, the length of a file, or even the contents of a file? Do I have N more?

The best solution is to pass in Predicate predicates, let the caller define the logic, and then copy the most common logic based on this method. The code is as follows:

/*** * Folder predicate matches *@paramThe file file *@paramThe predicate predicate matches an *@return              List<File>
 * @throws IOException  IOException
 */
public static List<File> listFilesInDirWithFilter(File file, Predicate<String> predicate) throws IOException {
	if(! file.exists()) {throw new IOException("File is not exist.");
     }

	List<File> fileList = new ArrayList<>();
	if (file.isDirectory()) {
		File[] files = file.listFiles();
		for(File f : Objects.requireNonNull(files)) { fileList.addAll(listFilesInDirWithFilter(f, predicate)); }}else {
		if(predicate.test(file.getName())) { fileList.add(file); }}return fileList;
}
Copy the code

For example, to handle IO, go directly to the code:

public static void readLine(BufferedReader br, Consumer<String> handle, boolean close) {
    String s;
    try {
        while(((s = br.readLine()) ! =null)) { handle.accept(s); }}catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(close && br ! =null) {
            try {
                br.close();
            } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

The method says what do you want? ! Forget it, do whatever you want, please feel free 😎~

Overloading: Write more in order to write less

Write more and to write less, this sentence at first listen to feel very contradictory, but programming experience more rich friend should be able to realize the power of method overloading, especially at the time of writing tools or the bottom interface, suggest you write a first instead of internal methods, and then a little bit to according to the need to reload it, will have unexpected benefits.

The simplest example is as follows:

/ / Root method
private static void readLine(BufferedReader br, Consumer<String> handle, boolean close) {
    String s;
    try {
        while(((s = br.readLine()) ! =null)) { handle.accept(s); }}catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(close && br ! =null) {
            try {
                br.close();
            } catch(IOException e) { e.printStackTrace(); }}}}// Overload method 1
public static void readLine(String path, Consumer<String> handle, boolean close) {
    try {
        BufferedReader br = new BufferedReader(new FileReader(path));
        readLine(br, handle, close);
    } catch(FileNotFoundException e) { e.printStackTrace(); }}// Overload method 2
public static void readLine(String path, Consumer<String> handle) {
	readLine(path, handle, true);
}
Copy the code

Overloading can enrich the way we call methods, write code that is semantically clear, and make utility classes or underlying interfaces much more powerful when combined with functional programming.

At the same time, when we need to adjust the logic of a certain method, we can also use the mode of continuing to reload to minimize the impact and try not to touch the code of other modules.

Ultimate: From design patterns to abstractions

It’s not so much how to write the least code, but how to write only the code that really counts.

When faced with this kind of problem, our first reaction is to design patterns, such as the template method pattern mentioned in the generics section above. A quick reference to my previous article:

  • I only need 5 minutes to write SSO
  • Design Patterns: From why Principles are needed to Practice

With good design patterns, or variants of them, we can get highly cohesive, low-coupling code, which is a great idea.

Another idea, everyone agrees: program = algorithm + data structure, choose the right data structure can be less effective, for example, when we do similar folder requirements, we will think of using a linked list or tree structure, in doing such as: How to efficiently send a birthday message to a user when thinking of the heap structure (compare the maximum value of the heap with the current time, meet the iteration, reduce traversal) and so on.

In fact, this is all abstract, or deep or shallow, when I first learned Java, the teacher would say: everything is an object, let’s take a look at the skills of each corresponding to what?

  • Multiple return values: encapsulated objects + generic constraints
  • Generic: A public interface that encapsulates an object. It is highly abstract
  • Functional methods: Treat a method as an object
  • Overloading: The continuous evolution of object methods (behavior)

So how do you only write code that really matters? The official point is: the change in the abstract, then exactly how to smoke?

This is something we need to explore a little bit, after all, weird technique is only a trail, but I will continue to explore.

The last

If you find this helpful:

  1. Of course, it’s important to support it
  2. In addition, search and follow the public number “is Kerwin ah”, together on the road of technology to go down ~ 😋