Java知识梳理之泛型、集合和映射表(五)

       部分代码的Github地址为:https://github.com/hzka/JavaBook02/tree/master/chap19
(一)基础概念
 1.泛型类的动机与优点:
       之前有学到过泛型类ArrayList与泛型接口Comparable。Java有提供ArrayList用于存储泛型类型的数据。可以保存字符串类型对象、数字类型对象。这两种类型可以称之为具体类型。<T>被称之为形式泛型类型,可以用实际具体类型进行替换。
2.ArrayList类:

                   Java知识梳理之泛型、集合和映射表(五)

      譬如创建一个字符串线性表: ArrayList<String> arrayList = new ArrayList<>();以后只能添加String类型字符串,若添加其他,就会产生错误。而且注意:泛型类型必须是引用类型。Int->Integer,double->Double等。当然编译器对于基本数据类型也会实现自动打包、解包。譬如:第二三行自动打包,第五行拆包。

ArrayList<Double> list = new ArrayList<>();
list.add(5.0);
list.add(3.3);
Double doubleObject = list.get(0);
double d = list.get(1);

3.定义泛型类和接口:
      将元素类型通用化为泛型,新命名栈类GenericStack。可使用接口或者类定义泛型,当使用类创建对象,必须声明具体类型。E或者T应该是Object的子类。

                             Java知识梳理之泛型、集合和映射表(五)

import java.util.ArrayList;

public class GenriStack <E>{
    private ArrayList<E> list = new ArrayList<>();

    //构造函数应当这么写。
    public GenriStack(){
    }
    public int getSize(){
        return list.size();
    }

    public E peek(){
        return list.get(getSize()-1);
    }

    public E pop(){
        E o = list.get(getSize()-1);
        list.remove(getSize()-1);
        return o;
    }

    public void push(E o){
        list.add(o);
    }

    public boolean isEmpty(){
        return list.isEmpty();
    }
}

       泛型可以提高软件的可靠性和可读性,因为某些错误可能是在编译时才能被检测到。泛型可能会有多个参数,可以用,隔开。
4.泛型方法:
       可以定义泛型接口(Comparable)和泛型类(GenriStack),也可以使用泛型类型来定义泛型方法。必须定义泛型类型为静态方法。必须要将泛型类型置于返回类型之前。

import java.util.ArrayList;
public class Main {
    public static void main(String[] args) {
        Integer[] integers = {1,2,3,4,5};
        String[] strings = {"London","Paris","Berlin"};
        Main.print(integers);
        Main.print(strings);

    }

    public static <E> void print(E[] lists){
        for(int i = 0;i<lists.length;i++){
            System.out.print(lists[i]+" ");
        }
        System.out.println();
    }
}

       受限泛型:将泛型指定为另外一种类型的子类型。譬如:受限的泛型类<E extends GeometricObject>将E指定为GeometricObject的泛型子类型。
5.原始类型和向后兼容
       可以使用泛型类而无需指定具体类型。譬如可以使用原始类型GenriStack stack_01 = new GenriStack<>();替代GenriStack<String> stack_01 = new GenriStack<>();可以向后兼容:但这两种代码风格后者更优。Java知识梳理之泛型、集合和映射表(五)

         前者不同类型比较可能会出现运行时错误,后者则是编译错误。
6.通配泛型类型:
       通配泛型类型有三种形式:通用通配符 ?、受限通配浮 ? extends T和下限通配符 ? super T。其中T是泛型类型。这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。为方便起见,大家可以在这里把T当成String,即String在类中怎么用,那T在类中就可以怎么用!所以下面的:定义变量,作为返回值,作为参数传入的定义就很容易理解了。
       (1)?是非受限通配,可以表示任一类型。(2)? extends T表示是T或者T的子类型;Java知识梳理之泛型、集合和映射表(五)

       (3)? super T指的是表示T或者T的一个父类型。

