包装类&简单认识泛型
1,包装类
在Java中,由于基本类型不是继承自Object类,为了在泛型代码中可以支持基本类型,故Java给每个基本类型都对应了一个包装类型。
所以在Java中,类型也可以面对对象。
(1)包装类-装箱和拆箱
在Java中,将基本类型赋值给包装类的行为称之为装箱,将包装类赋值给基本类型称之为拆箱。
装箱
如下述代码便是装箱:
Java中装箱是通过 valueOf 方法来实现的。
我们若是直接赋值给包装类,此时包装类会自动调用 valueOf 方法进行装箱,此时称之为隐式装箱;
我们若是手动调用 valueOf 进行装箱,此时称之为显式装箱。
拆箱
如下代码便是拆箱:
Java中拆箱是通过 (类型可变)intValue 方法实现的。
我们若是直接将包装类的值赋给基本类型,编译器会自动调用 intValue 方法,此时称之为饮食拆箱;
我们若是手动调用 intValue 方法进行拆箱,此时称之为显示拆箱。
拆箱不限制类型
如下述代码:
a原本是Integer类型,但是却可以被拆箱为double类型;
b原本是in类型,但是却被装箱为Float类型。
通过上述例子可以得出,无论是拆箱、装箱、不一定要包装类与基本类型一一匹配,其实还可以混搭的!
装箱之valueOf深入理解
我们首先要注意的一点,其是使用 == 对两个引用数据类型进行比较,那么比较的其实是两个对象的地址。
这也就说明,当使用100赋值装箱时,其地址是相同的;但是当使用200赋值装箱时,其地址不同了。这是为什么?
答:这一切要从 装箱的实现方法==》valueOf说起,以下是Integer包装类中重载的 valueOf 源码:
在Integer包装类中重载的valueOf 源码中我们可以看到:当我们赋值的数值在[low,high] 区间时,其会返回cache中的数据;但是当我们赋值的数值不在这个区间时,它会重新new一个Integer对象返回。
所以:当对Integer类进行装箱时,若是其赋值在-128 ~ 127 之间时,其会从cache数组中return数据;若是赋值不在这个区间时,return一个新new的对象。
(2)泛型
泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
直白地来说就是:可以传递类型
泛型的语法
//1 calss 泛型类名称<类型形参列表>{ //这里可以使用类型参数 } //例如 class className<T,Z,X....(可以放很多)>{ } //2 class 泛型类名称<类型形参列表> extends 继承类/*继承类可以使用泛型*/ { //这里可以使用类型参数 } //例如: class className<T,X,Z..> extends ParentClass<T1> { //这里只能使用部分类型参数 }
语法解释:
1:类名后的代表占位符,表示当前类是一个泛型类
泛型代码示例
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
擦除机制 -- 重要
擦除机制是编译泛型的方式,当泛型在编译时,会将所有的泛型替换为Object,对于这种机制,我们称之为擦除机制
泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束
语法:
class 泛型类名称<类型形参 extends 类型边界> { //... }
示例1:
因为此时Person只能接受Number类或Number类的子类作为实参,故当我们试图传递String时报错。
示例2:
假设我们需要对一个创建一个方法对泛型数组求最大值,如下:
因为泛型只能传递引用类型,故我们一定要保证所传递的类型能够进行比较,那么如何保证呢?答:只需要该类实现了Comparable接口并重写compareTo方法即可进行比较。所以我们可以对代码进行如下的修改:
这也是泛型的上界。
示例3(泛型方法):
以示例2的例子我们再进一步拓展。示例2中,我们每次调用findMaxValue方法之前都要实例化myArrays,但是一旦将findMaxValue方法改为静态方法,就会导致无法使用泛型T,那么是否可以直接对方法传递类型呢?答案是可以的,如下:
此类方法称之为泛型方法,即是使用方法来接收类型。
但是调用此类方法时要注意:在调用泛型方法时,编译器会根据方法参数的类型自动传递类型,故黄色箭头指向的可以省略,这个称之为类型推导。
顺序表 与 ArrayList
1,顺序表:
顺序表是线性表的一种,其底层是由一个动态可扩容的数组 + 计数器组成。
2,实现
import java.util.Arrays;
public class MyArrayList<T> {
private static final int DEFAULT_CAPACITY = 10;
private Object[] array;
private int size;
public MyArrayList() {
this.array = new Object[DEFAULT_CAPACITY];
this.size = 0;
}
public void add(T element) {
if (size == array.length) {
resizeArray();
}
array[size++] = element;
}
public T get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (T) array[index];
}
public void remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
System.arraycopy(array, index + 1, array, index, size - index - 1);
size--;
}
public int size() {
return size;
}
private void resizeArray() {
int newCapacity = array.length * 2;
array = Arrays.copyOf(array, newCapacity);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(array[i]);
if (i < size - 1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
public static void main(String[] args) {
MyArrayList<Integer> list = new MyArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
System.out.println("List: " + list);
System.out.println("Element at index 2: " + list.get(2));
list.remove(2);
System.out.println("List after removing element at index 2: " + list);
System.out.println("Size of list: " + list.size());
}
}
ArrayList的使用方法
1))构造方法
ArrayList有三种构造方法,分别是:无参构造,指定顺序表初始容量构造,利用其它Collection构造
无参构造
-
- 无参构造方法实际上不开辟空间。
- 当ArrayList第一次调用add方法时,会帮助ArrayList开辟十个元素的空间;
- 当顺序表的内存使用完时,add方法会以原内存大小的1.5倍进行扩容(如果1.5倍扩容的内存小于我们需要的内存,则会直接开辟我们所需的内存大小,并且每次扩容都会判断是否超过该顺序表最大容量)
指定内存大小构造
当指定的数大于零时:直接开辟指定大小的空间
当指定的数等于零时:按照无参构造方法执行
当指定的数小于零时:抛出异常
利用其它Collection 构造
构造方法源码如下:
可以看到上文中有这样一段代码
public ArrayList(Collection<? extends E> c) { //... }
参数链表中的代码是什么意思呢?接下来,我们一个一个进行拆分讲解:
collection-->表示实参必须使用 实现了 Collection接口的类
示例如下:
(仅示范一个,ArrayList的构造方法,不仅仅可以使用ArrayList类型进行构造,还能使用所有实现Collection接口的类进行构造,如:Stack、Vector、LinkedList...)
c-->表示泛型类型必须是该构造类的泛型类型或是其泛型的子类。即是说,如下图:
arrayLis实例化,调用构造方法,此时其泛型传递的是Number类型。这就代表其帮助他构造的对象类型,泛型传递的类型必须是 Number 或是 Number 的子类。
? 是通配符,因为我们并不知道帮助其构造的对象 对象泛型传递的类型是什么,所以使用通配符替代。
c是实参名,接收的是传递过来的对象
| 操作 | 描述 |
|--------------------|-------------------------------------------------------|
| add(E element) | 将指定的元素追加到列表的末尾 |
| add(int index, E element) | 将指定的元素插入到列表中的指定位置 |
| get(int index) | 返回列表中指定位置的元素 |
| set(int index, E element) | 用指定的元素替换列表中指定位置的元素 |
| remove(int index) | 移除列表中指定位置的元素 |
| remove(Object o) | 移除列表中首次出现的指定元素 |
| isEmpty() | 如果列表不包含任何元素,则返回 true |
| size() | 返回列表中的元素数 |
| clear() | 从列表中移除所有元素 |
| contains(Object o)| 如果列表中包含指定的元素,则返回 true |
| indexOf(Object o) | 返回列表中首次出现的指定元素的索引,如果不存在则返回 -1 |
| lastIndexOf(Object o) | 返回列表中最后出现的指定元素的索引,如果不存在则返回 -1 |
| toArray() | 返回一个包含列表所有元素的数组 |
| subList(int fromIndex, int toIndex) | 返回列表中指定范围的视图(子列表) |
| iterator() | 返回在列表上的迭代器,允许在列表中迭代元素 |
| forEach(Consumer<? super E> action) | 对列表中的每个元素执行给定的操作 |
| sort(Comparator<? super E> c) | 根据指定的比较器对列表进行排序 |
3))遍历方式
在 ArrayList 中可以使用三种方式进行遍历:for+小标,foreach,使用迭代器
以如下代码为例示范:
for循环遍历
foreach
使用迭代器
迭代器可以更便捷地为集合类进行遍历,使用方法如下:
使用迭代器之前需进行导包:import java.util.Iterator;
迭代器使用语法如下:
//使用前需要导包! Iterator<单个元素的类型> it = 集合类名.iterator(); while(it.hasNext()) { System.out.println(it.next()); }