关于java:cs61b-week5-Generics-Autoboxing

1.主动装箱与拆箱

正如咱们后面所学的,咱们能定义泛型类,比方LinkedListDeque<Item>和ArrayDeque<Item>
当咱们想去实例化一个应用泛型类的对象时,则必须把泛型替换为一种具体的类型。
回忆一下,Java有8种初始类型,初始类型之外的其余类型均是援用类型。
对于泛型而言,咱们并不能将<>中的generic type替换为初始类型,比方:
ArrayDeque<int>是一种语法错误,相同,咱们应用ArrayDeque<Integer>
对于每一种初始类型,其都关联一种援用类型,这些援用类型叫做”wrapper classes”(装箱类)

咱们假如应用泛型时必须作手动转换,行将初始类型转换为泛型

public class BasicArrayList {
    public static void main(String[] args) {
      ArrayList<Integer> L = new ArrayList<Integer>();
      L.add(new Integer(5));
      L.add(new Integer(6));

      /* Use the Integer.valueOf method to convert to int */
      int first = L.get(0).valueOf();
    }
}

如上的代码应用起来有一点宜人。侥幸的是,Java可能主动地做隐式转换,因而,以上代码咱们只需这样写:

public class BasicArrayList {
    public static void main(String[] args) {
      ArrayList<Integer> L = new ArrayList<Integer>();
      L.add(5);
      L.add(6);
      int first = L.get(0);
    }
}

Java可能进行主动装箱与拆箱,
假如咱们传递一个初始类型的变量,然而Java期待的是装箱类型,则会主动装箱,调用 blah(new Integer(20))

public static void blah(Integer x) {
    System.out.println(x);
}
int x = 20;
blah(x);

假如咱们传递一个装箱类型的变量,但Java期待的是初始类型,则会主动拆箱

public static void blahPrimitive(int x) {
    System.out.println(x);
}
Integer x = new Integer(20);
blahPrimitive(x);

正告:
在谈到主动装箱和解箱时,有几件事须要留神。

  • 数组永远不会被主动装箱或主动拆箱,比方你有一个整数数组 int[] x,并试图将其地址放入 Integer[] 类型的变量,编译器将不容许你的程序编译。
  • 主动装箱和拆箱对性能也肯定的影响。也就是说,依赖主动装箱和拆箱的代码会比不应用这种主动转换的代码慢。
  • 此外,装箱类型比初始类型应用更多的内存。在大多数古代编译器上,你的代码必须持有对Object的64bits援用,且每个Object还须要64位的开销,用来存储Object的动静类型等等。

Widening

除了主动装箱与拆箱之外,Java还有主动加宽,比方有一个函数,传值是double:

public static void blahDouble(double x) {
   System.out.println(“double: “ + x);
}

然而当咱们传入int

int x = 20;
blahDouble(x);

Java会认为intdouble窄,因而会将int主动加宽
反之,如果想要将更宽的初始类型转换为更窄的,须要强制类型转换

public static void blahInt(int x) {
   System.out.println(“int: “ + x);
}
double x = 20;
blahInt((int) x);

2.Immutability 不变量

不变量是指某一变量一旦进行赋值操作之后,其任何操作都不能使之被扭转,应用final关键字去指定一个变量为不变量
例如,在Java中,Integer,String是不变量,即便String内置很多函数,例如

  • charAt(int i):获取字符串第i项
  • compareTo(String s):与另一字符串比拟,字典序
  • concat(String s):链接另一字符串
  • split(String r):宰割字符串

以上函数均不是对原字符串做破坏性的批改,而是生成一个新的字符串正本返回,原字符串的值并不会受到影响,例如以下Data类则是一个不变量:

public class Date {
    public final int month;
    public final int day;
    public final int year;
    private boolean contrived = true;
    public Date(int m, int d, int y) {
        month = m; day = d; year = y;
    }
}