public class Main {
    public static void main(String[] args) {
        GenriStack <String> stack1 = new GenriStack<>();
        GenriStack<Object> stack2 = new GenriStack<>();
        stack2.push("Java");
        stack2.push(2);
        stack1.push("Sun");
        add(stack1,stack2);
    }
    private static <T> void add(GenriStack<T> stack1, GenriStack<? super T> stack2) {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
    }
}

                         Java知识梳理之泛型、集合和映射表(五)
        A、B表示类或者接口,而E表示泛型类型参数。
7.消除泛型和对泛型的限制
7.1 泛型存在于编译时,一旦编译器确定该类型是安全的,则会将其转换为原始类型。此称之为泛型消除;当编译泛型类、接口和方法时,编译器使用Object类型来替代泛型类型;当一个泛型是受限的,编译器会使用该受限类型来替换他。
7.2 一些限制:
7.2.1 不能使用new E()创建对象:原因是:运行时泛型不可用。
7.2.2 不能使用new E[]来创建数组。
7.2.3在静态上下文中不允许类的参数是泛型参数。
7.2.4 异常类不能使泛型的。
(二)实战:
1.对一个对象数组进行排序:
       完成功能:反省方法对一个Comparable对象数组进行排序,这些对象是Comparable接口的实例,它们使用ComparaTo方法进行比较。程序对整数数组、字符数组、String数组和double数组进行排序。
       代码思路:程序中的sort可对任何类型的数组进行排序,但前提是这些对象也是Comparable接口的实例。泛型类型定义为<E extends Comparable<E>> 。两重含义:(1)E是Comparable的子类型;(2)它还指定了比较的元素是E类型的。双重循环排序。

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        Integer[] integers = {2,4,3,9,7};
        Double[] doubles = {3.4,1.3,-22.1};
        Character[] characters = {'a','J','r'};
        String [] strings = {"Tom","Susan","Kim"};
        sort(integers);
        sort(doubles);
        sort(characters);
        sort(strings);
        printList(integers);
        printList(doubles);
        printList(characters);
        printList(strings);
    }
    public static <E extends Comparable<E>> void sort(E[] list){
        E currentMin;
        int currentminIndex;
        for(int i = 0;i<list.length-1;i++){
            currentMin = list[i];
            currentminIndex = i;

            for(int j=i+1;j<list.length;j++){
                //双重循环,假若前者大于后者,则记录当前的位置和数字。
                if(currentMin.compareTo(list[j])>0){
                    currentMin = list[j];
                    currentminIndex = j;
                }
            }
            //如果不等于,则交换。
            if(currentminIndex !=i){
                list[currentminIndex] = list[i];
                list[i] = currentMin;
            }
        }
    }
    public static void printList(Object[] lists){
        for(int i = 0;i<lists.length;i++){
            System.out.print(lists[i]+"  ");
        }
        System.out.println();
    }
}

2.泛型矩阵类
  Java知识梳理之泛型、集合和映射表(五)


public abstract class GenericMartix<E extends Number> {

    protected abstract E add(E o1, E o2);

    protected abstract E multiply(E o1, E o2);

    protected abstract E zero();

    public E[][] addMatrix(E[][] matrix1, E[][] martrix2) {
        if ((matrix1.length != martrix2.length) || (matrix1[0].length != martrix2[0].length)) {
            throw new RuntimeException("The matrix do not have the same size");
        }

        E[][] result = (E[][]) new Number[matrix1.length][matrix1[0].length];

        for (int i = 0; i < result.length; i++) {
            for (int j = 0; j < result[0].length; j++) {
                //调用的是add抽象方法。
                result[i][j] = add(matrix1[i][j] ,martrix2[i][j]);
            }
        }
        return result;
    }

    public E[][] multiplyMatrix(E[][] matrix1, E[][] martrix2) {
        if (matrix1[0].length != martrix2.length) {
            throw new RuntimeException("The matrix do not have compatible size");
        }

        E[][] result = (E[][]) new Number[matrix1.length][martrix2[0].length];

        for (int i = 0; i < result.length; i++) {
            for (int j = 0; j < result[0].length; j++) {
                result[i][j] = zero();
                //调用的是add抽象方法。
                for(int k = 0;k< matrix1[0].length;k++){
                    result[i][j] = add(result[i][j],multiply(matrix1[i][k],martrix2[k][j]));
                }
            }
        }
        return result;
    }

