1.自动装箱与拆箱
正如我们前面所学的,我们能定义泛型类,比如LinkedListDeque
当我们想去实例化一个使用泛型类的对象时,则必须把泛型替换为一种具体的类型。
回想一下,Java有8种初始类型,初始类型之外的其他类型均是引用类型。
对于泛型而言,我们并不能将<>中的generic type替换为初始类型,比如:
ArrayDeque
对于每一种初始类型,其都关联一种引用类型,这些引用类型叫做"wrapper classes"(装箱类)
文章图片
我们假设使用泛型时必须作手动转换,即将初始类型转换为泛型
public class BasicArrayList {
public static void main(String[] args) {
ArrayList
如上的代码使用起来有一点恼人。幸运的是,Java能够自动地做隐式转换,因此,以上代码我们只需这样写:
public class BasicArrayList {
public static void main(String[] args) {
ArrayList
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);
警告:
在谈到自动装箱和解箱时,有几件事需要注意。
文章图片
Widening
除了自动装箱与拆箱之外,Java还有自动加宽,比如有一个函数,传值是double:
public static void blahDouble(double x) {
System.out.println(“double: “ + x);
}
但是当我们传入int时
int x = 20;
blahDouble(x);
Java会认为int比double窄,因此会将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内置很多函数,例如
以上函数均不是对原字符串做破坏性的修改,而是生成一个新的字符串副本返回,原字符串的值并不会受到影响,例如以下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()后,再也无法更改其任何属性的值。
注意:
3.ArrayMap
public final ArrayDeque() deque = new ArrayDeque();
引用deque不能被重新赋值,也就是不能再让deque指向一个新的ArrayDeque,但是deque所指向的ArrayDeque的值可以改变,比如addLast(),addFirst()等等
本节我们将创造一个属于我们自己的ArrayMap,而非使用Java内置的Map,依据的数据结构是数组,并且使用泛型:
首先给出Map61B的接口:
package Map61B;
import java.util.List;
public interface Map61B
然后我们创建一个Array Map去implements该接口,其中包含的成员变量如下:
package Map61B;
import java.util.List;
import java.util.ArrayList;
public class ArrayMap
public ArrayMap() {
keys = (K[]) new Object[10];
values = (V[]) new Object[10];
size = 0;
}
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 == operatorpublic 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
至此,我们完成了ArrayMap中的所有method,在main()中测试:
public boolean containsKey(K key) {
int index = keyIndex(key);
return index > -1;
}
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;
}
}
public V get(K key) {
int index = keyIndex(key);
return values[index];
}
public int size() {
return size;
}
public List
public static void main(String[] args) {
ArrayMap m = new ArrayMap();
m.put("horse", 3);
m.put("fist", 6);
m.put("house", 9);
}
得到结果如下:
文章图片
4.ArrayMap and Autoboxing Puzzle
如果你写下如下测试:
@Test
public void test() {
ArrayMap
那么你将会得到编译错误的信息:
$ 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)的步骤是
从assertEquals(int,Integer)变成assertEquals(Object,Object)的步骤是
两种转换均有可能,因此编译器不会通过,解决方法之一则是强制类型转换
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(),假设我们为了适配上文中的键值对<"horse", 3>等等,这样写:
public static Integer get(Map61Bsim, String key) {
return null;
}
显然是不合适的,因为Map61B实际上是泛型Map,他并不是只适用于,那么如何让get()适合所有类型的键值对呢?也许你会模仿ArrayMap的get()方法,写成
public static V get(Map61B
但是编译器会报错,因为编译器并不知道K, V代表什么,考虑我们之前的技巧,通过给class header添加泛型:
public class MapHelper
上一步的编译器报错消失了,但是如何将K,V改成我们需要的类型呢?其工作方式是需要去实例化MapHelper,并向其中传递装箱类型的参数
MapHelper m = new MapHelper<>();
我们并不希望通过这样的方式去使用get()方法,那么如何以避免实例化泛型类的方式去传递类型参数呢?
也就是使用泛型方法,这次我们不再去类头声明添加<>泛型,而是在方法的返回类型之前添加
public static
调用举例:
ArrayMap
你并不需要显示地声明get()方法的
Type upper bounds
完成了get()方法,让我们来写一下maxKey():
public static
在前面的课程中我们知道,> 操作符只适用于初始类型之间的大小比较,而不适用于比较对象,回忆一下我们上节课的做法,使用方法compareTo()来进行对象之间的比较
public static
但是直接使用compareTo()肯定是不允许的,原因是该方法在MapHelper中并不存在,需要我们使用Java内置的Comparable接口,在泛型方法上直接extends的做法叫:type upper bounds
public static
【cs61b week5 -- Generics, Autoboxing】这里也许有人会疑问,为何不使用implements,而是extends,请注意,此处extends的含义与继承关系中的含义不同
文章图片
Just remember, in the context of inheritance, the extends keyword is active in giving the subclass the abilities of the superclass
On the other hand, in the context of generics, extends simply states a fact: You must be a subclass of whatever you're extending. When used with generics (like in generic method headers), extends imposes a constraint rather than grants new abilities.
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)