资讯专栏INFORMATION COLUMN

(Thinking in Java)第11章 持有对象

v1 / 1756人阅读

摘要:迭代器解决了这个问题。删除后于是我们可以写一个方法,接受一个类型,然后让他调用方法,这就不需要考虑这个是个还是了,也就是说,可以将遍历容器的操作与序列底层的结构分离,迭代器统一了对容器类的访问方式。十二和两种遍历的方法,与迭代器方法。

一、泛型和类型安全的容器
package tij.hoding;

import java.util.ArrayList;

public class Test {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        ArrayList apples=new ArrayList();
        for(int i=0;i<2;i++){
            apples.add(new Apple());
        }
        apples.add(new Orange());
        for(int i=0;i

在运行期的类型转换时会出现问题,因为使用get方法的时候取出来的其实object类型,之前程序中放进去了一个orange却要将他转成一个apple。
因此需要使用类型参数来指定这个容器实例可以保存的类型,通过使用类型参数,就可以在编译期放置将错误的类型放在容器中,上面的代码改成下面类型

package tij.hoding;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList apples=new ArrayList<>();
        for(int i=0;i<2;i++){
            apples.add(new Apple());
        }
        for(int i=0;i

同样,可以将容器的类型参数的指定类型以及其子类类型的实例变量放进这个容器中,例子略

二、基本概念

Java容器类可以划分为两个不同的概念

Collection
一个存储独立元素的序列,不同序列有不同的规则:List必须按照插入顺序保存元素;Set不能有重复元素;Queue按照排队规则确定对象产生的顺序。

Map
一组成对的简直对对象

然而大多数情况在编写代码的时候都是与接口打交道,唯一需要精确指定使用的容器类型的地方就是创建的时候,如下

List apples=new ArrayList();

就是说多数情况我们是面向接口编程的,但也并不都是这样,比如如果我们要使用LinkedList里的特有的功能,就不能用这种方法了。

三、添加一组元素

Collections和Arrays类中提供了很多与序列相关的方法,有啥自己去找API看去就行了。
注明

package tij.hoding;

import java.util.Arrays;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List snow1=Arrays.asList(new Crusty(),new Slush(),new Powder(),new Light());
        List snow2=Arrays.asList(new Light(),new Heavy());
    }
}
class Snow{}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}
class Crusty extends Snow{}
class Slush extends Snow{}

书上说snow2是创建不了的,我用的是JDK1.8发现是可以创建的。

四、容器的打印

没啥说的,数组的打印可以用Arrays.toString方法

五、List

List接口在Collection基础上添加了大量方法

基本的ArrayList,访问快,中间插入和移除较慢

LinkedList,进行中间的插入删除代价低
方法找API看

六、迭代器

对于List,add方法是插入元素的方法,get方法是去除元素的方法,但是这是有弊端的,如果使用容器,如果需要对元素进行操作,就必须要针对容器的确切类型进行编程,举个例子,如果要从一个List里取出元素,我们写了一些方法,但如果希望将这些方法再运用到同是Collection类的set类上,就不行了,因为set根本没有get方法。迭代器解决了这个问题。

package tij.hoding;

import java.util.ArrayList;
import java.util.Iterator;