    public static void printResult(Number[][] m1, Number[][] m2, Number[][] m3, char op) {
          for(int i = 0;i<m1.length;i++){
              for(int j = 0;j<m1[0].length;j++){
                  System.out.print(" "+m1[i][j]);
              }

              if(i == m1.length / 2){
                  System.out.print(" "+op+" ");
              }else{
                  System.out.print(" ");
              }

              for(int j = 0 ;j<m2.length;j++){
                  System.out.print(" "+m2[i][j]);
              }

              if(i == m1.length / 2){
                  System.out.print(" = ");
              }else{
                  System.out.print(" ");
              }

              for(int j = 0;j<m3.length;j++){
                  System.out.print(m3[i][j]+" ");
              }
              System.out.println();
          }
    }
}

public class Main {

    public static void main(String[] args) {
        Integer[][] m1 = new Integer[][]{{1, 2, 3}, {4, 5, 6}, {1, 1, 1}};
        Integer[][] m2 = new Integer[][]{{1, 1, 1}, {2, 2, 2}, {0, 0, 0}};

        IntegerMatrix integerMatrix = new IntegerMatrix();
        System.out.println("\nm1+m2 is:");
        GenericMartix.printResult(m1,m2,integerMatrix.addMatrix(m1,m2),'+');
        System.out.println("\nm1*m2 is:");
        GenericMartix.printResult(m1,m2,integerMatrix.multiplyMatrix(m1,m2),'*');
    }
}

(三)本章小结

Java知识梳理之泛型、集合和映射表(五)Java知识梳理之泛型、集合和映射表(五)

    Java知识梳理之泛型、集合和映射表(五)


(四)集合基础概念

        部分代码的Github地址为:https://github.com/hzka/JavaBook02/tree/master/chap21
1.集合基础概念:
       存储和处理无重复元素的高效数据结构,可以使用散列表HashSet、链式散列表LinkedHashSet和树形集TreeSet来创建集合。                           

          Java知识梳理之泛型、集合和映射表(五)

2.HashSet:
2.1.可使用无参构造方法来创建空的散列集。也可以由一个现有的合集创建散列集。默认情况下是初始容量为16、负载系数为0.75。譬如:默认情况下,当尺寸达到16*0.75 = 12时,容量将会翻倍至32。高负载降低空间开销、但会增加查找时间。
2.2.HashSet主要用来存储互不相同的任何元素。回顾Object类的hasCode方法,若对象相同,则两个对象的散列码必须一样,但两个不等的对象也有可能具有相同的散列码,复写hasCode方法。Java API中的Integer、Character和String都有复写hasCode方法。
2.3.代码示例:散列集存储字符串,foreach进行遍历。P2101项目。

 private static void TestingHashSet() {
        Set<String> set = new HashSet<>();
        set.add("London");
        set.add("Paris");
        set.add("New York");
        set.add("San Francisco");
        set.add("Beijing");
        set.add("New York");
        for(String s:set){
            System.out.print(s.toUpperCase()+"  ");
        }
}

       添加多次,一次存储。由于散列表中的元素是没有特定顺序的,因此我们使用LinkedHashSet来强加一个顺序。
2.4  应用Collection接口方法:
       HashSet可遍历,可以使用一些API方法,看看HashSet继承自Collection有哪些方法。P2101项目,可以使用add添加元素,remove删除元素,addAll添加某集合,contains是否包含某元素,removeAll删除某集合,retainall保留和某集合共同的元素。

3.LinkedHashSet
       链表实现了HashSet类,支持集合内元素排序,换言之,可以按照插入集合顺序进行提取,直接将2.3中第二行的HashSet换成LinkedHashSet来存储不重复的元素。HashSet要比LinkedHashSet更高效。
