1.包装类
1.1为什么要引入包装类?
Java中包含的8种基本数据类型不支持面向对象的编程机制。通过包装类可以将基本数据类型的值包装为引用数据类型的对象。
特殊:char对应的包装类为:Character;int对应的包装类为:Integer。
其它的基本数据类型所对应的包装类的名称与其基本数据类型一样,只不过首字母需要大写。
1.2基本概念
包装类和基本数据类型在进行转换时,引入了自动装箱和自动拆箱的概念。其中自动装箱是指将基本数据类型赋给对应的包装类变量;反之,拆箱就是将包装类对象类型直接赋给一个对应的基本数据类型变量。
public class Demo {
public static void main(String[] args) {
// 定义一个基本类型的变量a,并赋值为20
int a = 20;
// 自动装箱:将基本数据类型的变量a赋给Integer类型的变量b
Integer b = a;
System.out.println(b);
// 自动拆箱:将Integer类型的变量b赋给基本类型的变量a
int c = b;
System.out.println(c);
}
}
1.3实现基本数据类型、包装类以及字符串之间的相互转换。
1>通过引用数据类型字符串String类的valueOf()方法可以将8种基本数据类型转换为对应的字符串类型。
2>通过8种包装类的静态方法valueOf()既可以将对应的基本数据类型转换为包装类,也可以将变量内容匹配的字符串转换为对应的包装类(Character包装类除外)。
3>通过8种包装类的有参构造方法同样既可以将对应的基本数据类型转换为包装类,也可以将
变量内容匹配的字符串转换为对应的包装类(Character包装类除外)。
4>通过8种包装类的静态方法parseXxx()可以将变量内容匹配的字符串转换为对应的基本数据类型。
5>包装类都重写了Object类中的toString()方法,以字符串的形式返回被包装的基本数据类型的值。
public class Demo {
public static void main(String[] args) {
int num = 123;
// 1.通过String.valueOf()方法将基本类型转换为字符串
String string = String.valueOf(num);
System.out.println("将int变量转换为字符串的结果:" + string);
// 2.通过包装类的将基本类型和字符串转换为包装类
String str = "998";
Integer integer = Integer.valueOf(num);
Integer integer2 = Integer.valueOf(str);
System.out.println("将int变量转换为包装类的结果:" + integer);
System.out.println("将字符串变量转换为包装类的结果:" + integer2);
// 3.通过包装类的有参构造方法将基本类型和字符串转换为包装类
Integer integer3 = new Integer(num);
Integer integer4 = new Integer(str);
System.out.println("通过构造器将int变量转换为包装类的结果:" + integer3);
System.out.println("通过构造器将字符串变量转换为包装类的结果:" + integer4);
// 4.通过包装类的parseXxx()静态方法将字符串转换为基本类型
int parseInt = Integer.parseInt(str);
System.out.println("将字符串转换为基本类型的结果:" + parseInt);
// 5.通过包装类的toString()方法将包装类转换为字符串
String string2 = integer.toString();
System.out.println("将包装类转换为字符串的结果:" + string2);
}
}
运行结果:
注意:
1>除了Character外,包装类都有valueOf(String s)方法,可以根据String类型的参数创建包装类对象,但参数字符串s不能为null,而且字符串必须是可以解析为相应基本类型的数据,否则虽然编译通过,但运行时会报错。
Integer integer = Integer.valueOf("123");//合法
Integer integer = Integer.valueOf("12a");//不合法
2>除了Character外,包装类都有parseXxx(String s)的静态方法,将字符串转换为对应的基本类型的数据。参数s不能为null,而且同样必须是可以解析为相应基本类型的数据,否则虽然编译通过,但运行时会报错。
int integer=Integer.parseInt("123");//合法
Integer in=Integer.parseInt("itcast");//不合法
2.泛型
集合可以存储任意类型的对象元素,但是当把一个对象存入集合后,集合会“忘记”这个对象的类型,将该对象从集合中取出时,这个对象的编译类型就统一变成了Object类型。换句话说,在程序中无法确定一个集合中的元素到底是什么类型,那么在取出元素时,如果进行强制类型转换就很容易出错,而且操作麻烦。
引入泛型后限定了集合元素的数据类型,使得限定的数据类型程序在编译期间就会出现报错提示,避免程序在运行期间发生错误。
泛型只能接受类,所有的基本数据类型必须使用包装类。
2.1泛型定义类
示例1:泛型类引入多个类型参数以及使用
class MyClass<T,E>{
T value1;
E value2;
}
public class Demo {
public static void main(String[] args) {
MyClass<String,Integer> myclass=new MyClass<String,Integer>();
}
}
示例2:使用泛型定义Point类
//T表示参数,是一个占位的标记;如果有多个泛型就继续在后面追加
class Point<T> {
private T x;
private T y;
public void setX(T x) {
this.x = x;
}
public T getX() {
return x;
}
public void setY(T y) {
this.y = y;
}
public T getY() {
return y;
}
}
public class Demo {
public static void main(String[] args) {
// 设置数据
Point<String> point = new Point<>();
point.setX("东经80度");
point.setY("北纬20度");
// 取出数据
String x = point.getX();// 避免了向下转型
String y = point.getY();
System.out.println("x=" + x + ",y=" + y);
}
}
运行结果:
2.2泛型方法
示例1:泛型方法定义
class MyClass{
public <T> void testMethod(T t) {
System.out.println(t);
}
}
示例2:使用类型参数做返回值的泛型方法
class MyClass{
public <T> T testMethod(T t) {
return t;
}
}
示例3:泛型方法与泛型类共存
class MyClass<T>{
public void testMethod1(T t) {
System.out.println(t);
}
public <T> T testMethod2(T t) {
return t;
}
}
public class Demo {
public static void main(String[] args) {
MyClass<String> myclass=new MyClass<>();
myclass.testMethod1("hello,泛型类");
Integer i=myclass.testMethod2(100);
System.out.println(i);
}
}
运行结果:
解释:上述代码中,MyClass是泛型类,testMethod1是泛型类中的普通方法,而testMethod2是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准。
泛型类的实际类型参数是String,而传递给泛型方法的类型参数是Integer,两者互不相干。
但是,为了避免混淆,如果在泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,MyClass代码可以更改为这样:
class MyClass<T>{
public void testMethod1(T t) {
System.out.println(t);
}
public <E> E testMethod2(E e) {
return e;
}
}
2.3通配符
在程序类中追加了泛型的定义后,避免了ClassCastException的问题,但是又会产生新的情况:参数的统一问题。
示例1:导入
class Message<T> {
private T message;
public void setMessage(T message) {
this.message = message;
}
public T getMessage() {
return message;
}
}
public class Demo {
public static void main(String[] args) {
Message<String> message = new Message();
message.setMessage("hello");//如果泛型里面的类型设置不是String,而是Integer
fun(message); //这里会出现错误,只能接收String
}
public static void fun(Message<String> temp) {
System.out.println(temp.getMessage());
}
}
运行结果:
针对上面的问题,可以有如下的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符“?”来处理。
示例2:使用通配符
public class Demo {
public static void main(String[] args) {
Message<String> message = new Message();
message.setMessage(88);
fun(message);
}
public static void fun(Message<?> temp) {
//temp.setMessage(100);//无法修改
System.out.println(temp.getMessage());
}
这样的话,在“?”的基础上又产生了两个子通配符:
?extend类:设置泛型上限:
例如:?extends Number,表示只能够设置Number或其子类,例如:Integer、Double。
?super类:设置泛型下限:
例如,?super String,表示只能够设置String及其父类Object。
示例3:设置泛型上限
class Message<T extends Number> {// 设置泛型上限
private T message;
public void setMessage(T message) {
this.message = message;
}
public T getMessage() {
return message;
}
}
public class Demo {
public static void main(String[] args) {
Message<Integer> message = new Message();
message.setMessage(55);
fun(message);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Number> temp) {
// temp.setMessage(100);//仍然无法修改
System.out.println(temp.getMessage());
}
}
运行结果:
示例4:设置泛型下限
class Message<T extends Number> {// 设置泛型上限
private T message;
public void setMessage(T message) {
this.message = message;
}
public T getMessage() {
return message;
}
}
public class Demo {
public static void main(String[] args) {
Message<Integer> message = new Message();
message.setMessage(55);
fun(message);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? super Integer> temp) {
temp.setMessage(100);//此时可以修改
System.out.println(temp.getMessage());
}
}
运行结果:
总结:上限可以用在声明,不能修改;而下限只能用在方法参数,可以修改内容。
2.4泛型接口
泛型除了可以定义在类中,也可以定义在接口里面,这种情况我们称之为泛型接口。
示例1:定义一个泛型接口
interface TMessage<T>{//在接口上定义了泛型
public void print(T t);
}
示例2:在子类定义时继续使用泛型
interface IMessage<T> {// 在接口上定义了泛型
public void print(T t);
}
class MessageImp1<T> implements IMessage<T> {
public void print(T t) {
System.out.println(t);
}
}
public class Demo {
public static void main(String[] args) {
IMessage<Integer> message = new MessageImp1();
message.print(88);
}
}
运行结果:
示例3:在子类实现接口的时候明确给出具体类型
interface IMessage<T> {// 在接口上定义了泛型
public void print(T t);
}
class MessageImp1 implements IMessage<Integer> {
public void print(Integer t) {
System.out.println(t);
}
}
public class Demo {
public static void main(String[] args) {
IMessage<Integer> message = new MessageImp1();
message.print(88);
}
}
运行结果:
2.5类型擦除
泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
通俗地讲,泛型类和普通类在Java虚拟机内是没有什么特别的地方。
示例1:
class MyClass<T> {
private T message;
public void setMessage(T message) {
this.message = message;
}
public T getMessage() {
return message;
}
}
public class Demo {
public static void main(String[] args) {
MyClass<String> myClass1 = new MyClass<>();
MyClass<Integer> myClass2 = new MyClass<>();
System.out.println(myClass1.getClass() == myClass2.getClass());
}
}
运行结果:
解释:打印结果为true是因为MyClass和MyClass在JVM中的Class都是myClass.class。
实例2:观察类型擦除
import java.lang.reflect.Field;
class MyClass<T, E> {
private T message;
private E text;
public void setMessage(T message) {
this.message = message;
}
public T getMessage() {
return message;
}
public void setText(E text) {
this.text = text;
}
public E getText() {
return text;
}
}
public class Demo {
public static void main(String[] args) {
MyClass<String, Integer> myClass1 = new MyClass<>();
Class cls = myClass1.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getType());
}
}
}
运行结果:
**添加:**在泛型类被类型擦除的时候,之前的泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object类型,如果指定了上限如,则参数类型就会被替换成类型上限。