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

news/2024/6/25 12:03:36

一、泛型和类型安全的容器

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<apples.size();i++){
            Apple a=(Apple) apples.get(i);
        }
    }
}
class Apple {
    private static long counter;
    private final long id = counter++;
    long id() {
        return id;
    }
}
class Orange {}

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

package tij.hoding;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList<Apple> apples=new ArrayList<>();
        for(int i=0;i<2;i++){
            apples.add(new Apple());
        }
        for(int i=0;i<apples.size();i++){
            Apple a=(Apple) apples.get(i);
        }
    }
}
class Apple {
    private static long counter;
    private final long id = counter++;
    long id() {
        return id;
    }
}

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

二、基本概念

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

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

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

List<Apple> apples=new ArrayList<Apple>();

就是说多数情况我们是面向接口编程的,但也并不都是这样,比如如果我们要使用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<Snow> snow1=Arrays.asList(new Crusty(),new Slush(),new Powder(),new Light());
        List<Snow> 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<String> str_list = new ArrayList<String>();
        for (int i = 0; i < 10; i++)
            str_list.add("123134");
        Iterator<String> 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<Integer> priorityQueue = new PriorityQueue<Integer>();
        Random rand = new Random(47);
        for (int i = 0; i < 10; i++) {
            priorityQueue.offer(rand.nextInt(i + 10));
        }
        printQ(priorityQueue);
        List<Integer> ints = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2, 3,
                9, 14, 18, 21, 23, 25);
        priorityQueue = new PriorityQueue<Integer>(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<String> cs = new LinkedList<String>();
        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<String> {
    protected String[] words = "And that is how we know the Earth to be banana-shaped"
            .split(" ");

    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            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<String> ral = new ReversibleArrayList<String>(
                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<T> extends ArrayList<T> {
    public ReversibleArrayList(Collection<T> c) {
        super(c);
    }
    public Iterable<T> reversed() {
        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return reversed_iterator();
            }

            private Iterator<T> reversed_iterator() {
                return new Iterator<T>() {
                    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<String> ral = new ReversibleArrayList<String>(
                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<T> extends ArrayList<T> {
    public ReversibleArrayList(Collection<T> c) {
        super(c);
    }
    public Iterable<T> reversed() {
        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return reversed_iterator();
            }

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

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


http://www.niftyadmin.cn/n/1123775.html

相关文章

excel 2010 学习笔记一 Vlookup 函数的使用

有这么一句话说的好&#xff1a;在商用场合里&#xff0c;能证明你会基本的EXCEL操作技巧的两个检查标准就是会不会用VLOOKUP函数以及数据透视表功能&#xff0c;那么今天就来总结一下VLOOKUP的一些简单实用的功能。 1.VLOOKUP 的基本用法&#xff0c; 在EXCEL自带的帮助功能中…

抽象工厂模式之我用

我们关注的内容&#xff1a; 1 使用场景分析 2 案例代码解析 一 使用场景分析 抽象工厂模式&#xff0c;为创建一组相关或项目依赖的对象提供一个接口&#xff0c;而且无需指定他们具体的类。抽象工厂模式的通用类图如下&#xff1a; 通俗的来讲&#xff1a;拥有共同的属性和方…

MySQL缓存命中率概述

工作原理&#xff1a; 查询缓存的工作原理&#xff0c;基本上可以概括为&#xff1a; 缓存SELECT操作或预处理查询&#xff08;注释&#xff1a;5.1.17开始支持&#xff09;的结果集和SQL语句&#xff1b; 新的SELECT语句或预处理查询语句&#xff0c;先去查询缓存&#xff0c;…

最简单方法搞定 VirtualBox 虚拟机共享文件问题

太阳先人出品的VirtualBox还算将就&#xff0c;不要钱&#xff0c;不要序列号&#xff0c;不用编译&#xff08;直接rpm搞定&#xff09;。还是中文的&#xff0c;已经算不错老&#xff0c;最起码能解决LINUX下面用XP软件的90&#xff05;问题&#xff0c;和谐和谐两个系统麻&a…

Apache实现反向代理负载均衡

说到负载均衡LVS这套技术&#xff0c;有很多种实现方法。 本文所说&#xff0c;主要就是利用apache服务器实现反向代理&#xff0c;实现负载均衡。 首先&#xff0c;传统的正向代理如下图所示&#xff0c;正如我们用的游戏加速代理&#xff0c;大多的个人PC把请求发给正向代理服…

同济大学高等数学上册电子版_同济大学编高等数学上册第十一页例题几何解法...

这道题是描述这样一个规律&#xff1a;定义域对称的函数f(x),总是能被分解为偶函数g(x)与奇函数h(x)之和。书中使用代数解&#xff0c;很清晰简洁。f(x)f(-x) 肯定是 偶函数&#xff0c;f(x)-f(-x)肯定是奇函数。所以 这个偶函数这个奇函数2f(x);也就能拼出f(x)了(奇偶函数各/2…

获取rpm文件,不安装

2019独角兽企业重金招聘Python工程师标准>>> 比如你要将test.rpm用test.rpm里的某个文件&#xff0c;但你又不想安装test.rpm&#xff0c;那你可以试试这个办法。 随便建一个目录 mkdir aaa 把test.rpm放入目录aaa里并进入aaa目录 cp path/to/test.rpm aaa/ cd aaa …

在Oracle中使用rank()over()排名的问题

排序&#xff1a; ---rank()over(order by 列名 排序)的结果是不连续的&#xff0c;如果有4个人&#xff0c;其中有3个是并列第1名&#xff0c;那么最后的排序结果结果如&#xff1a;1 1 1 4 select scoreid, studentid,COURSENAME,totalexamscore , rank()over(order by TOTAL…