4.TreeSet
       LinkedHashset保持了元素插入时的顺序,但若要强加一个不同的顺序(升序或降序),则需要使用Treeset。介绍了一堆API而已。lower(“P”)小于P的最大元素。啥时候用?当更新一个集合,不需要保持元素排序关系,就应该使用散列集,但需要一个排好序的集合时,Treeset了解下。

        Set<String> set = new HashSet<>();
        set.add("London");
        set.add("Paris");
        set.add("New York");
        set.add("San Francisco");
        set.add("Beijing");
        set.add("New York");
        TreeSet<String> treeSet = new TreeSet<>(set);
        System.out.println("all tree set:" + treeSet);
        System.out.println("first()" + treeSet.first());
        System.out.println("first()" + treeSet.last());
        //小于toElement的那一部分
        System.out.println("headset?" + treeSet.headSet("New York"));
        //大于等于fromElement的那一部分
        System.out.println("tailset?" + treeSet.tailSet("New York"));
        System.out.println(treeSet.lower("P"));
        System.out.println(treeSet.higher("P"));
        System.out.println(treeSet.floor("P"));
        System.out.println(treeSet.ceiling("P"));
        System.out.println("Delete first"+treeSet.pollFirst());
        System.out.println("Delete first"+treeSet.pollLast());
        System.out.println("all tree set:" + treeSet);

5.比较集合和线性表的性能:
5.1.无重复元素排序方面,集合比线性表表现更高效;但线性表在通过索引访问元素方面非常有用。性能测试目标:测试一个元素是否在一个散列集、链式散列集、树形集、数组线性表以及链表中;(2)从上述中删除元素的执行时间。
5.2.示例代码:P2106代码

import java.util.*;

public class Main {
    static final int N = 5000;

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            list.add(i);
        }
//        洗牌一样,随机打乱原来的顺序。
        //HashSet
        Collections.shuffle(list);
        Collection<Integer> set1 = new HashSet<>(list);
        System.out.println("HashSet查找时间"+getTestTime(set1)+" s");
        System.out.println("HashSet删除时间"+getRemoveTime(set1)+" s");
        //LinkedHashSet
        Collection<Integer> set2 = new LinkedHashSet<>(list);
        System.out.println("LinkedHashSet查找时间"+getTestTime(set2)+" s");
        System.out.println("LinkedHashSet删除时间"+getRemoveTime(set2)+" s");
        //TreeSet
        Collection<Integer> set3 = new TreeSet<>(list);
        System.out.println("TreeSet查找时间"+getTestTime(set3)+" s");
        System.out.println("TreeSet删除时间"+getRemoveTime(set3)+" s");
        //ArrayList
        Collection<Integer> list1 = new ArrayList<>(list);
        System.out.println("ArrayList查找时间"+getTestTime(list1)+" s");
        System.out.println("ArrayList删除时间"+getRemoveTime(list1)+" s");
        //LinkedList
        Collection<Integer> list2 = new LinkedList<>(list);
        System.out.println("LinkedList查找时间"+getTestTime(list2)+" s");
        System.out.println("LinkedList删除时间"+getRemoveTime(list2)+" s");
    }

    private static long getTestTime(Collection<Integer> set) {
        long starttime = System.currentTimeMillis();
        for(int i = 0;i<N;i++){
            set.contains((int)(Math.random()*2*N));//一半在里面,一半在外面
        }
        return System.currentTimeMillis() - starttime;
    }
    private static long getRemoveTime(Collection<Integer> set) {
        long starttime = System.currentTimeMillis();
        for(int i = 0;i<N;i++){
            set.remove(i);
        }
        return System.currentTimeMillis() - starttime;
    }
}

Java知识梳理之泛型、集合和映射表(五)
        结论:测试元素是否在线性表或者集合里面,集合比线性表更高效。因此禁飞名单应当用集合实现。
6.单元素与不可变的合集和映射表。
                  Java知识梳理之泛型、集合和映射表(五)
(五)实战
1.对一个Java源文件中的关键字进行计数。
                              Java知识梳理之泛型、集合和映射表(五)
