一、 预备知识-泛型(Generic)
1.1、泛型的引入
比如:我们实现一个简单的顺序表
class MyArrayList{
public int[] elem;
public int usedSize;
public MyArrayList(){
this.elem = new int[10];
}
public void add(int key){
this.elem[usedSize] = key;
usedSize++;
}
public int getPos(int pos){
return this.elem[pos];
}
}
问题:此时我么发现我们实现的顺序表,只能保存 int 类型的元素,如果现在需要保存 指向 Person 类型对象的引用的顺序表,请问应该如何解决?如果又需要保存指向 Book 对象类型的引用呢?
泛型的意义
1、当我们指定数据类型之后,它会在编译器编译的时候,就帮你检查存储的数据类型是否匹配。自动对数据类型进行检查。
2、在我们获取元素的时候,发现数据类型不匹配,此时就会自动的将类型转换成相同类的数据。自动对类型进行强转类型转换。
下面顺序表的写法是不对的,只是为了暂时的用起来
class MyArrayList<E>{
public E[] elem;
public int usedSize;
public MyArrayList(){
this.elem = (E[])new Object[10];
}
public void add(E key){
this.elem[usedSize] = key;
usedSize++;
}
public E getPos(int pos){
return this.elem[pos];
}
}
面试问题:泛型是怎么编译的?
1.泛型是编译期的一种机制(擦除机制)。
擦除机制:它会把尖括号这些内容擦除掉,也就说程序运行的时候,就没有这些东西了。所以说程序运行起来,再去获取它的类型是不可能的,因为都被擦掉了。
然后我们再来看一下细节
1.2、泛型的总结
- 泛型是为了解决某些容器、算法等代码的通用性而引入,并且能在编译期间做类型检查。
- 泛型利用的是 Object 是所有类的祖先类,并且父类的引用可以指向子类对象的特定而工作。
- 泛型是一种编译期间的机制,即 MyArrayList< Person > 和 MyArrayList< Book > 在运行期间是一个类型。
- 泛型是 java 中的一种合法语法,标志就是尖括号 <>
二、预备知识-包装类(Wrapper Class)
Object 引用可以指向任意类型的对象,但有例外出现了,8 种基本数据类型不是对象,那岂不是刚才的泛型机制要
失效了?
实际上也确实如此,为了解决这个问题,java 引入了一类特殊的类,即这 8 种基本数据类型的包装类,在使用过程
中,会将类似 int 这样的值包装到一个对象中去。
2.1、基本数据类型和包装类直接的对应关系
基本就是类型的首字母大写,除了 Integer 和 Character
2.2、包装类的使用,装箱(boxing)和拆箱(unboxing)
例如:此时我们将一个字符串类型的数据转换成int类型的数据
public static void main(String[] args) {
String str = "123";
int b = Integer.valueOf(str);
System.out.println(b + 11);
}
什么是装包和拆包
- 装箱,装包: 就是将基本数据类型的数据转换成包装类类型 的数据
- 拆箱,拆包:就是将包装类类型的数据转换成基本数据类型的数据
阿里面试题,和装包和拆包有关
下面执行结果为什么会不相等???
public static void main(String[] args) {
Integer a = 129;
Integer b = 129;
System.out.println("是否相等: " + (a == b));
}
为什么???
三、List的使用
3.1、ArrayList 和 顺序表
List是一个接口,不能实例化,但是可以实例一个就提的对象,也可以通过具体的类来实例具体的对象
public static void main(String[] args) {
List<String> list = new ArrayList<>();
ArrayList<String> arrayList = new ArrayList<>();
}
下面的这张图是ArrayList实现的接口和继承的抽象类,但是这张图并不具体
下面这张图才是ArrayList具体实现的接口和类
【说明】
- ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
- ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
- ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
- 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
- ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
3.2、ArrayList 的构造方法
在只用java中的类库时,一定要先看一下类库中的构造方法
public static void main(String[] args) {
//不带参数的构造方法,默认大小为0
List<String> list1 = new ArrayList<>();
//带一个参数的构造方法,大小为10
List<String> list2 = new ArrayList<>(10);
//将list2作为参数传递,但是list2和list3里面存储的数据类型必须一致
List<String> list3 = new ArrayList<>(list2);
}
ArrAyList的三种打印方式
1. 直接打印
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("word");
list1.add("!!!");
System.out.println(list1);
}
2. 使用for和for-each循环打印
ArrayList本质上是一个数组,所以可以使用for循环打印,当然也可以使用for-each打印
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("word");
list1.add("!!!");
for (String str : list1) {
System.out.println(str + " ");
}
}
3. 迭代器打印
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next() + " ");
}
}
4、用于List相关的迭代器打印
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
ListIterator<String> it = list.listIterator();
while (it.hasNext()){
System.out.println(it.next() + " ");
}
3.3、ArrayList的常用方法
3.3.1、boolean add(E e) - 添加元素
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
System.out.println(list);
}
3.3.2、void add(int index, E element) - 在index位置添加元素E
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
list.add(1,"++++");
System.out.println(list);
}
3.3.3、boolean addAll(Collection<? extends E> c) - 尾插对象e(e是和ArrayList一样的对象)
相当于在顺序表后面在插入一个顺序表
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
List<String> list1 = new ArrayList<String>();
list1.add("*****");
list1.add("888");
list.addAll(list1);
System.out.println(list);
}
3.3.4、E remove(int index) - 删除index位置的元素
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
list.add("baidu");
System.out.println(list);
list.remove(1);
System.out.println(list);
}
3.3.5、boolean remove(Object o) - 删除第一次遇见的元素
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
boolean b = list.remove("b");
System.out.println(list);
}
3.3.6、E get(int index) - 获取index位置的元素
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
String str = list.get(2);
System.out.println(str);
System.out.println(list);
}
3.3.7、E set(int index, E element) - 将index位置的元素设置成element
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
String str = list.set(2,"***");
System.out.println(str);
System.out.println(list);
}
3.3.8、void clear() - 清空ArrayList
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
System.out.println(list);
list.clear();
System.out.println(list);
}
3.3.9、boolean contains(Object o) - 判断一个元素是否存在
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
boolean ret = list.contains("b");
System.out.println(ret);
System.out.println(list);
}
3.3.10、int indexOf(Object o) - 返回第一个o 的下表
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("b");
list.add("c");
int ret = list.indexOf("b");
System.out.println(ret);
System.out.println(list);
}
3.3.11、int lastIndexOf(Object o) - 返回最后一个o 的下表
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("b");
list.add("c");
int ret = list.lastIndexOf("b");
System.out.println(ret);
System.out.println(list);
}
3.3.11、List subList(int fromIndex, int toIndex) - 截取部分list
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("A");
list.add("M");
list.add("c");
List<String> sub = list.subList(1,3);
System.out.println(sub);
}
在截取的时候注意使用事项
四、ArrayList底层的扩容机制
得出结论:
1、如果ArrayList调用不带参数的构造方法,那么顺序表的大小为0,当第一次 add 的时候,整个数组的大小为10,当这10个放满的时候,开始扩容,以15倍的方式进行扩容。
2、如果调用指定容量大小的构造方法,那顺序表的大小就是指定的容量,如果放满了,还是以1.5倍的方式进行扩容。
五、模拟实现ArrayList
这次的实现就会比上次的更好一点,模拟源代码的实现
当创建好之后,顺序表里面的操作就按照下面的操作来写
add
在使用add函数的时候要注意,调用的 时候不带参数的构造方法,还是调用带参数的构造方法,如果满了就要扩容
public class MyArrayList<E> {
private Object[] elementData;
private int size;//代表有效的数据个数
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
public MyArrayList(){
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public MyArrayList(int capacity) {
//对参数进行判断
if(capacity > 0){
this.elementData = new Object[capacity];
}else if(capacity == 0){
this.elementData = new Object[0];
}else {
throw new IllegalArgumentException("初始化容量不能为负数");
}
}
/**
* 添加元素,添加的元素在数组的最后面,尾插
* @param e
* @return
*/
public boolean add(E e){
//
ensureCapacityInternal(size+1);
elementData[size+1] = e;
size++;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//计算出需要的容量
int capacity = calculateCapacity(elementData,minCapacity);
//拿着计算的容量,满了扩容,空的分配内存
ensureExplicitCapacity(capacity);
}
private void ensureExplicitCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? (Integer.MAX_VALUE) : (MAX_ARRAY_SIZE);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//1、是否之前的elemenData 数组分配内存
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
return Math.max(DEFAULT_CAPACITY,minCapacity);
}
//2、分配过就返回+1的值
return minCapacity;
}
}
add(int index, E e) - 在指定位置插入元素
/**
* 在index位置添加元素
* @param index
* @param e
*/
public void add(int index, E e) {
//判断index位置是否合法
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = e;
size++;
}
/**
* 判断index位置是否合法
* @param index
*/
private void rangeCheckForAdd(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("index位置不合法");
}
}
remove(object o) - 删除第一次遇到的元素 o
/**
* 删除第一次遇见的元素 0
* @param o
* @return
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/**
* 移动元素
* @param index
*/
private void fastRemove(int index) {
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
就简单的实现几个功能
面试题 - CVTE 删除第一个字符串中出现的第二个字符串中的字符
删除第一个字符串中出现的第二个字符串中的字符
例如 :
String str1= “welcome to cvte”
String str2= “come”
输出结果:wl t vte
我们遍历str1 ,看str2的字符是否在str1中,如果不在那就将这个字符放入ArrayList中,如果在那就不放进去
public static void main(String[] args) {
String str1 = "welcome to cvte";
String str2 = "come";
ArrayList<Character> list = new ArrayList<>();
for (int i = 0; i < str1.length(); i++) {
char ch = str1.charAt(i);
if(!str2.contains(ch+"")){
list.add(ch);
}
}
for (char ch : list) {
System.out.print(ch);
}
}
ArrayList 实践案例 - 扑克牌
目的:
1.构造一副扑克牌
2.揭牌
class Card{
private int rank;//数字
private String suit;//花色
public Card(String suit, int rank) {
this.rank = rank;
this.suit = suit;
}
public int getRank() {
return rank;
}
public String getSuit() {
return suit;
}
@Override
public String toString() {
return "[ 花色 "+ this.suit + " " + this.rank + " ]";
}
}
public class Dome {
//构造一副牌,这副牌里面没有大小王
//四个花色
private static String[] suits = {"♥","♠","♣","♦"};
private static ArrayList<Card> byCard(){
ArrayList<Card> cards = new ArrayList<>();
//每个花色匹配不同的数字,这样就创建好了一副牌
for (int i = 0; i < 4; i++) {
for (int j = 1; j <=13; j++) {
// String suit = suits[i];
// Card card = new Card(suit,j);
// cards.add(card);
cards.add(new Card(suits[i],j));
}
}
return cards;
}
//牌创建好之后就要洗牌
private static void shuFfle(ArrayList<Card> cards){
// 牌数是52,数组下标就是51
// 从最后一张牌开始,随机与 本身 或者 本身前面的任意一张牌 交换位置。
// 这样的做交换性 比 从开头洗 的 打乱顺序的 效率 高。
for (int i = cards.size()-1; i > 0 ; i--) {
Random random = new Random();
// 通过 Random的引用 random 去调用 nextInt() 方法。
// random.nextInt() 方法,根据括号中的值,随机生成 0 ~ 括号中的值
int rand = random.nextInt(i);
swap(cards,i,rand);
}
}
private static void swap(ArrayList<Card> cards,int i, int j){
// 我们现在是面向对象,ArrayList虽然底层是一个数组,但是需要使用方法,才能操作数组的元素
// 并不能像数组一样,直接操作
// Card tmp = list[i];
Card tmp = cards.get(i);// 获取 顺序表中,对应下标的元素
// list[i] = list[j];
cards.set(i,cards.get(j));// 将 j下标的元素,赋给 i 下标的元素,
// list[j] = tmp;
cards.set(j,tmp);
}
public static void main(String[] args) {
ArrayList<Card> car = byCard();//创建牌
System.out.println(car);
shuFfle(car);//洗牌
System.out.println(car);
System.out.println("三个人轮流揭牌5张牌");
ArrayList<ArrayList<Card>> hand = new ArrayList<>();
ArrayList<Card> hand1 = new ArrayList<>();
ArrayList<Card> hand2 = new ArrayList<>();
ArrayList<Card> hand3 = new ArrayList<>();
hand.add(hand1);
hand.add(hand2);
hand.add(hand3);
//每个人轮流揭牌
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 3; j++) {
Card card = car.remove(0);
hand.get(j).add(card);
}
}
System.out.println("第一个人的牌:" + hand1);
System.out.println("第二个人的牌:" + hand2);
System.out.println("第三个人的牌:" + hand3);
System.out.println("剩下的牌:" + car);
}
}