内部类和泛型

本文详细探讨了Java中的内部类,包括成员内部类、静态内部类、方法内部类和匿名内部类,阐述了它们的创建、访问权限以及与外部类的关系。此外,还讲解了泛型的引入、基本使用、泛型方法和泛型接口,强调了泛型在类型校验和安全性方面的优势,并分析了泛型与内部类之间的关系以及泛型的通配符和类型擦除概念。
摘要由CSDN通过智能技术生成

目录

一、内部类

1.内部类分类

a.成员内部类

1)成员内部类对象的创建

2)成员内部类能否定义静态属性,能否访问成员域和静态域

3)对外部类来说,可否在外部类的静态方法中使用成员内部类

b.静态内部类

1)创建静态内部类对象

2)静态内部类能否拥有成员域

3)静态内部类能否访问外部类的成员域

4)成员内部类与静态内部类小结

c.方法内部类

d.匿名内部类

概念

匿名内部类默认会继承一个类或实现一个接口

2.特点

1)内部类和外部类可以方便可以方便的互相访问彼此的private属性

2) 使用内部类可以曲线救国来实现“多继承”

3.内部类使用方法/规则

4.内部类的设计

二、泛型

1.泛型的引入

2.泛型的基本使用

使用泛型改造上面的point类

3.泛型方法

特点

4.泛型接口

1)子接口仍然保留泛型

2)子接口明确类型(接收String类型)

5.泛型相关总结

6.泛型和内部类的关系

1)成员内部类覆用外部类的泛型参数

2)静态内部类不覆用外部类的泛型参数

7.泛型的通配符

1):一般用在方法参数上

此时fun方法内部可否调用msg.set方法?(即可否setter?)

2)设置泛型上限:

a.规定了泛型的上线能否直接调用set设置值呢?

b.extends可以用在泛型的类的定义上

3)下限通配符:

a.下线通配符只能用在方法参数,不能作用于类的类型参数

b.下线通配符可以调用对象的setter方法

8.泛型的类型擦除


一、内部类

所谓内部类,就是将类结构的定义套在另一个类的内部

1.内部类分类

分四种:成员内部类;静态内部类;方法内部类;匿名内部类(Lambda表达式的前身)

eg:现实生活中处处存在内部类:汽车发动机和汽车-发动机这个类套在汽车类的内部【也属于一种封装(保护性)】

a.成员内部类

(类比成员方法:成员方法能访问静态域,不能拥有静态域(无法定义静态属性))

直接定义在类中,不加任何修饰符(static)定义的类,就是成员内部类--成员方法或属性。必须依赖外部类而存在,先产生一个外部类对象才能产生成员内部类对象。

public class Outter {//外部类
    //发动机-私有内部类,对外部完全隐藏,只在类的内部使用
    private class Inner{

    }
}

内部类和外部类可以方便可以方便的互相访问彼此的private属性

1)成员内部类对象的创建

1)外部类的内部创建:就和使用其它类没有区别

内部类名称  内部类引用=new  内部类();

 2)外部类的外部创建--前提:内部类对外部可见

外部类名称.内部类  引用=new  外部类().new 内部类();
Outter.Inner inner=new Outter().new Inner();
inner.test();

2)成员内部类能否定义静态属性,能否访问成员域和静态域

      不能定义静态属性:成员内部类必须要依赖于外部类,必须有外部类的对象才能产生,若成员内部类有静态属性(静态属性:没对象也能用),没有外部类对象也能访问了,矛盾。

      能访问成员域和静态域:外部类的静态域没有外部类对象都能访问,更何况成员内部类已经有一个外部类对象了。

3)对外部类来说,可否在外部类的静态方法中使用成员内部类

即可否在外部类main(主方法:静态方法-没对象也能用)中创建内部类对象?

      这句话类比相当于在静态方法中调用成员变量,肯定不可以调用。外部类的静态方法中根本没有外部类的对象,没有外部类的对象如何调用成员内部类。

b.静态内部类

(类比静态方法:静态方法能访问静态域,不能访问成员域)

      定义在类中,使用static修饰的内部类,静态内部类不需要依赖外部类对象就可以使用。静态内部类除了套在一个类的内部以外和其他类没有任何区别。

1)创建静态内部类对象

1)外部类的内部

ublic class Outter1 {
    static class Inner{
    }
    public void test(){//外部类成员方法
        Inner inner=new Inner();
    }
    public static void main(String[] args) {//外部类静态方法
        Inner inner=new Inner();
    }
}

问题:why外部类普通方法和静态方法都能创建静态内部类对象?

分析:类比类中静态变量,类中的静态属性没有类的对象就能使用,所以类的静态方法可以调用,成员方法更可以。(没对象都能调更何况有对象)

2)外部类的外部

