泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,
操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
总结:泛型指的就是,当定义的时候,不知道这个参数的具体类型,所以用一个符号代替;当真正调用的时候,才会确定这个符号代表的具体类型
泛型类
下面的例子定义了一个泛型类。KeyValue
是一个包含对键和值的泛型类。
文件:KeyValue.java
package com.yiibai.tutorial.generics.ci;
public class KeyValue<K, V> {
private K key;
private V value;
public KeyValue(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
Java
K,V
在KeyValue <K,V>
类称为泛型参数,它是某种数据类型的引用。当使用这个类,必须确定具体的参数。下面是使用 KeyValue
类的例子 -
泛型接口
public interface PromoInfoProvider<T> {
/**
* 获取优惠集合
* @return
*/
List<PromoInstance> getPromoInstances(TenantEnum tenant,T sourceData);
}
public class NearExpirePromoProvider implements PromoInfoProvider<ActiveNearExpire> {
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
}
泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型 。
泛型方法的形式如下:
private <T> returnType methodName(args1,args2){
}
泛型用作方法的方法参数
方法定义:
public <T> void testargs(T t) {
System.out.println(t.toString());
System.out.println("======");
}
调用:
public static void main(String[] args) throws InterruptedException {
TestGeneric testGeneric = new TestGeneric();
testGeneric.testargs(new Person("zhangsan", 20));
testGeneric.testargs(new City("nanjing", true));
}
执行结果如下:
Person [name=zhangsan, age=20]
======
City [name=nanjing, isLarge=true]
======
泛型用作方法返回值
package com.wp.generic;
public class TestGeneric {
public static void main(String[] args) {
City city = returnValue(false);
System.out.println(city.toString());
Person person = returnValue(true);
System.out.println(person.toString());
}
public static <T> T returnValue(boolean isFlag) {
if(isFlag) {
return (T) new Person("zhangsan", 20);
}else {
return (T) new City("nanjing", true);
}
}
}
执行结果如下:
City [name=nanjing, isLarge=true]
Person [name=zhangsan, age=20]
静态方法与泛型
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
泛型的类型擦除
什么是类型擦除
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。
比如List<Integre>
和 List<String>
,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。Java 编译器会在编译时尽可能的发现可能出错的地方。
类型擦除指的是,擦除泛型,并使用其限定类型(无限定的变量用Object)替换。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉 <> 的内容
原始类型Object
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
原始类型如下:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
有限定类型:
class Pair<T extends Comparable> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
擦除后类型如下:
class Pair {
private Comparable value;
public Comparable getValue() {
return value;
}
public void setValue(Comparable value) {
this.value = value;
}
}
正因为,在 Java 字节代码中,是不包含泛型中的类型信息,所以编译器在编译时,需要承担全部的类型检查工作。所以,编译器,就会认为,下面的代码编译不通过
public void inspect(List<Object> list) {
for (Object obj : list) {
System.out.println(obj);
}
list.add(1); // 这个操作在当前方法的上下文是合法的。
}
public void test() {
List<String> strs = new ArrayList<String>();
inspect(strs); // 编译错误
}
这段代码中,inspect 方法接受 List 作为参数,当在 test 方法中试图传入 List 的时候,会出现编译错误。
假设这样的做法是允许的,那么在 inspect 方法就可以通过 list.add(1) 来向集合中添加一个数字。这样在 test 方法看来,其声明为 List
因此,就得到一个结论,String虽然是Object的子类,但是List<String>
并不是List<Object>
的子类,也就是说,不能把List<String>
实例,赋值给List<Object>
的引用。
但是为了,解决这些问题,sun大师们,发明了**<? extends T>和<? super T>的办法**
也就是说,通配符的出现,是为了解决,如何在容器中,安全可靠的使用泛型。
通配符
在使用泛型类的时候,既可以指定一个具体的类型,如 List<String>
就声明了具体的类型是 String;
也可以用通配符? 来表示未知类型,如List<?>
就声明了 List 中包含的元素类型是未知的。
通配符所代表的其实是一组类型,但具体的类型是未知的。
接下来,我们使用通配符,解决下上面说的问题:
public void inspect(List<? extends String> list) {
for (Object obj : list) {
System.out.println(obj);
}
list.add(1); // 此时,这个操作,在当前方法的上下文是不合法的,编译报错。因为是往一个全部是String类型元素的集合中,添加一个Integer元素。
}
public void test() {
List<String> strs = new ArrayList<String>();
inspect(strs); // 编译错误
}
上面,我们巧妙的借助了通配符,解决了在容器中,安全可靠的使用泛型。
我们再看下,工作中,是怎么使用的?
public void calResSkuRealPrice(List<? extends SkuRealPriceCalItem> skuRealPriceCalItems, PromoInstance promoComponent) {
for (SkuRealPriceCalItem skuRealPriceCalItem : skuRealPriceCalItems) {
...
}
}
private List<CertainSkuRealPriceCalItem> getSkuRealPriceItems(PromoInstance usedPromo, CertainSkuRes certainSkuRes) {
List<CertainSkuRealPriceCalItem> skuRealPriceItems = new ArrayList<>();
...
realPriceCalculator.calResSkuRealPrice(skuRealPriceItems, usedPromo);
return skuRealPriceItems;
}
也就是说,我们在调用时,传递一个List<CertainSkuRealPriceCalItem>
的集合,而定义中,是List<? extends SkuRealPriceCalItem>
集合,
CertainSkuRealPriceCalItem是这个SkuRealPriceCalItem的子类,
借助通配符,我们近似认为List<CertainSkuRealPriceCalItem>
也 是List<? extends SkuRealPriceCalItem>
的子类
这样定义的好处是:所有SkuRealPriceCalItem的子类,都可以借助这个方法,计算出实际价格
下面,我们再来看看? super这种通配符的用法。
先说结论, ?super 的变量,能接收 各种超类的类型引用
public void append(Collection<? super Integer> integers, int n) {
for (int i = 1; i <= n; i++) {
integers.add(i);
}
}
public static void main(String[] args) throws InterruptedException {
TestSort testSort = new TestSort();
List<Integer> intList = new ArrayList<>();
testSort.append(intList,5);
System.out.println(intList); // [1, 2, 3, 4, 5]
List<Number> numList = new ArrayList<>();
testSort.append(numList,5);
System.out.println(numList); // [1, 2, 3, 4, 5]
}
Number是Integer的父类,而append既能接收List<Integer>
的类型引用,也能接收List<Number>
的类型引用
通过上面的例子:? extend A这里的extend指的是,传递的引用,泛型是A的子类;?super A 这里的super指的是,传递的引用,泛型是A的父类
这里,需要注意的是,apppend方法内,integers 变量,只能添加int类型的参数,不能添加Object类型的参数
如下:
public void append(Collection<? super Integer> integers, int n) {
for (int i = 1; i <= n; i++) {
integers.add(i);
}
integers.add(new Object());// 编译报错 capture of ? super Integer
}
Based on above reasoning and examples, let’s summarize our learning in bullet points.
- Use the
<? extends T>
wildcard if you need to retrieve object of type T from a collection. - Use the
<? super T>
wildcard if you need to put objects of type T in a collection.
反射
获取class对象
# 方案1
Employee e;
Class cl = e.getClass();
# 方案2
String className = "java.util.Random";
Class cl = Class.forName(className);
# 方案3
Class cl1 = Random.class; // if you import java.util.*;
Class cl2 = int.class;
Class cl3 = Double[].class;
System.out.println(Person.class);
System.out.println(Person[].class);
Bus<? super Person> personBus = new Bus<>();
System.out.println(personBus.getClass());
System.out.println(int.class);
System.out.println(int[].class);
输出
class com.wp.domain.Person
class [Lcom.wp.domain.Person;
class com.wp.domain.Bus
int
class [I
newInstance
e.getClass().newInstance();
The newInstance method calls theno-argument constructor to initialize the newly created object. An exception isthrown if the class does not have a no-argument constructor.
If you need to provide parameters for the constructor of a class,you must use the newInstance method in the Constructor class.
The three classes Field, Method, and Constructor in the java.lang.reflect package describe the fields, methods, and constructors of a class, respectively
The getFields, getMethods, and getConstructors methods of the Class class return arrays of the public fields, methods, and constructors that the class supports. This includes public members of superclasses.
The getDeclaredFields, getDeclaredMethods, and getDeclaredConstructors methods of the Class class return arrays consisting of all fields,methods, and constructors that are declared in the class. This includes private,package, and protected members, but not members of superclasses.
If f is an object of type Field (for example, one obtained from getDeclaredFields) and obj is an object of the class of which f is a field, then f.get(obj) returns an object whose value is the current value of the field of obj
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
f.setAccessible(true); // name is private field,
Object v = f.get(harry); //v的值为 Harry Hacker
But suppose we want to look at the salary field. That is a double, and in Java, number types are not objects.
To handle this, you can either use the getDouble method of the Field class,
or you can call get, whereby the reflection mechanism automatically wraps the field value into the appropriate wrapper class—in this case, Double
The call f.set(obj, value) sets the field represented by f of the object obj to the new value.
数组的copy
public static Object goodCopyOf(Object a, int newLength)
{
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
泛型数组
有2种方式创建泛型数组:1.通过泛型方法和反射创建 2.通过泛型类创建
通过泛型方法和反射创建:
public class Person {
private int id;
public Person(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person [id=" + id + "]";
}
}
import java.lang.reflect.Array;
public class GenericsArray {
public static <T> T[] getArray(Class<T> componentType,int length) {
return (T[]) Array.newInstance(componentType, length);
}
}
测试类:
public class TestGenericArray {
public static void main(String[] args) {
Person[] persons = new GenericsArray().getArray(Person.class, 10);
System.out.println(Arrays.toString(persons));
for (int i = 0; i < persons.length; i++) {
persons[i]=new Person(i);
}
System.out.println(Arrays.toString(persons));
}
}
通过泛型类创建:
public class GenericsArrayTwo<T> {
private Object[] array;
public GenericsArrayTwo(int size){
array = new Object[size];
}
public void put(int index, T item) {
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) {
return (T)array[index];
}
}
测试类:
public static void main(String[] args) {
GenericsArrayTwo<Person> genericsArrayTwo = new GenericsArrayTwo<>(5);
for (int i = 0; i < 5; i++) {
genericsArrayTwo.put(i, new Person(i));
}
for (int i = 0; i < 5; i++) {
Person person = genericsArrayTwo.get(i);
System.out.println(person.toString());
}
}
the Method class has an invoke method that lets you call the method that is wrapped in the current Method object. The signature for the invoke method is
Object invoke(Object obj, Object... args)
The first parameter is the implicit parameter, and the remaining objects provide the explicit parameters.
For a static method, the first parameter is ignored—you can set it to null.
However, there may be several methods with the same name, so you need to be careful that you get the right one. For that reason, you must also supply the parameter types of the desired method. The signature of getMethod is
Method getMethod(String name, Class... parameterTypes)
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
Overriding vs. Overloading in Java
Overloading occurs when two or more methods in one class have the same method name but different parameters.
Overriding means having two methods with the same method name and parameters (i.e., method signature). One of the methods is in the parent class and the other is in the child class. Overriding allows a child class to provide a specific implementation of a method that is already provided its parent class.
The real object type in the run-time, not the reference variable’s type, determines which overridden method is used at runtime.
There are many differences between method overloading and method overriding in java. A list of differences between method overloading and method overriding are given below:
No. | Method Overloading | Method Overriding |
---|---|---|
1) | Method overloading is used to increase the readability of the program. | Method overriding is used to provide the specific implementation of the method that is already provided by its super class. |
2) | Method overloading is performed within class. | Method overriding occurs in two classes that have IS-A (inheritance) relationship. |
3) | In case of method overloading, parameter must be different. | In case of method overriding, parameter must be same. |
4) | Method overloading is the example of compile time polymorphism. | Method overriding is the example of run time polymorphism. |
5) | In java, method overloading can’t be performed by changing return type of the method only. Return type can be same or different in method overloading. But you must have to change the parameter. | Return type must be same or covariant in method overriding. |
111
Stream
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
首先,flatMap的作用是:将多个流,合并成一个流。多个流的内容,直接变成合并后唯一的一个流内的内容。
那么上面的flatMap中的一长串的方法参数是干什么的?
这个mapper参数的返回值是Stream,输入参数是T,所以这个mapper的作用,就是使用这个函数,将T对象转化为Stream对象。
在将每个T对象都转化为每个Stream对象后,将所有的Stream对象 利用 flatMap ,合并成一个Stream对象,然后返回。
下面,看一个示例
String[] words = new String[]{"Hello","World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
a.forEach(System.out::print);
这里,就是将数组String[],利用Arrays::stream函数,转为Stream
结果输出:HeloWrd
使用flatMap方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用map(Array::stream)时生成的单个流被合并起来,即扁平化为一个流。
下图是运用flatMap的stream运行流程
工作中实际对Stream的使用
public static void main(String[] args) {
List<Meal> meals = new ArrayList<>();
Meal meal = new Meal();
meal.setMealName("午餐");
ArrayList<String> mealItemNameList = new ArrayList<>();
mealItemNameList.add("米饭");
mealItemNameList.add("牛肉");
mealItemNameList.add("蔬菜");
meal.setMealItemNameList(mealItemNameList);
meals.add(meal);
// 从List<Meal>------------->变成 Stream<Meal> 也就是,一条河流,每艘船都是一个meal,河流上有很多船,即有很多个meal
Stream<Meal> stream = meals.stream();
// Stream<List<String>>-----------> 每艘船都是一个List<String>
Stream<List<String>> listStream = meals.stream().map(Meal::getMealItemNameList);
// List<List<String>>--------------> 集合中,每个元素都是一个List<String>
List<List<String>> collect = meals.stream().map(Meal::getMealItemNameList).collect(Collectors.toList());
// 这里就使用到flatMap,将List<List<String>> 转为了 List<String>
List<String> collect1 = meals.stream().map(Meal::getMealItemNameList).filter(CollectionUtils::isNotEmpty)
.flatMap(List::stream).collect(Collectors.toList());
}
static class Meal{
String mealName;
List<String> mealItemNameList;
public String getMealName() {
return mealName;
}
public void setMealName(String mealName) {
this.mealName = mealName;
}
public List<String> getMealItemNameList() {
return mealItemNameList;
}
public void setMealItemNameList(List<String> mealItemNameList) {
this.mealItemNameList = mealItemNameList;
}
}
collect()方法
public class StreamTest {
/**
* stream.collect() 的本质由三个参数构成,
* 1. Supplier 生产者, 返回最终结果
* 2. BiConsumer<R, ? super T> accumulator 累加器
* 第一个参数是要返回的集合, 第二个参数是遍历过程中的每个元素,
* 将流中每个被遍历的元素添加到集合中
* 3. BiConsumer<R, R> combiner 合并器, 在有并行流的时候才会有用, 一个流时代码不会走到这里
* 将第二步遍历得到的所有流形成的list都添加到最终的list中,
* 最后返回list1
*/
@Test
public void Test() {
Stream<String> stream = Stream.of("hello", "world", "helloworld");
// 最原始和基础的方式
/*
List<String> list = stream.collect(
()->new ArrayList(),
(theList, item) -> theList.add(item),
(list1, list2) -> list1.addAll(list2)
);
*/
// 打印出更详尽的过程
List<String> listDetail = stream.collect(
() -> {
ArrayList<String> arrayList = new ArrayList<>();
System.out.println("第一个list诞生, size: " + arrayList.size());
return arrayList;
},
(theList, item) -> {
System.out.println("第二个list的size: " + theList.size());
theList.add(item);
},
(list1, list2) -> {
System.out.println("第三个list1的size: " + list1.size());
System.out.println("第四个list2的size: " + list2.size());
list1.addAll(list2);
}
);
/* 输出
第一个list诞生, size: 0
第二个list的size: 0
第二个list的size: 1
第二个list的size: 2
* */
// 使用方法引用来传递行为, 更加清晰易懂, new(新建) -> add(累加) -> addAll(合并)
List<String> list2 = stream.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
String concat = stream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
System.out.println(concat);
}
@Test
public void Test2() {
Stream<String> stream = Stream.of("hello", "world", "helloworld");
// 这样的写法兼具灵活和简单
ArrayList<String> list = stream.collect(Collectors.toCollection(ArrayList::new));
TreeSet<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
String s = stream.collect(Collectors.joining()); // 拼接成字符串
HashMap<String, String> map = stream.collect(HashMap::new, (x, y) -> {
x.put(y, y); // 自己做自己的key
}, HashMap::putAll);
}
}
Collectors#groupingBy
这个groupingBy方法,有3个重载方法
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
其中,第一个重载方法,很好理解,classifier就是返回一个分组的标准,即按照哪个字段,来进行分组
第二个重载方法,downstream指的是,在第一个分组完成以后,在每个小组内部,再按照downstream来进行分组
Collectors.groupingBy根据一个或多个属性对集合中的项目进行分组
数据准备:
public Product(Long id, Integer num, BigDecimal price, String name, String category) {
this.id = id;
this.num = num;
this.price = price;
this.name = name;
this.category = category;
}
Product prod1 = new Product(1L, 1, new BigDecimal("15.5"), "面包", "零食");
Product prod2 = new Product(2L, 2, new BigDecimal("20"), "饼干", "零食");
Product prod3 = new Product(3L, 3, new BigDecimal("30"), "月饼", "零食");
Product prod4 = new Product(4L, 3, new BigDecimal("10"), "青岛啤酒", "啤酒");
Product prod5 = new Product(5L, 10, new BigDecimal("15"), "百威啤酒", "啤酒");
List<Product> prodList = Lists.newArrayList(prod1, prod2, prod3, prod4, prod5);
分组
-
按照类目分组:
Map<String, List<Product>> prodMap= prodList.stream().collect(Collectors.groupingBy(Product::getCategory)); {"啤酒":[{"category":"啤酒","id":4,"name":"青岛啤酒","num":3,"price":10},{"category":"啤酒","id":5,"name":"百威啤酒","num":10,"price":15}], "零食":[{"category":"零食","id":1,"name":"面包","num":1,"price":15.5},{"category":"零食","id":2,"name":"饼干","num":2,"price":20},{"category":"零食","id":3,"name":"月饼","num":3,"price":30}]}
-
按照几个属性拼接分组:
-
Map<String, List<Product>> prodMap = prodList.stream().collect(Collectors.groupingBy(item -> item.getCategory() + "_" + item.getName())); {"零食_月饼":[{"category":"零食","id":3,"name":"月饼","num":3,"price":30}], "零食_面包":[{"category":"零食","id":1,"name":"面包","num":1,"price":15.5}], "啤酒_百威啤酒":[{"category":"啤酒","id":5,"name":"百威啤酒","num":10,"price":15}], "啤酒_青岛啤酒":[{"category":"啤酒","id":4,"name":"青岛啤酒","num":3,"price":10}], "零食_饼干":[{"category":"零食","id":2,"name":"饼干","num":2,"price":20}]}
-
根据不同条件分组
-
Map<String, List<Product>> prodMap= prodList.stream().collect(Collectors.groupingBy(item -> { if(item.getNum() < 3) { return "3"; }else { return "other"; } })); {"other":[{"category":"零食","id":3,"name":"月饼","num":3,"price":30},{"category":"啤酒","id":4,"name":"青岛啤酒","num":3,"price":10},{"category":"啤酒","id":5,"name":"百威啤酒","num":10,"price":15}], "3":[{"category":"零食","id":1,"name":"面包","num":1,"price":15.5},{"category":"零食","id":2,"name":"饼干","num":2,"price":20}]}
多级分组
要实现多级分组,我们可以使用一个由双参数版本的Collectors.groupingBy工厂方法创 建的收集器,它除了普通的分类函数之外,还可以接受collector类型的第二个参数。那么要进 行二级分组的话,我们可以把一个内层groupingBy传递给外层groupingBy,并定义一个为流 中项目分类的二级标准。
Map<String, Map<String, List<Product>>> prodMap= prodList.stream().collect(Collectors.groupingBy(Product::getCategory, Collectors.groupingBy(item -> {
if(item.getNum() < 3) {
return "3";
}else {
return "other";
}
})));
{"啤酒":{"other":[{"category":"啤酒","id":4,"name":"青岛啤酒","num":3,"price":10},{"category":"啤酒","id":5,"name":"百威啤酒","num":10,"price":15}]},
"零食":{"other":[{"category":"零食","id":3,"name":"月饼","num":3,"price":30}],
"3":[{"category":"零食","id":1,"name":"面包","num":1,"price":15.5},{"category":"零食","id":2,"name":"饼干","num":2,"price":20}]}
按子组收集数据
-
求总数
-
Map<String, Long> prodMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory, Collectors.counting())); {"啤酒":2,"零食":3}
-
求和
-
Map<String, Integer> prodMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory, Collectors.summingInt(Product::getNum))); {"啤酒":13,"零食":6}
-
把收集器的结果转换为另一种类型
-
Map<String, Product> prodMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Product::getNum)), Optional::get))); {"啤酒":{"category":"啤酒","id":5,"name":"百威啤酒","num":10,"price":15},"零食":{"category":"零食","id":3,"name":"月饼","num":3,"price":30}}
-
联合其他收集器
-
Map<String, Set<String>> prodMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory, Collectors.mapping(Product::getName, Collectors.toSet()))); {"啤酒":["青岛啤酒","百威啤酒"], "零食":["面包","饼干","月饼"]}
Collectors#toMap
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
这里,重点说下,第3个参数,mergeFunction
1.mergeFunction是什么意思呢?如下:
a merge function, used to resolve collisions between values associated with the same key, as supplied to {@link Map#merge(Object, Object, BiFunction)}
也就是说,map中,key是唯一的,当key值相同时,就会出现冲突了,此时,应该怎么处理呢?
具体的处理逻辑,就放到了mergeFunction中
所以,mergeFunction指的是,为了解决冲突,对多个相同key值,对应的value值,进行取舍、加减、替换等等逻辑
下面,看1个示例
public static void main(String[] args) {
Person zhangsan1 = new Person("zhangsan", 15);
Person lisi = new Person("lisi", 20);
Person zhangsan2 = new Person("zhangsan", 6);
ArrayList<Person> list = new ArrayList<>();
list.add(zhangsan1);
list.add(lisi);
list.add(zhangsan2);
Map<String, Integer> collect = list.stream().collect(Collectors.toMap(Person::getName, Person::getAge, (v1, v2) -> v1 + v2));
System.out.println(collect);
}
结果如下:
{lisi=20, zhangsan=21}
我们可以看到,就是将年龄进行相加
map和forEach方法
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer();
List<Person> list = new ArrayList<>();
Person p1 = new Person("zhangsan", 10);
Person p2 = new Person("lisi", 15);
list.add(p1);
list.add(p2);
List<Person> resultList = new ArrayList<>();
list.stream().map(p -> {
resultList.add(p);
return true;
});
System.out.println(resultList);
}
执行结果
[]
注意:在stream的map方法中,不能将传入的参数,添加到其他的list中。如上面的代码,即使添加了,也没有任何效果,如果想添加,可以使用forEach方法
如下:
list.stream().forEach(p -> {
resultList.add(p);
});
System.out.println(resultList);
执行结果如下:
[Person{id=0, name='zhangsan', age=10}, Person{id=0, name='lisi', age=15}]
使用stringBuffer进行拼接
public class StringBufferTest {
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer();
List<Person> list = new ArrayList<>();
Person p1 = new Person("zhangsan", 10);
Person p2 = new Person("lisi", 15);
list.add(p1);
list.add(p2);
list.stream().map(Person::getName).map(StringBufferTest::getNameStr).forEach(buffer::append);
System.out.println(list);
System.out.println(buffer.toString());
}
public static String getNameStr(String name){
StringBuffer nameBuffer = new StringBuffer();
return nameBuffer.append(name).append("_").toString();
}
}
执行结果如下:
[Person{id=0, name='zhangsan', age=10}, Person{id=0, name='lisi', age=15}]
zhangsan_lisi_
求出2个list的并集、交集、差集、补集
这里介绍2种方式,第一种是使用apacha.CollectionUtils工具类;第二种是使用List自带方法
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
List<String> list2 = new ArrayList<>();
list2.add("3");
list2.add("4");
list2.add("5");
list2.add("6");
new TestListUnion().test02(list1,list2);
}
// 使用apacha.CollectionUtils工具类
public void test01(List<String> list1,List<String> list2){
// 获取并集,自动去重 输出:[1, 2, 3, 4, 5, 6]
System.out.println(CollectionUtils.union(list1,list2));
// 获取交集 输出:[3, 4]
System.out.println(CollectionUtils.intersection(list1,list2));
// 获取交集的补集=并集-交集 。 输出:[1, 2, 5, 6]
System.out.println(CollectionUtils.disjunction(list1,list2));
// 获取差集=list1-list2 输出:[1, 2]
System.out.println(CollectionUtils.subtract(list1,list2));
}
// 使用List自带方法
public void test02(List<String> list1,List<String> list2){
// 获取交集
ArrayList<String> listCopy = new ArrayList<>(list1);
listCopy.retainAll(list2);
// 输出:[3, 4]
System.out.println(listCopy);
// 获取差集
ArrayList<String> listCopy2 = new ArrayList<>(list1);
listCopy2.removeAll(list2);
// 输出:[1, 2]
System.out.println(listCopy2);
// 并集(先做差集,再添加所有)
ArrayList<String> listCopy3 = new ArrayList<>(list1);
listCopy3.removeAll(list2);
listCopy3.addAll(list2);
// 输出: [1, 2, 3, 4, 5, 6]
System.out.println(listCopy3);
}
注意 : intersection和retainAll的差别
要注意的是它们的返回类型是不一样的,intersection返回的是一个新的List集合,而retainAll返回是Bollean类型那就说明retainAll方法是对原有集合进行处理再返回原有集合,会改变原有集合中的内容。
个人观点:1、从性能角度来考虑的话,List自带会高点,因为它不用再创建新的集合。2、需要注意的是:因为retainAll因为会改变原有集合,所以该集合需要多次使用就不适合用retainAll。
注意: Arrays.asList将数组转集合不能进行add和remove操作。
原因:调用Arrays.asList()生产的List的add、remove方法时报异常,这是由Arrays.asList() 返回的市Arrays的内部类ArrayList, 而不是java.util.ArrayList。Arrays的内部类ArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重新了这些方法而Arrays的内部类ArrayList没有重新,所以会抛出异常。
sort
在对list,使用Collections.sort(list)
,进行排序后,是按照升序,进行展示的。如下:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("ccc");
list.add("bbb");
list.add("aaa");
Collections.sort(list);
System.out.println(list); // [aaa, bbb, ccc]
}
我们还可以用一下API
Collections.sort(list,comparator)
这里的comparator的排序规则如下:
/**a negative integer, zero, or a positive integer as the
*first argument is less than, equal to, or greater than the
*second.
*/
int compare(T o1, T o2);
也就是说,如果返回值-1,那么代表o1 < o2
如果返回值1,那么代表o1 > o2
如果返回是0,那么代表 o1 =o2
这里的sort,分为升序 和 降序,2种排列方式,那么怎么区分是 升序 还是降序呢?
其实,很简单,Comparactor的compare方法,接受2个参数
int compare(T o1, T o2);
如果返回-1,那么o1 before o2,即o1在前,o2在后
如果返回1,那么o1 after o2,即o1在后,o2在前
如果返回0,则保留原样,不调整顺序
倒序demo如下
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",15));
list.add(new Person("lisi",18));
list.add(new Person("wangwu",10));
list.add(new Person("mazi",12));
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int age1 = o1.getAge();
int age2 = o2.getAge();
if (age1 < age2) {
return 1;
}
if (age1 > age2) {
return -1;
}
return 0;
}
});
System.out.println(list);
}
结果如下:
[Person{id=0, name='lisi', age=18}, Person{id=0, name='zhangsan', age=15}, Person{id=0, name='mazi', age=12}, Person{id=0, name='wangwu', age=10}]
升序demo如下:
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",15));
list.add(new Person("lisi",18));
list.add(new Person("wangwu",10));
list.add(new Person("mazi",12));
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int age1 = o1.getAge();
int age2 = o2.getAge();
if (age1 < age2) {
return -1;
}
if (age1 > age2) {
return 1;
}
return 0;
}
});
System.out.println(list);
}
结果如下:
[Person{id=0, name='wangwu', age=10}, Person{id=0, name='mazi', age=12}, Person{id=0, name='zhangsan', age=15}, Person{id=0, name='lisi', age=18}]
Java传值调用和传引用调用(参数引用为null的问题)
近期在项目中遇到一个场景,在多层级调用中需要传递上下文,调用过程中上线文对象可能为空,想通过一个公共方法处理上下文,当上下文为空时生成上下文对象,执行相关操作后将该上下文对象向后传递。大致逻辑如下:
public class Test {
public static void handleContext(Context context) {
if(context == null) {
context = new Context();
}
context.addNum();
}
public static void main(String[] args) {
Context context = null;
handleContext(context);
System.out.println(context.getNum());
}
static class Context {
private int num;
public int getNum() {
return num;
}
public void addNum() {
this.num ++;
}
}
}
测试执行报空指针错误,context没有按设想在handleContext方法中生成对象。原因是main方法在栈中创建了Context的引用并将其指向null,handleContext方法参数中的Context引用也被指向null,handleContext方法体在堆中创建Context对象,并将对象地址赋给方法参数中的Context引用,但main方法中的Context引用仍然为null,因此调用context.getNum()时报空指针。
这个是为什么呢?
我们先看下理论
基础类型变量:Java的8中基础数据类型:byte(8位)、short(16位)、int(32位)、long(64位)、float(32位)、double(64位)、char(16位)、boolean(8位),基础类型的数据存储在栈中,即是栈中分配内存空间存储所包含的值,其值就代表数据本身,值类型的数据具有较快的存取速度。
引用类型变量:除了基础类数据外,其余都是引用类型,包括类、数组等。引用类型数据的具体对象存放在堆中,而栈中存放的是该对象的内存地址。当引用类型没有赋值时,其引用为null,表示不指向任何对象。
基于上面的理论,我们来分析下
假设,刚开始main方法中context为null,表示context不指向任何对象,因此刚开始handleContext的方法参数,也不指向任何对象,接下来,handleContext的方法参数指向一个新对象,但是此时main.context仍然没有指向任何对象!!
注意:当我们使用引用传递时,如果传递的源头是一个null,那么是没有办法完成引用传递。因此,当我们想用引用传递,必须要保证传递的源头不是一个null
TreeSet是如何去重的
TreeSet是底层是基于TreeMap存储数据的。添加元素的操作,实际就是如何给TreeMap增加元素的操作。
添加元素,会作为TreeMap的key,而TreeMap的value是一个new Object(),首先会将key和value,构建成一个Entry,然后将Entry通过链表的方式(left/right),加入到TreeMap中
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
上面的核心代码,在这里
1.会获取Comparator比较器,
2.用Comparator,将新加的元素 和 已有的元素 进行比较,
3.如果cmp的值为0,那么将value放到相同位置,即key相同,那么新的key被直接丢弃了,集合长度不增加。
也就是说,如果往TreeMap中增加3个key相同的元素,其实TreeMap中,只会存储一个元素。这个功能其实非常好,可以用于去重
这里的Comparator,可以通过new TreeSet(Comparator<? super E> comparator),指定comparator
代码示例
public static void main(String[] args) {
Person p1 = new Person("zhangsan", 10);
Person p2 = new Person("zhangsan", 20);
ArrayList<Person> list = new ArrayList<>();
list.add(p1);
list.add(p2);
ArrayList<Person> collect = list.stream()
.collect(collectingAndThen(
toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))), ArrayList::new));
System.out.println(collect);
}
这里使用Person.name属性,作为比较的依据
输出结果
[Person{id=0, name='zhangsan', age=10}]
BigDecimal
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。
将常见的类型,转为bigDeciaml,使用valueOf方法
常用方法:±*/
-
add(BigDecimal)
BigDecimal对象中的值相加,返回BigDecimal对象
-
subtract(BigDecimal)
BigDecimal对象中的值相减,返回BigDecimal对象
-
multiply(BigDecimal)
BigDecimal对象中的值相乘,返回BigDecimal对象
-
divide(BigDecimal)
BigDecimal对象中的值相除,返回BigDecimal对象
-
toString()
将BigDecimal对象中的值转换成字符串
-
doubleValue()
将BigDecimal对象中的值转换成双精度数
-
floatValue()
将BigDecimal对象中的值转换成单精度数
-
longValue()
将BigDecimal对象中的值转换成长整数
-
intValue()
将BigDecimal对象中的值转换成整数
注意上面的doubleValue、floatValue、longValue、intValue,在bigDecimal转为int long float double,是怎么转换的呢?
其实很简单,我们记住,bigDecimal是一个能表示更高精度的数据类型,因为bigDecimal转long,其实就类似于float转int
在float转int,就是直接将小数部分直接抛弃掉,得到的整数部分,就是int
而bigDecimal转long,也是同样的道理,将小数部分直接抛弃掉,得到的整数部分,就是long
大小比较compareTo
java中对BigDecimal比较大小一般用的是bigdemical的compareTo方法
int a = bigdemical.compareTo(bigdemical2)
返回结果分析:
a = -1,表示bigdemical小于bigdemical2;
a = 0,表示bigdemical等于bigdemical2;
a = 1,表示bigdemical大于bigdemical2;
举例:a大于等于b
new bigdemica(a).compareTo(new bigdemical(b)) >= 0
bigdemica这个类,最大的使用场景,就是对精度进行严格的控制
- 不建议,会造成精度损失
BigDecimal num1 = new BigDecimal(2.222222);
- 不建议,虽然不会损失精度
BigDecimal num2 = new BigDecimal(2);
- 建议,使用字符串格式构造,不会损失精度
BigDecimal num = new BigDecimal("2.222222");
roundingMode
要想对原始数据,进行精度的改变,就需要用到setScale方法 和 RoundingMode模式
public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
return setScale(newScale, roundingMode.oldMode);
}
public enum RoundingMode {
UP(BigDecimal.ROUND_UP),
DOWN(BigDecimal.ROUND_DOWN),
CEILING(BigDecimal.ROUND_CEILING),
FLOOR(BigDecimal.ROUND_FLOOR),
HALF_UP(BigDecimal.ROUND_HALF_UP),
HALF_DOWN(BigDecimal.ROUND_HALF_DOWN),
HALF_EVEN(BigDecimal.ROUND_HALF_EVEN),
UNNECESSARY(BigDecimal.ROUND_UNNECESSARY);
final int oldMode;
private RoundingMode(int oldMode) {
this.oldMode = oldMode;
}
public static RoundingMode valueOf(int rm) {
switch(rm) {
case BigDecimal.ROUND_UP:
return UP;
case BigDecimal.ROUND_DOWN:
return DOWN;
case BigDecimal.ROUND_CEILING:
return CEILING;
case BigDecimal.ROUND_FLOOR:
return FLOOR;
case BigDecimal.ROUND_HALF_UP:
return HALF_UP;
case BigDecimal.ROUND_HALF_DOWN:
return HALF_DOWN;
case BigDecimal.ROUND_HALF_EVEN:
return HALF_EVEN;
case BigDecimal.ROUND_UNNECESSARY:
return UNNECESSARY;
default:
throw new IllegalArgumentException("argument out of range");
}
}
}
2 舍入模式
2.1 UP
public final static int ROUND_UP = 0;
定义:远离零方向舍入。
解释:始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值。
图示:
示例:
输入数字 | 使用 UP 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 6 |
2.5 | 3 |
1.6 | 2 |
1.1 | 2 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -2 |
-1.6 | -2 |
-2.5 | -3 |
-5.5 | -6 |
**
**
2.2 DOWN
public final static int ROUND_DOWN = 1;
定义:向零方向舍入。
解释:从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 5 |
2.5 | 2 |
1.6 | 1 |
1.1 | 1 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -1 |
-2.5 | -2 |
-5.5 | -5 |
**
**
2.3 CEILING
public final static int ROUND_CEILING = 2;
定义:向正无限大方向舍入。
解释:如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于RoundingMode.DOWN。注意,此舍入模式始终不会减少计算值。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 6 |
2.5 | 3 |
1.6 | 2 |
1.1 | 2 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -1 |
-2.5 | -2 |
-5.5 | -5 |
**
**
2.4 FLOOR
public final static int ROUND_FLOOR = 3;
定义:向负无限大方向舍入。
解释:如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于RoundingMode.UP。注意,此舍入模式始终不会增加计算值。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 5 |
2.5 | 2 |
1.6 | 1 |
1.1 | 1 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -2 |
-1.6 | -2 |
-2.5 | -3 |
-5.5 | -6 |
**
**
2.5 HALF_UP (Half指的中点值,例如0.5、0.05,0.15等等)
public final static int ROUND_HALF_UP = 4;
定义:向最接近的数字方向舍入,如果与两个相邻数字的距离相等,则向上舍入。
解释:如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同RoundingMode.DOWN。注意,此舍入模式就是通常学校里讲的四舍五入。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 6 |
2.5 | 3 |
1.6 | 2 |
1.1 | 1 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -2 |
-2.5 | -3 |
-5.5 | -6 |
**
**
2.6 HALF_DOWN
public final static int ROUND_HALF_DOWN = 5;
定义:向最接近的数字方向舍入,如果与两个相邻数字的距离相等,则向下舍入。
解释:如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同RoundingMode.DOWN。注意,此舍入模式就是通常讲的五舍六入。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 5 |
2.5 | 2 |
1.6 | 2 |
1.1 | 1 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -2 |
-2.5 | -2 |
-5.5 | -5 |
**
**
2.7 HALF_EVEN
public final static int ROUND_HALF_EVEN = 6;
定义:向最接近数字方向舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
解释:如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同RoundingMode.HALF_DOWN。注意,在重复进行一系列计算时,根据统计学,此舍入模式可以在统计上将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。此舍入模式类似于 Java 中对float 和double 算法使用的舍入策略。
图示:
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 6 |
2.5 | 2 |
1.6 | 2 |
1.1 | 1 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -2 |
-2.5 | -2 |
-5.5 | -6 |
**
**
2.8 UNNECESSARY
public final static int ROUND_UNNECESSARY = 7;
定义:用于断言请求的操作具有精确结果,因此不发生舍入。
解释:计算结果是精确的,不需要舍入,否则抛出 ArithmeticException。
示例:
输入数字 | 使用 DOWN 舍入模式将输入数字舍入为一位数 |
---|---|
5.5 | 抛出 ArithmeticException |
2.5 | 抛出 ArithmeticException |
1.6 | 抛出 ArithmeticException |
1.1 | 抛出 ArithmeticException |
1.0 | 1 |
-1.0 | -1 |
-1.1 | 抛出 ArithmeticException |
-1.6 | 抛出 ArithmeticException |
-2.5 | 抛出 ArithmeticException |
-5.5 | 抛出 ArithmeticException |
工作中使用
1.优惠省的钱,这个值是负数,类型是double类型,尽量给用户多省点钱,向小取整
/**
* 省的钱,计算到分 -487.3----> -488
*
* @param discountPrice 省的钱
* @return
*/
public static long calDiscountToCent(double discountPrice) {
return BigDecimal.valueOf(discountPrice).setScale(0, BigDecimal.ROUND_UP).longValue();
}
HashMap
hashMap#compute
相信各位在开发的过程中总能遇见类似这样的需求:查询Map中的某一key值,如果有就将值加一,没有就put新的key值为1 (或对值进行其他操作)。
public static void main(String[] args) throws IOException {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("aa");
list.add("aa");
list.add("bb");
list.add("cc");
new TestWp().test2(list);
}
public void test2(ArrayList<String> list) {
HashMap<String, Integer> map = new HashMap();
for (String s : list) {
map.compute(s,(k,oldValue) -> {
if (oldValue == null) {
return 1;
}else {
return oldValue+1;
}
});
}
System.out.println(map); // {cc=2, bb=2, aa=3}
}
key - 键
remappingFunction - 重新映射函数,用于重新计算值
返回值 :
原 value 不存在,计算的新value为null,则返回 null 并不进行操作
原 value 不存在,计算的新 value 不为null,添加新的键值对,返回新value
原 value 存在,计算的新 value 为null,删除原值,返回新value
原 value 存在,计算的新 value 不为null,重新赋值并返回通过 remappingFunction 重新计算后的值
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
这个方法作用: 如果指定的key在这个map中,没有对应的value,那么就执行后面的函数,将会面的函数执行的结果作为value,键为之前的key,将这个键值对加入到这个map中。
如果对应的value存在,将这个value作为computeIfAbsent方法的返回值返回。
实例:
public void test01() {
Map<String, List<String>> map = new HashMap<>();
List<String> list;
// 一般这样写
// list = map.get("list-1");
// if (list == null) {
// list = new LinkedList<>();
// map.put("list-1", list);
// }
// list.add("one");
// 使用 computeIfAbsent 可以这样写
list = map.computeIfAbsent("list-1", k -> new ArrayList<>());
list.add("one");
System.out.println(map.toString());
}
输出为
{list-1=[one]}
看下这个方法的另一个使用
public void test02() {
Map<String, Boolean> whoLetDogsOut = new HashMap<>();
whoLetDogsOut.computeIfAbsent("list-1", k -> printKValue(k));
System.out.println(whoLetDogsOut.toString());
}
static boolean printKValue(String k) {
System.out.println("k value is :" + k);
return true;
}
输出为
k value is :list-1
{list-1=true}
如果key对应的value存在的话:将这个value作为computeIfAbsent方法的返回值返回。
public void test03() {
Map<String,String> map = new HashMap<>();
map.put("aa", "zhangsan");
String computeIfAbsent = map.computeIfAbsent("aa", k -> new String(k));
System.out.println("result is :"+computeIfAbsent);
}
hashMap#merge
IntStream
range&rangeClosed
区别:
就是开区间和闭区间的区别
如:[1,20)和[1,20]的区别
demo如下:
//3.创建数字流
IntStream.of(1, 2, 3);//返回一个intStream
IntStream.range(1,20).forEach(i-> System.out.print(i+","));//返回一个1-19的数字流
System.out.println();
IntStream.rangeClosed(1,20).forEach(i-> System.out.print(i+","));//返回的一个1-20的数字流
结果:
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
数组的复制
Arrays.copyOf方法
Arrays.copyOf(array, to_index);
Arrays.fill(array, from_index, to_index);
第一个方法其实就是返回一个数组,而这个数组就等于数组array的前to_index个数,也就是array[0] ~ array[to_index - 1]。
而第二种方法也只是加了一个初始的位置,即返回一个数组等于array[from_index] ~ array[to_index - 1]。
这里要注意一下,不管是上面哪种使用方法,都务必记住时不包含array[to_index]这个数。
示例:
package base;
import java.util.Arrays;
public class Arrays_copyOf
{
public static void main(String[] args)
{
int a[] = {4, 3, 6, 5, 1, 2};
int b[] = Arrays.copyOf(a, 4);
int c[] = Arrays.copyOfRange(a, 2, 4 + 1);
for (int i = 0; i < b.length; i++)
System.out.print(b[i] + " ");
System.out.println();
for (int i = 0; i < c.length; i++)
System.out.print(c[i] + " ");
System.out.println();
}
}
StringBuilder
StringJoiner
demo
public static void main(String[] args) {
StringJoiner stringJoiner = new StringJoiner(",");
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("ccc");
list.add("bbb");
list.add("ddd");
list.stream().forEach(stringJoiner::add);
System.out.println(stringJoiner.toString());
}
输出:
aaa,ccc,bbb,ddd
源码分析:
StringJoiner是 JDK8新增的字符串增强类,用于构造由分隔符分隔的字符序列,并可选择性地从提供的前缀开始和以提供的后缀结尾。
其实,内部使用的还是StringBuilder
属性
private final String prefix; //前缀
private final String delimiter; //分隔符
private final String suffix; //后缀
/*
* StringBuilder value -- at any time, the characters constructed from the
* prefix, the added element separated by the delimiter, but without the
* suffix, so that we can more easily add elements without having to jigger
* the suffix each time.
*/
//值,没有后缀
//底层依旧是StringBuilder
private StringBuilder value;
/*
* By default, the string consisting of prefix+suffix, returned by
* toString(), or properties of value, when no elements have yet been added,
* i.e. when it is empty. This may be overridden by the user to be some
* other value including the empty String.
*/
//空值,默认情况下是 前缀 + 后缀,但是可以用 setEmptyValue 方法覆盖
private String emptyValue;
/**
* Sets the sequence of characters to be used when determining the string
* representation of this {@code StringJoiner} and no elements have been
* added yet, that is, when it is empty. A copy of the {@code emptyValue}
* parameter is made for this purpose. Note that once an add method has been
* called, the {@code StringJoiner} is no longer considered empty, even if
* the element(s) added correspond to the empty {@code String}.
*
* @param emptyValue the characters to return as the value of an empty
* {@code StringJoiner}
* @return this {@code StringJoiner} itself so the calls may be chained
* @throws NullPointerException when the {@code emptyValue} parameter is
* {@code null}
*/
//通过该方法可以自定义空值
public StringJoiner setEmptyValue(CharSequence emptyValue) {
this.emptyValue = Objects.requireNonNull(emptyValue,
"The empty value must not be null").toString();
return this;
}
构造方法
/**
* Constructs a {@code StringJoiner} with no characters in it, with no
* {@code prefix} or {@code suffix}, and a copy of the supplied
* {@code delimiter}.
* If no characters are added to the {@code StringJoiner} and methods
* accessing the value of it are invoked, it will not return a
* {@code prefix} or {@code suffix} (or properties thereof) in the result,
* unless {@code setEmptyValue} has first been called.
*
* @param delimiter the sequence of characters to be used between each
* element added to the {@code StringJoiner} value
* @throws NullPointerException if {@code delimiter} is {@code null}
*/
public StringJoiner(CharSequence delimiter) {
this(delimiter, "", "");
}
/**
* Constructs a {@code StringJoiner} with no characters in it using copies
* of the supplied {@code prefix}, {@code delimiter} and {@code suffix}.
* If no characters are added to the {@code StringJoiner} and methods
* accessing the string value of it are invoked, it will return the
* {@code prefix + suffix} (or properties thereof) in the result, unless
* {@code setEmptyValue} has first been called.
*
* @param delimiter the sequence of characters to be used between each
* element added to the {@code StringJoiner}
* @param prefix the sequence of characters to be used at the beginning
* @param suffix the sequence of characters to be used at the end
* @throws NullPointerException if {@code prefix}, {@code delimiter}, or
* {@code suffix} is {@code null}
*/
public StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
//分隔符、前缀、后缀不能为null,但是可以为空字符串
Objects.requireNonNull(prefix, "The prefix must not be null");
Objects.requireNonNull(delimiter, "The delimiter must not be null");
Objects.requireNonNull(suffix, "The suffix must not be null");
// make defensive copies of arguments
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
//空值为前缀 + 后缀
this.emptyValue = this.prefix + this.suffix;
}
核心方法add
/**
* Adds a copy of the given {@code CharSequence} value as the next
* element of the {@code StringJoiner} value. If {@code newElement} is
* {@code null}, then {@code "null"} is added.
*
* @param newElement The element to add
* @return a reference to this {@code StringJoiner}
*/
public StringJoiner add(CharSequence newElement) {
prepareBuilder().append(newElement);
return this;
}
private StringBuilder prepareBuilder() {
if (value != null) {
value.append(delimiter);
} else {
value = new StringBuilder().append(prefix);
}
return value;
}
可以看到每次add操作其实都是操作value,会先追加分隔符,再追加真正要add的元素,从这里也可以看到value是不包含后缀的,因为这样每次add时就不需要处理后缀了,比较方便。
方法merge
/**
* Adds the contents of the given {@code StringJoiner} without prefix and
* suffix as the next element if it is non-empty. If the given {@code
* StringJoiner} is empty, the call has no effect.
*
* <p>A {@code StringJoiner} is empty if {@link #add(CharSequence) add()}
* has never been called, and if {@code merge()} has never been called
* with a non-empty {@code StringJoiner} argument.
*
* <p>If the other {@code StringJoiner} is using a different delimiter,
* then elements from the other {@code StringJoiner} are concatenated with
* that delimiter and the result is appended to this {@code StringJoiner}
* as a single element.
*
* @param other The {@code StringJoiner} whose contents should be merged
* into this one
* @throws NullPointerException if the other {@code StringJoiner} is null
* @return This {@code StringJoiner}
*/
public StringJoiner merge(StringJoiner other) {
Objects.requireNonNull(other);
if (other.value != null) {
//这里为啥需要把other原来的长度记录下来
//因为other可能就是本身this,后面的prepareBuilder会对this增加分隔符delimiter,
//所以如果不记录other原来的长度,当other=this时,builder.append会
//导致value后面多一个delimiter
final int length = other.value.length();
// lock the length so that we can seize the data to be appended
// before initiate copying to avoid interference, especially when
// merge 'this'
StringBuilder builder = prepareBuilder();
builder.append(other.value, other.prefix.length(), length);
}
return this;
}
从merge代码可以看到,merge是把other的主体部分直接搬过来,所以这里会有一个问题,假如两个StringJoiner的分隔符不一样,那么merge之后也会存在这种问题,其实可以将merge理解为把other作为一个整体add就行了。
Q:merge为啥需要把other原来的长度记录下来
A:因为other可能就是本身this,后面的prepareBuilder()时会先append(delimiter),会对this增加分隔符delimiter,所以如果不记录other原来的长度,当other=this时,builder.append会导致value后面多一个delimiter,因此此时append还是得以添加前的length为准
方法length
/**
* Returns the length of the {@code String} representation
* of this {@code StringJoiner}. Note that if
* no add methods have been called, then the length of the {@code String}
* representation (either {@code prefix + suffix} or {@code emptyValue})
* will be returned. The value should be equivalent to
* {@code toString().length()}.
*
* @return the length of the current value of {@code StringJoiner}
*/
public int length() {
// Remember that we never actually append the suffix unless we return
// the full (present) value or some sub-string or length of it, so that
// we can add on more if we need to.
return (value != null ? value.length() + suffix.length() :
emptyValue.length());
}
长度为 前缀 + 具体元素 + 分隔符 + 后缀。
方法toString
/**
* Returns the current value, consisting of the {@code prefix}, the values
* added so far separated by the {@code delimiter}, and the {@code suffix},
* unless no elements have been added in which case, the
* {@code prefix + suffix} or the {@code emptyValue} characters are returned
*
* @return the string representation of this {@code StringJoiner}
*/
@Override
public String toString() {
if (value == null) {
return emptyValue;
} else {
if (suffix.equals("")) {
return value.toString();
} else {
int initialLength = value.length();
String result = value.append(suffix).toString();
// 保持value依旧不包含后缀
value.setLength(initialLength);
return result;
}
}
}
value 保持依旧不包含后缀。
toString为啥不直接 return value.toString() + suffix
由于toString调用比较频繁,所以就涉及 String + String 与 StringBuilder.append的效率问题了。
在这里涉及到字符串动态添加,StringBuilder.append效率更高。
异常
java中的异常 分为检查异常(checked exception) 和非检查异常(unckecked exception).
非检查异常 包含 Error 和 RuntimeException。
如何区分检查异常 和 非检查异常呢?
很简单,以编译来进行区分。编译之前和编译时,IDEA提示,要捕获的异常,这些异常都属于 检查异常。
编译后,部署到生产环境中,运行过程中,出现的异常,这些都属于 非检查异常。
检查异常:如SQLException , IOException,ClassNotFoundException
非检查异常: 除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
对于非检查异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题
调用和执行区分
调用和执行,这两个概念是比较近似的,但是,在实际代码中,区别很大。
可以看到,一个完整的过程,是分为2个部分。
左边是调用,右边是执行。先将调用顺序确定后,然后依次执行代码
回调
通过consumer实现回调
其实,回调的事情很简单,A命令B做事情,B就是做完事情之后,主动将结果通知到 A。
如果一个target类,想让Helper类帮助他,做一件事情,做完后,把结果通知到 target中。
最笨的办法,就是在helper类中,直接调用target类的方法。
其次,是使用回调,先注册一个接口,然后将target类实现这个接口,将target注册到Helper中,当Helper做完后,调用这个接口的抽象方法,实现把结果通知到 target中。
最好的一种做法:
这中做法和第二种做法有点类似,只是更加的抽象。
在Target中,先创建一个consumer,然后将这个consumer添加到Helper中,注意,这里使用的链式写法。
在Helper类中维护一个Consumer容器,然后做完事情之后,调用consumer.accept(retStr);,将结果传递到target中
package com.wp.call;
import java.util.function.Consumer;
public class Target {
public static void main(String[] args) {
Helper helper = new Helper();
Consumer<String> consumer = retValue ->{
System.out.println("Target receive return value is :"+retValue);
};
helper.accept(consumer).doSome("eat");
}
}
package com.wp.call;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class Helper {
ArrayList<Consumer<String>> arrayList = new ArrayList<>();
public void doSome(String someStr) {
System.out.println("Helper do someStr:" + someStr);
String retStr = someStr + ", ok";
for (Consumer<String> consumer : arrayList) {
consumer.accept(retStr);
}
}
public Helper accept(Consumer<String> consumer) {
arrayList.add(consumer);
return this;
}
}
如果使用异步,代码如下:
package com.wp.call;
import java.util.function.Consumer;
public class Target {
public static void main(String[] args) {
Helper helper = new Helper();
Consumer<String> consumer = retValue ->{
System.out.println("Target receive return value is :"+retValue);
};
// helper.accept(consumer).doSome("eat");
helper.accept(consumer).doSomeAsyc("eat asyc");
System.out.println("wait helper eat asyc------");
}
}
package com.wp.call;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class Helper {
ArrayList<Consumer<String>> arrayList = new ArrayList<>();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
public void doSomeAsyc(String someStr) {
newFixedThreadPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Helper do someStr:" + someStr);
String retStr = someStr + ", ok";
for (Consumer<String> consumer : arrayList) {
consumer.accept(retStr);
}
});
}
public Helper accept(Consumer<String> consumer) {
arrayList.add(consumer);
return this;
}
}
通过匿名回调函数实现回调
首先定义一个接口,使用泛型
public interface VisionCallback<T> {
void onResult(T t);
void onError(int i);
}
然后定义一个javaBean,结果类
package com.wp.callback;
public class WorkResult {
private int workTime;
private String dowhat;
public WorkResult() {
super();
}
public WorkResult(int workTime, String dowhat) {
super();
this.workTime = workTime;
this.dowhat = dowhat;
}
public int getWorkTime() {
return workTime;
}
public void setWorkTime(int workTime) {
this.workTime = workTime;
}
public String getDowhat() {
return dowhat;
}
public void setDowhat(String dowhat) {
this.dowhat = dowhat;
}
@Override
public String toString() {
return "WorkResult [workTime=" + workTime + ", dowhat=" + dowhat + "]";
}
}
定义一个工作类(就是B类)
package com.wp.callback;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Worker {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
public void work(WorkResult result,VisionCallback<WorkResult> visionCallback) {
System.out.println("worker do....");
newFixedThreadPool.execute(() ->{
try {
TimeUnit.SECONDS.sleep(2);
int i=0;
if(i<1) {
result.setDowhat("repair air condition");
result.setWorkTime(8);
visionCallback.onResult(result);
}else {
visionCallback.onError(i);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
}
定义一个管理类
package com.wp.callback;
public class Manager {
public static void main(String[] args) {
Worker worker = new Worker();
WorkResult workResult = new WorkResult();
System.out.println("manager call worker dosomething....");
worker.work(workResult, new VisionCallback<WorkResult>() {
@Override
public void onResult(WorkResult workResult) {
System.out.println("Manager reveive onResult,workResult:"+workResult.toString());
}
@Override
public void onError(int i) {
System.out.println("onError,errorcode:"+i);
}
});
System.out.println("manager done,wait worker do");
}
}
Class 对象
isAssignableFrom
public boolean isAssignableFrom(Class<?> cls)
Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter.
It returns true if so; otherwise it returns false.
If this Class object represents a primitive type, this method returns true if the specified Class parameter is exactly this Class object; otherwise it returns false.
Specifically, this method tests whether the type represented by the specified Class parameter can be converted to the type represented by this Class object via an identity conversion or via a widening reference conversion.
See The Java Language Specification, sections 5.1.1 and 5.1.4 , for details.
Parameters:
cls - the Class object to be checked
Returns:
the boolean value indicating whether objects of the type cls can be assigned to objects of this class
Throws:
NullPointerException - if the specified Class parameter is null.
Since:
JDK1.1
看一个示例:
class A{
}
class B extends A{
}
class C extends B{
}
public class test {
public static void main(String[] args) {
A a = new A();
B b = new B();
B b1 = new B();
C c = new C();
System.out.println(a.getClass().isAssignableFrom(a.getClass()));
System.out.println(a.getClass().isAssignableFrom(b.getClass()));
System.out.println(a.getClass().isAssignableFrom(c.getClass()));
System.out.println(b1.getClass().isAssignableFrom(b.getClass()));
System.out.println(b.getClass().isAssignableFrom(c.getClass()));
System.out.println("=====================================");
System.out.println(A.class.isAssignableFrom(a.getClass()));
System.out.println(A.class.isAssignableFrom(b.getClass()));
System.out.println(A.class.isAssignableFrom(c.getClass()));
System.out.println("=====================================");
System.out.println(Object.class.isAssignableFrom(a.getClass()));
System.out.println(Object.class.isAssignableFrom(String.class));
System.out.println(String.class.isAssignableFrom(Object.class));
}
}
运行结果如下:
true
true
true
true
true
=====================================
true
true
true
=====================================
true
true
false