Java 8 What's going on? Two: Functions and primitives

Source: Internet
Author: User

"Editor's note" the author of this article is dedicated to natural language processing for many years Pierre-yves Saumont,pierre-yves has more than 30 lectures on Java software Development books, since 2008 began to work in Alcatel-lucent company, as a software development engineer.

This paper mainly introduces the functions and primitives in Java 8, which is presented by the domestic ITOM management platform OneAPM.

Tony Hoare The invention of the empty citation as "a million dollar mistake". Perhaps the use of primitives in Java can be called "Million dollar Errors". There is only one reason for creating primitives: performance. Primitives have nothing to do with object language. The introduction of automatic boxing and unpacking is a good thing, but there is still much to be desired. May be implemented later (it is said to have been included in the Java 10 roadmap). At the same time, we need to deal with primitives, which is a problem, especially when it comes to using functions.

Functions of Java 5/6/7

Before Java 8, the consumer could create a function like this:

Public interface Function<t, u> {U apply (T T); } function<integer, integer> addTax = new Function<integer, integer> () {@Override public Integer apply (   Integer x) {return x/100 * (100 + 10);  }   }; System.out.println (addtax.apply (100));

The code produces the following results:

110

Java 8 brings in Function<T, U> interface and lambda syntax. We no longer need to define our own functional interfaces, and we can use the following syntax:

Function<integer, integer> addTax = x, x/100 * (100 + 10); System.out.println (addtax.apply (100));

Note In the first example, the author uses an anonymous class file to create a named function. In the second example, using the lambda syntax has no effect on the result. There is still an anonymous class file, and a named function.

An interesting question is: " x what type is it?" "The type in the first example is obvious. It can be inferred from the function type. Java knows that the function parameter type is Integer because the function type is obviously Function<Integer, Integer> . The first Integer is the type of the parameter, and the second Integer is the return type.

Boxing is automatically used to int convert and back as needed Integer . This is discussed below.

Can I use anonymous functions? Yes, but there is a problem with the type. This won't work:

System.out.println ((X-x/100 * (+)). Apply (100));

This means that we cannot replace the identifier addTax itself (function) with the value of the identifier addTax . In this case, you need to restore the type information that is missing now because Java 8 cannot infer the type.

The most obvious lack of type is the identifier x . The following attempts can be made:

System.out.println ((Integer x), x/100 * +). Apply (100));

After all, in the first example, it could have been written like this:

Function<integer, integer> AddTax = (Integer x), x/100 * 100 + 10;

This should be enough to allow Java to speculate on the type, but it is not successful. What you need to do is define the type of the function. It is not sufficient to clarify the type of the function parameter, even if the return type has been clarified. There is one serious reason for this: Java 8 knows nothing about functions. It can be said that functions are ordinary objects plus ordinary methods, that's all. So it needs to be a definite type like this:

System.out.println (((Function<integer, integer>) x, x/100 * + +). Apply (100));

Otherwise, it will be interpreted as:

System.out.println (((Whatever<integer, integer>) x, x/100 * +). Whatever (100));

So lambda is just a syntactic simplification of the role that anonymous classes perform in the Function (or Whatever ) interface. It is actually irrelevant to the function.

Assuming Java has only a apply method Function interface, this is not a big problem. But what about the original language? If Java is just an object language, the Function interface is not. But it's not. It is only a vague use of object-oriented (and therefore referred to as object-oriented ). The most important category in Java is the primitive, which is not a good fusion of primitive and object-oriented programming.

Automatic boxing has been introduced in Java 5 to help solve this problem, but automatic boxing is a serious constraint on performance, which is also related to how Java evaluates. Java is a strict language, followed by an immediate evaluation rule. The result is that each time a primitive needs an object, it must be boxed in the original language. Each time an object requires a primitive, the object must be disassembled. If you rely on automatic boxing and unpacking, you may incur a large amount of overhead for boxing and unpacking multiple times.