public class Test {
    public static void main(String[] args) {
        ArrayList str_list = new ArrayList();
        for (int i = 0; i < 10; i++)
            str_list.add("123134");
        Iterator it = str_list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        it = str_list.iterator();
        for (int i = 0; i < 5; i++) {
            it.next();
            it.remove();
        }
        System.out.println("----删除后");
        it = str_list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

于是我们可以写一个方法,接受一个collection类型,然后让他调用iterator()方法,这就不需要考虑这个collection是个list还是set了,也就是说,可以将遍历容器的操作与序列底层的结构分离,迭代器统一了对容器类的访问方式。

1.ListIterator

这是个List专用的迭代器,他可以双向移动,可以生成索引,可以使用set方法对元素进行替换,而且在创建的时候还可以指从哪开始迭代,具体看API

七、LinkedList

LinkedList里有很多功能类似的方法,比如peek、element、getFirst,这是因为常以linkedList为基础制作堆栈等数据结构,在这种数据结构中使用push啊pop啊更合适,虽然我觉得有一些= =额= =恩。。。。

八、Stack

没啥好说的,就是用Linkedlist实现,LIFO

九、Set

Set保证集合内元素的唯一性,同时Set也有不同的类型:HashSet使用了散列函数;TreeSet将元素存储在红黑树数据结构中,可以完成元素按比较方法的排序;LinkedHashSet也使用了散列,但它通过使用链表维护了元素插入顺序

十、Map

存储映射关系的数据结构

十一、Queue

FIFO的容器
看API挺简单的

1.PriorityQueue

优先级队列声明下一个弹出的元素是最需要的元素(优先级最高)。当用PriorityQueue上调用offer方法插入一个对象时,这个对象会在队列中被排序,可以使用对象的自然排序也可以提供自己的Comparator来修改排序规则。PriorityQueue可以确保调用各种方法时,根据优先级进行操作。

package tij.hoding;

import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        PriorityQueue priorityQueue = new PriorityQueue();
        Random rand = new Random(47);
        for (int i = 0; i < 10; i++) {
            priorityQueue.offer(rand.nextInt(i + 10));
        }
        printQ(priorityQueue);
        List ints = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2, 3,
                9, 14, 18, 21, 23, 25);
        priorityQueue = new PriorityQueue(ints);
        printQ(priorityQueue);
    }
    @SuppressWarnings("rawtypes")
    static void printQ(Queue queue) {
        while (queue.peek() != null) {
            System.out.print(queue.remove() + " ");
        }
        System.out.println();
    }
}
十二、Collection和Iterator

两种遍历的方法,foreach与迭代器方法。
同时有一种默认的AbstractCollection,继承他可以较为简单的将自己的类变成Collection类型而不用实现原Collection全部的方法

十三、Foreach与迭代器

因为有了迭代器,所以有了Foreach功能,这个功能真的很强大

package tij.hoding;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

public class Test {

    public static void main(String[] args) {
        Collection cs = new LinkedList();
        Collections.addAll(cs, "Take the long way home".split(" "));
        for (String s : cs) {
            System.out.print(s + " ");
        }
    }
}

而这是因为Java se5引入了Iterable几口,实现了Iterable接口的类都可应用于Foreach之中。比如下面

package tij.hoding;

import java.util.Iterator;

public class Test {

    public static void main(String[] args) {
        for (String s : new IterableClass()) {
            System.out.print(s + " ");
        }
    }
}
class IterableClass implements Iterable {
    protected String[] words = "And that is how we know the Earth to be banana-shaped"
            .split(" ");

    @Override
    public Iterator iterator() {
        return new Iterator() {
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < words.length;
            }
            @Override
            public String next() {
                return words[index++];
            }
        };
    }

}

首先要搞懂Foreach语句中for(element:container)里的container要是iterable类型的才可以,foreach在遍历时,首先调用container的iterator方法得到这个iterator,然后就是在调用这个iterator的的hasNext和next方法

1.适配器方法惯用法

假如现在有一个类记录了一串儿单词,希望能运用Foreach方法将他遍历,只要让他实现Iterable接口实现Iterator功能就可以了,但是现在希望他既能顺向遍历,又能逆向遍历,这就不好办了,因为你只能重写Iterator一次啊。
一种解决方案就是所谓的适配机器方法,当你有一个接口并需要另一个接口的时候,编写适配器就可以解决问题。

package tij.hoding;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

public class Test {

    public static void main(String[] args) {
        ReversibleArrayList ral = new ReversibleArrayList(
                Arrays.asList("To be or not to be".split(" ")));
        for (String s : ral) {
            System.out.print(s + " ");
        }
        System.out.println();
        for (String s : ral.reversed()) {
            System.out.print(s + " ");
        }
    }
}

@SuppressWarnings("serial")
class ReversibleArrayList extends ArrayList {
    public ReversibleArrayList(Collection c) {
        super(c);
    }
    public Iterable reversed() {
        return new Iterable() {
            @Override
            public Iterator iterator() {
                return reversed_iterator();
            }

            private Iterator reversed_iterator() {
                return new Iterator() {
                    int current = size() - 1;
                    public boolean hasNext() {
                        return current > -1;
                    }
                    public T next() {
                        return get(current--);
                    }
                };
            }
        };
    }
}

其实这个程序我看了半天才看懂,首先要搞懂Foreach语句中for(element:container)里的container要是iterable类型的才可以,foreach在遍历时,首先调用container的iterator方法得到这个iterator,然后就是在调用这个iterator的的hasNext和next方法。然后再顺序遍历的时候,传入的是ral,然后调用ral自身的iterator本身的hasNext和next方法,就是顺序遍历了;在逆向遍历的时候,传入的是一个新的iterable,得到的也是一个新的iterator,然后调用的就是这个新的iteratoriterator的两个方法。
另外我想了一下next方法里的get方法是谁的,然后用了内部类访问外部的方法也能调用

