44_内部类

59 篇文章 2 订阅
22 篇文章 1 订阅

内部类

内部类的基本概念

  • 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。也就是类内部的类。
  • 类中的内容:成员变量、成员方法、构造方法、静态成员(变量、方法,隶属于类层级的)、构造块和静态代码块、内部类。

实际作用

  • 当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节(可以对外不可见,也不需要可见)并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法(相当于是外部类的成员,成员和成员是一家人,所以可以直接访问)。

内部类的分类

  • 普通内部类 - 直接将一个类的定义放在另外一个类的类体中。隶属于对象层级,通过引用.的方式访问。
  • 静态内部类 - 使用static关键字修饰的内部类,隶属于类层级。通过类名.的方式访问。
  • 局部内部类 - 直接将一个类的定义放在方法体的内部时。
  • 匿名内部类 - 就是指没有名字的内部类。

普通(成员)内部类的格式

  • 语法规则:
    访问修饰符 class 外部类的类名 {
    	访问修饰符 class 内部类的类名 {
    		内部类的类体;
    	}
    }
    

普通内部类的使用方式

  • 普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。

  • 普通内部类和普通类一样可以使用final或者abstract关键字修饰。

    • 普通内部类还可以使用private或protected关键字进行修饰。
  • 普通内部类需要使用外部类对象来创建对象。

  • 如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。在内部类中获取内部类的成员变量使用:this.成员变量名 外部类中的成员变量获取:外部类类名.this.成员变量名(笔试考点

    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 14:33
     * @description 编程实现普通内部类的定义和使用     -   文档注释
     */
    public class NormalOuter {
    
        private int cnt = 1;
    
        // 定义普遍内部类, 隶属于外部类的成员,并且是对象层级,只要没有static关键字修饰,它都是对象层级而不是类层级
        // 内部类应该是对当前类好使,对外部类不可见,故而把public关键字改为private
        public /*final*/ class NormalInner { // 加了final内部类可以限制该内部类和该类其它内部类之间不能继承,即该类中其它内部类不能继承该内部类
        /*private class NormalInner {*/
            private int ia = 2;
            private int cnt = 3;
    
            public NormalInner() {
                System.out.println("普通内部类的构造方法体执行到了!");
            }
    
            public void show() {
                System.out.println("外部类中变量cnt的数值为:" + cnt); // 1
                System.out.println("ia = " + ia); // 2
            }
    
            public void show2(int cnt) {
                System.out.println("形参变量 cnt = " + cnt); // 局部优先原则
                System.out.println("内部类中 cnt = " + this.cnt); // 3
                System.out.println("外部类中 cnt = " + NormalOuter.this.cnt); // 1 外部类的类名.this 可以得到它所在的外部类中的this关键字,然后就可以得到外部类中成员变量的值
            }
        }
    }
    
    
    
    
    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 14:44
     * @description
     */
    public class NormalOuterTest {
    
        public static void main(String[] args) {
    
            // 1、声明NormalOuter类型的引用指向该类型的对象
            NormalOuter no = new NormalOuter();
            // 2、声明NormalOuter类中内部类的引用指向内部类的对象
            // 'com.lagou.module02.task05.NormalOuter' is not an enclosing clas
            // NormalOuter.NormalInner ni = new NormalOuter.NormalInner(); // . 就是'的'的意思,对象层级的东西要想使用,得使用引用.的方式
            // 私有了内部类之后:'com.lagou.module02.task05.NormalOuter.NormalInner' has private access in 'com.lagou.module02.task05.NormalOuter'
            NormalOuter.NormalInner ni = no.new NormalInner();
            NormalOuter.NormalInner ni2 = new NormalOuter().new NormalInner();
    
            // 3、调用内部类的show方法]
            ni.show();
            System.out.println("------------------------------------");
            ni.show2(4);
        }
    }
    
    

静态内部类的格式

  • 语法规则:
    访问修饰符 class 外部类的类名 {
    	访问修饰符 static class 内部类的类名 {
    		内部类的类体;
    	}
    }
    

静态内部类的使用方式

  • 静态内部类不能直接访问外部类的非静态成员。

  • 静态内部类可以直接创建对象,不再需要外部类先创建对象了。

  • 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。(笔试考点

    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 15:35
     * @description 实现静态内部类的定义和使用
     */
    public class StaticOuter {
    
        private int cnt = 1;            // 隶属于对象层级
        private static int snt = 2;     // 隶属于类层级
    
        public /*static*/ void show() {
            System.out.println("外部类的show方法就是这里!");
        }
    
        /**
         * 定义静态内部类      有static关键字修饰隶属于类层级
         */
        public static class StaticInner {
            private int ia = 3;
            private static int snt = 4;
    
            public StaticInner() {
                System.out.println("讲台内部类的构造方法体执行到了!");
            }
    
            public void show() {
                System.out.println("ia = " + ia); // 3
                System.out.println("外部类中的 snt = " + snt); // 2
                // System.out.println("外部类中的 cnt = " + cnt); // Error:Non-static field 'cnt' cannot be referenced from a static context
                // 不能在静态内容中访问非静态成员  静态上下文中不能访问非静态的成员,因为此时可能还没有创建对象
            }
    
            public void show2(int snt) { // 就近原则
                System.out.println("snt = " + snt); // 参数变量snt的值
                System.out.println("内部类中的成员 snt = " + StaticInner.snt); // 4
                System.out.println("外部类中的成员 snt = " + StaticOuter.snt); // 2
                // StaticOuter.this.show(); // Non-static method 'show()' cannot be referenced from a static context
                // 'com.lagou.module02.task05.StaticOuter.this' cannot be referenced from a static context
                // StaticOuter.show();
                new StaticOuter().show();
            }
        }
    }
    
    
    
    
    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 15:48
     * @description
     */
    public class StaticOuterTest {
    
        public static void main(String[] args) {
    
            // 1、声明StaticInner类型的引用指向该类型的对象
            StaticOuter.StaticInner si = new StaticOuter.StaticInner(); // 类层级可以直接类名.访问了,不需要new 对象,所以此处可以直接这样创建对象,而不用像之前一样先创建外部类对象
            // 2、调用show方法进行测试 $作为标识符的命名规范,日常开发中我们一般不会使用到它。但是在内部类的字节码文件名处会使用到
            // 内部类的字节码文件名为:外部类类名$内部类类名
            si.show();
    
            System.out.println("--------------------------");
            si.show2(5);
        }
    }
    
    

局部(方法)内部类的格式

  • 语法规则:
    访问修饰符 class 外部类的类名 {
    	访问修饰符 返回值类型 成员方法名(形参列表) {
            class 内部类的类名 {
                内部类的类体;
            }
    	}
    }
    
  • 局部内部类不需要使用 public、private、protected等访问修饰符修饰。

局部内部类的使用方式

  • 局部内部类只能在该方法的内部可以使用。

  • 局部内部类可以在方法体内部直接创建对象。

  • 局部内部类不能使用访问控制符和static关键字修饰符。(啥时候见过在局部变量上加访问控制符和static关键字吗?)

  • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。(笔试考点

    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 16:31
     * @description 编程实现局部内部类的定义和使用
     */
    public class AreaOuter {
    
        private int cnt = 1;
    
        public void show() {
    
            // 定义一个局部变量进行测试, Java8开始默认理解为final关键字修饰的变量
            // 虽然可以省略final关键字,但建议还是加上
            // int ic = 4;
            final int ic = 4;
            /**
             * 在局部内部类中使用外部类中方法体中的局部变量必须声明为final的主要原因:
             *      局部变量从声明之后一直到方法结束期间一直都是有效的,局部内部类也是如此。
             *      明显,局部变量的有效范围大于局部内部类的有效范围,因为局部变量先于局部内部类声明,意味着二者的有效范围或者是有效区域是不一样的。
             *      当内部类中使用到局部变量的时候,底层有个原理:它会把局部变量拷贝一份拿到内部类的内部去使用。
             *      在使用过程中,为什么加final是因为:如果不加final的话意味着该局部变量的值是可以改变的。
             *      我拷贝了一份拿到里面傻傻的在使用,结果你外面背着我把这个值给改了。外部类一旦把这个值改了,内部类里面拿到的使用的这个值跟外部类中真实的值就不一样了。
             *      不一样就会造成数据的不一致性问题。为了避免这种错误的发生,就要求加final。
             *      之所以要避免这个问题是因为二者的声明周期是不一致的。
             */
    
            // 定义局部内部类,只在当前方法体的内部好使     拷贝一份局部变量然后操作
            /*public*/ class AreaInner { // Modifier 'public' not allowed here
    
                private int ia = 2;
    
                public AreaInner() {
                    System.out.println("局部内部类的构造方法!");
                }
    
                public void test() {
                    int ib = 3;
                    System.out.println("ia = " + ia); // 2
                    System.out.println("cnt = " + cnt); // 1
                    // ic = 5; // 不加final:Variable 'ic' is accessed from within inner class, needs to be final or effectively final
                    // ic = 5; Cannot assign a value to final variable 'ic' 加了final关键字后
                    System.out.println("ic = " + ic); // 4
                }
            }
    
            // 声明局部内部类的引用指向局部内部类的对象
            AreaInner ai = new AreaInner();
            ai.test();
        }
    }
    
    
    
    
    
    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 16:37
     * @description
     */
    public class AreaOuterTest {
    
        public static void main(String[] args) {
    
            // 1、声明外部类类型的引用指向外部类的对象
            AreaOuter ao = new AreaOuter();
            // 2、通过show方法的调用实现局部内部类的定义和使用
            ao.show();
        }
    }
    
    

回调模式的概念和编程

  • 回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象(因为接口不能new对象);而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)。

  • 为什么叫回调?我把我的对象传给你,然后你又根据我的对象回来调用我内部的方法,所以这才叫回调,回过头来调我的方法。所以这叫做回调模式

    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 17:28
     * @description
     */
    public interface AnonymousInterface {
    
        // 自定义抽象方法
        public abstract void show();
    }
    
    
    
    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 17:38
     * @description
     */
    public class AnonymousInterfaceImpl implements AnonymousInterface {
        // 写接口实现类的时候都习惯在接口名后+Impl Impl时implements 的缩写
    
        @Override
        public void show() {
            System.out.println("这里是接口的一个实现类!!!");
        }
    }
    
    
    
    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 17:30
     * @description
     */
    public class AnonymousInterfaceTest {
    
        // 假设已有下面的方法,请问如何调用下面的方法?
        // AnonymousInterface ai = new AnonymousInterfaceImpl();
        // 典型的接口类型的引用指向实现类型的对象,形成了多态
        public static void test(AnonymousInterface ai) {
            // 编译阶段调用父类版本,运行调用实现类重写的版本
            ai.show();
        }
    
        public static void main(String[] args) {
    
            // AnonymousInterfaceTest.test(() -> System.out.println("淳神好帅!!!"));
            // Error:AnonymousInterfaceTest.test(new AnonymousInterface()); // 'AnonymousInterface' is abstract; cannot be instantiated 接口不能实例化
            AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
        }
    }
    
    

开发经验分享

  • 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
    1. 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
    2. 使用上述匿名内部类的语法格式得到接口/类类型的引用即可;

匿名内部类的语法格式(重点

  • 接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};

    package com.lagou.module02.task05;
    
    /**
     * @author hhc19
     * @date 2022/1/1 17:30
     * @description
     */
    public class AnonymousInterfaceTest {
    
        // 假设已有下面的方法,请问如何调用下面的方法?
        // AnonymousInterface ai = new AnonymousInterfaceImpl();
        // 典型的接口类型的引用指向实现类型的对象,形成了多态
        public static void test(AnonymousInterface ai) {
            // 编译阶段调用父类版本,运行调用实现类重写的版本
            ai.show();
        }
    
        public static void main(String[] args) {
    
            // AnonymousInterfaceTest.test(() -> System.out.println("淳神好帅!!!"));
            // Error:AnonymousInterfaceTest.test(new AnonymousInterface()); // 'AnonymousInterface' is abstract; cannot be instantiated 接口不能实例化
            AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
    
            System.out.println("------------------------------------------");
            // 使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};
            // 使用匿名内部类的优势:new完对象,这个类的价值就不存在了,就可以销毁它的存储空间了
            AnonymousInterface ait = new AnonymousInterface() {
                @Override
                public void show() {
                    System.out.println("匿名内部类就是这么玩的,虽然你很抽象!!!");
                }
            };
    
            // 从Java8开始提出新特性:Lambda表达式,可以简化上述代码,格式为:(参数列表) -> {方法体}
            AnonymousInterface ait2 = () -> System.out.println("lambda表达式原来是如此简单");
    
            AnonymousInterfaceTest.test(ait);
            AnonymousInterfaceTest.test(ait2);
        }
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 定义抽象基类Shape,其中包含一个纯虚函数计算面积。然后派生出五个派生类:Circle(圆形)、Square(正方形)、Rectangle(长方形)、Trapezoid(梯形)和Triangle(三角形),分别实现计算面积的虚函数。最后,使用基类指针数组,每个数组元素指向一个派生类的对象,并计算每个图形的面积并输出。 具体实现如下: ```c++ #include <iostream> using namespace std; const float pi = 3.14159f; class Shape { public: virtual float area() = ; // 纯虚函数,计算面积 }; class Circle : public Shape { private: float radius; public: Circle(float r) : radius(r) {} float area() { return pi * radius * radius; } }; class Square : public Shape { private: float side; public: Square(float s) : side(s) {} float area() { return side * side; } }; class Rectangle : public Shape { private: float length, width; public: Rectangle(float l, float w) : length(l), width(w) {} float area() { return length * width; } }; class Trapezoid : public Shape { private: float top, bottom, height; public: Trapezoid(float t, float b, float h) : top(t), bottom(b), height(h) {} float area() { return (top + bottom) * height / 2; } }; class Triangle : public Shape { private: float base, height; public: Triangle(float b, float h) : base(b), height(h) {} float area() { return base * height / 2; } }; int main() { Shape* shapes[5]; // 基类指针数组 shapes[] = new Circle(2.f); shapes[1] = new Square(3.f); shapes[2] = new Rectangle(2.f, 4.f); shapes[3] = new Trapezoid(2.f, 4.f, 3.f); shapes[4] = new Triangle(3.f, 4.f); float totalArea = .f; for (int i = ; i < 5; i++) { totalArea += shapes[i]->area(); cout << "Shape " << i + 1 << " area: " << shapes[i]->area() << endl; delete shapes[i]; // 释放内存 } cout << "Total area: " << totalArea << endl; return ; } ``` 输出结果: ``` Shape 1 area: 12.5664 Shape 2 area: 9 Shape 3 area: 8 Shape 4 area: 9 Shape 5 area: 6 Total area: 44.5664 ``` 其中,Shape 1 是圆形,半径为 2.,面积为 12.5664;Shape 2 是正方形,边长为 3.,面积为 9;Shape 3 是长方形,长为 4.,宽为 2.,面积为 8;Shape 4 是梯形,上底为 2.,下底为 4.,高为 3.,面积为 9;Shape 5 是三角形,底为 3.,高为 4.,面积为 6。最后,所有图形的面积之和为 44.5664。 ### 回答2: 抽象基类shape定义如下: ``` class shape { public: virtual float area() = 0; // 计算面积的虚函数 }; ``` 派生类circle、square、rectangle、trapezoid和triangle继承自抽象基类shape,各自实现area()函数: ``` class circle : public shape { public: circle(float r) : m_radius(r) {} virtual float area() { return m_radius * m_radius * pi; } private: float m_radius; }; class square : public shape { public: square(float s) : m_side(s) {} virtual float area() { return m_side * m_side; } private: float m_side; }; class rectangle : public shape { public: rectangle(float l, float w) : m_length(l), m_width(w) {} virtual float area() { return m_length * m_width; } private: float m_length; float m_width; }; class trapezoid : public shape { public: trapezoid(float a, float b, float h) : m_top(a), m_bottom(b), m_height(h) {} virtual float area() { return (m_top + m_bottom) * m_height / 2; } private: float m_top; float m_bottom; float m_height; }; class triangle : public shape { public: triangle(float b, float h) : m_base(b), m_height(h) {} virtual float area() { return m_base * m_height / 2; } private: float m_base; float m_height; }; ``` 使用基类指针数组,每一个数组元素指向一个派生类的对象: ``` shape* shapes[5]; shapes[0] = new circle(3.0f); shapes[1] = new square(2.0f); shapes[2] = new rectangle(3.0f, 4.0f); shapes[3] = new trapezoid(2.0f, 4.0f, 3.0f); shapes[4] = new triangle(5.0f, 2.0f); ``` 计算每个图形的面积并输出: ``` for (int i = 0; i < 5; i++) { std::cout << "shape " << i+1 << " area = " << shapes[i]->area() << std::endl; } ``` 其中,pi被定义为全局变量: ``` const float pi = 3.14159f; ``` 完整代码如下: ``` #include <iostream> const float pi = 3.14159f; class shape { public: virtual float area() = 0; // 计算面积的虚函数 }; class circle : public shape { public: circle(float r) : m_radius(r) {} virtual float area() { return m_radius * m_radius * pi; } private: float m_radius; }; class square : public shape { public: square(float s) : m_side(s) {} virtual float area() { return m_side * m_side; } private: float m_side; }; class rectangle : public shape { public: rectangle(float l, float w) : m_length(l), m_width(w) {} virtual float area() { return m_length * m_width; } private: float m_length; float m_width; }; class trapezoid : public shape { public: trapezoid(float a, float b, float h) : m_top(a), m_bottom(b), m_height(h) {} virtual float area() { return (m_top + m_bottom) * m_height / 2; } private: float m_top; float m_bottom; float m_height; }; class triangle : public shape { public: triangle(float b, float h) : m_base(b), m_height(h) {} virtual float area() { return m_base * m_height / 2; } private: float m_base; float m_height; }; int main() { shape* shapes[5]; shapes[0] = new circle(3.0f); shapes[1] = new square(2.0f); shapes[2] = new rectangle(3.0f, 4.0f); shapes[3] = new trapezoid(2.0f, 4.0f, 3.0f); shapes[4] = new triangle(5.0f, 2.0f); for (int i = 0; i < 5; i++) { std::cout << "shape " << i+1 << " area = " << shapes[i]->area() << std::endl; } return 0; } ``` ### 回答3: 回答: 抽象基类 shape 可以定义如下: ``` class shape { public: virtual float area() const = 0; }; ``` 其中,虚函数 `area` 返回该形状的面积,`const` 保证该函数不能修改对象内部状态。 接下来,定义五个派生类:circle、square、rectangle、trapezoid 和 triangle,它们都继承自 base 类,同时实现 `area` 方法: ``` class circle : public shape { private: float radius; public: circle(float r) : radius(r) {} float area() const override { return 3.14159f * radius * radius; } }; class square : public shape { private: float side; public: square(float s) : side(s) {} float area() const override { return side * side; } }; class rectangle : public shape { private: float width, height; public: rectangle(float w, float h) : width(w), height(h) {} float area() const override { return width * height; } }; class trapezoid : public shape { private: float top, bottom, height; public: trapezoid(float t, float b, float h) : top(t), bottom(b), height(h) {} float area() const override { return (top + bottom) * height / 2; } }; class triangle : public shape { private: float base, height; public: triangle(float b, float h) : base(b), height(h) {} float area() const override { return base * height / 2; } }; ``` 接下来,我们可以通过指针数组动态创建派生类的对象: ``` int main() { const int n = 5; shape *shapes[n] = { new circle(1.0f), new square(2.0f), new rectangle(2.0f, 3.0f), new trapezoid(1.0f, 2.0f, 3.0f), new triangle(2.0f, 3.0f) }; for (int i = 0; i < n; i++) { cout << "Area of shape " << (i+1) << " is " << shapes[i]->area() << endl; delete shapes[i]; } return 0; } ``` 在 main 函数中,通过指针数组创建了五个派生类的对象,并输出它们的面积。 需要注意的是,动态创建的对象需要在程序结束前销毁,这里使用 `delete` 来删除之前 new 出来的每个对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值