这个类是不可变的,当实例化Date()后,再也无奈更改其任何属性的值。
留神

  • 将援用申明为final并不会使援用指向的对象不可变!例如,思考以下代码片段:

     public final ArrayDeque<String>() deque = new ArrayDeque<String>();

    援用deque不能被从新赋值,也就是不能再让deque指向一个新的ArrayDeque,然而deque所指向的ArrayDeque的值能够扭转,比方addLast(),addFirst()等等

  • 申明final并非必须的,有时候申明变量为private一样能够做到禁止内部拜访与批改该变量,然而应用reflection API,甚至能够对公有变量进行更改!咱们的不变性概念是基于咱们没有应用这个非凡性能。

3.ArrayMap

本节咱们将发明一个属于咱们本人的ArrayMap,而非应用Java内置的Map,根据的数据结构是数组,并且应用泛型:
首先给出Map61B的接口:

package Map61B;
import java.util.List;

public interface Map61B<K, V> {
    /* Returns true if this map contains a mapping for the specified key. */
    boolean containsKey(K key);

    /* Returns the value to which the specified key is mapped. No defined
     * behavior if the key doesn't exist (ok to crash). */
    V get(K key);

    /* Returns the number of key-value mappings in this map. */
    int size();

    /* Associates the specified value with the specified key in this map. */
    void put(K key, V value);

    /* Returns a list of the keys in this map. */
    List<K> keys();
}

而后咱们创立一个Array Map去implements该接口,其中蕴含的成员变量如下:

package Map61B;

import java.util.List;
import java.util.ArrayList;

public class ArrayMap<K, V>implements Map61B<K, V> {
    private K[] keys;
    private V[] values;
    int size;

}
  1. 构造函数:
    public ArrayMap() {
        keys = (K[]) new Object[10];
        values = (V[]) new Object[10];
        size = 0;
    }
  1. keyIndex(K key):查询键key,如果存在,返回key在keys[]数组中的下标,不存在返回-1
    private int keyIndex(K key) {
        for (int i = 0;i < size; i++) {   
            if (keys[i].equals(key)) {
                return i;
            }
        }
        return -1;
    }

留神咱们初始化的keys[]有10个大小(容许更多),循环终止为i < size,而非 keys.length是因为keys[]数组外面存在一些为null的空位,咱们只需比拟理论已增加的key的size即可,那些null值不用比拟
其次,为什么不应用 keys[i] == key?而应用keys[i].equals(key)
因为==实际上是指两个援用的内存盒指向的Object雷同,即是否指向同一个Object,而非单纯的值相等
而equals(Object o)则比拟两个Object的值是否相等,每当咱们应用关键字 new 创立一个Object时,它都会为该对象创立一个新的内存地位,举例:

// Java program to understand
// the concept of == operator

public class Test {
    public static void main(String[] args)
    {
        String s1 = "HELLO";
        String s2 = "HELLO";
        String s3 = new String("HELLO");

        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
        System.out.println(s1.equals(s2)); // true
        System.out.println(s1.equals(s3)); // true
    }
}

more detail you can see

  1. containKey(K key):是否存在键key,是返回true,否返回false

     public boolean containsKey(K key) {
         int index = keyIndex(key);
         return index > -1;
     }
  2. put(K key, V value):向Map中新增键值对<key,value>,如果已存在key,则间接将key映射至value,如果不存在则在Map开端新增键值对(每次增加均为间断,非间断)

     public void put(K key,V value) {
         int index = keyIndex(key);
         if (index == -1) {
             keys[size] = key;
             values[size] = value;         
             size = size + 1;
         } else {
             values[index] = value;
         }
     }
  3. get(K key):返回键key所对应的值value

     public V get(K key) {
         int index = keyIndex(key);
         return values[index];
     }
  4. size():返回Map中总的键值对的数目

     public int size() {
         return size;
     }
  5. key():以List模式返回Map中所有存在的key

     public List<K> keys() {
         List<K> keylist = new ArrayList<>();
         for (int i = 0;i < keys.length; i++) {
             keylist.add(keys[i]);
         }
         return keylist;
     }

