In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/01 Report--
This article introduces the relevant knowledge of "what is the thinking method of Java8 function". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Functional thinking, implementation and maintenance system
What other features do you want the program to have in order to make it easy to use?
You will want it to have a good structure, and the structure of the best class should reflect the structure of the system, so that it can be easily understood.
Even in software engineering, indicators are provided to evaluate the rationality of the structure, such as coupling (whether the components in the software system are independent of each other) and cohesion (how the relevant parts of the system cooperate).
For most programmers, the most important daily concern is debugging during code maintenance: code crashes when it encounters some unexpected values.
Why did this happen?
How did it get into this state?
Think about how many code maintenance concerns you have can be attributed to this category! It is obvious that the "no side effects" and "invariance" put forward by functional programming are of great help to solve this problem.
Shared variable data
Unpredictable variable modification problems arise from shared data structures being read and updated by multiple methods in the code you maintain.
Suppose several classes hold references to a list at the same time.
So who has ownership of this list?
What happens if a class modifies it?
Do other classes expect this change?
How do other classes know that the list has changed?
Do we need to notify all classes that use the list of this change?
Or should each class prepare a defensive backup of data for a rainy day?
In other words, due to the use of variable shared data structures, it is difficult for us to track changes in the various parts of your program.
Suppose there is a system that does not modify any data. Maintaining such a system will be an unparalleled dream, because you will no longer receive any unexpected reports caused by certain objects that have modified a data structure in some places. If a method does not modify the state of its embedded class or other objects, and uses return to return all the calculation results, then we call it pure or side-effect-free.
More specifically, which factors will cause side effects? In short, the side effect is that the effect of the function is beyond the scope of the function itself. Here are some examples.
In addition to the initialization within the constructor, any modification to the data structure in the class, including the assignment of fields (a typical example is the setter method).
Throw an exception.
Perform input / output operations, such as writing data to a file.
From another point of view, if there are no side effects, we should consider immutable objects. Immutable objects are objects that, once initialized, are not modified by any method. This means that once an immutable object is initialized, it will never enter an unpredictable state. You can safely share it without keeping any copies, and because they will not be modified, they are thread-safe.
If all the components of the system can follow this principle, the system can use multi-core concurrency mechanism without lock, because any one method will not interfere with other methods.
Declarative programming
Generally, a system is implemented by programming, and there are two ways of thinking.
Focus on how to achieve
How to do
One focuses on how to achieve it, such as: "do this first, then update that, and then."
For example, if you want to calculate the most expensive transactions in the list, you usually need to execute a series of commands:
Take a transaction from the list and compare it with the temporary most expensive transaction
If the transaction is more expensive, set the temporary most expensive transaction to that transaction
Then take the next transaction from the list and repeat the above.
This "how to do" style of programming is very suitable for classic object-oriented programming, and sometimes we call it "imperative" because its instructions are very similar to the underlying words of the computer. such as assignments, conditional branches, and loops, like the following code:
Transaction mostExpensive = transactions.get (0); if (mostExpensive = = null) throw new IllegalArgumentException ("Empty list of transactions") for (Transaction t: transactions.subList (1, transactions.size ()) {if (t.getValue () > mostExpensive.getValue ()) {mostExpensive = t;} pay attention to what to do
What to do
The other way is to focus more on what to do. Using Stream API, you can specify a query like this:
Optional mostExpensive = transactions.stream () .max (comparing (Transaction::getValue))
This query leaves the details of how it is finally implemented to the library. We call this idea internal iteration. Its great advantage is that your query now reads like a problem statement, which is much more concise than understanding a series of commands.
Programming in this "what to do" style is often referred to as declarative programming. You set the rules, give what you want to achieve, and let the system decide how to achieve it. The benefits are obvious, and the code written in this way is closer to the problem statement.
Why to use functional programming
Functional programming practices declarative programming ("you just need to use expressions that don't affect each other, describe what you want to do, and it's up to the system to choose how to do it") and side-effect-free calculation. these two ideas can help you build and maintain the system more easily.
Some language features, such as constructions and transitive behaviors, are necessary to implement declarative programming in a natural way, making our programs easier to read and write. You can use Stream to concatenate several operations to express a complex query. These are the features of functional programming languages.
What is functional programming?
The simplest answer to the question "what is functional programming" is "it is a way of programming with functions." So what is a function?
It's easy to imagine a method that takes an integer and a floating-point parameter and returns a floating-point result-- it also has side effects, constantly updating shared variables as the number of calls increases.
In the context of functional programming, a "function" corresponds to a mathematical function: it accepts zero or more parameters, produces one or more results, and has no side effects. You can think of it as a black box that receives input and produces some output.
The difference between this type of function and the one you see in the Java programming language is important (we can't imagine that mathematical functions like log or sin have side effects). In particular, if a mathematical function is called with the same parameters, the result it returns must be the same.
When talking about "functions", what we want to say is "like mathematical functions-- no side effects". As a result, some subtle problems in programming follow. What we mean is that each function can only be built using functions and mathematical ideas like if-then-else?
Or do we allow some non-functional operations to be performed within the function, as long as the results of these operations are not exposed to the rest of the system? In other words, if the program has a certain side effect, but the side effect is not perceived by other callers, can we assume that the side effect does not exist? The caller does not need to know or care about these side effects at all, because it has no effect on it at all.
When we want to define the difference between the two, we call the first pure functional programming and the latter functional programming.
Functional Java programming
In programming practice, you can't use the Java language to complete a program in pure functional form.
For example, Java's IUnip O model includes methods with side effects (calling Scanner.nextLine has a side effect, which reads a line from a file, usually with completely different results from two calls).
However, it is still possible to write near-purely functional implementations for the core components of your system. In the Java language, if you want to write functional programs, the first thing you need to do is to make sure that no one is aware of the side effects of your code. Suppose such a function or method has no side effects, adding one to the value of a field when entering the method body and minus one to the field before exiting the method body. For a single-threaded program, this method has no side effects and can be seen as a functional implementation.
On the other hand, if another thread can view the value of the field-or worse, the method will be called concurrently by multiple threads at the same time-then this method cannot be called a functional implementation.
Of course, you can encapsulate the method body of the method with a lock to cover up the problem, and you can even claim that the method conforms to the functional convention again. However, after doing so, you lose the ability to execute two method calls concurrently on both cores of your multi-core processor. Its side effects may not be visible to the program, but it is visible to the programmer because the program runs slower!
Our rule is that functions or methods called "functions" can only modify local variables. In addition, the objects it refers to should be immutable. With this stipulation, we expect all fields to be of type final, and all reference type fields to point to immutable objects. In the following content, you will see that we actually allow fields in newly created objects in the method to be updated, but these fields are not visible to other objects and will not affect the results of subsequent calls because of the save.
Our aforementioned criteria are incomplete, and there is an additional condition to be a true functional program, but it was not taken seriously at the beginning. To be called a function, a function or method should not throw any exception. There is an extremely simple and dogmatic explanation for this: you should not throw an exception, because once an exception is thrown, it means that the result is terminated; unlike the black box pattern we discussed earlier, return returns an appropriate result value.
However, this rule seems to conflict with our actual mathematical use: although legitimate mathematical functions return a definite result for each legal parameter value, it may be more appropriate for many general mathematical operations to strictly call them local functions (partial function). This function returns a definite result for some input values, or even most input values, but for other input values, its results are undefined and do not even return any results.
A typical example of this is division and square operation, which happens if the second Operand of division is 0, or if the parameter of square is negative. It seems natural to model these situations in the way Java throws an exception. There is a certain dispute here, and some authors think that it is acceptable to throw an exception that represents a serious error, but catching an exception is a non-functional control flow, because this operation violates the rule of "pass parameters and return results" defined in the black box model, which leads to the third arrow representing exception handling.
So, if you don't use exceptions, how do you model functions like division? The answer is to use the Optional type: you should avoid letting sqrt use a function signature such as double sqrt (double) because it may throw an exception; instead, we recommend that you use Optional sqrt (double)-in this way, the function either returns a value indicating a successful call or an object indicating that it cannot perform the specified operation.
Of course, this means that the caller needs to check whether the method returns an empty Optional object. This sounds expensive, and based on our previous comparison between functional programming and pure functional programming, from a practical point of view, you can choose to use exceptions locally to avoid exposing the results to other methods through interfaces, which not only gains the advantages of functional programming, but also does not over-inflate the code.
Finally, as a functional program, if the library functions of your functions or method calls have side effects, you must try to hide their non-functional behavior, otherwise these methods cannot be called (in other words, you need to make sure that any changes to the data structure are invisible to the caller, and you can do this by copying for the first time or catching any exceptions that may be thrown)
Reference transparency
These restrictions on "no perceptible side effects" (not changing the variables visible to the caller, not doing I _ peg O, and not throwing exceptions) all imply reference transparency. If a function just passes the same parameter value and always returns the same result, then the function is referential transparent.
The String.replace method is referenced transparently, because calls like "raoul" .replace (the replace method always returns the same result (the replace method returns a new string, replacing all uppercase R with lowercase r) rather than updating its this object, so it can be considered functional.
In other words, no matter where and when a function is called, it has the characteristics of a function if it can always get the same result continuously with the same input.
This also explains why we don't think of Random.nextInt as a functional approach. In the Java language, using the Scanner object to read input from the user's keyboard also violates the principle of reference transparency, because you may get different results each time you call nextLine. However, adding two variables of type final int will always get the same result, because in this way the contents of the variables will not be changed.
Reference transparency is an important attribute of understanding programs. It also includes the optimization of the value of a variable that is expensive or takes a long time to calculate (through a preservation mechanism rather than double counting), which is often referred to as memorization or caching.
In the Java language, there is also a more complex issue about reference transparency. Suppose you call a method that returns a list twice. These two calls return two different lists in memory, but they contain the same elements. If these lists are treated as mutable object values (and therefore different), then the method is not referenced transparently. If you plan to treat these lists as simple values (unmodifiable), it makes sense to treat these values as the same, in which case the method is clearly referenced. In general, in functional programming, you should choose to use functions with transparent references.
Comparison between object-oriented programming and functional programming
Let's start with a comparison between functional programming and (extreme) typical object-oriented programming, and eventually you'll find that Java8 thinks these styles are just one extreme of object-oriented. As a Java programmer, there is no doubt that you have used some kind of functional programming, and you must have used what we call extreme object-oriented programming. Due to changes in hardware (such as multicore) and programmers' expectations (such as using database-like query-like languages to manipulate data), Java's software engineering style is becoming more and more functional to some extent.
There are two views on this issue.
A method that supports extreme object orientation: everything is an object, and the program either updates the field or invokes methods that update the objects associated with it.
Another view supports functional programming with transparent references, arguing that methods should not be modified (to externally visible) objects.
In practice, Java programmers often mix these styles. You may use an iterator that contains a variable internal state to traverse a data structure while calculating the sum of variables in the data structure in a functional way.
Functional programming practice
SubsetsMain
An example functional programming exercise: given a list List, such as {1,4,9}, construct a List whose members are subsets of the class table {1,4,9}-regardless of the order of elements for the time being. The subset of {1,4,9} is {1,4,9}, {1,4}, {1,9}, {4,9}, {1}, {4}, {9} and {}.
There are a total of 8 such subsets, including empty subsets. Each subset is represented by List, which is the expected List type in the answer.
A special explanation is also needed for "the subset of {1,4,9} can be divided into two parts that contain 1 and do not contain 1". A subset that does not contain 1 is simply {4,9}, and a subset that contains 1 can be obtained by inserting 1 into each subset of {4,9}. In this way, we can use Java to implement the program in a simple, natural, top-down functional programming (a common programming error is to assume that an empty list does not have a subset)
Static List subsets (List list) {if (list.isEmpty ()) {List ans = new ArrayList (); ans.add (Collections.emptyList ()); return ans;} Integer first = list.get (0); List rest = list.subList (1line list.size ()); List subans = subsets (rest) List subans2 = insertAll (first, subans); return concat (subans, subans2);}
If the input given is {1,4,9}, the final answer given by the program is {{}, {9}, {4}, {4,9}, {1}, {1,9}, {1,4,9}}.
Assume that the missing methods insertAll and concat are functional themselves, and infer that your subsets method is also functional, because nothing in this method modifies the existing structure. This is the famous method of induction.
Static List insertAll (Integer first, List lists) {List result = new ArrayList (); for (List list: lists) {List copyList = new ArrayList (); copyList.add (first); copyList.addAll (list); result.add (copyList);} return result;}
But we hope you don't use it like that.
Static List concat (List a, List b) {a.addAll (b); return a;}
However, what we really recommend to you is the following approach:
Static List concat (List a, List b) {List r = new ArrayList (a); r.addAll (b); return r;}
The second version of concat is purely functional. Although it internally modifies the object (adding elements to list r), the result it returns is based on parameters without modifying any of the passed-in parameters. In contrast, the first version is based on the fact that no one needs to use the value of subans again after executing the concat (subans, subans2) method call. This is true for the subsets we define, so using a simplified version of concat is a good choice. But it also depends on how you look at your time, and are you willing to spend your time trying to locate weird flaws? Or do you spend a little bit of money to create a copy of the object?
No matter how you explain this not-so-pure concat method, "it will only be used in scenarios where the first parameter can be forcibly overridden, or only in this subsets method, any changes to subsets will follow this standard for code review." once one day in the future, someone finds that some parts of the code can be reused and seems to work, your future debugging nightmare begins.
Keep in mind that when considering programming problems, using a functional approach, focusing on the input parameters of the function and the output (that is, what you want to do) is usually much more effective than thinking about how to do and modify things early in the design phase.
Recursion and iteration
Recursion
Purely functional programming languages usually do not include iterative constructors such as while or for. Because this type of constructor often hides traps that tempt you to modify the object.
For example, in a while loop, the condition of the loop needs to be updated; otherwise the loop will not be executed once, or it will enter the state of an infinite loop. However, loops are very useful in many cases.
The function also allows changes if no one can perceive it, which means that we can modify local variables. The for-each loop we used in Java, for (Apple a: apples {}), if rewritten as an iterator, the code is as follows:
Iterator it = apples.iterator (); while (it.hasNext ()) {Apple apple = it.next (); / /...}
This is not a problem, because when changes occur, these changes (including changes to the state of the iterator using the next method and the assignment of apple variables within the while loop) are not visible to the caller of the method. However, using a for-each loop, such as the following search algorithm, can cause problems because the loop modifies the data structure shared by the caller:
Public void searchForGold (List l, Stats stats) {for (String s: l) {if ("gold" .equals (s)) {stats.incrementFor ("gold");}
In fact, for functions, the loop body has an inevitable side effect: it modifies the state of the stats object, which is shared with the rest of the program.
For this reason, pure functional programming languages such as Haskell directly remove such operations with side effects! How do you program after that? The answer to the comparative theory is that every program can use recursive rewriting without modification, thus avoiding the use of iterations. With recursion, you can eliminate iterative variables that need to be updated at each step. A classic teaching problem is to write a function to calculate factorial (the parameter is positive) iteratively or recursively (assuming the input value is greater than 1). The code is as follows.
Factorial calculation of iterative form
Static int factorialIterative (int n) {int r = 1; for (int I = 1; ia * b);}
Talk about efficiency. As a user of Java, I'm sure you've realized that enthusiastic supporters of functional programs will always tell you that you should use recursion instead of iteration. However, in general, the cost of performing a recursive method call is much higher than iterating through a single machine-level branch instruction. Why? Each time a factorialRecursive method call is executed, a new stack frame is created on the call stack to hold the state of each method call (that is, the multiplication it requires), which guides the program to run until the end.
This means that your recursive iterative method consumes memory in proportion to the input it receives. This is why if you use a large input to execute the factorialRecursive method, it is easy to encounter StackOverflowError exceptions:
Exception in thread "main" java.lang.StackOverflowError
Does this mean that recursion is useless? Of course not! Functional languages provide a way to solve this problem: tail-tone optimization (tail-call optimization). The basic idea is that you can write an iterative definition of the factorial, but the iterative call occurs at the end of the function (so we say the call occurs at the tail). This new type of iterative invocation is much faster after optimization. As an example, the following is a tail-recursive definition of a factorial.
PS. Learn from each other between iterative and recursive methods. Personally, I don't think it's good to follow the code example. Think of the merging of "algorithm 4th"-insertion sorting algorithm, this algorithm can be a better example of "tail-recursion".
Static long factorialTailRecursive (long n) {return factorialHelper (1, n);} static long factorialHelper (long acc, long n) {return n = = 1? Acc: factorialHelper (acc * n, nmur1);}
The method factorialHelper is a tail-recursive function because the recursive call occurs at the end of the method. Compared to our previous definition of the factorialRecursive method, the last operation of this method is multiplied by n to get the result of the recursive call.
This form of recursion is very meaningful, now we do not need to save the intermediate value of each recursive calculation on different stack frames, the compiler can decide to reuse a stack frame for calculation. In fact, in the definition of factorialHelper, the immediate number (the intermediate result of factorial calculation) is passed directly to the method as a parameter. You no longer have to assign separate stack frames to each recursive call to track the intermediate values of each recursive call-- these values can be accessed directly through the parameters of the method.
The bad news is that Java currently does not support this optimization. But using tail-recursion may be a better way than traditional recursion, because it opens the door for eventual compiler optimization. Many modern JVM languages, such as Scala and Groovy, already support this form of recursive optimization, achieving the same results as iterations (they run at almost the same speed). This means that adhering to pure functions can enjoy its purity without losing the efficiency of execution.
When programming with Java 8, we have a suggestion that you should try to use Stream instead of iterative operations to avoid the impact of changes. In addition, if recursion allows you to implement the algorithm in a more refined way without any side effects, you should replace iterations with recursion. In fact, we see that examples using recursive implementations are easier to read and easier to implement and understand (for example, the subset example we showed earlier). Most of the time the efficiency of programming is much more important than slight differences in execution time.
This is the end of the content of "what is the thinking method of Java8 function". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.