外部类.内部类 引用=new 外部类.内部类();
Outter1.Inner inner=new Outter1.Inner();

静态内部类就是一个普通的类,只是套在了一个类的内部而已

2)静态内部类能否拥有成员域

      普通类可以定义自己的成员变量,静态内部类可以存在成员域,即可以拥有自己的成员属性,只是不能访问外部类的成员域而已。

public class Outter1 {
    //静态内部类就是个普通类,只是把它套在了outter1的里面而已
    static class Inner{
        static int num=10;
        int age=1;//成员属性
    }

3)静态内部类能否访问外部类的成员域

      不能。外部类的成员变量是有对象才能访问,此时内部类是静态,就没有外部类对象,因此无法直接访问。

4)成员内部类与静态内部类小结

1)成员内部类可以访问外部类的成员域和静态域,但是不能拥有静态域

2)不能在外部类的静态方法中使用成员内部类

3)静态内部类可以拥有成员域,但不能直接访问外部类的成员域可以通过new一个外部类对象来访问】,但是静态域可以随便访问

c.方法内部类

      直接定义在方法内部的类,不允许使用任何访问修饰符,对外部完全隐藏【类比局部变量,出了这个方法类就没了】。

public class Outter2 {
    public void fun(){
        //方法内部类,出了这个方法就没了,不能出现任何访问修饰符和static
        class Inner{
            
        }
    }
}

方法内部类无法定义static域。方法内部类中若使用了方法的形参,该形参为隐式的final声明(JDK8之后,JDK8之前方法内部使用了形参必须要使用final声明)。除此外,和成员内部类用法相同。

分析:Inner未使用num这个变量,若使用:

分析:报错:方法内部类使用了方法的形参,形参num现在就是隐式的final声明,值不能修改

不修改值就可以执行【即不进行+-等运算操作】

d.匿名内部类

(lambda表达式的前身【函数式编程】)

概念

匿名内部类是方法内部类的特殊版本,直接不写类名称。99%用在方法传参过程,匿名内部类遵从方法内部类所有要求,并多出相关要求:

匿名内部类默认会继承一个类或实现一个接口

继承普通类或抽象类都可以,一般是继承抽象类

1)普通方法【不使用匿名类方式的接口传参使用】

public class Outter3 {
    public static void fun(IMessage msg){
        msg.printMsg();
    }

    public static void main(String[] args) {
        IMessage msg=new IMessageImpl();
        fun(msg);
    }
}
interface IMessage{
    void printMsg();
}
class IMessageImpl implements IMessage{

    @Override
    public void printMsg() {
        System.out.println("普通用法");
    }
}

2)使用匿名内部类

public class Outter3 {
    public static void fun(IMessage msg){
        msg.printMsg();
    }

    public static void main(String[] args) {
        fun(new IMessage() {//不是创建了接口,是创建了个匿名内部类,只不过这个类实现了IMessage接口。里面覆写了printMsg方法
            //fun()括号中一大堆的这就是一个匿名内部类
            //等同于创建了一个类实现了IMessage接口,创建了该类的对象
            @Override
            public void printMsg() {
                System.out.println("匿名内部类的用法");
            }
        });
    }
}
interface IMessage{
    void printMsg();
}

 

2.特点

1)内部类和外部类可以方便可以方便的互相访问彼此的private属性

public class Outter {
    private String msg="outter类中的msg属性";
    //-----------------------------------------------------------------------
    private class Inner{
        private int num=10;
        public void test(){
            //直接访问外部类中的msg属性
            System.out.println(msg);
        }
    }
    //----------------------------------------------------------------------
    public void fun(){
        //通过内部类的对象访问内部类的私有属性
        Inner inner=new Inner();
        //访问Inner类的私有属性
        System.out.println(inner.num);
        inner.test();
    }

    public static void main(String[] args) {
        Outter outter=new Outter();
        outter.fun();
    }
}

2) 使用内部类可以曲线救国来实现“多继承”

class A{
     int numA=10;
}
class B{
     int numB=20;
}
public class E {
    class C extends A{ }
    class D extends B{ }
    public void test(){
        C c=new C();
        D d=new D();
        System.out.println(c.numA);
        System.out.println(d.numB);
    }

    public static void main(String[] args) {
        E e=new E();
        e.test();
    }
}

3.内部类使用方法/规则

1.成员内部类的创建需要依赖外部类对象,在没有外部类对象之前,无法创建成员内部类对象

eg:心脏就是成员内部类,在没有人体的情况下,是无法直接创建心脏这个对象

2.内部类是一个相对独立的实体,与外部类不是is a关系

3.内部类和外部类可以方便的访问彼此的私有域:内部类可以直接访问外部类的元素和方法(包括私有域),外部类必须通过内部类的对象来访问内部类的元素和方法(包括私有域)

原因:内部类中隐藏了外部类对象

