【泛型和内部类的关系】

之前我们初步认识了泛型和内部类,相关的知识点都比较简单,感兴趣的小伙伴可以点击下面的链接去看看哦

目录

泛型和内部类的关系

泛型的通配符

引例

< ? >

上限通配符

下限通配符

类型擦除


泛型和内部类的关系

成员内部类会复用外部类的泛型参数,但静态内部类不会复用外部类的泛型参数

public class MyOutter <T>{
    //成员内部类
    private class Inner{
        public void test(T t1){
            System.out.println(t1);
        }
    }
    //静态内部类
    private static class Inner1<T>{
        public void fun(T t2){
            System.out.println(t2);
        }
    }
    public static void main(String[] args){
        MyOutter<String> outter = new MyOutter();
        MyOutter<String>.Inner inner = outter.new Inner();
        inner.test("hello");
        inner.test(123);
        MyOutter.Inner1<Integer> inner1 = new Inner1<>();
        inner1.fun(100);
    }
}

可以看到,成员内部类的泛型参数和外部类的不一致时,就会报错,而静态内部类和外部类的泛型参数无关。

泛型的通配符

引例

何为泛型通配符?我们先看下面这引例:

public class Message<T>{
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }
    public static void fun(Message<String> msg){
        System.out.println(msg.getMsg());
    }

    public static void main(String[] args) {
        Message<String> msg = new Message<>();
        msg.setMsg("String类型");
        fun(msg);
    }
}
//输出:String类型

上面的代码在主方法创建泛型类的对象,确定类型为 String 类型,调用 fun 方法就输出了设置的msg。可是,当我们又创建一个不是 String 类型的对象时呢?

 可以看到,当对象类型为整型时就无法调用 fun 方法了,fun方法的方法参数规定了形参的接收类型,由于泛型的强类型校验,不同的类型完全不能通过校验,要匹配不同的类型就要重载多次 fun 方法。这时就引出了通配符来解决问题。

< ? >

一般用在方法参数,表示可以接收该类所有类型的泛型变量。它只能用在方法的形参上,不可以用在类定义和方法返回值上

    public static void fun(Message<?> msg){
        System.out.println(msg.getMsg());
    }
    public static void main(String[] args) {
        Message<String> msg = new Message<>();
        msg.setMsg("String类型");
        fun(msg);
        Message<Integer> msg1 = new Message<>();
        msg1.setMsg(123);
        fun(msg1);
        Message<Double> msg2 = new Message<>();
        msg2.setMsg(15.5);
        fun(msg2);
    }
//输出:String类型
       123
       15.5

要注意,<?>只能调用对象的getter方法来获取值,不能调用setter方法来设置值,因为此时无法确实传入的对象类型

<? extends 类>  上限通配符

表示 ? 可以指代任意类型,但该类型必须是后面类的类型或者它的子类,如图String不是继承与Number类,因此无法通过fun方法接收

 设置泛型的上限依然不能调用setter方法来设置值,以上面的代码为例, ? 是接收Number及其子类,但是子类之间是不能相互转换的

 extends 可以用在泛型类的定义上,它是唯一一个可以定义在类型参数的通配符:

<? super 类> 下限通配符

此时 ?表示可以指代任意类型,但是该类型必须是后面类的父类。只能用在方法参数,不能用在类的类型参数

例:<? super String>

此时?只能是String或者Object

 此时我们就可以使用setter方法设置值,因为不论设置何种类型,规定好的下限对象可以发生天然的向上转型变为父类

类型擦除

我们之前已经讲过,泛型是作用在编译期间的一种机制,实际上运行期间是没有这么多类的,泛型就是典型的语法糖。那运行期间是什么类型呢?这里就是类型擦除在做的事情。

语法糖是一个术语,指计算机语言 中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

类型擦除就是所有泛型类型参数,若没有设置泛型上限,则编译之后统一擦除为Object类型,若设置了泛型上限,则编译之后统一擦除为相应的泛型上限。

我们创建几个对象,来比较一下他们的类型地址:

public class Message<T >{
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }
    public static void fun(Message<? super String> msg){
        msg.setMsg("hello");
        System.out.println(msg.getMsg());
    }
    public static void main(String[] args) {
        Message<String> msg = new Message<>();
        Message<Integer> msg1 = new Message<>();
        Message<Double> msg2 = new Message<>();
        //.getClass()获取类型
        System.out.println(msg.getClass() == msg1.getClass());
        System.out.println(msg.getClass() == msg2.getClass());
    }
}
//输出:true
        true

这里使用到的 getClass 方法是反射中获取对象类型的方法。

我们知道 " == " 比较的是地址,可见三个对象定义时都是不同的泛型类型,编译之后的类型却是一样的,那么它们编译之后的类型是什么呢?

    public static void main(String[] args) throws NoSuchFieldException {
        Message<String> msg = new Message<>();
        Message<Integer> msg1 = new Message<>();
        Message<Double> msg2 = new Message<>();
        
        //获取一个类的属性
        Field field = msg.getClass().getDeclaredField("msg");
        Field field1 = msg1.getClass().getDeclaredField("msg");
        Field field2 = msg2.getClass().getDeclaredField("msg");

        //获取类型
        System.out.println(field.getType());
        System.out.println(field1.getType());
        System.out.println(field2.getType());
    }
//输出:
class java.lang.Object
class java.lang.Object
class java.lang.Object

可以看到,编译之后都成了 Object 类型,我们再演示一个设置了泛型上限的例子:

public class Message<T extends Number>{
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }

    public static void main(String[] args) throws NoSuchFieldException {

        Message<Integer> msg1 = new Message<>();
        Message<Double> msg2 = new Message<>();


        Field field1 = msg1.getClass().getDeclaredField("msg");
        Field field2 = msg2.getClass().getDeclaredField("msg");
                                    
        System.out.println(field1.getType());
        System.out.println(field2.getType());

    }
}
//输出:
class java.lang.Number
class java.lang.Number

### Java 、反射推断的关系 #### 的概念及其作用 (Generics)是指能够广适用的类,在编程中提供了更高的灵活性安全性。通过使用,可以在定义类、接口以及方法时不指定具体的数据类,而是采用形式参数表示数据类[^2]。 #### 反射机制概述 Java中的反射(Reflection)允许程序在运行期间获取有关其自身的结构信息并操作内部属性。这包括访问私有字段、调用未公开的方法或者实例化带有任意数量参数的新对象等强大功能。对于而言,`Class<T>`用于指明的具体类,并可以通过此类创建类的对象;当无法直接 `new` 出一个未知类的对象时,则可借助于反射技术实现这一点[^4]。 #### 类推断的工作原理 类推断(Type Inference)是JVM的一项特性,它使得编译器能够在某些情况下自动识别表达式的实际类,而无需显式声明。这意味着程序员不必总是写出冗长复杂的签名——只要上下文足够清晰,编译器就能理解意图并正确处理代码逻辑。例如,在涉及方法的地方,如果输入参数已经明确了所需的操作数种类,那么返回值也可以被合理推测出来[^3]。 #### 三者间的联系 - **与反射**:虽然擦除意味着大多数关于的信息会在编译后丢失,但是仍然有一些方式可以获得这些信息,比如通过通配符(`?`)配合上限或下限来保留部分类信息。此外,即使是在运行期失去了确切的类细节,依然可以用反射绕过这一限制,动态地构建具有特定类的对象。 - **与类推断**:两者共同提高了开发效率。一方面,减少了重复代码的数量;另一方面,类推断简化了语法书写过程。二者相辅相成,使开发者既能享受静态类系统的保障又能体验到接近脚本语言般的便捷性。 - **反射与类推断**:尽管反射打破了编译时期的许多约束条件,但它并不影响类推断的效果。实际上,正是由于存在类推断的支持,即便是在面对复杂多变的实际应用场景时,也能保持良好的性能表现而不必担心过多的手动类标注带来的负担。 ```java // 使用推断的例子 public <T> List<T> createList(T... elements){ return Arrays.asList(elements); } // 调用此函数时不需要显式指出 T 是什么类 createList(1, 2, 3); // 编译器会自动推导出 Integer[] ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值