4、包装类的知识
4.1 基本数据类型和包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了 一个包装类型。
4.2 装箱和拆箱
装箱和拆箱也可也被称为装包和拆包。
装箱:将一个基本数据类型值放入对象的某个属性中。
拆箱:将一个包装类型中的值取出放到一个基本数据类型中。
这里我们举例来更清楚的认识装箱和拆箱:
public class Test { public static void main(String[] args) { int a = 10; Integer integer1 = new Integer(a); //手动装箱 Integer integer2 = Integer.valueOf(100); //手动装箱 int b = integer1.intValue(); //手动拆箱 } }
4.3 自动装箱和拆箱
由上面的例子我们可以看出,手动装箱和拆箱会带来不少的代码量,为了减少开发者的负担,Java中提供了自动转换机制,比如:
public class Test { public static void main(String[] args) { Integer integer = 100; //自动装箱 int a = integer; //自动拆箱 } }
4.5 一道面试题
以下代码输出什么?
public class Test { public static void main(String[] args) { Integer a1 = 100; Integer a2 = 100; System.out.println(a1 == a2); Integer a3 = 200; Integer a4 = 200; System.out.println(a3 == a4); } }
结果是:true false
为什么是这样的答案?这里我们去看一下对应的字节码文件再分析:
通过观察字节码文件,我们可以看到,在自动装箱的过程中,调用了 Integer.valueOf 方法,那么我们就去看一看 valueOf 方法中做了一件什么事:
通过查看源码,我们也能看出此方法将始终缓存 -128到127范围内的值, 通过查看对应的 low 和 high 值也可也发现 low为 -128,high为127,cache 是一个缓存数组。
接着我们来阅读下这段代码的操作,如果传入的值是介于 -128和127 之间,则直接返回缓存数组对应下标的值,比如传入的值是 -127 也就返回 chache[-127+(-(-128))],也即1下标位置的值!
如果超出了 -128到127 的范围则是新 new 一个对象返回,只要是 new 就一定是一个新对象,地址也是唯一的。
而且引用类型用 == 比较,比较的是引用的对象的地址,看完上面的介绍,你能弄明白为什么输出 true 和 false 吗?
5、泛型方法
定义泛型方法的语法:
方法限定符 <类型形参列表>返回值类型 方法名称(形参列表) {
//...code
}
5.1 普通泛型方法
这里我们就举一个很简单的例子:
public class Test { public <T> T getValue(T value) { return value; } public static void main(String[] args) { Test test = new Test(); int ret = test.<Integer>getValue(150); //不使用类型推导 System.out.println(ret); double d = test.getValue(12.5); //使用类型推导 System.out.println(d); } }
这就是泛型方法,这里面有个关键词,类型推导,什么是类型推导呢?
类型推导就是编译器会根据你传参的数据,自动推断出你要传递的类型实参,你也可以不使用类型推导,他们的效果都是一样的。
5.2 静态泛型方法
既然有普通泛型方法,同理,也有静态的泛型方法,也就是在修饰符后面加上 static,静态泛型方法跟普通静态方法一样,都是通过类名访问,不依赖于对象:
public class Test { public static<T> T getValue(T value) { return value; } public static void main(String[] args) { int ret = Test.<Integer>getValue(150); //不使用类型推导 System.out.println(ret); double d = getValue(12.5); //使用类型推导(静态方法可以直接访问同类中静态方法,可以不借助类名) System.out.println(d); } }
6、通配符
6.1 引出通配符
我们先来看这样的一段代码:
class Message<T> { private T message ; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class TestDemo { public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); } public static void main(String[] args) { Message<String> message = new Message<>(); message.setMessage("欢迎来到篮球哥的博客!"); fun(message); } }
如果你仔细观察,TestDemo 类中的 fun 方法是有局限性的,他的形参就限制了传过来的 Missage类的类型必须是String,也就是说,形参能接收的对象的类型参数必须是String类型。
所以如果我们 new Missage对象时,类型实参传的是 Integer 呢?fun方法就会报错:
所以为了解决以上的问题,就有了通配符的概念!
6.2 认识通配符
泛型T是确定的类型,一旦传类型了,就定下来了,而通配符的出现,就会使得更灵活,或者说更不确定,就好像他是一个垃圾箱,可以接收所有的泛型类型,但又不能让用户随意更改!
通配符:?
现在我们就把上面的代码更改一下,运用上通配符:
public class TestDemo { public static void fun(Message<?> temp){ System.out.println(temp.getMessage()); } public static void main(String[] args) { Message<Integer> message1 = new Message<>(); message1.setMessage(123); fun(message1); Message<String> message2 = new Message<>(); message2.setMessage("欢迎来到篮球哥的博客!"); fun(message2); } }
这样我们的代码就不会出错,但是,你不能通过 fun 方法去修改你传递对象的内容,为什么呢?
站在 fun 的角度,他使用了 ? 接收可以任意泛型类,所以他不能确定自己接收了什么对象的!也就无法对对象的值进行更改!
这样代码还是不够好,如果真的什么泛型类都能接收,那不是乱套了,所以在此基础上,又增加了通配符的上界和下界!
6.3 通配符的上界
语法:<? extends 上界> 例如:<? extends Person>
表示只能接收的实参类型是 Person 或者 Person的子类!
图例:
这里我们写一段伪代码,更改上面用例的方法:
public static void fun(Message<? extends Person> temp){ //temp.setMessage(new Student()); //仍然无法修改! //temp.setMessage(new Person()); //仍然无法修改! Person person = temp.getMessage(); System.out.println(person); }
为什么还是不能修改对象的属性呢?因为 temp 接收的是 Person 或 Person的子类,此时接收的是哪个子类无法确定,也就无法设置对象的属性。
因为我们知道只能接收 Person以及他的子类,所以我们就可以拿 Person 类型来接收 getMessage 的对象,因为 Person是他们的父类,获取的是子类对象就可以实现向上转型,是安全的。
总结: 通配符的上界,不能进行写入数据,只能进行读取数据。
6.4 通配符的下界
语法:<? extends 下界> 例如:<? super Person>
表示只能接收的实参类型是 Person 或者 Person的父类!
图例:
这里我们写一段伪代码,更改上面用例的方法:
public static void fun(Message<? super Person> temp){ temp.setMessage(new Student()); //可以修改,因为添加的是他的子类 temp.setMessage(new Person()); //可以修改,因为添加的是他本身 //Person person = temp.getMessage(); // 不能接收,不知道获取的是哪个父类 System.out.println(temp.getMessage()); //只能输出 }
为啥下界就可以设置对象的属性呢?因为只能接收本身以及父类的类型,所以我们可以setMessage 传子类对象,但是不能传递父类,因为修改成子类对象是向上转型是安全的,如果 setMessaget 传父类对象的话就是向下转型则不安全!
为啥不能 getMessage呢?因为你不知道形参接收的类型是哪个父类,只能去输出内容!
总结:通配符的下界,不能进行读取数据,只能写入数据。