⭐⭐

public class Outter {
    private String msg="outter类中的msg属性";
    class Inner{
        private int num=10;
        private String msg="内部类的msg属性";
        public void test(){
            //直接访问外部类中的msg属性
            //此处直接访问了外部类的私有成员变量msg
            //成员变量得通过对象访问,但此处并没有。说明外部类对象默认被传入了内部类中了System.out.println(msg);[没有private String msg="内部类的msg属性";时,打印的是外部类的msg属性]
            System.out.println(Outter.this.msg);//Outter.this表示明确调用的是外部类的msg属性。取掉打印出的就是内部类的msg属性
            System.out.println(Outter.this);//outter.this是传入的隐藏外部类对象
        }
    }
    public void fun(){//fun方法中产生了内部类对象
        Inner inner=new Inner();
        inner.test();
    }
    public static void main(String[] args) {
        Outter outter=new Outter();//外部产生外部类对象
        outter.fun();
    }
}

4.内部类的设计

      内部类的设计不是我们现在的首选,写数据结构代码时如链表,Node就是典型的内部类设计,可以使用成员内部类或静态内部类都可以,对外隐藏封装即可。

二、泛型

1.泛型的引入

坐标类Point{
  x
  y
}  

Object用于接收所有类型,有包装类的自动拆装箱,基本类型自动装箱变为Integer或Double让Object接收。

public class Point {
    private Object x;
    private Object y;

    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
    public static void main(String[] args) {
        Point point=new Point();
        //自动装箱
        point.setX(10.1);
        point.setY(20.2);
        //自动拆箱
        double x=(double) point.getX();
        double y=(double) point.getY();
        System.out.println("x="+x+" y="+y);
        Point point1=new Point();
        point1.setX("东经101度");
        point1.setY("北纬55度");
        String x1= (String) point1.getX();
        String y1=(String) point1.getY();
        System.out.println("x1="+x1+" y1="+y1);
    }
}

      隐藏风险:在强制类型转换处:此时x和y都是相同类型,若用户输入的x,y的类型不同,在强转时就会发生错误

类型转换异常:

      当x,y不小心设置为不同的类型时,再强转时就会发生运行时异常(类型转换异常),强转时发生的,这个错误在编译期间无法发现。->泛型应运而生

2.泛型的基本使用

守门员:在编译器检查类型是否正确(即主要用于参数校验)

      所谓泛型,就是指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象时)再进行类型的定义。

public class MyClass<T> {
    /**
     * <>叫类型参数的声明
     * 类声明后的<>中的T被称为类型参数,用于指代任意类型⭐⭐
     * 实际上这个T只是个代表,写啥都行,表示此时value1,value2都是在类定义时没有明确类型,只有在使用时才告知编译器类型。
     * 出于规范而言,类型参数用单个的大写字母来代替
     * T:代表任意类
     * E:表示Element的意思,或是异常
     * K;V:K与V搭配使用:Map(Integer,String)
    */
    T value1;
    T value2;
    }

分析:这里就相当于把T换为Integer类型了,如果用了其他类型直接编译就会报错,而不是在运行时报错。若此时value1和value2的类型都需要通过,则定义多个类型参数:

使用泛型改造上面的point类

/**
 * 泛型坐标类
 * 此时x,y是相同类型,使用一个类型参数(校验时就能发现出现类型错误)
 */
public class Point<T> {
    private T x;
    private T y;
    public T getX() {
        return x;
    }
    public void setX(T x) {
        this.x = x;
    }
    public T getY() {
        return y;
    }
    public void setY(T y) {
        this.y = y;
    }
    public static void main(String[] args) {
        Point<String> point=new Point<>();
        point.setX("东经120度");
        point.setY("北纬20度");
        String x= point.getX();//无需强转
        String y= point.getY();
    }
}

1)当创建Point对象时设置的类型和实际存储类型不同时,编译会报错,程序无法启动,提前将问题暴露在编译期间。

2)泛型不光在编译期就会校验类型以外,使用泛型还不需要强制类型转换

3.泛型方法

泛型方法是指方法有自己的泛型参数

public <T> void test(T t){}

特点

1.看到<>,表示这是一个泛型的声明,就和class是类的声明,interface是接口的声明一样

2.泛型方法中的T和类中泛型参数T是两码事

3.泛型方法始终以自己的类型参数为准,和类中的类型参数无关,为了避免混淆,一般定义泛型方法时,尽量避免使用类中使用过的类型参数字母。

public class MyClass<T,E> {
    //T,E代表两种不同的类型参数,在具体使用时可以相同也可以不同
    T value1;
    E value2;

    /**
     * 泛型方法
     */
    public <S> S test(S s){
        System.out.println(s);
        return s;
    }
    public static void main(String[] args) {
        MyClass<Integer,String> myClass=new MyClass<>();
        myClass.value1=10;
        myClass.test("哇哦");
    }
}

