精华内容
下载资源
问答
  • 用高阶函数轻松实现Java对象的深度遍历前言背景知识简单介绍幺元(identity)Java的函数类型函数类型签名函数幺元高阶函数Stream流式计算里高阶函数对象的遍历List列表遍历Map映射浅遍历Set集合的浅遍历...

    前言:高阶函数是函数式编程中较为初级的一项技术。笔者首先用高阶函数实现了对象浅遍历,然后在其基础上轻松实现了对象的深度遍历。然后将代码稍加改造,实现了带路线追踪的深度遍历和部分遍历。最后,笔者通过之前的例子对函数式编程进行进一步讨论。笔者希望通过本文向大家展示函数式编程相对于传统的编程模式在思路上的不同。

    前言

    高阶函数是函数式编程中较为初级的一项技术。笔者首先用高阶函数实现了对象浅遍历,然后在其基础上轻松实现了对象的深度遍历。然后将代码稍加改造,实现了带路线追踪的深度遍历和部分遍历。最后,笔者通过之前的例子对函数式编程进行进一步讨论。笔者希望通过本文向大家展示函数式编程相对于传统的编程模式在思路上的不同。

    背景知识的简单介绍

    本小节主要面对不太了解函数式编程的读者,如果你也是Functional Lover,可以跳过这一节。

    幺元(identity)

    幺元的概念非常简单。不严谨地说,幺元就是某个类型的基本单元。在数学上,对于类型A的某个元素e和任意元素a,在运算符上,如果满足公式e ⊕ a = a ⊕ e = a,那么e就是类型A的幺元(双边)。

    例如,相对于运算符+而言,Integer的幺元就是0String的幺元就是"";对于*而言,Integer的幺元就是1;对于运算而言,Boolean的幺元就是false;其他的,列表的幺元就是空列表,对象的幺元就是空对象,每次当我们new一个对象的时候,实际上就是创建了一个幺元。请注意,null不是对象的幺元

    Java的函数类型

    Java 8添加了如下函数类型:一元函数Function、一元消费器Consumer、一元谓词Predicate、二元函数BiFunction、二元消费器BiConsumer、二元谓词BiPredicate等等。事实上,这些类型都只是接口,没有具体的对象实现,她们代表的是不同入参和回参的方法。本质上,方法就是函数。例如:

    // 方法写法
    // 接受一个整型入参,返回值类型为整型
    Integer increase (Integer x) { return x = 1; }
    increase(1); // 2
    
    // 函数写法
    // 一元函数:一个参数,一个返回值,类似于 y = f(x)
    Function<Integer, Integer> increase = x -> x + 1;
    increase.apply(1); // 2
    
    // 其他函数
    // 一元消费器:只有入参,没有返回值
    Consumer printOne = System.out::println; // 等价于 print = o -> System.out.println(o);
    printOne.accept(1);
    // 一元谓词:返回布尔型的函数
    Predicate<Integer> equalOne = x -> x == 1;
    equalOne.test(1);
    // 二元函数:两个参数,一个返回值,类似于 y = f(x1, x2)
    BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
    // 等等......
    

    简单的说,我们只是将方法声明成了变量。从此,方法也成为Java名词王国中的一等公民

    函数的类型签名

    既然函数变成了变量,那么变量总是有类型的,在函数变量名的左边就是函数类型。其中<>外的是自身的类型,<>内最后一个是返回值类型。类型签名是一个函数的灵魂。我们以上面的add为例,她的类型为BiFunction<Integer, Integer, Integer>。其中,最外面的BiFunction表示这是一个二元函数,前两个Integer表示两个参数必须是整型,最后一个Integer表示返回值是整型。

    函数的幺元

    既然函数有了类型,那就必然有幺元,那么函数的幺元是什么呢?其实很简单,函数的幺元就是:什么都不做

    Function identity = o -> o; // 一元函数幺元
    Consumer pass = o -> {}; // 一元消费器幺元
    

    高阶函数

    函数有了类型,又可以接收参数,那么函数是否可以做为参数传入和返回呢?答案是肯定的。如果一个函数的返回类型还是函数,那么这个函数就是一个高阶函数

    我们写一个稍微复杂点的函数的类型签名,这个一元函数接收一个一元函数做为参数,并返回一个一元函数:

    Function<Function<Integer, Integer>, Function<Integer,Integer>>
        increaseBeforeApplyF = f -> x -> f.apply(x + 1);
    // 调用方式
    Integer y = increaseBeforeApplyF.apply(someFunction).apply(x);
    // 部分调用
    Function<Integer,Integer> somePartialFunction = increaseBeforeApplyF.apply(someFunction);
    

    首先,你可能会狐疑为什么这么多的.apply.accept.test,她们只是表示要调用一下。我们并不能像方法一样去调用函数,因为最初的Java认为什么都可以是对象,唯独方法不是对象。

    另外,你可能想问,当发生部分调用的时候系统做了什么。事实上系统什么也没做,因为啥也做不了。只有完全调用后,系统才会开始计算。这种特性称为惰性求值

    Stream流式计算里的高阶函数

    高阶函数并不罕见,Java 8中的流式计算都是高阶函数,主要用来处理集合类型,深受广大程序员的喜爱。在各种编程语言中几乎都支持以下高阶函数:

    • map 映射函数:接收一个一元函数,将集合中所有元素都映射到另一个集合上
    • reduce 归纳函数:接收一个幺元,和一个二元函数,将集合中所有元素归纳成一个元素
    • filter 过滤函数:接收一个一元谓词,返回集合中谓词断言为真的元素集合

    注: 本文为了区分高阶函数和低阶函数,所有的高阶函数都使用F做为结尾。并且尽量不使用方法的写法,目的为了将函数的类型签名集中在一处,方便阅读。

    对象的浅遍历

    我们不用思考究竟要举什么样的例子来说明我们实现了对象的遍历,这种传统的思路是非常有害的。我们只需要知道将来有个很普通的函数f,她需要遍历处理对象中的所有元素。因此,我们写的遍历函数应当是一个修饰、或者是一个增强,她可以将一个普通的函数f升级成为一个可以遍历对象元素的函数。

    我们所要遍历的目标,可能是集合或者是一个非集合对象,他们可能有若干个元素或属性,每个元素或属性又有可能是一个集合或者是一个非集合对象。直接思考如何深度遍历是见很困难的事情,我们先思考如何实现单层的浅遍历。

    List列表的遍历

    我们可以从最简单的List列表写起:
    traversalListF - 接收一个普通的函数f做为参数,返回一个可以消费List的消费器,最终会遍历List的索引,将每个元素都用f处理一下

    /** 列表单层遍历修饰符 */
    private static final Function<Function, Consumer<List>>
        traversalListF = f -> l -> IntStream.range(0, l.size()).forEach(i -> l.set(i, f.apply(l.get(i))));
    

    我们可以用x -> x + 1去测试一下List<Integer> list = Arrays.asList(1, 2, 3),然后将list打印出来,看看我们写的遍历器有没有将元素各个加1。事实上,根本没有这个必要,也非常不“函数范儿”。事实上,测试的时候,我们只需要知道我们的函数确实遍历到了每个元素就可以了,具体遍历做什么并不重要。

    我们写一个辅助函数beforeF:这个函数依次接收一个函数f和一个消费器c做为参数,并在f处理入参之前,先用c消费一下入参。

    /** 前置调用修饰符 */
    public static final Function<Function, Function<Consumer, Function>>
        beforeF = f -> c -> o -> { c.accept(o); return f.apply(o); };
    

    那么我们可以这样测试:遍历的对象原封不动,但是在此之前将他们打印出来。

    /** 函数幺元 */
    private static final Function identity = o -> o;
    
    //测试
    List<Integer> list = Arrays.asList(1, 2, 3);
    traversalListF.apply(beforeF.apply(identity).apply(System.out::println)).accept(list);
    
    // 输出
    1
    2
    3
    

    同样,我们可以写一个afterF

    /** 后置调用修饰符 */
    public static final Function<Function, Function<Consumer, Function>>
        afterF = f -> c -> o -> { Object oo = f.apply(o); c.accept(oo); return oo; };
    
    // 测试
    traversalListF.apply(afterF.apply(beforeF.apply(x -> x + 1).apply(System.out::println)).apply(System.out::println)).accept(list);
    // 输出
    1
    2
    2
    3
    3
    4
    

    有些人可能会笑了:这样岂不是可以一行写完所有代码?其实beforeFafterF表达的是两个函数之间的顺序关系。更深一步来讲,不仅顺序结构,选择结构、循环结构都只是一种关系或者亦可称为一种数据结构,本质上没有区别。在数学家眼里:这个世界没有什么东西是对象,有的只是关系

    Map映射的浅遍历

    对Map的遍历也很简单:
    traversalMapF - 接收一个函数f,将Map里所有的值都处理一下

    /** 映射单层遍历修饰符 */
    private static final Function<Function, Consumer<Map>>
        traversalMapF = f -> m -> m.forEach((k,v) -> m.put(k, f.apply(v)));
    
    // 测试
    Map<String, Integer> map = new HashMap();
    map.put("first", 1);
    map.put("second", 2);
    map.put("third", 3);
    traversalMapF.apply(beforeF.apply(identity).apply(System.out::println)).accept(map);
    // 输出
    1
    2
    3
    

    Set集合的浅遍历

    对Set的遍历要特殊一些,因为处理完之后,元素的数量并不一定和原来相等。
    traversalSetF - 使用traversalListF帮助Set遍历对象,将生成的元素重新加入到Set中

    /** 集合单层遍历修饰符 */
    private static final Function<Function, Consumer<Set>>
        traversalSetF = f -> s -> {
            List l = new ArrayList(s);
            traversalListF.apply(f).accept(l);
            s.clear();
            s.addAll(l);
        };
    
    //测试
    Set<Integer> set = new HashSet();
    set.add(1);
    set.add(2);
    set.add(3);
    traversalSetF.apply(x -> 1).accept(set);
    set.forEach(System.out::println);
    //输出
    1
    

    非集合类型的Object浅遍历

    要想遍历一个未知对象的所有属性,只能使用反射。我们先写一个高阶函数modifyF
    modifyF - 她依次接收处理的函数f、处理的对象o和和她的属性名field,并用f去处理ofield

    /** 反射修改对象属性 */
    private static final Function<Function, Function<Object, Consumer<Field>>>
        modifyF = f -> o -> field -> {
            try {
                field.setAccessible(true);
                field.set(o, f.apply(field.get(o)));
            } catch (IllegalArgumentException | IllegalAccessException e){ /* pass.accept(o) */ }
        };
    

    然后我们去写遍历对象属性的函数traversalObjectF,要注意对象的静态属性其实是全局变量,不能当作对象的成员去看待。
    traversalObjectF - 遍历对象o的所有属性,如果是静态属性,则跳过;如果不是,就用f去处理o的属性

    /** 谓词: 属性在对象中是否static修饰 */
    private static final Predicate<Field> isStaticField = field -> Modifier.isStatic(field.getModifiers());
    
    /** 对象单层遍历器 */
    private static final Function<Function, Consumer>
        traversalObjectF = f -> o -> Stream.of(o.getClass().getDeclaredFields()).forEach(
            field -> {
                if (isStaticField.test(field))  ; /* pass.accept(field) */
                else                            modifyF.apply(f).apply(o).accept(field);
            });
    

    我们测试一下:

    class Person {
      private String name;
      private String englishName;
      private String age;
      private String sex;
      // ... oh, my god! a lot of get/set
    }
    //测试
    Person myDaughter = new Person();
    myDaughter.setName("Xinyu");
    myDaughter.setEnglishName("Alice");
    myDaughter.setAge("1");
    myDaughter.setSex("Female");
    
    traversalObjectF.apply(beforeF.apply(identity).apply(System.out::println)).accept(myDaughter);
    //输出
    Xinyu
    Alice
    1
    Female
    

    有人会较真,age怎么可能是String类型,分明应该是Integersex也有可能声明为Boolean。对于任意一个对象,不可能所有的属性类型都是相同的,我们的函数应该只处理她能处理的类型,不要越俎代庖。

    我们再写一个高阶matchingFtypeF来解决这个问题。
    matchingF - 依次接收谓词p、函数f、对象o,如果p断言为真,那么就使用f去处理对象,否则什么都不做
    typeF - 接收一个类型,使用matchingF去约束函数的作用范围(仅当对象类型符合预期)

    /** 谓词匹配修饰符 */
    public static final Function<Predicate, Function<Function, Function>>
        matchingF = p -> f -> o -> p.test(o) ? f.apply(o) : o; /* identity.apply(o) */
    
    /** 类型匹配修饰符 */
    public static final Function<Class, Function<Function, Function>> 
        typeF = clazz -> matchingF.apply(o -> o.getClass().equals(clazz));
    

    我们再测试一下:

    class Person {
      private String name;
      private String englishName;
      private Long age; // Hope to live long
      private Boolean sex;
      // ... oh, my god! a lot of get/set
    }
    //测试
    Person myDaughter = new Person();
    myDaughter.setName("Xinyu");
    myDaughter.setEnglishName("Alice");
    myDaughter.setAge(1L);
    myDaughter.setSex(true);
    traversalObjectF.apply(afterF.apply(typeF.apply(String.class).apply(x -> "Hello, " + x)).apply(System.out::println)).accept(myDaughter);
    //输出
    Hello, Xinyu
    Hello, Alice
    1
    true
    

    以后需要遍历对象时,只需要typeF去约束一下函数就可以了,用matchingF则可以实现更灵活的约束。下文我们不会再使用typeFmatchingF,因为我们只是想实现遍历,具体做什么是没有意义的。

    通用对象的浅遍历

    到目前为止,我们都没有考虑对象为null的情况。因为我们不会一开始就使用上述遍历器去遍历对象。其实不止是null,如果不小心直接传入了一个Integer或者String,我们也会傻眼的。

    我们首先写一个traversalF,让普通的函数f具有识别上述情况的能力。
    traversalF - 依次接收fo
    1. 如果onull,那什么也不做,也就是跳过;
    2. 如果o的类型(注意不是o本身)是final修饰的,那么我们认为已经遇到了f可以处理的对象;
    3. 如果都不是,那么同样需要跳过。

    /** 谓词: 对象是否为null */
    private static final Predicate isNull = Objects::isNull; /* o -> o == null */
    
    /** 谓词: 反射判断对象的类型是否final修饰 */
    private static final Predicate isFinalClass = o -> Modifier.isFinal(o.getClass().getModifiers());
    
    /** 通用对象的遍历修饰符 */
    private static final Function<Function, Function>
        traversalF = f -> o ->
                isNull.test(o)       ?  o : /* identity.apply(o) */
                isFinalClass.test(o) ?  f.apply(o) :
                                        o; /* identity.apply(o) */
    
    //注意:不可以写成 traversalF = f -> o -> isFinalClass.test(o) ?  f.apply(o) : o;
    

    这里使用了三元运算符的? :的级联,可以清晰的表示向下依次进行匹配返回。因为在函数式编程中,只有表达式的概念,没有逻辑结构的概念。如果不是Java个别写法不支持,我们将干掉所有的ifelsebreakcontinueforwhilereturn。一切,为了流畅地阅读代码。

    然后,我们写一个函数getTraversalF,让普通的函数f可以自动选择适合的高阶遍历函数。
    getTraversalF - 她类似简单工厂模式,通过判断对象是否实现了集合的接口,来获取指定的遍历器。如果以后还有需要特殊处理的类型,那只要加入到遍历器字典就可以了。

    /** 单层遍历器字典 */
    private static final Map<Class, Function> traversalFMap = new HashMap<>();
    static {
        traversalFMap.put(List.class, traversalListF);
        traversalFMap.put(Map.class, traversalMapF);
        traversalFMap.put(Set.class, traversalSetF);
    }
    private static final Function defaultTraversalF = traversalObjectF;
    
    /** 获取实现已知接口的遍历修饰符 */
    private static final Function<Object, Function<Function, Consumer>> 
        getTraversalF = o -> {
                Optional<Class> op = traversalFMap.keySet().stream().filter(clazz -> clazz.isAssignableFrom(o.getClass())).findAny();
                return op.isPresent() ? traversalFMap.get(op.get()) : defaultTraversalF;
            };
    

    最后,我们写一个静态方法simpleTraversal,包装最后的通用对象浅遍历方法。并且我们传入一个String类型的对象,来看看simple是否识别。

    /** 浅遍历 */
    public static final Consumer simpleTraversal (Function f) {
        return o -> getTraversalF.apply(o).apply(traversalF.apply(f)).accept(o);
    }
    
    //测试
    simpleTraversal(beforeF.apply(identity).apply(System.out::println)).accept("Hello, world!");
    //输出
    Hello, world!
    

    深度遍历

    终于开始深度遍历了。但是在深度遍历之前,我们需要先思考一下“深度遍历”究竟是什么。如果你的思维已经转变过来,那么你就会很自然地想到:既然各种类型的浅遍历是对函数的一种修饰和增强,那么深度遍历同样也是。

    两步实现深度遍历

    显然我们最终需要实现深度遍历fullTraversal方法应该具有如下的特点:
    fullTraversal - 首先将f增强为深度遍历的函数,然后用getTraversalF增强获取指定遍历器,就可以深度遍历对象了

    // depthTraversalF = ?
    
    /** 全遍历 */
    public static final Consumer fullTraversal (Function f) {
        return o -> getTraversalF.apply(o).apply(depthTraversalF.apply(f)).accept(o);
    }
    

    怎么写这个depthTraversalF呢?我们先简单点,使用写一个nextTraversalf能够进行进行下一层遍历:
    depthTraversalF - 类似traversalF,依次断言isNullisFinalClass,如果都不是,则说明这个对象不能用f处理,需要进行跳过,但是在跳过对象之前,需要对对象进行遍历

    // nextTraversal = ?
    
    private static final Function<Function, Function>
        depthTraversalF = f -> o -> 
                isNull.test(o)       ?  o : /* identity.apply(o) */
                isFinalClass.test(o) ?  f.apply(o) :
                                        beforeF.apply(identity).apply(nextTraversal.apply(f)).apply(o);
    

    即使你忘记做“跳过”动作(beforeF.apply(identity))也没有关系,编译器会提醒你的。

    事实上,nextTraversal做的事情和fullTraversal一样,就像接力似的,继续将深度遍历的能力增强给full。最终的形式如下

    /** 深度遍历修饰(双递归) */
    private static final Function<Function, Function>
        depthTraversalF = f -> o -> 
                isNull.test(o)       ?  o : /* identity.apply(o) */
                isFinalClass.test(o) ?  f.apply(o) :
                                        beforeF.apply(identity).apply(fullTraversal(f)).apply(o);
    
    /** 全遍历 */
    public static final Consumer fullTraversal (Function f) {
        return o -> getTraversalF.apply(o).apply(depthTraversalF.apply(f)).accept(o);
    }
    

    测试一下

    我们写个稍复杂的例子测试一下:

    //测试:我的女儿家人有爸爸妈妈,和两个小狗朋友,爸爸的家人有爷爷和奶奶
    class Person {
        private String name;
        private Long age; // I hope we all live long
        private Map<String, Person> family;
        private List<Person> friends;
        // ... oh, my god! a lot of get/set
    }
    Person grandfather = new Person();
    grandfather.setName("Grandpa");
    Person grandmother = new Person();
    grandmother.setName("Grandma");
    Map<String, Person> myParents = new HashMap<>();
    myParents.put("grandfather", grandfather); myParents.put("grandmother", grandmother);
    Person me = new Person();
    me.setName("Larry"); me.setAge(30L);
    me.setFamily(myParents);
    
    Person lover = new Person();
    lover.setName("Lotus"); lover.setAge(30L);
    Map<String, Person> family = new HashMap<>();
    family.put("father", me); family.put("mother", lover);
    
    Person doggy = new Person();
    doggy.setName("Doggy"); doggy.setAge(2L);
    Person puppy = new Person();
    puppy.setName("Puppy"); puppy.setAge(3L);
    
    Person myDaughter = new Person();
    myDaughter.setName("Alice"); myDaughter.setAge(1L);
    myDaughter.setFamily(family);
    List<Person> friends = new ArrayList<>();
    friends.add(doggy); friends.add(puppy);
    myDaughter.setFriends(friends);
    
    fullTraversal(beforeF.apply(identity).apply(System.out::println)).accept(myDaughter);
    //输出
    Alice
    1
    Lotus
    30
    Larry
    30
    Grandma
    Grandpa
    Doggy
    2
    Puppy
    3
    

    我们数一数,一共设置了12次IntegerString的属性,也一共输出了12个值。深对遍历对象的功能就这么轻松实现了!

    总结一下

    到这里先总结一下,我们究竟做了什么。我们并没有具体实现深度遍历时要对对象做些什么,我们仅仅是描述了一种遍历的结构,这种结构描述了一个普通函数f到一个普通对象o的一种关系

    我们一共写了以下和深度遍历相关的函数identitybeforeFisNullisFinalClassisStaticFieldtraversalListFtraversalMapFtraversalSetFmodifyFtraversalObjectFgetTraversalFdepthTraversalFfullTraversal共13个函数表达式,每个函数都非常的简洁明了,就像平时说话一样。同时,我们并没有约束遍历时具体做什么,任何一个普通的函数都可以升级成深度遍历的函数。或者换句话说:我们通过“说出”这13句话,就让计算机明白了什么是深度遍历

    常规的编程模式是通过向计算机下达指令,step by step,一步一步地完成某个功能。这种编程模式叫做命令式编程。但是事实上,“一步一步下达指令”是计算机擅长的事情,并不是人类擅长的。而我们今天采用的方式,是通过向计算机描述,来让计算机理解某项功能。这种编程模式叫做声明式编程。其实声明式编程也不是很罕见的东西,常用的SQLXML/HTML/CSS都是声明式的(想想我们平时写的SQL语句是不是也只有一行)。

    函数式编程就是声明式编程的一种。简洁、不易出错、更加接近自然语言、高度抽象等等,这些都是函数式编程时的优势。在Java的泛型约束下,笔者书写深度遍历的代码时,几乎是“一遍灵”的,节约了大量的测试和debug时间。但同样值得注意的是,函数式编程提笔尤重,更多的时间是花费在思考和类型设计上。有人曾经开玩笑:命令式程序员把代码写完了,发现函数式程序员还在纸上写写画画;函数式程序员代码写完测试一下OK,发现命令式程序员还在debug。

    带追踪器的深度遍历

    上文的例子将深度遍历做成了黑盒,我们不能直观的看到函数f的遍历过程。下面我们将之前的代码,改写成一个带有路线追踪能力的深度遍历。可以假设,我们将提供一个函数trace专门负责追踪遍历的路线,而f的执行则可能需要参考trace追踪的结果。

    也就说我们的路线追踪函数trace应该是一个二元函数,将对象的属性不断地放入到上次追踪的队列中,返回最新的队列;同时普通的函数f也应该是一个二元函数,她依靠已有的队列来决定如何处理传入的对象。同样的,ftrace究竟是什么并不重要。我们只需要知道f要处理对象,trace要处理队列就可以了。

    //猜想
    BiFunction trace = (property, lastQueue) -> ??; // push property into queue, and return queue
    BiFunction f = (o, latestQueue) -> ??; // handle object according to queue, and return object
    

    同时,我们还要明确什么样的属性才适合放入队列中,显然对象的成员名和Map的键适合做为属性,List和Set的索引则意义不大。

    改写之前的函数

    因此,对于List和Set,带追踪器的遍历函数只要改变入参数目和函数类型签名就可以了,队列不要变化;而Map和Object,我们则需要将键和属性放入到trace中,获取最新的队列,然后传给f

    /** 列表单层遍历器(带追踪器) */
    private static final BiFunction<BiFunction, BiFunction, BiConsumer<List, Object>>
        traversalListBF = (f, trace) -> (l, queue) -> IntStream.range(0, l.size()).forEach(i -> l.set(i, f.apply(l.get(i), queue)));
    
    /** 集合单层遍历器(带追踪器) */
    private static final BiFunction<BiFunction, BiFunction, BiConsumer<Set, Object>>
        traversalSetBF = (f, trace) -> (s, queue) -> {
            List l = new ArrayList(s);
            traversalListBF.apply(f, trace).accept(l, queue);
            s.clear();
            s.addAll(l);
        };
    
    /** 映射单层遍历器(带追踪器) */
    private static final BiFunction<BiFunction, BiFunction, BiConsumer<Map, Object>>
        traversalMapBF = (f, trace) -> (m, queue) -> m.forEach((k,v) -> m.put(k, f.apply(v, trace.apply(k, queue))));
    
    /** 反射修改对象属性(追踪属性) */
    private static final BiFunction<BiFunction, BiFunction, BiFunction<Object, Object, Consumer<Field>>>
        modifyBF = (f, trace) -> (o, queue) -> field -> {
            try {
                field.setAccessible(true);
                field.set(o, f.apply(field.get(o), trace.apply(field, queue)));
            } catch (IllegalArgumentException | IllegalAccessException e){ /* biPass.accept(o, queue) */ } 
        };
    
    /** 对象单层遍历器(带追踪器) */
    public static final BiFunction<BiFunction, BiFunction, BiConsumer>
        traversalObjectBF = (f, trace) -> (o, queue) -> Stream.of(o.getClass().getDeclaredFields()).forEach(
        field -> {
            if (isStaticField.test(field))  ; /* pass.accept(field) */
            else                            modifyBF.apply(f, trace).apply(o, queue).accept(field);
        });
    
    /** 单层带追踪器遍历器字典 */
    private static final Map<Class, BiFunction> traversalBFMap = new HashMap<>();
    private static final BiFunction defaultTraversalBF = traversalObjectBF;
    static {
        traversalBFMap.put(List.class, traversalListBF);
        traversalBFMap.put(Map.class, traversalMapBF);
        traversalBFMap.put(Set.class, traversalSetBF);
        //traversalBFMap.put(Object.class, traversalObjectBF);
    }
    private static final Function<Object, BiFunction<BiFunction, BiFunction, BiConsumer>>
        getTraversalBF = o -> {
            Optional<Class> op = traversalBFMap.keySet().stream().filter(clazz -> clazz.isAssignableFrom(o.getClass())).findAny();
            return op.isPresent() ? traversalBFMap.get(op.get()) : defaultTraversalBF;
        };
    
    private static final BiFunction biIdentity = (o1, o2) -> o1;
    
    public static final Function<BiFunction, Function<BiConsumer, BiFunction>>
            beforeBF = f -> c -> (o, queue) -> { c.accept(o, queue); return f.apply(o, queue); };
    
    private static final BiFunction<BiFunction, BiFunction, BiFunction>
        depthTraversalBF = (f, trace) -> (o, queue) ->
                isNull.test(o)       ?  o : /* biIdentity.apply(o, queue) */
                isFinalClass.test(o) ?  f.apply(o, queue) :
                                        beforeBF.apply(biIdentity).apply(traceTraversal(f, trace)).apply(o, queue);
    
    /** 带追踪器的遍历 */
    public static final BiConsumer traceTraversal (BiFunction f, BiFunction trace) {
        return (o, queue) -> getTraversalBF.apply(o).apply(depthTraversalBF.apply(f, trace), trace).accept(o, queue);
    }
    

    测试一下

    现在可以测试了。事实上,在测试的时候,我们没有必要真的使用队列去完成路线追踪,只要具有和队列类似的追加功能就可以了。我们先简单点,就将对象的各个层级属性,设计成类似文件系统的/x/y/z的形式。因此我们可以这样测试:

    //测试
    BiFunction pathTrace = (unknown, lastPath) -> unknown.getClass().equals(Field.class) ? lastPath + "/" + ((Field)unknown).getName() : lastPath + "/" + unknown.toString();
    BiConsumer printPath = (o, path) -> System.out.println(path + " = " + o);
    traceTraversal(beforeBF.apply(biIdentity).apply(printPath), pathTrace).accept(myDaughter, ""); // 传入String的幺元做为初始路径
    //输出
    /name = Alice
    /age = 1
    /family/mother/name = Lotus
    /family/mother/age = 30
    /family/father/name = Larry
    /family/father/age = 30
    /family/father/family/grandmother/name = Grandma
    /family/father/family/grandfather/name = Grandpa
    /friends/name = Doggy
    /friends/age = 2
    /friends/name = Puppy
    /friends/age = 3
    

    部分遍历

    假设现在我们要写一个部分遍历的方法partial。直接在f里判断当前遍历的对象是否需要处理是不能实现部分遍历的,因为f被增强后一定是个全遍历函数。即我们需要一个二元谓词p,当p去断言当前的oqueue为假时,就不再遍历。改动非常小,只要在深度遍历增强函数中添加一行! p.test(o, queue) ? o :就可以了。但是现在的首要问题是,我们的函数接收参数的形式变成了(f, p, trace) -> (o, queue) -> ...,这是三元高阶函数,但是Java并没有TriFunction这样的类型。这个没关系,我们写个接口告诉Java就行了。

    @FunctionalInterface
    public interface TriFunction<U, T, S, R> {
        R apply(U u, T t, S s);
    }
    

    @FunctionalInterface会告诉Java,这是一个函数接口,请向方法那样去调用她。在编辑器提示下,改造起来是非常快的,这里不再详细解释,有兴趣的朋友可以测试一下。

    private static final TriFunction<BiFunction, BiPredicate, BiFunction, BiFunction>
        depthTraversalTF = (f, p, trace) -> (o, queue) ->
                isNull.test(o)       ?  o : /* biIdentity.apply(o, queue) */
                ! p.test(o, queue)   ?  o : /* biIdentity.apply(o, queue) */
                isFinalClass.test(o) ?  f.apply(o, queue) :
                                        beforeBF.apply(biIdentity).apply(partial(f, p, trace)).apply(o, queue);
    public static final BiConsumer partial (BiFunction f, BiPredicate p, BiFunction trace) {
        return (o, queue) -> getTraversalBF.apply(o).apply(depthTraversalTF.apply(f, p, trace), trace).accept(o, queue);
    }
    

    有些朋友乍一看会有很大的疑惑:为什么浅层遍历没有改造,只传入了f, trace,没有p,那么继续遍历时p的断言还会有效吗?我们可以看一下partial:我们可以看到,浅层遍历的高阶函数传入的f其实是depthTraversalTF.apply(f, p, trace),里面已经包含了p。表面上看是我们将函数做为参数传递给了高阶函数,但事实上,对计算机而言,这是做不到的事情,实际情况是高阶函数将最后接收到的对象(o, queue)回传给了传入的(f, p, trace)。也就是说,高阶函数仅仅是组织了参数之间的关系,她没有办法单独起作用。就像一开始说的那样,如果高阶函数没有接收到完整的参数,那么计算机什么也不会做,因为什么也做不了。

    无状态的讨论

    再写部分遍历的时候,笔者刻意地将trace放在三个入参的最后,因为trace并不是个普通的函数。

    那么我们现在讨论一个遗留的问题:我们写的trace究竟是什么?她该怎样去写?假设我们使用下面的代码去完成上述功能,能得到正确的结果吗?

    //测试
    BiFunction<Object, List<String>, List<String>> queueTrace = (unknown, lastQueue) -> {
            if(unknown.getClass().equals(Field.class))  lastQueue.add(((Field)unknown).getName());
            else                                        lastQueue.add(unknown.toString()); 
            return lastQueue;
    };
    BiConsumer<Object, List<String>> printQueue = (o, queue) -> System.out.println(queue.stream().reduce("", (x1, x2) -> x1 + "/" + x2) + " = " + o);
    traceTraversal(beforeBF.apply(biIdentity).apply(printQueue), queueTrace).accept(myDaughter, new ArrayList<String>()); // 传入List的幺元做为初始队列
    

    trace怎么写

    执行上面的代码时,会发现我们打印的队列会越来越长,每次遍历都会将追踪到的路径加入到同一个队列中。这并不算太糟,至少我们的运行结果是确定的。但是如果traceTraversal内部是多线程执行的,那么f接收到的queue将不会再有意义。

    之所以会产生这样的问题,是因为传入的初始幺元new ArrayList<String>()相对于traceTraversal是一个全局对象,而在命令式编程语言中确实存在全局变量被污染的情况。因此,为了保证高并发,我们总是要将控制加入到我们的代码中。

    事实上,在函数式编程中,并不需要这么做。我们之前描述过:”我们的路线追踪函数trace应该是一个二元函数,将对象的属性不断地放入到上次追踪的队列中,返回最新的队列“。那我们每次都返回一个最新的队列就可以了。

    // 正确的写法
    BiFunction<Object, List<String>, List<String>> queueTrace = (unknown, lastQueue) -> {
            List<String> lastQueue_ = new ArrayList<>();
            lastQueue_.addAll(lastQueue);
            if(unknown.getClass().equals(Field.class))  lastQueue_.add(((Field)unknown).getName());
            else                                        lastQueue_.add(unknown.toString()); 
            return lastQueue_;
        };
    

    在函数式编程语言(例如Lisp、Haskell、Clojure、Scala等等)中,并不存在全局变量的概念,只有局部变量(自由变量和约束变量),并且一个函数总是会返回一个新的对象。因此,在传统编程语言去书写函数式代码时,我们需要额外注意这一点。

    有人会质疑创建新对象带来的性能损耗。事实上,这种损耗是值得的(并且Java new的效率也是非常高的),因为这种方式消灭了依赖状态而运行的代码,让我们真正实现了无锁高并发/并行。加入锁控制只会让我们的代码更加复杂、更加不可控。

    没错,函数式编程语言不仅没有那么多的关键字(ifelsefor…),没有全局变量,甚至连锁都可以没有。虽然这种说法过于绝对,但在函数式编程中确实可以让你体验”自由“的感觉。

    trace是什么

    如果抽象来看,trace实际上是traceTraversal运行时的状态迁移函数。trace总是接收当前的状态和之前的状态,返回下一个新的状态。

    事实上,我们所有代码都可以抽象成如下的形式:

    Machine(i=1n(fi),state(cs,ls)).load(data,is),(is=,cs=,ls=) Machine(\sum_{i=1}^{n}(f_i), state(cs, ls)).load(data, is), (is = 初始状态, cs = 当前状态, ls = 上一个状态)

    正是这个状态函数state是一个无状态的函数,她只依赖传入参数,使得我们的程序是无状态的。

    函数式编程的优势

    我们这里总结一下函数式编程的优势:

    1. 自由、简洁、更加接近自然语言
    2. 高度抽象,天生反模式、反算法
    3. 惰性求值,减少不必要的计算
    4. 天生无状态,天生支持高并发/并行

    骚年,扔掉GoF,拿起眼前这把剑吧,让我们为了自由去战斗!

    (什么什么?你问我什么是单子?好吧,单子不过是自函子范畴上的一个幺半群罢了…T_T)

    展开全文
  • 集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合的对象就是指集合中对象的引用(reference); 集合类型主要有3种:set(集)、list(列表)和map(映射); 集合接口分为:Collection和Map,...
  • 【导入】 现在的程序中,怎么存放多个数据?...集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合的对象就是指集合中对象的引用(reference); 集合类型主要有3种:set(集)、list(...

    【导入】

    1. 现在的程序中,怎么存放多个数据? ------- 在学习集合之前就知道 数组
      数组缺点:只能存放相同类型的数据。长度不可变。遍历方式单一。
    2. 由于数组存在缺点,所以产生了多种集合。
    3. 集合类存放于java.util包中;
    4. 集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference);
    5. 集合类型主要有3种:set(集)、list(列表)和map(映射);
    6. 集合接口分为:Collection和Map,list、set实现了Collection接口

    【目录】

    一、Collection接口

    (一)概述

    英文名称Collection,是用来存放对象的数据结构。其中长度可变,而且集合中可以存放不同类型的对象。并提供了一组操作成批对象的方法。 数组的缺点:长度是固定不可变的,访问方式单一,插入、删除等操作繁琐。

    (二)collection 继承结构

    在这里插入图片描述

    • Collection接口
      • List接口:数据有序,可以重复。
        • ArrayList子类
        • LinkedList子类
      • Set接口:数据无序,不可以存重复值
        • HashSet子类
      • Map接口:键值对存数据
        • HashMap
    • Collections工具类

    (三)常用方法

    • boolean add(E e):添加元素。
    • boolean addAll(Collection c):把小集合添加到大集合中 。
    • boolean contains(Object o) : 如果此 collection 包含指定的元素,则返回 true。
    • boolean isEmpty() :如果此 collection 没有元素,则返回 true。
    • Iterator<E> iterator():返回在此 collection 的元素上进行迭代的迭代器。
    • boolean remove(Object o) :从此 collection 中移除指定元素的单个实例。
    • int size() :返回此 collection 中的元素数。
    • Objec[] toArray():返回对象数组
    • 更多常用方法参考----API(1.6或者1.8版本的)

    (四)测试案例

    package cn.tedu.collection;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Iterator;
    
    //这个类用来测试Collection接口
    public class Test_Collection {
    	public static void main(String[] args) {
    		//1、创建对象
    		Collection<String> col = new ArrayList<>();
    		//2、常用方法
    		col.add("jack");//添加元素
    		col.add("韩美美");
    		col.add("rose1");
    		col.add("rose2");
    //		col.add(100);
    //		col.clear();//清空集合
    		
    		System.out.println(col.contains("jack"));//判断集合中是否包含指定元素
    		System.out.println(col.equals("韩美美")+"----");//判断集合是否和指定元素相等
    		System.out.println(col.hashCode());//返回集合对象在内存中的哈希码值
    		System.out.println(col.isEmpty());//判断是否为空,空返回true,不为空返回false
    		System.out.println(col.remove("rose1"));//移出某个元素,返回是否删除成功
    		System.out.println(col.size());//获取集合的长度
    		System.out.println(Arrays.toString(col.toArray()));//把集合转成Object[]数组
    		
    		//集合间的操作
    		Collection<String> col2 = new ArrayList<>();
    		col2.add("1");
    		col2.add("2");
    		System.out.println(col.addAll(col2));//向col集合中添加col2集合的元素
    		System.out.println(col.containsAll(col2));//判断col中是否包含col2的元素
    //		System.out.println(col.removeAll(col2));//删除col集合中的col2的元素
    //		System.out.println(col.retainAll(col2));//删除col集合的元素
    		System.out.println(col);
    		
    		//迭代器:迭代/遍历集合中的元素
    		Iterator it = col.iterator();
    		while (it.hasNext()) {//hasNext()判断集合中有下一个元素吗
    			System.out.println(it.next());//next()获取遍历到的元素
    		}
    	}
    }
    
    

    二、List接口

    (一)概述

    1. 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地 控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素;
    2. 与 set 不同,列表通常允许重复的元素;

    (二)特点

    1. 数据有序
    2. 允许存放重复元素
    3. 元素都有索引

    (三)常用方法

    • 省略从Collection接口继承过来的方法
    • List接口特有方法
      • void add(int index, E element) :在列表的指定位置插入指定元素(可选操作)。
      • boolean addAll(int index, Collection<? extends E> c)
      • E get(int index) :返回列表中指定位置的元素。
      • int indexOf(Object o):返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
      • int lastIndexOf(Object o) :返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
      • ListIterator<E> listIterator() :返回此列表元素的列表迭代器(按适当顺序)。
      • E remove(int index) :移除列表中指定位置的元素(可选操作)。
      • E set(int index, E element) :用指定元素替换列表中指定位置的元素(可选操作)。
      • List<E> subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。

    (四)测试案例

    package cn.tedu.collection;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ListIterator;
    
    public class Test_List {
    	public static void main(String[] args) {
    		//1、创建对象
    		List<String> list = new ArrayList();
    		//2、常用方法
    		//从Collection接口继承过来的方法
    		list.add("xiongda");
    		list.add("xionger");
    		list.add("guangtouq");
    		list.add("xiongda");
    		list.add("guangtouq");		
    		
    		//list接口特点:元素都有序, + 允许存放重复元素
    		System.out.println(list);
    		//3、List接口的特有方法 -- 都是可以按照索引操作的方法
    		list.add(3,"钢铁侠");//在指定下标处添加元素
    		System.out.println(list);
    		System.out.println(list.get(2));//获取指定下标对于的元素
    		System.out.println(list.indexOf("xionger"));//获取指定元素第一次出现的下标值
    		System.out.println(list.lastIndexOf("guangtouq"));//获取指定元素最后一次出现的下标值
    		System.out.println(list.remove(1));//删除指定下标的元素,并返回
    		System.out.println(list.set(0, "hanmeimei"));//把指定下标的元素替换成指定元素
    		
    		List<String> newList = list.subList(1, 3);//截取list,含头不含尾[a,b)
    		System.out.println(newList);
    		
    		List<String> list2 = new ArrayList();
    		list2.add("99");
    		list2.add("98");
    		list2.add("97");
    		System.out.println(list.addAll(2,list2));//在指定下标处添加集合
    		System.out.println(list);
    		
    		//4、迭代器
    		/*
    		 * 1、普通for循环
    		 * 2、foreach
    		 * 3、继承Collection接口iterator()
    		 * 4、List接口listiterator()
    		 */
    		//(1)普通for循环
    		System.out.println("========one========");
    		for (int i = 0; i < list.size(); i++) {
    			System.out.println(list.get(i));
    		}
    		System.out.println("========two========");
    		//(2)foreach
    		for (String string : list) {
    			System.out.println(string);
    		}
    		System.out.println("========three========");
    		//(3)继承Collection接口iterator()
    		Iterator<String> it = list.iterator();
    		while (it.hasNext()) {
    			System.out.println(it.next());
    		}
    		System.out.println("========four========");
    		//(4)List接口listiterator()
    		ListIterator<String> it2 = list.listIterator();
    		while (it2.hasNext()) {//判断后面有没有元素
    //			System.out.println(it2.next());//获取元素
    			String str = it2.next();
    			System.out.println(str);
    		}
    		
    		/*
    		 * Collection接口iterator() --- 返回值Iterator(父接口)
    		 * List接口    listIterator() --- 返回值ListIterator(子接口)
    		 *  	--- 继承了父接口所以功能的同时,又有功能扩展
    		 */
    	}
    }
    
    

    三、ArrayList子类

    (一)概述

    1. 存在于java.util包中;
    2. 内部用数组存放数据,封装了数组的操作,每个对象都有下标;
    3. 内部数组默认初始容量是10。如果不够会以1.5倍容量增长;
    4. 查询快,增删数据效率会降低。

    (二)创建对象

    new ArrayList();//初始容量是10
    

    (三)测试案例

    package cn.tedu.collection;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    //这个类用来测试ArrayList
    public class Test_ArrayList {
    	public static void main(String[] args) {
    		//1、创建对象
    		/*
    		 * ArrayList特点:
    		 * 	1、底层是个数组,所以适合查询,不适合增删
    		 * 	2、底层会为数组自动扩容,1.5倍方式扩容:
    		 */
    		List<String> list = new ArrayList<>();
    		
    		//2、常见方法
    		list.add("aaa");//存入数据
    		list.add("123");
    		list.add("ccc");
    		list.add("ddd");
    		System.out.println(list);//list中内容
    		System.out.println(list.size());//集合长度
    		System.out.println(list.get(1));//根据下标获取元素
    		        
    		System.out.println();
    		System.out.println(list.remove(2));//移除下标对应的元素
    		System.out.println(list);
    		
    		//下标遍历
            for (int i = 0; i < list.size(); i++) {
                   System.out.print(list.get(i));
            }
            //Iterator迭代遍历,封装了下标
            Iterator<String> it = list.iterator();
            while (it.hasNext()) {//如果有数据
            	String s = it.next();//一个一个向后遍历
            	System.out.println(s);
        	}      
    	}
    }
    
    

    四、LinkedList子类

    (一)概述

    • 双向链表,两端效率高。底层就是数组和链表实现的。
      在这里插入图片描述

    (二)特点

    1. 底层维护了一个链表结构;
    2. 适用于增删多而查询少的场景;
    3. 对于链表中的节点操作效率并不高,但是对于首尾两个节点操作效率高。

    (三)创建对象

    LinkedList<E> list = new LinkedList<>();
    

    (四)常用方法

    • void addFirst(E e):将指定元素插入此列表的开头。
    • void addLast(E e): 将指定元素添加到此列表的结尾。
    • E getFirst(): 返回此列表的第一个元素。
    • E getLast():返回此列表的最后一个元素
    • E removeFirst():移除并返回此列表的第一个元素。
    • E removeLast():移除并返回此列表的最后一个元素。
    • boolean offer(E e):将指定元素添加到此列表的末尾(最后一个元素)。
    • boolean offerFirst(E e):在此列表的开头插入指定的元素。
    • boolean offerLast(E e):在此列表末尾插入指定的元素。
    • E peek():获取但不移除此列表的头(第一个元素)。
    • E peekFirst():获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
    • E peekLast():获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
    • E poll():获取并移除此列表的头(第一个元素)
    • E pollFirst():获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
    • E pollLast():获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。

    (五)测试案例

    package cn.tedu.collection;
    
    import java.util.Iterator;
    import java.util.LinkedList;
    
    public class Test_LinkedList {
    	public static void main(String[] args) {
    		//1、创建对象
    		//特点:和list一样,元素有序,可以重复
    		LinkedList<Integer> list = new LinkedList<>();
    		//2、常用方法
    		list.add(100);
    		list.add(200);
    		list.add(300);
    		list.add(400);
    		list.add(500);
    		System.out.println(list);
    		
    		list.addFirst(0);//添加首元素
    		list.addLast(99);//添加尾元素
    		System.out.println(list);
    		
    		System.out.println(list.getFirst());//获取首元素
    		System.out.println(list.getLast());//获取尾元素
    		System.out.println(list+"=====");
    		
    		System.out.println(list.removeFirst());//移出首元素
    		System.out.println(list.removeLast());//移出尾元素
    		System.out.println(list);
    		
    		//3、查询效率for  iterator
    		
    		for (int i = 0; i < list.size(); i++) {
    			System.out.println(list.get(i));
    		}
    		System.out.println("============");
    		//迭代查询
    		Iterator<Integer> it = list.iterator();
    		while (it.hasNext()) {
    //			System.out.println(it.next());
    			Integer in=it.next();
    			System.out.println(in);
    		}
    	}
    }
    
    

    五、Set接口

    (一)概述

    1. 一个不包含重复元素的 collection;
    2. 数据无序(因为set集合没有下标);
    3. 由于集合中的元素不可以重复,常用于给数据去重

    (二)特点

    1. HashSet:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复;
    2. TreeSet:底层就是TreeMap,也是红黑树的形式,便于查找数据;
    3. HashMap实现中,当哈希值相同的对象,会在同一个hash值的位置存储不同属性的数据。

    (三)常用方法

    • boolean add(E e):添加元素。
    • boolean addAll(Collection c):把小集合添加到大集合中 。
    • boolean contains(Object o) : 如果此 collection 包含指定的元素,则返回 true。
    • boolean isEmpty() :如果此 collection 没有元素,则返回 true。
    • Iterator<E> iterator():返回在此 collection 的元素上进行迭代的迭代器。
    • boolean remove(Object o) :从此 collection 中移除指定元素的单个实例。
    • int size() :返回此 collection 中的元素数。
    • Objec[] toArray():返回对象数组
    • 更多常用方法参考----API(1.6或者1.8版本的)

    (四)测试案例

    package cn.tedu.collection;
    
    import java.util.HashSet;
    import java.util.Iterator;
    
    //这个类用来测试Set集合去重
    /*
     * 总结:
     * 	如果想要使用set集合,给你的自定义对象去重的话,需要同时重写hashCode()和equals()
     * 	重写hashCode():原来的实现方式是:只要new就会给计算一个新的哈希码值
     * 			现在需要按照对象的属性值计算哈希码值,就算new两次,属性值一样时也必须产生相同哈希码值
     * 	重写equals():原来比的是两个对象间的地址值,现在要去比较两个对象间的属性值,如果属性值都一样,必须
     */
    public class Test_Set {
    	public static void main(String[] args) {
    		HashSet<Student> set = new HashSet<>();
    		//创建元素
    		Student s1 = new Student("西门庆",20);
    		Student s2 = new Student("武大郎",19);
    		Student s3 = new Student("潘金莲",21);
    		Student s4 = new Student("小龙女",23);
    		//添加时,新元素会和老元素比
    		set.add(s1);
    		set.add(s2);
    		set.add(s3);
    		set.add(s4);
    
    		//往set中添加重复元素
    		Student s5 = new Student("武大郎",19);
    		Student s6 = new Student("潘金莲",21);
    		//默认:添加时查找对象的hash值,没有查到就存起来
    		//所以必须让hash值一致才可以
    		set.add(s5);
           	set.add(s6);
           	//问题1:属性相同时还是认为是两个对象...
           	
           	System.out.println(set);
    //       	System.out.println(s2.hashCode());
    //       	System.out.println(s5.hashCode());
           	
           	//遍历
           	Iterator it = set.iterator();
           	while(it.hasNext()) {
           		System.out.println(it.next());
           	}
        }
    }
    	 
    //创建Student类/	 
    class Student {
    	private String name;
        private int age;
        
        public Student(String name, int age) {
        	this.name = name;
        	this.age = age;
        }
        public Student() {
        }
        public String getName() {
        	return name;
        }
        public void setName(String name) {
        	this.name = name;
        }
        public int getAge() {
        	return age;
        }
        public void setAge(int age) {
        	this.age = age;
        }
        
        @Override
        public String toString() {
        	return "Student [name=" + name + ", age=" + age + "]";
        }
        
        //需求:属性值都一样就看做是同一个对象
        @Override
        public boolean equals(Object obj) {
        	if (this == obj)
        		return true;
        	if (obj == null)
        		return false;
        	if (getClass() != obj.getClass())
        		return false;
        	Student other = (Student) obj;
        	if (age != other.age)
        		return false;
        	if (name == null) {
        		if (other.name != null)
        			return false;
        	} else if (!name.equals(other.name))
        		return false;
        	return true;
        }
        
    	//HashSet默认:添加时查找对象的hash值,没有查到就存起来
    	//所以必须让hash值一致才可以
        //必须用算法,不然的话,hash值相同时会挂一串
    	public int hashCode() {
    		//return 0;//效率低
    		//让基本类型*31,引用类型就用自己的hash值
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + age;
    		result = prime * result + ((name == null) ? 0 : name.hashCode());
    		return result;
        }
    }
    
    

    六、HashSet子类

    (一)概述

    • 此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。
    • 它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
    • 此类允许使用 null 元素。

    (二)创建对象

    Set<E> set = new HashSet<>();
    

    (三)常用方法

    与collection基本一样,具体的还需要参考api手册

    • boolean add(E e):添加元素。
    • boolean addAll(Collection c):把小集合添加到大集合中 。
    • boolean contains(Object o) : 如果此 collection 包含指定的元素,则返回 true。
    • boolean isEmpty() :如果此 collection 没有元素,则返回 true。
    • Iterator<E> iterator():返回在此 collection 的元素上进行迭代的迭代器。
    • boolean remove(Object o) :从此 collection 中移除指定元素的单个实例。
    • int size() :返回此 collection 中的元素数。
    • Objec[] toArray():返回对象数组
    • 更多常用方法参考----API(1.6或者1.8版本的)

    (四)测试案例

    package cn.tedu.collection;
    
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    public class Test_HashSet {
    	public static void main(String[] args) {
    		//1、创建对象
    		Set<Integer> set = new HashSet<>();
    		
    		//2、常用方法
    		set.add(10);
    		set.add(2);
    		set.add(3);
    		set.add(4);
    		set.add(5);
    
    		set.add(4);
    		set.add(5);
    		
    		//set集合的特点:元素无序,元素不能重复
    		System.out.println(set);
    		
    		//TODO 测试常用方法,与collection一样
    		
    		//迭代遍历
    		Iterator it = set.iterator();
    	    while(it.hasNext()) {
    	    	System.out.println(it.next());
    	    }
    	}
    }
    
    

    七、Map接口

    (一)概述

    • java.util接口 Map<K,V>
    • 类型参数: K - 此映射所维护的键的类型,V - 映射值的类型。
    • 也叫哈希表、散列表。常用于存 键值对 结构的数据。其中的键不能重复,值可以重复

    (二)特点

    • 可以根据键 提取对应的值;
    • 键不允许重复,如果重复值会被覆盖;
    • 存放的都是无序数据;
    • 初始容量是16,默认的加载因子是0.75

    (三)继承结构

    在这里插入图片描述

    (四)常用方法

    • V put(K key, V value):将指定的值与此映射中的指定键关联(可选操作)。
    • V remove(Object key):如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
    • V get(Object key):返回指定键所映射的值
    • void clear():从此映射中移除所有映射关系
    • boolean containsKey(Object key):如果此映射包含指定键的映射关系,则返回 true。
    • boolean containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true。
    • boolean equals(Object o):比较指定的对象与此映射是否相等。
    • int hashCode():返回此映射的哈希码值。
    • boolean isEmpty():如果此映射未包含键-值映射关系,则返回 true。
    • int size():返回此映射中的键-值映射关系数。
    • void putAll(Map<? extends K,? extends V> m)
    • Collection<V> values():返回此映射中包含的值的 Collection 视图。
    • Set<K> keySet():返回此映射中包含的键的 Set 视图。
    • Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的 Set 视图

    (五)测试案例

    package cn.tedu.Map;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    //这个类用来测试Map接口
    public class Test_Map {
    	public static void main(String[] args) {
    		//1、创建map对象
    		Map<Integer, String> map = new HashMap<>();
    		
    		//2、常用方法
    		//向map集合中添加数据时,需要同时指定key和value的值
    		map.put(9529, "秋香");
    		map.put(9527, "唐伯虎");
    		map.put(9528, "如花");
    		map.put(9528, "石榴姐");
    		/*
    		 * 特点:
    		 * 	1、数据无序 
    		 * 	2、当key相同时,value值被覆盖 
    		 */
    		System.out.println(map);
    		
    //		map.clear();
    		System.out.println(map.containsKey(9527));//是否包含指定的key
    		System.out.println(map.containsValue("如花"));//是否包含指定的value
    		System.out.println(map.equals("石榴姐"));//判断两者是否相等
    		System.out.println(map.get(9528));//根据指定的key,找到对应的value
    		System.out.println(map.hashCode());//计算map在内存中的哈希码值
    		System.out.println(map.isEmpty());//判断集合是否为空
    		System.out.println(map.size());//返回集合的长度
    		System.out.println(map.remove(9527));//根据key删除元素,并把value返回
    		
    		Collection<String> col = map.values();//把map中的所有值获取到并存入collection中
    		System.out.println(col);//打印
    		
    		//迭代map集合
    //		Set<E> keySet()
    //		Set<Map.Entry<K, V>> entrySet()
    		//1、把所有的key放入set中,遍历set
    		Set<Integer> set = map.keySet();//把所有的key放入set中
    		
    		//foreach可以用在数组遍历时,或者List遍历时,或者Set遍历时
    		for (Integer integer : set) {
    			String value = map.get(integer);
    			System.out.println("foreach遍历:key-"+integer+",value-"+value);
    		}
    
    		Iterator<Integer> it = set.iterator();
    		while (it.hasNext()) {
    			Integer key = (Integer) it.next();//获取set中的元素,也就是每个key
    			String value = map.get(key);//拿着每个key去map里面找对应的value
    			System.out.println(key +"===="+ value);
    		}
    		
    		//2、把所有的key和value封装成Entry对象,并放入set中,遍历set
    		Set<Entry<Integer, String>> set2 = map.entrySet();
    		Iterator<Entry<Integer, String>> it2 = set2.iterator();
    		while (it2.hasNext()) {
    			Map.Entry<Integer, String> entry = (Map.Entry<Integer, String>) it2
    					.next();//遍历set得到每个entry对象
    			Integer key = entry.getKey();//获取每个key
    			String value = entry.getValue();//获取每个value
    			System.out.println(key+"----"+value);
    		}
    	}
    }
    
    

    八、HashMap

    • HashMap的键要同时重写hashCode()和equals()
      • hashCode()用来判断确定hash值是否相同
      • equals()用来判断属性的值是否相同
      • equals()判断数据如果相等,hashCode()必须相同
      • equals()判断数据如果不等,hashCode()尽量不同

    (一)概述

    • 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键;
    • HashMap底层是一个Entry数组,当存放数据时会根据hash算法计算数据的存放位置;
    • 算法:hash(key)%n,n就是数组的长度;
    • 当计算的位置没有数据时,就直接存放,当计算的位置有数据时也就是发生hash冲突的时候/hash碰撞时,采用链表的方式来解决的,在对应的数组位置存放链表的头结点;
    • 对链表而言,新加入的节点会从头结点加入;

    在这里插入图片描述

    (二)原理

    • 当需要向map中存储元素时,底层会根据hash算法计算对象的存储位置;
    • 如果这个位置上没有存过元素,就直接存放,就相当于直接作为数组的元素存在;
    • 如果这个位置上存过数据,就会形成链表的结构–hash碰撞/冲突;
    • 我们尽量要让每个对象拥有自己的哈希值,尽量放在数组结构上。避免链表的形成,因为链 表查询效率低。

    (三)测试案例

    package cn.tedu.Map;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class Test_HashMap {
    	public static void main(String[] args) {
    		//1、创建对象
    		Map<String, String> map = new HashMap<>();
    		
    		//2、存数据
    		map.put("1", "jack");
    		map.put("2", "rose");
    		map.put("3", "tom");
    		map.put("4", "jerry");
    		
    		//3、迭代map集合 -- keySet()  entrySet()
    		Set<String> set = map.keySet();//把key放在set里
    		for (String key : set) {//遍历set得到每个key
    			String value = map.get(key);
    			System.out.println(key + "-----" + value);
    		}
    		
    		//把map里的每条记录封装成Entry对象存入set
    		Set<Entry<String,String>> set2 = map.entrySet();
    		for (Entry<String, String> entry : set2) {//遍历set得到每个Entry对象
    			System.out.println(entry.getKey()+ "====="+ entry.getValue());
    		}
    	}
    }
    
    
    • 迭代查询原理图
      在这里插入图片描述

    (四)统计字符串中字符出现的次数

    package cn.tedu.Map;
    
    import java.util.HashMap;
    import java.util.Scanner;
    
    public class Test2_Map {
    	public static void main(String[] args) {
    		//1、接收用户输入的字符串
    		String input = new Scanner(System.in).nextLine();
    		
    		//定义map存数据 -- 类似于a=1,b=2,c=5
    		HashMap<Character, Integer> map = new HashMap<>();
    		
    		//2、遍历字符串,获取每个字符
    		/*
    		 * 字符串长度用:length()
    		 * 数组长度用:size()
    		 */
    		for (int i = 0; i < input.length(); i++) {
    			char key = input.charAt(i);//根据下标获取对应的字符
    			
    			//3、统计字符出现过几次 -- map里的value是多少
    			Integer value = map.get(key);
    			if (value == null) {//如果之前没有存过字符,value存的就是默认值null
    				map.put(key, 1);//把value中的默认值null改成1,说明第一次统计这个字符的次数
    			}else {//如果value不是默认值,也就是之前统计过了
    				map.put(key, value+1);	//把value的值改成原来的数字+1,就ok了
    			}
    		}
    		System.out.println(map);
    	}
    }
    
    

    九、Collections工具类

    (一)常用方法

    1. Collections.sort(List<> list):根据元素的自然顺序 对指定列表按升序进行排序;
    2. Collections.max():根据元素的自然顺序,返回给定 collection 的最大元素;
    3. Collections.min():根据元素的自然顺序 返回给定 collection 的最小元素;
    4. Collections.swap(List,i,j):在指定列表的指定位置处交换元素;
    5. Collections.addAll():

    (二)测试案例

    package seday12;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    public class Test4_Collections {
           public static void main(String[] args) {
                  List<String> list = new ArrayList();
                  
                  //添加多个元素
                  Collections.addAll(list, 
                                "3","30","23","15","29","12","26");
                  
                  //元素排序
                  Collections.sort(list);
              //默认是字符顺序:[12,15,23,26,29, 3, 30]
                  System.out.println(list);
                  
                  //自己定义比较方式
                  Collections.sort(list, new Comparator<String>() {
                         //自定义比较器,sort()自动调用
                         @Override
                         public int compare(String o1, String o2) {
                                //把字符串转成int比大小
                                int a = Integer.parseInt(o1);
                                int b = Integer.parseInt(o2);
    //o1大是正数,o1小是负数,相等是0
                                return a-b;
                         }
                  });
                  System.out.println(list);
           }
    }
    

    【下篇预告】

    • 进程
    • 线程

    【友情提示】

    • 集合可以说是基础里面用到的最多的,也是最常用的,其重要性不言而喻;
    • 相比较下一篇文章《线程》而言,集合的重要性更高,但是作为新手入职的话,线程用到的不会很多,所以有时间慢慢去理解,但是要想尽快入职企业,还是要掌握好!!!
    • 又到了兔八哥说再见的时候了,┏(^0^)┛!!!点赞加关注,学习不迷路;
    • 下篇文章见

    在这里插入图片描述

    展开全文
  • // 遍历List然后根据这个id来去查找林外一张表 String sql = "select * from nreport where nid in("; for(int i=0;i<list.size();i++){ // sql = "where id in(1,2,3)"; // 如果当前获取这个id不是...
  • 先简单说说容器吧,容器也叫集合,它的出现是因为我们需要对大量引用的对象进行管理。大体上可以分为两大部分:Collection和Map Collection和Map都是接口,在他们的下面还有其它的东西,先用一个图来表示一下关系。 ...

    唠嗑

    越学越发现不会的东西越来越多,果然学习就是一个不断发现自己有多菜的过程,不管怎么样好歹拓宽了眼界吧。

    容器

    先简单说说容器吧,容器也叫集合,它的出现是因为我们需要对大量引用的对象进行管理。大体上可以分为两大部分:Collection和Map
    Collection和Map都是接口,在他们的下面还有其它的东西,先用一个图来表示一下关系。
    在这里插入图片描述
    而今天我就想主要学习一下List接口以及他的下面三个类。

    List

    首先List的特点是有序可重复。有序的意思就是遍历的时候会按照插入的顺序,但不一定是按数字顺序。比如如果插入的时候我是插入“1”、“3”、“2”,那么遍历出来也是“1”、“3”、“2”的顺序,不会变成“1”、“2”、“3”。可重复就是我可以插入相同的,比如我可以插入“1”、“1”、“1”。
    他的底下有三种实现类:ArrayList、LinkedList、Vector。
    这三种类都实现了List接口,在源代码中也都实现了方法,以ArrayList为例简单讲讲。

    常用方法

    常用方法:
    1、add()。此方法用于在ArrayList中增加一个元素,如果只传入一个参数就是按顺序添加,如果传入下标+值就是在指定位置插入。

    在这里插入图片描述

    2、get()。这个方法就是传入一个下标,用于获取他的值。
    在这里插入图片描述

    3、indexOf()。这个方法传入一个对象,用于获取这个对象第一次出现在集合中的索引的位置。如果没有出现过就返回-1。返回的是索引,索引是从0开始的。
    在这里插入图片描述

    4、remove()。删除,传入一个“索引”,删除该索引对应的元素。
    在这里插入图片描述

    5、set()。更改,传入一个索引,与一个值,把集合中该索引处的元素更改为传入的值。
    在这里插入图片描述

    6、subList()。传入两个索引值A、B,把这个集合的索引值之间的元素返回。注意包左不包右,也就是说会返回第1索引和第2索引的元素。
    在这里插入图片描述

    7、addAll()。这个方法用于在一个集合的指定位置插入另外一个集合的所有元素,如果另外一个集合之中有元素,返回true;否则返回false。
    在这里插入图片描述

    8、迭代器。有两种迭代器。一种是iterator,一种是listIterator。他们都是接口,区别就是listIterator其实继承了iterator接口,功能更为强大。
    还是说一下分别常用的方法吧。
    iterator:

    • hasNext()。用于判断迭代器中是否还有元素。有则返回true,否则返回false
    • next()。用于返回迭代器的下一个元素。
    • remove()。用于删除迭代器新返回的元素。

      在这里插入图片描述

      简单的来说就是,建了迭代器并绑定集合之后,迭代器会在集合的开头(黑色箭头处),如下图,此时,迭代器的下一个元素就是元素1。如果执行了next()方法,那么迭代器就会跑到红色箭头处,它的下一个元素就是元素2。
      PS:注意,只要执行了next(),迭代器就会往下跑。比如:第一次执行next(),那么迭代器会先返回元素1,然后迭代器就跑到了红色箭头处。再执行next(),迭代器就会返回元素2,然后跑到下一个地方。
      还有就是remove()方法在执行之前,一定要先执行next()方法,让他知道谁是返回的元素,不然会抛出错误。

      在这里插入图片描述

      通过上面三种方法的使用,我们可以非常轻松的遍历集合。

    listIterator:

    • next()。一样的返回迭代器的下一个元素。

    • nextIndex()。返回迭代器下一个元素的索引。

    • previous()。返回迭代器的上一个元素。

    • previousIndex()。返回迭代器上一个元素的索引

    • add()。在迭代器位置之前插入元素。

      在这里插入图片描述

      1、一开始,迭代器处在黑色箭头处,所以此时调用next()方法返回的是1。然后迭代器跑到了红色箭头处,所以nextIndex()返回的是迭代器之后的元素“3”的索引
      2、插入了5后,由于迭代器在红色箭头处,所以是往红色箭头的上一个位置插入,就变成了图中的样子。然后运行previous()就的时候就返回红色箭头的上一个元素。也就是5。同时,迭代器的位置会向前移动到黑色箭头处。这时运行previousIndex(),返回的是迭代器之前的元素的索引,也就是“1”的索引
      3、值得一提的是,我发现迭代器的add也会在集合中加入新的元素。

      在这里插入图片描述
      \换个行

    • hasNext()。如果迭代器之后还有元素就返回true,否则返回false。(正向遍历)

    • hasPrevious()。如果迭代器之前还有元素就返回true,否则返回false。(逆向遍历)

      在这里插入图片描述

      迭代器新建的时候位置在黑色箭头处,所以他之前没有元素,他之后有元素。
      在这里插入图片描述
      \换个行

    • remove()。这个方法用于删除,只能在调用了next()或者previous()后使用。
      当调用了next()方法,再调用remove(),它会删除迭代器刚刚返回的元素。如果是调用了previous(),再调用remove(),那么它会删除迭代器返回的元素。

    • set()。这个方法用于更改指定元素的值,只能在调用了next()或者previous()后使用。
      当调用了next()或者previous,再调用set(),它就会把next()或者previoous()返回的最后一个元素的值改成set()里传入的值。这里要强调最后一个的意思就是,我可以调用很多次next()或者previous(),但最终只会修改最后一个元素的值。

      在这里插入图片描述


    看示例图的时候对着代码的分步来看。黑色箭头代表迭代器。
    第一份示例图
    在这里插入图片描述

    第二份示例图
    在这里插入图片描述

    第三份示例图
    在这里插入图片描述

    至此大概是简单学习完常用的几种方法了。
    其实最主要的还是两个迭代器。
    相同点:都是对集合进行遍历。
    区别:ListIterator的功能更为强大,有add、set、hasPrevious、previous、nextIndex、previousIndex等方法。但是ListIterator只能对List类型的使用,使用范围较小。

    三种实现类的区别

    1、ArrayList的底层数据结构其实是一个数组,只不过ArrayList可以不断扩充数组的容量,实现动态添加,且它可以存放任意类型的对象。只不过由于所有方法都在单一线程里运行,因此是线程不安全的。
    2、LinkedList的底层数据结构是链表,双向链表。它存储数据是使用结点来存储数据,而非数组,有一个first结点指向表头,last结点指向表尾。也是线程不安全的。
    3、Vector也是一样采用数组来存储数据, 是一个可变长度的数组类型,类似ArrayList,但是它是线程安全的,因为他的方法前边加了synchronized关键字,意思指同步,无论有多少个线程想执行这个程序,他们只能one by one,当一个线程在执行的时候,其他线程只能等待。

    线程安全:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况(百度百科)
    线程不安全:不提供数据保护,多个线程可以同时访问一个数据,造成得到的数据是脏数据。举个例子,线程A和线程B同时对一个数据进行增减操作,线程A加了50,然后上传,线程B减了100,也上传,那么最后这个数据就是脏数据。正确的线程安全应该是先+50再-100,或者先-100再+50。


    查询速率和插入速率的比较:
    1、ArrayList和Vector都是使用可变数组存储数据,所以插入慢,查找的时候因为有下标,所以查找快。但是Vector因为使用了锁,所以安全但是性能比ArrayList差。
    2、LinkedList使用了双向链表来存储数据,插入数据时只需要记录本项的前后,所以插入快,但是查询的时候需要进行向前或向后遍历,因此查询稍慢,且也是不安全的。

    碎碎念

    久违的碎碎念了,这东西我写了3天,不知道是我不用心还是真的多,晕了~好好加油吧

    展开全文
  • List集合特有的遍历功能 去除ArrayList集合类中重复字符串元素 思想: 我们创建一个集合并添加完元素,其中有重复怎么做? 我们可以在创建一个新的集合,然后遍历集合,每遍历一个旧集合元素,就拿这个元素...

    List集合特有的遍历功能
    去除ArrayList集合中自定义的元素

    去除ArrayList集合类中重复字符串元素

    思想:
    我们创建一个集合并添加完元素,其中有重复的,怎么做?
    我们可以在创建一个新的集合,然后遍历旧集合,每遍历一个旧集合元素,就拿这个元素和新集合里的元素对比,如果有就不添加到新集合,如果没有就添加

    import java.util.ArrayList;
    import java.util.Iterator;
    class test{
    	public static void main(String[] args){
    	//创建集合对象
    	ArrayList a = new ArrayList();
    	
    	//添加元素
    	a.add("hello");
    	a.add("world");
    	a.add("Java");
    	a.add("hello");
    	a.add("world");
    	a.add("Java");
    	a.add("hello");
    	a.add("world");
    	a.add("Java");
    	a.add("hello");
    	a.add("world");
    	a.add("Java");
    
    	//创建一个新集合对象
    	ArrayList aa = new ArrayList();
    	
    	//遍历旧集合对象
    	Iterator it = a.iterator();
    	while(it.hasNext()){
    		String s = (String)it.next();
    		//判断元素是否在旧集合中存在
    		if(!aa.contains(s)){
    			aa.add(s);
    			}
    		}
    	//遍历新集合
    	for(int i = 0 ; i < aa.size() ; i++){
    		String ss = (String)aa.get(i);
    		System.out.println(ss);
    		}
    	}
    }
    

    输出结果为:
    hello
    world
    Java

    展开全文
  • 比如在 Java 中,我们通过 List 集合的下标来遍历 List 集合元素,在 Python 中,给定一个 list 或 tuple,我们可以通过 for 循环来遍历这个 list 或 tuple ,这种遍历就是迭代。 可是,Python for 循环抽象...
  • 第6章 Java的类(Class)和对象(Object) 132 教学视频:59分钟 6.1 驾驶汽车向类(Class)世界进发 132 6.1.1 汽车带来问题 132 6.1.1 类组成 134 6.1.3 使用自定义Car类 136 6.1.4 类和对象 139 ...
  • 一个action里面得到一个List集合里面放着User对象 ,我怎么再把这个集合封装,然后再在jsp页面用$.ajax()这个方法把他遍历出来在jsp页面显示? 希望帮我实现以下,讲详细一点,麻烦给我一个例子
  • 第6章 Java的类(Class)和对象(Object) 132 教学视频:59分钟 6.1 驾驶汽车向类(Class)世界进发 132 6.1.1 汽车带来问题 132 6.1.1 类组成 134 6.1.3 使用自定义Car类 136 6.1.4 类和对象 139 ...
  • 第6章 Java的类(Class)和对象(Object) 132 教学视频:59分钟 6.1 驾驶汽车向类(Class)世界进发 132 6.1.1 汽车带来问题 132 6.1.1 类组成 134 6.1.3 使用自定义Car类 136 6.1.4 类和对象 139 ...
  • 疯狂JAVA讲义

    2014-10-17 13:35:01
    学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件位置,也需要指定目标文件文件名,这里使用javac编译Java程序时怎么不需要指定目标文件文件名呢? 13 1.5.3 运行Java程序 14 1.5.4 根据...
  • 可是我怎么遍历出"小红"和"小明"   我写了个Student Class有Name和Age属性 (List<Student>)list 没有用.. 换成数组也没用...   <p>JSON反序列化? 我知道, 'DejaVu Sans Mono', 'Bitstream Vera Sans ...
  • Java】数组列表

    2021-05-07 10:42:32
    ArrayList封装了一个动态再分配的对象数组。 对于需要动态数组时,可能会用Vector或ArrayList。怎么选择呢? 不需要同步的时候选择ArrayList,要同步的选Vector。 理由:Vector类的所有方法都是同步的。可以安全地从...
  • 有一个对应实体对象 封装所有字段 外加上字段 avg, [/code] 不知道你这个实体是怎么组织? 要放到什么集合中?怎么放? [b]问题补充:[/b] [code="java"] class Tab{ private String mc; private ...
  • java 面试题 总结

    2009-09-16 08:45:34
     Collection是集合上级接口,继承与他接口主要有Set 和List. Collections是针对集合一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 10、&和&&区别。 &是位运算符...
  • 关于List接口

    2020-10-23 09:52:55
    用于顺序访问集合对象的元素,无需知道集合对象的底层实现。 Iterator 怎么使用? Iterator():要求容器返回一个Iterator。Iterator将准备好返回序列第一个元素。 next():获得序列中下一个元素 。 hasNext...
  • 通常情况下,如果你要处理一组相同对象的集合,你会怎么做? 把它们放进List里面,然后再用Iterator方法遍历它们。 有没有想过让你自己设计的对象具有这样的iterator()方法?不用自己去一个一个的new它们。 好吧,你...
  • java面试题收集(07)

    2020-08-17 23:00:54
    (1)Iterator在遍历元素过程中,有线程修改集合元素会有ConcurrentModificationEception异常。 (2)Iterator本身不具有装载数据功能,需依附Collection对象使用。 (3)next()是用游标指向方式返回下一个元素...
  • 想实现商品分类显示,但是不知道怎样从jsp页面传值给dao层,以及查询语句怎么写? 求大神帮助。 这是jsp页面: ``` <%@ page language="java" import="java.util.*" contentType="text/html; ...
  • ActionForm嵌套对象数组

    2008-08-14 16:26:15
    在action中怎么才能直接TestForm 对象,并且可以遍历TestSubForm数组</p><br />问题补充:</strong><br />假如按 zhai puhong方法把 private TestSubForm[] subTest; 改为 private list sub...
  • 记两天面试题

    2019-05-07 22:00:03
    NO1 1。Java的基本数据类型有哪些,对应包装类型呢? 那他们有什么区别呢?...怎么遍历map和list呢?说几种方法 8。你知道拼接字符串方法有哪些? 9。String StringBuffer StringBuilder区别 10。...
  • 南方面试题锦

    2021-05-24 11:39:40
    1)双重for循环去重遍历集合所有元素 然后进行判断是否有相同元素两两比较如果相等话就删除第二个 2)HashSet去重 把List集合所有元素存入HashSet对象 接着把List集合元素全部清空 最后把HashSet对象元素全部添加到...
  • <p>Java <code>HashMap</code> 是散列表实现,有必要 review 一下一个工业级散列表实现要考量 Key Point: <ol><li>散列表三个基本元素:key、散列函数、value, key 通过散列函数计算得到 ...
  • /* 遍历回帖集合,分别取出每一条记录中回帖人和回帖内容,并添加到 * 对应的集合中,方便下一步遍历筛选 */ for(int i=0;i();i++){ Vector<String> v = vvs.get(i); String user = v.get(1); ...
  • asp.net知识库

    2015-06-18 08:45:45
    在Asp.net中如何用SQLDMO来获取SQL Server中的对象信息 使用Relations建立表之间的关系并却使用PagedDataSource类对DataList进行分页 通过作业,定时同步两个数据库 SQLSERVER高级注入技巧 利用反射实现ASP.NET控件和...

空空如也

空空如也

1 2
收藏数 31
精华内容 12
关键字:

java怎么遍历list的对象集合

java 订阅