JavaSE高级开发-泛型:

JavaSE高级开发-泛型:

JDK1.5以后引入的三大常用新特性:

  1. 泛型:解决参数的转换问题;
  2. 枚举:enum;
  3. 注释:Annotation;

一、泛型问题引出:

假设需要定义一个可以描述坐标点的类Point,需要提供两个属性x,y。对于这两个属性的内容可以有如下的选择:

  • x = 10; y = 20;
  • x = 10.1; y = 20.1;
  • x = 东经80度; y = 北纬20度;

在Java中,只有Object型可以保存所有的类型。

【相关代码如下】:

class Point{
    private Object x;
    private Object y;
    public void setX(Object x) {
        this.x = x;
    }

    public Object getX() {
        return x;
    }

    public void setY(Object y) {
        this.y = y;
    }

    public Object getY() {
        return y;
    }
}
public class test{
    public static void main(String[] args) {
        Point point1 = new Point();
        //自动装箱并且向上转型为Object:
        point1.setX(10);
        point1.setY(20);
        //由Object强制向下转型为int,并且自动拆箱:
        int x = (Integer)point1.getX();
        int y = (Integer)point1.getY();
        System.out.println("整型坐标:("+x+","+y+")");
        Point point2 = new Point();
        //自动装箱并且向上转型为Object:
        point2.setX(10.1);
        point2.setY(20.1);
        //由Object强制向下转型为double,并且自动拆箱:
        double z = (Double)point2.getX();
        double w = (Double)point2.getY();
        System.out.println("浮点型坐标:("+z+","+w+")");
        Point point3 = new Point();
        //自动装箱并且向上转型为Object:
        point3.setX("东经20度");
        point3.setY("北纬40度");
        //由Object强制向下转型为String,并且自动拆箱:
        String a = (String)point3.getX();
        String b = (String)point3.getY();
        System.out.println("字符串坐标:("+a+","+b+")");

        //与point4代码对比:
        Point point5 = new Point();
        point5.setX(10);
        point5.setY("北纬40度");
        System.out.println("字符串坐标:("+point5.getX()+","+point5.getY()+")");
        //错误代码:在主方法中,是用户输入,但是这里用户并不知道x的
        //坐标是double,接收方也不知道用户的输入,就会产生问题:
        //ClassCastException(指两个没有关系的对象因为强转而出现的异常),所以说
        //向下转型并不安全,会带来隐患。
        Point point4 = new Point();
        point4.setX(10);
        point4.setY("北纬40度");
        String c = (String)point4.getX();
        String d = (String)point4.getY();
        System.out.println("字符串坐标:("+c+","+d+")");
    }
}

【注意】:

  • 在进行向下转型的过程中,会出现程序错误的问题,因此向下转型是不安全的操作,会带来隐患;
  • 当开发中的程序可以避免向下转型(通过泛型),也就意味着安全隐患被消除,尽量不要使用向下转型;

二、泛型类的基本使用:

(1)泛型的定义:

  • 指的是在类定义的时候,并不会设置类中的属性或方法中的参数的具体类型,而是在类使用的时候在进行定义。

(2)如何进行泛型的操作?

  • 做一个类型标记的声明。

(3)泛型类的基本语法:

class MyClass<T>{
	T  value1;
}

【注意】:

  • <>中的T称为类型参数,用于指代任何类型;
  • 实际上这个T你可以任意写,但是出于规范的目的,还是建议我们用单个大写字母来代替类型参数,常见的有:
    - T:代表一般的任意类;
    - E:代表Element的意思,或者Exception异常的意思;
    - K:代表Key的意思;
    - V:代表value的意思,通常与K一起配合使用;
    - S:代表Subtype的意思;
  • 如果一个类被的形式定义,那么它就被称为泛型类;

(4)泛型类的使用:

  • MyClass myClass = new MyClass;
  • MyClass myClass = new MyClass;

【注意】:

  • 泛型只能接受类,所以的基本数据类型必须使用包装类!
  • 泛型类可以接收多个类型参数;

三、泛型方法:

泛型不仅可以定义类,还可以单独来定义方法。

(1)泛型方法的定义格式:

class MyClass{
	public <T> void testMethod(T t){
  		System.out.println(t);
	 }
}

【注意】:

  • 泛型类与泛型方法不同的是,放在public static 之后,返回值的前面;
  • <>中的T,在类中是类型参数,但是在方法中,T是参数化类型,并不是运行时真正的参数;
  • 声明的类型参数,也可以当做返回值的类型,如下代码所示:
class MyClass{
    public static <T> T testMethod(T t){
        return t;
    }
}
public class Test1{
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        System.out.println(myClass.testMethod(10));
    }
}    

泛型方法与泛型类可以同时存在,如下代码所示:

class MyClass <T>{
        //泛型方法:使用别的类型参数名,避免与泛型类混淆
        public <E> E printT(E e){
            return e;
        }
        public void ptintOthers(){
            //整型:
            System.out.println(123);
        }
    }
    public class test{
        public static void main(String[] args) {
            MyClass myClass = new MyClass();
            myClass.ptintOthers();
            //String类型:
            System.out.println(myClass.printT("123木头人"));
        }
    }

【注意】:

  • 泛型类中的类型参数与泛型方法中的类型参数没有相应的联系,泛型方法始终以自己定义的类型参数为准;
  • 如果在泛型类中定义了一个泛型方法,为了避免与泛型类中的参数类型混淆,可以重新使用一个类型参数名,尽量使泛型类与泛型方法不重名;

四、通配符(重点)