Other languages solve this problem in different ways, allowing only objects to solve the conversion problem in the background. They may have "value classes", which are objects supported by the primitives. In this function, the programmer only uses the object, and the compiler uses the primitive language only (the description is too simplified, but reflects the basic principles). Java allows programmers to control primitives directly, which increases the difficulty of the problem and poses more security risks because programmers are encouraged to use the primitive as a business type, which is meaningless in object-oriented programming or functional programming. (I'll talk about that in another article.) )

You're welcome to say that we shouldn't worry about the cost of packing and unpacking. If a Java program with this feature runs too slowly, the programming language should be fixed. we should not try to solve the lack of language itself with bad programming skills . Using primitives will make this language work against us, not for us. If the problem cannot be solved by repairing the language, then we should switch to a different programming language. However, there may not be a lot of reasons for this, the most important of which is that only Java pays us to program, and none of the other languages. The result is that we are not solving business problems, but solving Java problems. Using primitives is a Java problem, and the problem is not small.

Now don't use the object, rewrite the example with the primitive. The selected function takes the type Integer parameter and returns Integer . To replace these, Java has a IntUnaryOperator type. Whoa, there's something wrong here! Guess what, define the following:

public interface Intunaryoperator {int applyasint (int operand); ...   }

The problem is too simple to recall apply .

Therefore, the use of the primitive rewrite example is as follows:

Intunaryoperator AddTax = x, x/100 * (100 + 10); System.out.println (Addtax.applyasint (100));

Or use an anonymous function:

System.out.println (((intunaryoperator) x, x/100 * (+)). Applyasint (100));

If you just want to int return int the function, it is easy to implement. But the practical problems are more complicated. java.util.functionThere are 43 (function) interfaces in the Java 8 package. In fact, they do not all represent functions and can be categorized as follows:

    • 21 functions with one parameter, 2 functions for returning objects to objects, and 19 for various types of objects to primitive or primitive to object functions. 2 Object-to-object functions have 1 special cases for parameters and return values that are of the same type.

    • 9 functions with 2 parameters, of which 2 are (objects, objects) to objects, 7 are of various types (objects, objects) to the primitive or (primitive, primitive) to the primitive language.

    • 7 are effects, not functions, because they do not return any values and are only used to get side effects. (It's strange to call these "functional Interfaces".) )

    • 5 are "suppliers", meaning that these functions do not take arguments, but return a value. These can be functions. In the world of functions, some special functions are called parameterless functions (indicating that their number of tuples or total functions is 0). As functions, the values they return may never change, so they allow constants to be treated as functions. In Java 8, their duty is to return various values according to the variable context. Therefore, they are not functions.

It's so messy! And the methods of these interfaces have different names. An object function has a method called, and apply the method of returning the digitized primitive is called, applyAsInt applyAsLong or applyAsDouble . booleanThe returned function has a method called test , the vendor's method is called or,, get getAsInt getAsLong getAsDouble , or getAsBoolean . (They didn't dare to call it test a BooleanSupplier "predicate" with a method, without a function.) I am really curious about why! )

It is important to note that there is no corresponding function, and there are byte char no functions short float that correspond to two or more meta-numbers.

Needless to say, this is ridiculous, but we have to stick to it. As long as Java can infer the type, we feel that everything is going well. However, once you try to control the function in a functional way, you will quickly face the problem of Java's inability to infer types. Worst of all, sometimes Java can infer the type, but will remain silent, continue to use another type, not the one we want to use.

How to find the right type

Suppose I want to use a function of three parameters. Since Java 8 does not have an out-of-the-box feature interface, the author has only one choice: to create its own feature interface, or as mentioned in the previous article (Java 8), take the curry. Create three object parameters, and return the function interface of an object straightforward:

Interface function<t, u, V, r> {R apply (T, T, u, u, V, v); }

However, two types of problems may arise. First, you may need to deal with the primitives. The parameter type is also not helpful. You can create special forms of functions, using primitives instead of objects. Finally, the 8 class primitives, 3 parameters, and a return value are counted, except for the different versions of the function in 6561. Why do you think Oracle is not included in Java 8 TriFunction ? (To be exact, they only put a limited number of parameters, the BiFunction Object return type is int , long or, or the double parameter and return type are the same int , long or, there are Object 9 results from 729 possibilities.) )

A better solution is to use a unboxing. Just need to use,, and Integer Long Boolean so on, then let Java to deal with. Any other action can be a source of evil, such as premature optimization (see http://c2.com/cgi/wiki?PrematureOptimization).

Another way (in addition to creating a functional interface of three parameters) is to take the curry. If the parameter is not evaluated at the same time, it forces the curry. It also allows a function with only one parameter, limiting the number of possible functions to within 81. If only use boolean , int long and, and double , this number will drop to 25 (4 primitive types plus two positions Object equivalent to 5 x 5).

The problem is that it can be difficult to use curry for a function that returns primitives or uses primitives as arguments. The following is the same example used in the previous article (one of Java 8), but now uses the primitive language:

Intfunction<intfunction<intunaryoperator>> inttointcalculation = x + y * z;       Private Intstream Calculate (Intstream stream, int a) {return Stream.map (inttointcalculation.apply (b). Apply (a));     } Intstream stream = Intstream.of (1, 2, 3, 4, 5); Intstream newstream = Calculate (stream, 3);

Note that the result is not a stream containing values 5, 8, 11, 14, and 17, and the first stream will not contain values 1, 2, 3, 4, and 5. newStreamThere is no evaluation at this stage and therefore does not contain a value. (This issue will be discussed in the next article).

In order to see the results, it is necessary to evaluate the flow, perhaps by binding a terminal operation to enforce. The method can be called by collect . However, before this operation, the author will use the boxed method to bind the result to a non-terminal function. boxedmethod binds a stream to a function that converts the primitive to the corresponding object. This simplifies the evaluation process:

System.out.println (newstream.boxed (). Collect (ToList ()));

This is shown as:

[5, 8, 11, 14, 17]

You can also use anonymous functions. However, Java cannot infer types, so I must provide assistance:

Private Intstream Calculate (Intstream stream, int a) {return Stream.map ((Intfunction<intfunction<intunaryopera   tor>>) x, Y, z, x + y * z). Apply (b). Apply (a));   } Intstream stream = Intstream.of (1, 2, 3, 4, 5); Intstream newstream = Calculate (stream, 3);

The curry itself is very simple, as long as you do not forget the author in other articles mentioned a point:

(x, Y, z) w

interpreted as:

W-Z, y, x

Finding the right type is slightly more complicated. Remember that each time you use a parameter, you will return a function, so you need a function from the parameter type to the object type (because the function is the object). In this case, each parameter type is int , so it needs to be parameterized with the return function type IntFunction . Because the final type is IntUnaryOperator (this is the IntStream map requirement of the method of the class), the result is as follows:

Intfunction<intfunction<...<intunaryoperator>>>

The author uses two of the three parameters, all of which are of the type int , so the types are as follows:

Intfunction<intfunction<intunaryoperator>>

Can be compared to using the auto-boxed version:

Function<integer, Function<integer, Function<integer, integer>>>

If you can't decide on the right type, you can start with automatic boxing, just replace the final type you want (because it is map the type of the parameter):

Function<integer, Function<integer, intunaryoperator>>

Note that you may have just used this type in your program:

Private Intstream Calculate (Intstream stream, int a) {return Stream.map ((Function<integer, Function<integer,     intunaryoperator>>) x, Y, z, x + y * z). Apply (b). Apply (a));     } Intstream stream = Intstream.of (1, 2, 3, 4, 5); Intstream newstream = Calculate (stream, 3);

You can then replace each with the version of the primitive you are using Function<Integer... , as shown below:

Private Intstream Calculate (Intstream stream, int a) {return Stream.map ((Function<integer, Intfunction<intuna ryoperator>>) x, Y, z, x + y * z). Apply (b). Apply (a)); }

