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

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

相关文章
|
26天前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
45 2
|
3天前
|
安全 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版)
|
22天前
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
34 7
|
1月前
|
存储 算法 Java
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
36 2
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
|
21天前
|
存储 安全 Java
如何理解java的泛型这个概念
理解java的泛型这个概念
|
26天前
|
存储 缓存 Java
|
27天前
|
安全 Java
【Java 第六篇章】泛型
Java泛型是自J2 SE 1.5起的新特性,允许类型参数化,提高代码复用性与安全性。通过定义泛型类、接口或方法,可在编译时检查类型安全,避免运行时类型转换异常。泛型使用尖括号`&lt;&gt;`定义,如`class MyClass&lt;T&gt;`。泛型方法的格式为`public &lt;T&gt; void methodName()`。通配符如`?`用于不确定的具体类型。示例代码展示了泛型类、接口及方法的基本用法。
10 0
|
27天前
|
Java
【Java基础面试四十五】、 介绍一下泛型擦除
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。
|
27天前
|
Java
【Java基础面试四十四】、 说一说你对泛型的理解
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。
|
1月前
|
Java
【Java】内部类、枚举、泛型
【Java】内部类、枚举、泛型