(1)问题引入:
使用泛型类很好的避免了向下转型,使得程序变得安全,不在出现ClassCastException的问题,但是,使用泛型会带来参数不统一的问题,观察下面的代码:

class Message <T>{
    private T message;
    public T getMessage(){
        return message;
    }
    public void setMessage(T message){
        this.message = message;
    }
}
public class test{
    public static void main(String[] args) {
        Message<String> message1 = new Message<String>();
        message1.setMessage("hello world");
        fun(message1);
        
        /**
         * 注意:如果用户输入的不是String类型,就会出现新的问题
         * Message<String> message2 = new Message<String>();
         * message2.setMessage(123);
         * fun(message2);//这里就会出现错误
         */
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}

(2)解决泛型类型带来的参数不统一的问题——使用通配符
现在需要的解决方案:可以接收所有类型的泛型,但是又不能够让用户随意修改,这时候就可以通过通配符“?”解决此问题,示例代码如下所示:

class Message <T>{
    private T message;
    public T getMessage(){
        return message;
    }
    public void setMessage(T message){
        this.message = message;
    }
}
public class test{
    public static void main(String[] args) {
        Message<String> message1 = new Message<String>();
        message1.setMessage("hello world");
        fun(message1);

        Message<Integer> message2 = new Message<Integer>();
        message2.setMessage(123);
        fun(message2);
    }
    //注意:此时使用通配符“?”代表它可以接收任意类型,但是由于
    //类型不确定,所以无法修改。
    public static void fun(Message<?> temp){
        System.out.println(temp.getMessage());
    }
}

(3)在“?”的基础上又产生的两个子通配符:
? extends 类:设置泛型上限:
[例如]:
? extends Number,表示只能够设置Number或其子类,比如:Integer、Double等;

? super 类:设置泛型下限:
[例如]:
? super String,表示只能够设置String及其父类Object;

▼观察设置泛型上限:可以用在声明,不能修改

class Message<T extends Number>{
    private T message;
    public T getMessage(){
        return message;
    }
    public void setMessage(T message){
        this.message = message;
    }
}
public class test{
    public static void main(String[] args) {
       Message<Integer> message = new Message<Integer>();
       message.setMessage(123);
       fun(message);
    }
    //表示只能够设置Number类及其子类:
    //此时使用通配符“?”表示它可以接收任何类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Number> temp){
       //temp.setMessage(100);依然无法修改
       System.out.println(temp.getMessage());
    }
}

▼观察设置泛型下限:只能够用在方法参数中,可以修改

class Message<T>{
    private T message;

    public T getMessage() {
        return message;
    }
    public void setMessage(T message){
        this.message = message;
    }
}
public class test{
    public static void main(String[] args) {
        Message<String> message = new Message();
        message.setMessage("123木头人");
        fun(message);
    }
    public static void fun(Message<? super String> temp){
        //此时可以修改:
        temp.setMessage("123");
        System.out.println(temp.getMessage());
    }
}

五、泛型接口

泛型可以定义在类中,也可以定义在接口里面,这就是所谓的泛型接口。

(1)泛型接口的定义:

Interface IMessage<T> {
	public void print(T  t);
}

(2)对于这个接口,实现子类的两种做法:
【在子类定义的时候继续使用泛型】:

interface IMessage<T>{
    public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{
    @Override
    public void print(T t) {
        System.out.println(t);
    }
}
public class test{
    public static void main(String[] args) {
        IMessage<String> msg = new MessageImpl<>();
        msg.print("123木头人");
    }
}

【在子类实现接口的时候明确给出具体的做法:

interface IMessage<T>{
    public void print(T t);
}
class MessageImpl implements IMessage<String>{
    @Override
    public void print(String  t) {
        System.out.println(t);
    }
}
public class test{
    public static void main(String[] args) {
        IMessage<String> msg = new MessageImpl();
        msg.print("123木头人");
    }
}

六、类型擦除
泛型是在JDK1.5之后才引进的概念,由上述可以看出,泛型代码可以与之前的版本很好的兼容,这是因为,泛型信息仅仅存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,专业属于就叫做类型擦除,也就是说,泛型与普通的类在Java虚拟机内没有什么不同。

class MyClass<T>{
    private T message;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
    public void testMethod1(T t){
        System.out.println(t);
    }
}
public class test{
    public static void main(String[] args) {
        MyClass<String> myclass1 = new MyClass<>();
        MyClass<Integer> myClass2 = new MyClass<>();
        //注意:getClass() class<? extends MyClass>设置了泛型类的上限。
        System.out.println(myclass1.getClass() == myClass2.getClass());
        //结果是:true,因为MyClass<String>和MyClass<Integer>在JVM中的Class
        //都是MyClass.class
    }
}

【观察泛型中的类型擦除】:

import java.lang.reflect.Field;
class MyClass<T, E>{
    private T message;
    private E text;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
    public E getText() {
        return text;
    }
    public void setText(E text) {
        this.text = text;
    }
    public void print(T t, E e){
        System.out.println(t+","+e);
    }
}
public class test{
    public static void main(String[] args) {
        MyClass<String, Integer> myClass = new MyClass<>();
        //取得类对象
        Class cls = myClass.getClass();
        Field[] field = cls.getDeclaredFields();
        for (Field field1: field) {
            System.out.println(field1.getType());
        }
    }
}

【注意】:
在泛型类被类型擦除的时候,之前的泛型类参数部分如果没有指定上限,如则会被转译成普通的Object类型,如果指定了上限如则类型参数就会被替换为类型上限。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值