注意:(1)Java中的关键字和保留字共53个;(2)Arrays.asList()将数组转为线性表。代码如下:

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main {
    public static void main(String[] args) throws Exception {
        Scanner input = new Scanner(System.in);
        System.out.println("Enter a Java Source file:");
        String filename = input.nextLine();
        File file = new File(filename);
        if (file.exists()) {
            System.out.println(countKeywords(file));
        } else {
            System.out.println("File " + filename + " not exists");
        }
    }

    private static int countKeywords(File file) throws Exception {
        String[] keywords = {"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
                "continue", "default", "do", "double", "else", "enum", "extends", "for", "final", "finally", "float", "goto", "if",
                "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected",
                "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws",
                "transient", "try", "void", "volatile", "while", "true", "false", "null"};
        System.out.println(keywords.length);
        Set<String> keywordset = new HashSet<>(Arrays.asList(keywords));
        int count = 0;
        Scanner input = new Scanner(file);
        while (input.hasNext()) {
            String word = input.next();
            if (keywordset.contains(word)) {
                System.out.print("  " + word);
                count++;
            }
        }
        System.out.println();
        return count;
    }
}

(六)映射表
1.映射表基础概念:                               
        映射表是一种按照键值对存储元素的容易,通过键来获取、删除、更新键值对。映射表中不应该有重复的键。每个键都有一个对应的值。

                                  Java知识梳理之泛型、集合和映射表(五)

      映射表主要有以下三种:散列映射表HashMap、链式散列映射表LinkedHashMap以及树形映射表TreeMap,都定义于Map接口中。

                              Java知识梳理之泛型、集合和映射表(五)

2.Map接口:
      Map接口提供查询(containskey、containsValue、isEmpty和size)、更新(clear、put、putAll和remove)和获取合集的值和合集的键的方法。HashMap、LinkedHashMap以及TreeMap是三个具体的实现。HashMap定位、插入、删除高效;LinkedHashMap支持映射表中条目的排序。分为插入顺序和访问顺序两种。TreeMap在遍历排好顺序的键时很高效。
         Java知识梳理之泛型、集合和映射表(五)

                                 Java知识梳理之泛型、集合和映射表(五)
         
(七)实战
1.创建三个映射表,建立学生与年龄之间的映射关系。姓名为键、年龄为值。P2108项目。
由结果可知,HashMap条目顺序随机,TreeMap按键升序,LinkedHashMap则是按照最后一次被访问的时间从早到晚排序的。  Java知识梳理之泛型、集合和映射表(五)

public class Main {

    public static void main(String[] args) {
        Map<String,Integer> hashMap = new HashMap<>();
        hashMap.put("Smith",30);
        hashMap.put("Anderson",31);
        hashMap.put("Lewis",29);
        hashMap.put("cook",29);
        System.out.println("HashMap:"+hashMap);

        Map<String,Integer> treeMap = new TreeMap<>(hashMap);//在这调用了putall方法。
        System.out.println("TreeMap:"+treeMap);

        Map<String,Integer> linkedHashMap = new LinkedHashMap<>(16,0.75f,true);
        linkedHashMap.put("Smith",30);
        linkedHashMap.put("Anderson",31);
        linkedHashMap.put("Lewis",29);
        linkedHashMap.put("cook",29);
        System.out.println(linkedHashMap.get("Lewis"));
        System.out.println(linkedHashMap);
}
2.单词出现的次数:统计一个文本中单词出现的次数,按照字母顺序显示这些单词和它们对应出现的次数。P2109项目。
注意:Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。你是否已经对每次从Map中取得关键字然后再取得相应的值感觉厌倦?使用Map.Entry类,你可以得到在同一时间得到所有的信息。
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Main {
    public static void main(String[] args) {
        String text = "Good morning. Have a good class. Have a good visit. Have fun!";
        //TreeMap按键升序
        Map<String, Integer> map = new TreeMap<>();
        String[] words = text.split("[ \n\t\r.,;:!?(){}]");
        for (int i = 0; i < words.length; i++) {
            String key = words[i].toLowerCase();
            if (key.length() > 0) {
                if (!map.containsKey(words[i])) {
                    map.put(key, 1);
                } else {
                    int value = map.get(key);
                    value++;
                    map.put(key, value);
                }
            }
        }
        //程序获取集合中映射表的条目。
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        for (Map.Entry<String, Integer> entry : entrySet) {
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }
    }
}

(八)总结:

Java知识梳理之泛型、集合和映射表(五)