一丶关于泛型
所谓泛型,主要就是为了解决类型锁死的问题,什么意思?
就是说在一个类中,如果我们设置了这个类成员变量的类型,这个时候如果想要接收其他类型,那么就不行,也就是说类型转换,转换不了的情况。
关于这一点我们用一个案例来进行解释,因为ArrayList可以接收任意类型参数,所以:
public static void main(String[] args) {
//使用ArrayList来接收任意类型的参数
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add(10086);
//全部转化为字符串来进行查看
String s = null;
for(int i = 0;i < arrayList.size();i++){
s = (String)arrayList.get(i);
}
System.out.println(s.length());
}
这里运行下来的话,会报出一个异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
虽然说这个问题在运行阶段被检测了出来,但是如果说是编译阶段就可以直接检测出来不好吗?
那么为了解决这个问题我们怎么办呢?
那么我们在初始设置这个变量的时候,我们就让它的类型不确定,然后使用它的时候再定义。那个时候我们用的是什么类型他就是什么类型。如果这个时候类型异常,就是类型转换异常,这个时候编译器会直接报错。
是不是感觉有点绕?
那么我们用一个例题来进行具体解释。
泛型例题
public class Generic {
private int[] array;
int size;
//构造器直接初始化数组
public Generic(int size) {
array = new int[size];
size = 0;
}
//添加元素
public void add(int data){
//先检测是否有位置进行添加
if(size == array.length){
System.out.println("位置已满");
}
//如果没满就进行尾插
array[size++] = data;
}
//获取元素
public int get(int data){
if(data > size){
System.out.println("下标越界了");
}
return array[data];
}
//获取数组长度
public int size(){
return size;
}
public static void main(String[] args) {
Generic gen = new Generic(10);
gen.add(1);
gen.add(2);
gen.add(3);
gen.add(4);
System.out.println(gen.get(3));
System.out.println(gen.size());
}
}
很明显这个最后运行结果是:
4
4
那么这里问题来了,如果说我想要给这个数组中储存其他类型我要怎么办呢?
没办法,我在刚开始定义的时候,我就定义的是int类型,后面也只能使用int类型来进行填入。
那么如果要重新获取其他类型就要重写了,比如:
public class MyTest {
Persion[] array;
int size;
//构造器直接初始化数组
public MyTest(int size) {
array = new Persion[size];
size = 0;
}
//添加元素
public void add(Object data){
//先检测是否有位置进行添加
if(size == array.length){
System.out.println("位置已满");
}
//如果没满就进行尾插
array[size++] = (Persion) data;
}
//获取元素
public Persion get(int data){
if(data > size){
System.out.println("下标越界了");
}
return array[data];
}
//获取数组长度
public int size(){
return size;
}
public static void main(String[] args) {
MyTest myA
那可能rray = new MyTest(10);
myArray.add(new Persion("HanMeiMei", "女", 12));
myArray.add(new Persion("LiLeiLei", "男", 13));
myArray.add(new Persion("LiLeiLei", "男", 13));
myArray.add(new Persion("LiLeiLei", "男", 13));
System.out.println(myArray.size());
}
}
可以发现这里特别麻烦,因为每一次的引入都要重写一个类然后再使用。
那可能会有小伙伴说我们直接写一个类,然后类型参数设置为Object就可以了。
这样真的可以吗?我们来试试:
关于Object类型
还是直接给代码:
public class MyArray {
private Object[] array;
private int size;
public MyArray(int initCap){
array = new Object[initCap];
size = 0;
}
public void add(Object p){
if(size == array.length){
System.out.println("元素已经存满了,无法再添加元素");
}
array[size++] = p;
}
public Object get(int index){
if(index >= size){
throw new IndexOutOfBoundsException("数组下标越界");
}
return array[index];
}
public int size(){
return size;
}
public static void main(String[] args) {
MyArray myArray = new MyArray(10);
myArray1.add(new Person("HanMeiMei", "女", 12));
myArray1.add(new Person("LiLeiLei", "男", 13));
myArray1.add(new Person("LiLeiLei", "男", 13));
myArray1.add(new Person("LiLeiLei", "男", 13));
System.out.println(myArray1.size());
MyArray myArray2 = new MyArray(10);
myArray2.add(new Animal("小七", "金黄色"));
myArray2.add(new Animal("元宝", "灰黑色"));
System.out.println(myArray2.size());
}
}
//上述代码和前面差不多一样,就是参数是Object类就是了,然后下面补上两种引用类型
class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
}
class Animal {
String name;
String color;
public Animal(String name, String color) {
this.name = name;
this.color = color;
}
}
运行结果如下:
好像是没有什么太大的问题,毕竟Object是可以接收一切的类型。
但是真的没有问题嘛?
问题一:转型
作为Object类,我们可以用他来接收一切类型,那么就意味着我们可以向上转型和向下转型,可是向下转型是不安全的,很容易出错。
我们修改原来的main方法:
public static void main(String[] args) {
MyArray myArray1 = new MyArray(10);
myArray1.add(new Person("HanMeiMei", "女", 12));
myArray1.add(new Person("LiLeiLei", "男", 13));
myArray1.add(new Person("LiMing", "男", 13));
myArray1.add(new Person("Wangtiezhu", "男", 13));
myArray1.add(new Animal("小七", "金黄色"));
myArray1.add(new Animal("元宝", "灰黑色"));
System.out.println(myArray1.size());
Person p1 = (Person) myArray1.get(1);
System.out.println(p1.name);
Animal a1 = (Animal)myArray1.get(4);
System.out.println(a1.name);
}
然后运行此代码
可以看出代码是没有什么问题的,但是如果说Object数组里面元素特别多,你这个时候
就
很
难
分
辨
出
来
到
底
是
那
个
子
类
对
象
的
引
用
指
向
了
父
类
对
象
\color{red}{就很难分辨出来到底是那个子类对象的引用指向了父类对象}
就很难分辨出来到底是那个子类对象的引用指向了父类对象。
所以这个时候,就很容易出现错误。
问题二:关于基础数据类型
这里其实就是一个问题,Object可以接收int等基础数据类型嘛?
答案是:
不可以
关于这一点的解答,我们接下来继续往后看。
关于泛型的使用
那么为了解决上述的问题一,我们这里就需要使用泛型,就是说我们在定义的时候就先不要给它定义类型。然后用的时候再说,具体如下:
public class MyArray<E> {
private E[] array;
private int size;
//建造构造器
public MyArray(int size){
array = (E[]) new Object[size];
size = 0;
}
//构造添加方法
public void add(E e){
if(size == array.length){
throw new ArrayIndexOutOfBoundsException("没地方添加啦!");
}
array[size++] = e;
}
//得到该数组下标
public E get(int index){
if(index > size){
throw new ArrayIndexOutOfBoundsException("数组下标越界啦!");
}
return array[index];
}
public int size(){
return size;
}
}
class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
}
class Animal {
String name;
String color;
public Animal(String name, String color) {
this.name = name;
this.color = color;
}
}
测试泛型
我们接下来进行测试,也就是准备添加main方法。
这里的话因为使用array1时候定义的是Person类型,所以说添加Animal类型就不行。这里注意了:
1. 泛型是作用在编译期间的一种机制,即运行期间没有泛型的概念
2. 泛型代码在运行期间,会将类型擦除掉,底层实际使用的也是Object实现的,
但是用户不需要在代码中做强制类型转换的事情了,让编译器在编译阶段检查。
二丶关于包装类
这里的包装类就是为了解决上述问题二,就是关于接收int类型问题。
int不是一个类,所以不能使用Object来进行接收,这个时候我们提供了一个类叫做包装类。
包装类也就是单纯为了接收基础数据类型而生的。具体如下:
除了int类型和char类型其他类型包装都是原形。
具体使用还是用咋们测试泛型的代码:
关于包装类这里需要说的其实就是两点。也就是上面代码的隐藏问题,array.add添加的真的是int嘛?
自动装箱
这里其实指的就是基础数据类型到对应的包装类
public static void main(String[] args) {
//看一下这三种创建方式有区别嘛?
Integer a = Integer.valueOf(1);
Integer b = 1;
Integer c = (Integer) 1;
System.out.println(a == b && b== c );
}
其实是没有的,直接看结果。
自动拆箱
自动拆箱就是包装类到对应基础数据类型的过程
看一下下面的三种拆箱方法:
public static void main(String[] args) {
Integer a = Integer.valueOf(1);
Integer b = 1;
Integer c = (Integer) 1;
System.out.println(a == b && b== c );
//看这里!!!!看这里!!!看下面代码!!!
int i = a.intValue();
int j = a;
int k = (int)a;
System.out.println(i == j && j == k);
}
}
关于面试题
看这段代码:
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}
运行结果:
true;
false;
为什么呢?
这里其实就是一个基础数据类型到对应包装类的自动装箱的过程,在这个过程内他会使用IntegerCache 来缓存一定范围的值,IntegerCache 默认情况下范围为:-128~127。
本题中的 127 命中了 IntegerCache,所以 c 和 d 是相同对象。