Then it is:

Private Intstream Calculate (Intstream stream, int a) {return Stream.map ((intfunction<intfunction< intunaryoperator>>) x, Y, z, x + y * z). Apply (b). Apply (a)); }

Note that three versions can be compiled and run, the only difference being whether or not automatic boxing is used.

When Anonymous

As seen in the above example, Lambdas is good at simplifying the creation of anonymous classes, but there is no reason to name the created paradigm. The use of named functions includes:

    • function re-use

    • function test

    • function substitution

    • Program Maintenance

    • Program Document Management

The named function plus the curry allows the function to be completely independent of the environment ("referential transparency"), making the program more secure and modular. But it also has difficulty. The use of primitives adds to the difficulty of distinguishing the curry function categories. Worse still, the primitives are not the right type of business to use, so the compiler is not helping. For specific reasons, see the following example:

Double tax = 10.24; Double limit = 500.0; Double delivery = 35.50; Doublestream stream3 = Doublestream.of (234.23, 567.45, 344.12, 765.00);         Doublestream stream4 = Stream3.map (x, {double total = x/100 * (+ +);           if (Total > Limit) {total = total + delivery;     } return total; });

To use a named Curry function instead of an anonymous "snap" function, it is not difficult to determine the correct type. There are 4 parameters, which return doubleunaryoperator , then the type should be doublefunction<doublefunction<doublefunction< Doubleunaryoperator>>> . However, it is easy to put the parameter position incorrectly:

 Doublefunction<doublefunction<doublefunction<doubleunaryoperator>>> computetotal  = x -> y -> z -> w -> {        double total = w / 100 *  (100 + x);        if  (total > y)  {            total = total + z;          }         return total;      };      doublestream stream2 = stream.map ( Computetotal.apply (tax). Apply (limit). Apply (delivery)); 

How do you know x , y what, z and w what? There is actually a simple rule: The parameters that are evaluated by using the method directly are in the first place, according to the order of the methods used, for example,,,, tax limit delivery x y and z . The parameters from the stream are finally used, so it corresponds to w .

However, there is a problem: if the function passes the test, we know it is correct, but there is no way to ensure that it is used correctly. For example, if we use parameters in a wrong order:

Doublestream stream2 = Stream.map (computetotal.apply (limit). Apply (tax). Apply (delivery));

You will get:

[1440.8799999999999, 3440.2000000000003, 2100.2200000000003, 4625.5]

Instead of:

[258.215152, 661.05688, 379.357888, 878.836]

This means that you not only need to test the function, but also test it for each use. Wouldn't it be nice to be able to make sure that parameters that are not in the correct order are not compiled?

This is all the content that uses the correct type system. The use of primitives for business types is not good, and there is never a better outcome. But now that there is a function, there is a more reason not to do so. This question will be discussed in detail in other articles.

Please expect

This article explains that using primitives is probably more complex than using objects. The function of using primitives in Java 8 is a mess, but worse. In the next article, I'll talk about using primitives in a stream.

OneAPM can provide you with an end-to-end Java application Performance solution, and we support all common Java frameworks and application servers to quickly discover system bottlenecks and pinpoint the root cause of the anomalies. Minute-level deployment, instant experience, Java monitoring has never been easier. To read more technical articles, please visit the OneAPM Official technology blog.

Java 8 What's going on? Two: Functions and primitives

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.