【Java SE】认识泛型(下)

简介: 通过前面JavaSE的语法知识储备,如果现在让你们创建如标题一样的数组,你会怎么创建呢?

4、包装类的知识

4.1 基本数据类型和包装类

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了 一个包装类型。

image.png

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呢?因为你不知道形参接收的类型是哪个父类,只能去输出内容!

总结:通配符的下界,不能进行读取数据,只能写入数据。  

相关文章
|
5天前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
6 1
|
13天前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
23 5
|
10天前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
14 1
|
13天前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
16 2
|
1月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
30 9
Java——包装类和泛型
|
1月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
10天前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
11 0
|
1月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
1月前
|
Oracle Java 关系型数据库
Java(TM) Platform SE binary 已停止工作”的解决方法
Java(TM) Platform SE binary 已停止工作”的解决方法
134 2
|
17天前
|
Java
【Java】什么是泛型?什么是包装类
【Java】什么是泛型?什么是包装类
13 0