⭐⭐

4.泛型接口

public interface INews<T> {
    //返回值是T类型 T test(T t);
    T getNews(T t);
}

子类NewsImpl在实现接口时有两种选择:要么继续保留泛型,要么定义子类时明确类型

1)子接口仍然保留泛型

public class NewsImpl<T> implements INews<T>{
    @Override
    public T getNews(T t) {
        return null;
    }
}

2)子接口明确类型(接收String类型)

public class NewsImpl2<String> implements INews<String>{
    @Override
    public String getNews(String string) {
        return null;
    }
}

5.泛型相关总结

1)看到<T>就是泛型声明,T称为类型参数,泛型指的是定义类或方法时参数或属性的类型不确定,只有当真正运行时,才确定的一种方式。

2)泛型方法始终以自己的类型参数为准,而且可以独立于泛型类存在。普通类中也可以定义一个泛型方法。

3)泛型接口,子类要么继续保留泛型:class MessageImpl<T> implements IMessage<T>;或者在实现子类时明确泛型的类型,子类就是普通类。

6.泛型和内部类的关系

      非静态内部类(成员内部类)会覆用外部类的泛型参数,静态内部类不会覆用外部类的泛型参数

1)成员内部类覆用外部类的泛型参数

    对于外部类的泛型参数T,在成员内部类中的参数t2是复用了外部类的泛型参数, 主方法中定义的外部类泛型参数是String,此时对于内部类泛型参数就得是String,如下图,整型则会报错。

2)静态内部类不覆用外部类的泛型参数

      观察如下静态内部类,此时它的泛型参数就和外部类的泛型不同但没有报错,即它不会复用外部类的泛型参数,它的参数是可以自己任意设置的,不依外部类参数类型走。

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

7.泛型的通配符

分析:

      对于fun方法,它的方法参数中规定了形参的接收类型为Message<String>类型,此时它只能接收String类型的Message变量,对于上述Integer型接收时就会报错。这是由于泛型的强类型校验,不同类型完全不能通过校验,要匹配不同类型的Message对象就得重载很多fun方法,这样的话就本末倒置,将问题搞复杂了,此时我们就引入了泛型的通配符。:

1)<?>:一般用在方法参数

一般用在方法的形式参数上,表示可以接收该类所有类型的泛型变量

      此时应用了通配符?,fun方法就可以接收所有类型的Message对象,既可以接收String类型(fun(msg)),又可以接收Interge类型(fun(msg1))的范型变量。

提问:

此时fun方法内部可否调用msg.set方法?(即可否setter?)

      不可。定义fun方法的时候根本不知道外部到底会传入什么类型的Message对象,如何设置内容呢?<?>只能调用对象的getter方法来获取属性,由于此时根本无法确定传入对象的类型,因此无法调用对象的setter来设置值。

2)设置泛型上限:<? extends 类>

      <? extends 类>表示?可以指代任意类型,但是该类型必须是后面类的子类。eg:<? extends Number>,此时?可以是Number类本身或者Number类的子类,除此之外其他类型都不可以。

注:Number:数值型包装类的基本父类

      String不是Number类中的,所以extends Number时String不可访问,Integer,Double不继承String类,所以extends String时它两不可访问。这就是设置了上限,让继承的那个类及它的子类都能访问,其他的不可访问。

a.规定了泛型的上线能否直接调用set设置值呢?

 

      不可以,?表示可以接收Number及其子类,但是你仍不知道它接收的是什么类型的值,子类之间是不能互相转换的,假设子类是Dog类,你设置的是Cat类的值,不就出错了吗。设置泛型的上线通配符仍然不能Set具体值,只能getter取得。

b.extends可以用在泛型的类的定义上

唯一一个可以定义在的类型参数上的通配符 

T继承了Number,则T可以指代任意Number及其子类的类型,T仅限于Number及其子类的类型。

3)下限通配符:<? super 类>

      此时?可以表示指代任意类型,但是该类型必须是后面类的父类。eg:<? super String>此时表示?必须是String及其父类(即此时?只能指代String或Object)。

a.下线通配符只能用在方法参数,不能作用于类的类型参数

 

b.下线通配符可以调用对象的setter方法

      下限通配符可以调用对象的setter方法设置一个具体的属性值(下线类型的),因为你无论?是什么类型,规定好的下限对象一定可以通过向上转型变为父类

8.泛型的类型擦除

      类型擦除是泛型典型的“语法糖”【所谓语法糖,就是只在编译器生效,属于我们为了方便程序开发引入的一种机制】,实际上泛型在通过javac编译之后,泛型就不见了。

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

1)未在类中设置泛型上线,统一擦除为Object:

2)设置了泛型上线,擦除为相应的类型上限:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值