ReversibleArrayList.this.get()

于是我们就可以弄很多很多不同的遍历规则了,比如随机遍历啊

package tij.hoding;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class Test {

    public static void main(String[] args) {
        ReversibleArrayList ral = new ReversibleArrayList(
                Arrays.asList("To be or not to be".split(" ")));
        for (String s : ral) {
            System.out.print(s + " ");
        }
        System.out.println();
        for (String s : ral.reversed()) {
            System.out.print(s + " ");
        }
        System.out.println();
        for (String s : ral.randomized()) {
            System.out.print(s + " ");
        }
    }
}

@SuppressWarnings("serial")
class ReversibleArrayList extends ArrayList {
    public ReversibleArrayList(Collection c) {
        super(c);
    }
    public Iterable reversed() {
        return new Iterable() {
            @Override
            public Iterator iterator() {
                return reversed_iterator();
            }

            private Iterator reversed_iterator() {
                return new Iterator() {
                    int current = size() - 1;
                    public boolean hasNext() {
                        return current > -1;
                    }
                    public T next() {
                        return ReversibleArrayList.this.get(current--);
                    }
                };
            }
        };
    }
    public Iterable randomized(){
        return new Iterable(){
            @Override
            public Iterator iterator() {
                List shuffle= ReversibleArrayList.this;
                Collections.shuffle(shuffle,new Random(47));
                return shuffle.iterator();
            }
        };
    }
}

可以注意到在随机遍历的时候,实际返回了一个新的序列的iterator这是为了不打乱原始的序列的顺序
附一张简单的容器分类

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67583.html

相关文章

  • Thinking in Java10 内部类

    摘要:内部类中也可以取得这个外部类对象引用。创建成员内部类对象的时候需要外部类对象。另外在方法中的内部类不能加等权限修饰符,只能加和修饰符。可以在接口内部定义内部类,而且他们即使没有修饰,也会自动变成的。 Thinking in Java捞干货,写笔记 一、成员内部类 1.最基本使用 public class Demo { class Contents{ privat...

    Brenner 评论0 收藏0
  • Thinking in Java13 字符串

    摘要:四上的操作看五格式化输出运用和语言很相似和是等价的哟类格式化说明符转换六正则表达式网上教程学七扫描输入新增了类。 一、不可变String String类型的对象是不可变的,所有的改变实际上都是创建了一个新的String对象,另外当String作为传入参数的时候,其实实际上传入的是这个引用的一个拷贝,这个方法结束了之后这个传入的引用也就消失了,原来的那个String不会受到方法内的影响而...

    feng409 评论0 收藏0
  • Thinking in Java 对象导论

    摘要:基类导出类导出类继承了基类的特点,基类和导出类具有相同的基础接口,形成两者差异的做法在导出类中添加新方法在导出类型中添加新的接口元素,扩展了接口。覆盖在导出类用创建方法的新定义,覆盖基类中的方法定义纯粹替代,只覆盖。 一、抽象过程 建模基于计算机的结构 解空间的解 汇编语言:对底层机器的轻微抽象 命令式语言:汇编语言的抽象 建模基于待解决问题 问题空间的元素 面向对象 二、每个...

    joyqi 评论0 收藏0
  • Thinking in Java14 类型信息

    摘要:通过运行时类型信息,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。编程应该尽量面向接口编程,应该对类型信息尽量的少了解二对象看书,书上写得好静态语句块在这个类被加载的时候运行。 一、为什么需要RTTI Run-Time Type Information。通过运行时类型信息,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。编程应该尽量...

    tomorrowwu 评论0 收藏0
  • Thinking in Java20 注解

    摘要:而这可以通过注解办到,在代码中以指令语言的形式化方法来为代码提供更多信息。有注解的说明这个成员变量是一个列名,然后根据注解信息来生成相应的语句。也就是说把注解信息提取了出来。 注解是向代码中添加信息的一种方法,并且在之后还可以使用这些数据就比如这个方法是用来剥香蕉的,但是我们看就是一串代码,我们没办法在代码里写一段指令说我这个程序是用来剥香蕉的,当然除了注释。而这可以通过注解办到,在代...

    刘玉平 评论0 收藏0

发表评论

0条评论

v1

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<