当前位置:网站首页>2021-04-12 - new features lambda expression and function functional interface programming
2021-04-12 - new features lambda expression and function functional interface programming
2022-07-08 01:17:00 【Mengqi D Xiaowu】
New characteristics Lambda Expression and Function Functional interface programming
Catalog
1、lambda stay JSR(Java Specification Requests)
2、lambda stay JLS(Java LanguageSpecifications)
3、lambda The expression in jvm standard
5、Collectors The art of collection
5.2 partitioningBy The collector
Preface :jdk1.8 Launched lambda Expression and functional interface programming , This paper mainly from java in JSR Requirements specification to JLS The language specification goes to JVM standard , Introduce lambda and function.
1、lambda stay JSR(Java Specification Requests)
First , If you want to know lambda, Start with the requirements specification , We from JSR Requirements can be seen ,JSR335 That is to say lambda Requirements specification , We can do it download, Open as shown in the figure below :
Above ,Overview in , The overview defines the following functions :
- Lambda expressions and method references——lambda Expressions and method references
- Enhanced type inference and target typing—— Enhance type inference and target type conversion
- Default and static methods in interfaces—— Default and static methods in the interface
These three new features, It stipulates our lambda;jdk in util New API, also , Use a lot of util Under the stream, About function As shown in the figure below :
Contents The content includes the following three points :
- Specification changes , It's mainly about Java Update of language specification , And support for Java Virtual machine specification ( here ) and Java Object serialization specification ( here ) Change of
- Java SE API Update summary
- Informal about new languages and library functions ( Non normative ) summary
2、lambda stay JLS(Java LanguageSpecifications)
stay JLS standard in , We can have a look lambda Canonical definition of expression :
Defined ,lambda Expression in assignment context 、 Call context 、 Cast context , Otherwise, the compiler will report an error .
The evaluation of the expression will produce a function Interface instance . The evaluation of an expression does not necessarily lead to the execution of the body of the expression , contrary , This may happen later when the appropriate method of the functional interface is called .
Next is ,Lambda Parameters and Lambda Body, Explicit and implicit parameter types ; If the formal parameter has an inferred type , Then these types are from lambda Derived from the function interface type for which the expression is directed .
More details , Let's not go over it here . Interested students can check the official website by themselves .
3、lambda The expression in jvm standard
For example, we aim at one class View its bytecode file ,javap -v Filtering.class.
It's obvious , Functional interfaces at the bottom of bytecode are InvokeDynamic, Through the following jdk You can see in the source code , The bottom is newInvokeDynamicItem, When class build when Put it into the constant pool .
invokedynamic The execution method of this opcode will be associated with a dynamic call point object (Call Site object), This call site The object will point to a specific bootstrap Method ( The binary byte stream information of the method is BootstrapMethods In the property sheet ) Implementation ,invokedynamic The call of instructions will have a unique call chain , Unlike other instructions that call methods directly , The actual operation process is also relatively more complex .
/**
* Adds an invokedynamic reference to the constant pool of the class being
* build. Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and is
* normally not needed by class generators or adapters.</i>
*
* @param name
* name of the invoked method.
* @param desc
* descriptor of the invoke method.
* @param bsm
* the bootstrap method.
* @param bsmArgs
* the bootstrap method constant arguments.
*
* @return a new or an already existing invokedynamic type reference item.
*/
Item newInvokeDynamicItem(final String name, final String desc,
final Handle bsm, final Object... bsmArgs) {
// cache for performance
ByteVector bootstrapMethods = this.bootstrapMethods;
if (bootstrapMethods == null) {
bootstrapMethods = this.bootstrapMethods = new ByteVector();
}
int position = bootstrapMethods.length; // record current position
Execute by the following method ,// (site.)invokedynamic(a*):R => mh = site.getTarget(); mh.invokeBasic(a*). At the bottom of the stack // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn.
In the underlying jvm in generateOopMap.cpp, It can be seen as follows .
Included in Frame.java Stack in .
case Opcodes.INVOKEDYNAMIC:
pop(item.strVal2);
push(cw, item.strVal2);
break;
No more details , Interested students can learn more by themselves .
4、Stream flow
The definition of flow : A sequence of elements generated from sources that support data processing operations
Stream API Basically return Stream In itself , So many operations can be connected in series into a pipe , Just like the flow style (fluent style). This optimizes the operation , For example, delay execution (laziness) And short circuit ( short-circuiting)
4.1 Two kinds of stream flow
•stream() − Create a serial stream for the collection .
•parallelStream() − Create parallel streams for collections .
4.2 Concise traversal
stream Provides a way of internal traversal forEach(), adopt forEach It can greatly simplify the code of collection traversal
4.3 Filter
stream Provides a method for data filtering filter(), And stream Other API When used together, it can simply realize data filtering
public class Filtering {
public static void main(String...args){
// Filter out vegetables
List<Dish> vegetarianMenu =
menu.stream()
//filter Parameters are predicates That is to say boolean type
.filter(Dish::isVegetarian) //method reference
.collect(toList());
vegetarianMenu.forEach(System.out::println);
// Filter the specified elements duplicate removal
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()// duplicate removal
.forEach(System.out::println);
//limit Truncation stream
List<Dish> dishesLimit3 =
menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
dishesLimit3.forEach(System.out::println);
//skip: skip n Elements
List<Dish> dishesSkip2 =
menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
dishesSkip2.forEach(System.out::println);
}
public static final List<Dish> menu =
Arrays.asList( new Dish("pork", false, 800, Type.MEAT),
new Dish("beef", false, 700, Type.MEAT),
new Dish("chicken", false, 400, Type.MEAT),
new Dish("french fries", true, 530, Type.OTHER),
new Dish("rice", true, 350, Type.OTHER),
new Dish("season fruit", true, 120, Type.OTHER),
new Dish("pizza", true, 550, Type.OTHER),
new Dish("prawns", false, 400, Type.FISH),
new Dish("salmon", false, 450, Type.FISH));
}
4.4 mapping
stream Provides a method for data mapping map(), And stream Other API When used together, data mapping can be easily realized
public class Mapping {
public static void main(String...args){
// map
List<String> dishNames = menu.stream()
.map(Dish::getName)
// .map(dish -> dish.getName())
.collect(toList());
System.out.println(dishNames);
// map
List<String> words = Arrays.asList("Hello", "World");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
System.out.println(wordLengths);
// flatMap
words.stream()
// Cut into single words
.flatMap((String line) -> Arrays.stream(line.split("")))
.distinct()// duplicate removal
.forEach(System.out::println);
// flatMap
List<Integer> numbers1 = Arrays.asList(1,2,3,4,5);
List<Integer> numbers2 = Arrays.asList(6,7,8);
List<int[]> pairs =
numbers1.stream()
// Two list In the middle, two vertical pairs are combined to form a two-dimensional array
.flatMap((Integer i) -> numbers2.stream().map((Integer j) -> new int[]{i, j})
)
// Filter two-dimensional arrays The sum of the two columns is 3 Multiple
.filter(pair -> (pair[0] + pair[1]) % 3 == 0)
.collect(toList());
pairs.forEach(pair -> System.out.println("(" + pair[0] + ", " + pair[1] + ")"));
}
}
4.5 Look for the element
public class Finding {
public static void main(String...args){
if(isVegetarianFriendlyMenu()){
System.out.println("Vegetarian friendly");
}
System.out.println(isHealthyMenu());
System.out.println(isHealthyMenu2());
Optional<Dish> dish = findVegetarianDish();
dish.ifPresent(d -> System.out.println(d.getName()));
}
// amount to ||
private static boolean isVegetarianFriendlyMenu(){
return menu.stream().anyMatch(Dish::isVegetarian);
}
// amount to &&
private static boolean isHealthyMenu(){
return menu.stream().allMatch(d -> d.getCalories() < 1000);
}
// amount to !=
private static boolean isHealthyMenu2(){
return menu.stream().noneMatch(d -> d.getCalories() >= 1000);
}
// Match any one Applicable to concurrent flow execution
private static Optional<Dish> findVegetarianDish(){
return menu.stream().filter(Dish::isVegetarian).findAny();
}
}
4.6 reduce Method
reduce Method reduces the flow to a value . In terms of functional programming language , This is called folding (fold)
public class Reducing {
public static void main(String...args){
List<Integer> numbers = Arrays.asList(3,4,5,1,2);
// grammar reduce( The starting point , Algorithm rules );
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println(sum);
int sum2 = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum2);
int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b));
System.out.println(max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
min.ifPresent(System.out::println);
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
System.out.println("Number of calories:" + calories);
}
}
4.7 stream actual combat
public class PuttingIntoPractice {
public static void main(String...args){
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
// Query 1: Find all transactions from year 2011 and sort them by value (small to high).
List<Transaction> tr2011 = transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(tr2011);
// Query 2: What are all the unique cities where the traders work?
List<String> cities =
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(toList());
System.out.println(cities);
// Query 3: Find all traders from Cambridge and sort them by name.
List<Trader> traders =
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(comparing(Trader::getName))
.collect(toList());
System.out.println(traders);
// Query 4: Return a string of all traders’ names sorted alphabetically.
String traderStr =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.reduce("", (n1, n2) -> n1 + n2);
System.out.println(traderStr);
// Query 5: Are there any trader based in Milan?
boolean milanBased =
transactions.stream()
.anyMatch(transaction -> transaction.getTrader()
.getCity()
.equals("Milan")
);
System.out.println(milanBased);
// Query 6: Update all transactions so that the traders from Milan are set to Cambridge.
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Milan"))
.forEach(trader -> trader.setCity("Cambridge"));
System.out.println(transactions);
// Query 7: What's the highest value in all the transactions?
int highestValue =
transactions.stream()
.map(Transaction::getValue)
.reduce(0, Integer::max);
System.out.println(highestValue);
}
}
public class Trader {
private String name;
private String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
public String getName(){
return this.name;
}
public String getCity(){
return this.city;
}
public void setCity(String newCity){
this.city = newCity;
}
@Override
public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
public class Transaction {
private Trader trader;
private int year;
private int value;
public Transaction(Trader trader, int year, int value)
{
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader(){
return this.trader;
}
public int getYear(){
return this.year;
}
public int getValue(){
return this.value;
}
@Override
public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}
5、Collectors The art of collection
Collectors Class implements many reduction operations , For example, converting streams to collections and aggregate elements .Collectors Can be used to return a list or string
5.1 Grouping
Multi level grouping 、 Various complex groupings
public class Grouping {
enum CaloricLevel { DIET, NORMAL, FAT };
public static void main(String... args) {
System.out.println("Dishes grouped by type: " + groupDishesByType());
System.out.println("Dish names grouped by type: " + groupDishNamesByType());
System.out.println("Dish tags grouped by type: " + groupDishTagsByType());
System.out.println("Caloric dishes grouped by type: " + groupCaloricDishesByType());
System.out.println("Dishes grouped by caloric level: " + groupDishesByCaloricLevel());
System.out.println("Dishes grouped by type and caloric level: " + groupDishedByTypeAndCaloricLevel());
System.out.println("Count dishes in groups: " + countDishesInGroups());
System.out.println("Most caloric dishes by type: " + mostCaloricDishesByType());
System.out.println("Most caloric dishes by type: " + mostCaloricDishesByTypeWithoutOprionals());
System.out.println("Sum calories by type: " + sumCaloriesByType());
System.out.println("Caloric levels by type: " + caloricLevelsByType());
}
// to groupingBy Method passes a Function( In the form of method references ), It extracts every single channel in the stream Dish Of Dish.Type.
// So let's take this Function It's called the classification function , Because it's used to divide the elements in the stream into different groups
private static Map<Dish.Type, List<Dish>> groupDishesByType() {
return menu.stream().collect(groupingBy(Dish::getType));
}
// Multi level grouping , You can use a two parameter version of Collectors.groupingBy Collector created by factory method ,
// In addition to ordinary classification functions , It's acceptable collector The second parameter of type .
private static Map<Dish.Type, List<String>> groupDishNamesByType() {
return menu.stream().collect(groupingBy(Dish::getType, mapping(Dish::getName, toList())));
}
private static Map<Dish.Type, Set<String>> groupDishTagsByType() {
return menu.stream().collect(
groupingBy(Dish::getType,
flatMapping(dish -> dishTags.get( dish.getName() ).stream(), toSet())
)
);
}
private static Map<Dish.Type, List<Dish>> groupCaloricDishesByType() {
// return menu.stream().filter(dish -> dish.getCalories() > 500).collect(groupingBy(Dish::getType));
return menu.stream().collect(groupingBy(Dish::getType, filtering(dish -> dish.getCalories() > 500, toList())));
}
private static Map<CaloricLevel, List<Dish>> groupDishesByCaloricLevel() {
return menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
}
private static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupDishedByTypeAndCaloricLevel() {
return menu.stream().collect(
groupingBy(Dish::getType,
groupingBy((Dish dish) -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} )
)
);
}
private static Map<Dish.Type, Long> countDishesInGroups() {
return menu.stream().collect(groupingBy(Dish::getType, counting()));
}
private static Map<Dish.Type, Optional<Dish>> mostCaloricDishesByType() {
return menu.stream().collect(
groupingBy(Dish::getType,
reducing((Dish d1, Dish d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)));
}
private static Map<Dish.Type, Dish> mostCaloricDishesByTypeWithoutOprionals() {
return menu.stream().collect(
groupingBy(Dish::getType,
collectingAndThen(
reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2),
Optional::get)));
}
private static Map<Dish.Type, Integer> sumCaloriesByType() {
return menu.stream().collect(groupingBy(Dish::getType,
summingInt(Dish::getCalories)));
}
// Customize the internal implementation
private static Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType() {
return menu.stream().collect(
groupingBy(Dish::getType, mapping(
dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
},
toSet())));
}
}
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
public enum Type { MEAT, FISH, OTHER }
@Override
public String toString() {
return name;
}
public static final List<Dish> menu =
Arrays.asList( new Dish("pork", false, 800, Type.MEAT),
new Dish("beef", false, 700, Type.MEAT),
new Dish("chicken", false, 400, Type.MEAT),
new Dish("french fries", true, 530, Type.OTHER),
new Dish("rice", true, 350, Type.OTHER),
new Dish("season fruit", true, 120, Type.OTHER),
new Dish("pizza", true, 550, Type.OTHER),
new Dish("prawns", false, 400, Type.FISH),
new Dish("salmon", false, 450, Type.FISH));
public static final Map<String, List<String>> dishTags = new HashMap<>();
static {
dishTags.put("pork", asList("greasy", "salty"));
dishTags.put("beef", asList("salty", "roasted"));
dishTags.put("chicken", asList("fried", "crisp"));
dishTags.put("french fries", asList("greasy", "fried"));
dishTags.put("rice", asList("light", "natural"));
dishTags.put("season fruit", asList("fresh", "natural"));
dishTags.put("pizza", asList("tasty", "salty"));
dishTags.put("prawns", asList("tasty", "roasted"));
dishTags.put("salmon", asList("delicious", "fresh"));
}
}
5.2 partitioningBy The collector
partitioningBy The collector : By a predicate ( A function that returns a Boolean value ) As a classification function , It's called a partition function . Advantages of zoning : Is that the partition function returns true or false Two sets of stream element lists of , You can partition twice
public class Partitioning {
public static void main(String... args) {
System.out.println("Dishes partitioned by vegetarian: " + partitionByVegeterian());
System.out.println("Vegetarian Dishes by type: " + vegetarianDishesByType());
System.out.println("Most caloric dishes by vegetarian: " + mostCaloricPartitionedByVegetarian());
}
private static Map<Boolean, List<Dish>> partitionByVegeterian() {
return menu.stream().collect(partitioningBy(Dish::isVegetarian));
}
private static Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType() {
return menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
}
// Veggies and non veggies have the most calories map aggregate
// Demonstrate secondary partitioning
private static Object mostCaloricPartitionedByVegetarian() {
return menu.stream().collect(
partitioningBy(Dish::isVegetarian,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
}
}
5.3 reduce
public class Reducing {
public static void main(String... args) {
System.out.println("Total calories in menu: " + calculateTotalCalories());
System.out.println("Total calories in menu: " + calculateTotalCaloriesWithMethodReference());
System.out.println("Total calories in menu: " + calculateTotalCaloriesWithoutCollectors());
System.out.println("Total calories in menu: " + calculateTotalCaloriesUsingSum());
}
private static int calculateTotalCalories() {
return menu.stream().collect(reducing(0, Dish::getCalories, (Integer i, Integer j) -> i + j));
}
private static int calculateTotalCaloriesWithMethodReference() {
return menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));
}
private static int calculateTotalCaloriesWithoutCollectors() {
return menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
}
private static int calculateTotalCaloriesUsingSum() {
return menu.stream().mapToInt(Dish::getCalories).sum();
}
}
5.4 Summarizing
public class Summarizing {
public static void main(String... args) {
System.out.println("Nr. of dishes: " + howManyDishes());
System.out.println("The most caloric dish is: " + findMostCaloricDish());
System.out.println("The most caloric dish is: " + findMostCaloricDishUsingComparator());
System.out.println("Total calories in menu: " + calculateTotalCalories());
System.out.println("Average calories in menu: " + calculateAverageCalories());
System.out.println("Menu statistics: " + calculateMenuStatistics());
System.out.println("Short menu: " + getShortMenu());
System.out.println("Short menu comma separated: " + getShortMenuCommaSeparated());
}
private static long howManyDishes() {
return menu.stream().collect(counting());
}
private static Dish findMostCaloricDish() {
return menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)).get();
}
private static Dish findMostCaloricDishUsingComparator() {
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
BinaryOperator<Dish> moreCaloricOf = BinaryOperator.maxBy(dishCaloriesComparator);
return menu.stream().collect(reducing(moreCaloricOf)).get();
}
private static int calculateTotalCalories() {
return menu.stream().collect(summingInt(Dish::getCalories));
}
private static Double calculateAverageCalories() {
return menu.stream().collect(averagingInt(Dish::getCalories));
}
private static IntSummaryStatistics calculateMenuStatistics() {
return menu.stream().collect(summarizingInt(Dish::getCalories));
}
private static String getShortMenu() {
return menu.stream().map(Dish::getName).collect(joining());
}
private static String getShortMenuCommaSeparated() {
return menu.stream().map(Dish::getName).collect(joining(", "));
}
}
6、 Summary
Through the above, , Basically, I have a preliminary understanding lambda How expressions are defined 、 analysis 、 perform . Not only Lambda It's very convenient to use , Performance is also better than anonymous inner classes in most cases . We're writing lambda Expression time , Try to write the code of the introduction , Reduce internal variable capture ( Because this will create additional variable objects ). I hope this article can give you some reference .
边栏推荐
- 2.非线性回归
- Invalid V-for traversal element style
- AI zhetianchuan ml novice decision tree
- Swift get URL parameters
- 11. Recurrent neural network RNN
- Smart grid overview
- 130. 被围绕的区域
- Codeforces Round #804 (Div. 2)
- Share a latex online editor | with latex common templates
- 10. CNN applied to handwritten digit recognition
猜你喜欢
Know how to get the traffic password
Kuntai ch7511b scheme design | ch7511b design EDP to LVDS data | pin to pin replaces ch7511b circuit design
Jemter distributed
[necessary for R & D personnel] how to make your own dataset and display it.
Scheme selection and scheme design of multifunctional docking station for type C to VGA HDMI audio and video launched by ange in Taiwan | scheme selection and scheme explanation of usb-c to VGA HDMI c
Design method and application of ag9311maq and ag9311mcq in USB type-C docking station or converter
网络模型的保存与读取
For the first time in China, three Tsinghua Yaoban undergraduates won the stoc best student thesis award
13. Enregistrement et chargement des modèles
利用GPU训练网络模型
随机推荐
Frrouting BGP protocol learning
Smart grid overview
Basic realization of line chart (II)
USB type-C docking design | design USB type-C docking scheme | USB type-C docking circuit reference
AI遮天传 ML-回归分析入门
Vs code configuration latex environment nanny level configuration tutorial (dual system)
7. Regularization application
High quality USB sound card / audio chip sss1700 | sss1700 design 96 kHz 24 bit sampling rate USB headset microphone scheme | sss1700 Chinese design scheme explanation
Connect to the previous chapter of the circuit to improve the material draft
EDP to LVDS conversion design circuit | EDP to LVDS adapter board circuit | capstone/cs5211 chip circuit schematic reference
The weight of the product page of the second level classification is low. What if it is not included?
General configuration title
Understanding of sidelobe cancellation
利用GPU训练网络模型
1. Linear regression
Use "recombined netlist" to automatically activate eco "APR netlist"
Definition and classification of energy
FIR filter of IQ signal after AD phase discrimination
New library launched | cnopendata China Time-honored enterprise directory
Macro definition and multiple parameters