至此,咱们实现了ArrayMap中的所有method,在main()中测试:

    public static void main(String[] args) {
        ArrayMap<String, Integer> m = new ArrayMap<String, Integer>();
        m.put("horse", 3);
        m.put("fist", 6);
        m.put("house", 9);
    }

失去后果如下:


4.ArrayMap and Autoboxing Puzzle

如果你写下如下测试:

@Test
public void test() { 
    ArrayMap<Integer, Integer> am = new ArrayMap<Integer, Integer>();
    am.put(2, 5);
    int expected = 5;
    assertEquals(expected, am.get(2));
}

那么你将会失去编译谬误的信息:

$ javac ArrayMapTest.java
ArrayMapTest.java:11: error: reference to assertEquals is ambiguous
    assertEquals(expected, am.get(2));
    ^
    both method assertEquals(long, long) in Assert and method assertEquals(Object, Object) in Assert match

报错信息说咱们对assertEquals()的调用不置可否,其中(long,long)和(Object,Object)型的调用均有可能,为何会造成这样的起因呢?
因为调用am.get(2)实际上是返回Integer型,而expected是int型,实际上咱们的调用是

assertEquals(int,Integer)

与Java的主动转换无关,从assertEquals(int,Integer)变成assertEquals(long,long)的步骤是

  • int 加宽为 long
  • Integer(am.get(2))主动拆箱为 int ,int 加宽为 long

从assertEquals(int,Integer)变成assertEquals(Object,Object)的步骤是

  • int 主动装箱为Integer

两种转换均有可能,因而编译器不会通过,解决办法之一则是强制类型转换

assertEquals((Integer) expected, am.get(2));

5.泛型办法

思考以下咱们下面ArrayMap外面的get()办法:

    public V get(K key) {
        int index = keyIndex(key);
        return values[index];
    }

其实存在bug,假如key不存在则keyIndex(key)会返回-1,再调用 return values[-1]则会引起ArrayIndexOutOfBoundException
因而当初咱们增加一个class MapHelper去解决这个问题,MapHelper中蕴含两个static办法:

  • get(Map61B, key):返回在map中key对应的value,如果不存在,返回null
  • maxKey(Map61B):返回ArrayMap中所有keys的最大值

咱们首先来尝试写一下get(),假如咱们为了适配上文中的键值对<“horse”, 3>等等,这样写:

    public static Integer get(Map61B<String, Integer>sim, String key) {
        return null;
    }

显然是不适合的,因为Map61B实际上是泛型Map,他并不是只实用于<String, Integer>,那么如何让get()适宜所有类型的键值对呢?兴许你会模拟ArrayMap的get()办法,写成

    public static V get(Map61B<K, V>sim, K key) {
        return null;
    }

然而编译器会报错,因为编译器并不知道K, V代表什么,思考咱们之前的技巧,通过给class header增加泛型:

public class MapHelper <K,V>

上一步的编译器报错隐没了,然而如何将K,V改成咱们须要的类型呢?其工作形式是须要去实例化MapHelper,并向其中传递装箱类型的参数

MapHelper<String, Integer> m = new MapHelper<>();

咱们并不心愿通过这样的形式去应用get()办法,那么如何以防止实例化泛型类的形式去传递类型参数呢?

也就是应用泛型办法,这次咱们不再去类头申明增加<>泛型,而是在办法的返回类型之前增加

public static <K,V> V get(Map61B<K,V> map, K key) {
    if map.containsKey(key) {
        return map.get(key);
    }
    return null;
}

调用举例:

ArrayMap<Integer, String> isMap = new ArrayMap<Integer, String>();
System.out.println(mapHelper.get(isMap, 5));

你并不需要显示地申明get()办法的<K, V>的具体类型,Java可能自动识别出isMap 是<Integer,String>类型

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理