• JavaSE 第七章 面向对象基础(下)接口&内部类&包装类&注解


    活动地址:CSDN21天学习挑战赛

    7.4 接口

    7.4.1 概述

    • 代码含义
      Java中的interface

    • 现实含义
      对外提供规则

    • 生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?USB是通用串行总线的英文缩写,是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。只须将设备插入计算机的USB端口中,系统会自动识别和配置。有了USB,我们电脑需要提供的各种插槽的口越来越少,而能支持的其他设备的连接却越来越多。

    • 那么我们平时看到的电脑上的USB插口、以及其他设备上的USB插口是什么呢?

    • 其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范的一种具体设备而已。

    • 根据时代发展,USB接口标准经历了一代USB、第二代USB 2.0和第三代USB 3.0 。

    • USB规格第一次是于1995年,由Intel、IBM、Compaq、Microsoft、NEC、Digital、North、Telecom等七家公司组成的USBIF(USB Implement Forum)共同提出,USBIF于1996年1月正式提出USB1.0规格,频宽为1.5Mbps。

    • USB2.0技术规范是有由Compaq、Hewlett
      Packard、Intel、Lucent、Microsoft、NEC、Philips共同制定、发布的,规范把外设数据传输速度提高到了480Mbps,被称为USB2.0的高速(High-speed)版本.

    • USB 3.0是最新的USB规范,该规范由英特尔等公司发起,USB3.0的最大传输带宽高达5.0Gbps(640MB/s),USB3.0
      引入全双工数据传输。5根线路中2根用来发送数据,另2根用来接收数据,还有1根是地线。也就是说,USB 3.0可以同步全速地进行读写操作。

    在这里插入图片描述

    • 电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。

    • 这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面相接口的低耦合,为系统提供更好的可扩展性和可维护性。

    • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系

      • 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范

      • 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范

    在这里插入图片描述

    7.4.2 定义格式

    • 接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
    • 引用数据类型:数组,类,枚举,接口,注解

    1、接口的声明格式

    [修饰符] interface 接口名 {
    	// 公共的静态常量
      	// 公共的抽象方法
      	// 公共的默认方法(JDK1.8以上)
      	// 公共的静态方法(JDK1.8以上)
    	// 私有方法(JDK1.9以上)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    示例:

    public interface Test3 {
    
        // 静态常量
        double PI = 3.14 ;
    
        // 抽象方法
        void method1() ;
        void method2() ;
    
        default void method3() {
            System.out.println("默认方法3");
        }
        
        default void method4() {
            System.out.println("默认方法5");
        }
        
        static void show() {
            System.out.println("静态方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2、接口的成员说明

    • 接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
    • 在JDK8之前,接口中只允许出现:
      (1)公共的静态的常量:其中public static final可以省略
      (2)公共的抽象的方法:其中public abstract可以省略

    接口是从多个相似类中抽象出来的规范,不需要提供具体实现

    • 在JDK1.8时,接口中允许声明默认方法和静态方法:
      (3)公共的默认的方法:其中public可以省略,建议保留,但是default不能省略
      (4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
    • 在JDK1.9时,接口又增加了:
      (5)私有方法

    除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。

    注意:接口不能实例化

    3、面试题

    1、为什么接口中只能声明公共的静态的常量?
    因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。

    例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
    USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA

    2、为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?

    它违反了接口作为一个抽象标准定义的概念。

    静态方法:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
    默认方法
    (1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。
    (2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。

    3、为什么JDK1.9要允许接口定义私有方法呢?
    因为我们说接口是规范,规范是需要公开让大家遵守的
    私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。

    7.4.3 接口的使用

    1、使用接口的静态成员

    接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量

    class Test4 {
        public static void main(String[] args) {
            System.out.println(Test3.PI);
            Test3.show();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、类实现接口(implements)

    • 接口不能创建对象,但是可以被类实现implements ,类似于被继承)。

    • 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

    [修饰符] class 实现类 implements 接口 {
    	// 重写接口中的抽象方法【必须】,如果实现类是抽象类可以不重写
      	// 重写接口中的默认方法【可选】
    }
    
    [修饰符] class 实现类 extends 父类 implements 接口 {
    	// 重写接口中的抽象方法【必须】,如果实现类是抽象类可以不重写 
      	// 重写接口中的默认方法【可选】
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    • 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。默认方法可以选择保留,也可以重写。

    • 重写时,default关键字就不用再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了

    • 接口中的静态方法不能被继承也不能被重写

    示例代码:

    public interface Animal {
        
        // public abstarct 两个关键字是可以省略不写的
        void eat() ;
        
        void sleep() ;
        
        default void fly() {} ;
        
        static void run() {} ;
    }
    
    interface Fish extends Animal {
        
    }
    
    class Cat implements Animal{
    
        @Override
        public void eat() {
            
        }
    
        @Override
        public void sleep() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3、使用接口的非静态方法

    • 对于接口的静态方法,直接使用“接口名.”进行调用即可

      • 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
    • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用

      • 接口不能直接创建对象,只能创建实现类的对象
    class Test1 {
        public static void main(String[] args) {
            Animal.run();
            Animal animal = new Cat() ;
            animal.eat();
            animal.sleep();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4、接口的多实现(implements)

    • 在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

    格式:

    【修饰符】 class 实现类  implements 接口1,接口2,接口3。。。{
    	// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
      	// 重写接口中默认方法【可选】
    }
    
    【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
        // 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
      	// 重写接口中默认方法【可选】
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。

    示例:

    public interface A {
        
        void method1() ;
        
        void method2() ;
    }
    
    interface B {
        
        void method1() ;
        
        void method3() ;
    }
    
    class C implements A , B {
    
        @Override
        public void method1() {
            
        }
    
        @Override
        public void method3() {
    
        }
    
        @Override
        public void method2() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    5、接口的多继承 (extends)

    • 一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方法。

    示例:

    public interface A {
    
        void method1() ;
    }
    
    interface B {
    
        void method2() ;
    }
    
    interface C extends A , B {
        
    }
    
    class D implements C {
    
        @Override
        public void method1() {
            
        }
    
        @Override
        public void method2() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 所有父接口的抽象方法都有重写。

    • 方法签名相同的抽象方法只需要实现一次。

    6、接口与实现类对象构成多态

    • 引用实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。

    7.3.4 冲突问题

    1、默认方法冲突问题

    (1)亲爹优先原

    • 则当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。 代码如下:

    定义接口:

    public interface Friend {
        default void play() {
            System.out.println("Friend------出去玩");
        }
    }
    
    class Father {
        public void play() {
            System.out.println("Father------出去玩");
        }
    }
    
    class Son extends Father implements Friend {
    
    }
    
    class Test2 {
        public static void main(String[] args) {
            Son son = new Son();
            son.play();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 在不重写同名方法时,默认输出的是父类的方法

    在这里插入图片描述

    • 重写保留super.play();

    在这里插入图片描述

    • 重写保留Friend.super.play();
      在这里插入图片描述
    • 完全重写,用自己的方法体

    在这里插入图片描述

    7.5 内部类

    7.5.1 概述

    1、什么是内部类?

    • 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。

    2、为什么要声明内部类呢?

    • 总的来说,遵循高内聚低耦合的面向对象开发总原则。便于代码维护和扩展。
    • 具体来说,当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。

    3、内部类都有哪些形式?

    根据内部类声明的位置(如同变量的分类),我们可以分为:
    (1)成员内部类:

    • 静态成员内部类
    • 非静态成员内部类

    (2)局部内部类

    • 有名字的局部内部类
    • 匿名的内部类

    7.5.2 成员内部类

    • 如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。

    格式:

    [修饰符] class 外部类 {
    	[修饰符] [static] class 内部类类名 {
        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、静态内部类

    • static修饰的成员内部类叫做静态内部类

    它的特点:

    • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

      • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

      • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员

      • 可以使用abstract修饰,因此它也可以被其他类继承

      • 可以使用final修饰,表示不能被继承

      • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。

    • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

      • 外部类只允许public或缺省的
    • 只可以在静态内部类中使用外部类的静态成员

      • 在静态内部类中不能使用外部类的非静态成员

      • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别

    • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象(通常应该避免这样使用)

    其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。

    2、非静态成员内部类

    • 没有static修饰的成员内部类叫做非静态内部类。

    非静态内部类的特点:

    • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

      • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

      • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。

      • 可以使用abstract修饰,因此它也可以被其他类继承

      • 可以使用final修饰,表示不能被继承

      • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。

    • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

      • 外部类只允许public或缺省的
    • 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的

    • 在外部类的静态成员中不可以使用非静态内部类哦

      • 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
    • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象(通常应该避免这样使用)

      • 如果要在外部类的外面使用非静态内部类的对象,通常在外部类中提供一个方法来返回这个非静态内部类的对象比较合适

      • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

    public class Inner {
        public static void main(String[] args) {
            Outer.InnerClass inn = new Outer.InnerClass() ;
             inn.method2();
    
            Outer.InnerClass.method1();
        }
    }
    
    class Outer {
    
        private static String a = "外部静态变量a-----" ;
        private static String b = "外部静态变量b-----" ;
        private String c = "外部非静态变量c-----" ;
        private String d = "外部非静态变量d-----" ;
    
    
        static class InnerClass {
            static String a = "内部静态变量a-----" ;
            String c = "内部非静态变量c-----" ;
    
            public static void method1() {
                System.out.println("外部静态变量a" + Outer.a);
                System.out.println("外部静态变量b" + Outer.b);
    //            System.out.println("外部非静态变量c" + Outer.c); // 报错,语法错误,非静态变量不能直接通过类名访问
    //            System.out.println("外部非静态变量d" + Outer.d); // 报错,语法错误,非静态变量不能直接通过类名访问
    
                System.out.println("内部静态变量a" + a);
    //            System.out.println("内部非静态变量c" + c);   // 报错,静态方法只能访问静态变量
                System.out.println("---------------------------------------");
    
                System.out.println("通过创建对象来访问外部非静态变量c" + new Outer().c);
                System.out.println("通过创建对象来访问外部非静态变量d" + new Outer().d);
    
                System.out.println("通过创建对象来访问内部非静态变量c" + new Outer.InnerClass().c);
            }
    
            public void method2() {
                System.out.println("外部静态变量a" + Outer.a);
                System.out.println("外部静态变量b" + Outer.b);
    //            System.out.println("外部非静态变量c" + Outer.c); // 报错,语法错误,非静态变量不能直接通过类名访问
    //            System.out.println("外部非静态变量d" + Outer.d); // 报错,语法错误,非静态变量不能直接通过类名访问
    
                System.out.println("内部静态变量a" + a);
                System.out.println("---------------------------------------");
                System.out.println("内部非静态变量c" + c);
    
                System.out.println("通过创建对象来访问外部非静态变量c" + new Outer().c);
                System.out.println("通过创建对象来访问外部非静态变量d" + new Outer().d);
    
                System.out.println("通过创建对象来访问内部非静态变量c" + new Outer.InnerClass().c);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述

    • 创建静态内部类格式:
    外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
    变量.非静态成员();
    
    • 1
    • 2
    • 创建非静态内部类格式
    外部类名 变量1 = new 外部类();
    外部类名.非静态内部类名 变量 = 变量1.new 非静态内部类名();
    变量.非静态成员();
    
    • 1
    • 2
    • 3

    示例:

    public class Demo1 {
        public static void main(String[] args) {
            
            // 创建静态内部类对象
            Outer1.Inner1 inner1 = new Outer1.Inner1() ;
            
            // 创建非静态内部的对象
            Outer1 o = new Outer1() ;
            Outer1.Inner2 inner2 = o.new Inner2() ;
    //        Outer1.Inner2 inner21 = new Outer1.Inner2() ;     // 报错
    
        }
    }
    
    class Outer1 {
    
        static class Inner1 {
    
        }
        
        class Inner2 {
            
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    7.5.3 局部内部类

    1、局部内部类

    语法格式:

    [修饰符] class 外部类 {
      	[修饰符] 返回值类型 方法名([形参列表]) {
      		[final/abstarct] class 内部类 {
            
            }
      	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 局部内部类的特点:
    • 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构

      • 可以继承自己的想要继承的父类,实现自己想要实现的父接口,和外部类的父类和父接口无关
      • 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量

      • 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承

      • 可以使用final修饰,表示不能被继承

      • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。

        • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
    • 和成员内部类不同的是,它前面不能有权限修饰符等

    • 局部内部类如同局部变量一样,有作用域

    • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法

    • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量

      • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final

      • 为什么在局部内部类中使用外部类方法的局部变量要加final呢?考虑生命周期问题

    public class Demo2 {
        public static void main(String[] args) {
            B b = new B();
            b.test();
    
            B.method();
            b.method1();
        }
    }
    
    interface A {
        void method() ;
    }
    
    class B {
    
        private static String a = "外部类静态变量a" ;
        private String b = "外部类非静态变量b" ;
    
        public static void method() {
            System.out.println("外部类方法");
            final String a = "局部变量a" ;
    
            class Inner {
    
                public void method() {
                    System.out.println("外部类静态变量a" + B.a);
    //                System.out.println("外部类静态变量b" + b);   // 报错,静态方法中不允许访问非静态变量
                    System.out.println("方法中的局部变量a" + a);
                }
    
            }
    
            Inner inner = new Inner();
            inner.method();
    
        }
    
        public void method1() {
            System.out.println("外部类方法");
            final String a = "局部变量a" ;
    
            class Inner {
    
                public void method() {
                    System.out.println("外部类静态变量a" + B.a);
                    System.out.println("外部类静态变量b" + b);
                    System.out.println("方法中的局部变量a" + a);
                }
    
            }
    
            Inner inner = new Inner();
            inner.method();
    
        }
    
        public void test() {
            new A() {
                @Override
                public void method() {
                    System.out.println("匿名内部类");
                }
            }.method();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    2、匿名内部类

    当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?

    (1)编写类,继承这个父类或实现这个接口
    (2)重写父类或父接口的方法
    (3)创建这个子类或实现类的对象

    因为考虑到这个子类或实现类是一次性的,创建一个对象并使用变量接收就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免使用过后占用内存资源的问题。

    new 父类([实参列表]) {
    	重写方法
    }
    //()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
    
    
    new 父类() {
    	重写方法
    }
    //()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    匿名内部类是没有名字的类,因此在声明类的同时就创建好了对象。

    注意:

    • 匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。

    例如:

    • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态

    • 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final

    匿名内部类能做什么?

    (1)使用匿名内部类的对象直接调用方法

    public class Demo3 {
        public static void main(String[] args) {
            new TestA() {
                @Override
                public void testA() {
                    System.out.println("aaaaa");
                }
            }.testA();
        }
    }
    
    interface TestA {
    
        void testA() ;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (2)通过父类或父接口的变量多态引用匿名内部类的对象

    public class Demo3 {
        public static void main(String[] args) {
            TestA a = new TestA() {
                @Override
                public void testA() {
                    System.out.println("aaaaa");
                }
            } ;
            a.testA();
        }
    }
    
    interface TestA {
    
        void testA() ;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (3)匿名内部类的对象作为实参

    public class Demo3 {
    
        public static void test(TestA a) {
            a.testA();
        }
    
    
        public static void main(String[] args) {
            test(new TestA() {
                @Override
                public void testA() {
                    System.out.println("aaaaa");
                }
            });
        }
    }
    
    interface TestA {
    
        void testA() ;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    7.6 包装类

    7.6.1 包装类

    • Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),那么基本数据类型的数据就需要用包装类来包装。

    在这里插入图片描述

    7.6.2 装箱与拆箱

    • 装箱:把基本数据类型转为包装类对象。

    转为包装类的对象,是为了使用专门为对象设计的API和特性

    • 拆箱:把包装类对象拆为基本数据类型。

    转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等

    示例:

    public class Demo1 {
        public static void main(String[] args) {
            /**
             * 装箱,把基本数据类型转为包装类对象
             * 拆箱,包包装类对象拆为基本数据类型
             */
    
            Integer i1 = new Integer(4) ; // 使用构造函数进行包装
            Integer i2 = Integer.valueOf(6) ;   // 使用包装类中的valueOf()方法进行包装
    
            System.out.println(i1);
            System.out.println(i2);
    
            int i = i1.intValue() ; // 拆箱
            System.out.println(i);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 在JDK1.5之后,Java可以实现自动装箱和拆箱

    只能与自己对应的类型之间才能实现自动装箱和自动拆箱

    Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
    i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
    //加法运算完成后,再次装箱,把基本数值转成对象。
    
    • 1
    • 2
    • 3
    Integer i = 1;
    Double d = 1;//错误的,1是int类型
    
    • 1
    • 2

    7.6.3 包装类的一些API

    1、基本数据类型和字符串之间的转换

    (1)把基本数据类型转为字符串

    // 将基本数据类型转为字符串
    int a = 10 ;
    // 方式一
    String str = a + "" ;
            
    // 方式二
    String str1 = Sting.valueOf(a) ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    (2)把字符串转为基本数据类型

    • String转换为对应的基本数据类型,除了Character类之外,其他所有的包装类都具有parseXxx()静态方法可以将字符串参数转换成对应的基本数据类型,例如:
    • public static int parseInt(String s) : 将字符串参数转换为对应的int基本数据类型
    • public static long parseLong(String s) : 将字符串参数转换为对应的long基本数据类型
    • public static double parseDouble(String s) : 将字符串参数转换为对应的double基本数据类型

    示例:

    public class StringToNum {
        public static void main(String[] args) {
    
            // 将字符串转换为基本数据类型
            // 使用包装类中的parseXxx()方法
            int i = Integer.parseInt("1234") ;
            long l = Long.parseLong("123") ;
            double d = Double.parseDouble("3.1415") ;
            boolean b = Boolean.parseBoolean("true") ;
    
            System.out.println(i);	// 1234
            System.out.println(l);	// 123
            System.out.println(d);	// 3.1415
            System.out.println(b)	// true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 或者将字符串转换为包装类,然后自动拆箱为对应的基本数据类型(建议使用parseXxx()方法)
    • public static Integer valueOf(String s):将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型

    • public static Long valueOf(String s):将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型

    • public static Double valueOf(String s):将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型

    注意:

    • 如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException的异常。

    在这里插入图片描述
    示例:

    public class StringToNum {
        public static void main(String[] args) {
    
            // 将字符串转换为基本数据类型
            // 使用包装类中的valueOf()方法
            int i = Integer.valueOf("1234") ;
            long l = Long.valueOf("123") ;
            double d = Double.valueOf("3.1415") ;
            boolean b = Boolean.valueOf("true") ;
    
            System.out.println(i);	// 1234
            System.out.println(l);	// 123
            System.out.println(d)	// 3.1415
            System.out.println(b);	// true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2、最大最小值

    public class MaxAndMin {
        public static void main(String[] args) {
            System.out.println(Integer.MAX_VALUE);
            System.out.println(Integer.MIN_VALUE);
    
            System.out.println(Long.MAX_VALUE);
            System.out.println(Long.MIN_VALUE);
    
            System.out.println(Double.MAX_VALUE);
            System.out.println(Double.MIN_VALUE)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    3、字符转大小写

    public class UpperAndLower {
        public static void main(String[] args) {
            char toUpperCase = Character.toUpperCase('a');
            char toLowerCase = Character.toLowerCase('A');
    
            System.out.println("a---->" + toUpperCase);	// a---->A
            System.out.println("A---->" + toLowerCase)	// A---->a
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、整数转进制

    public class ScaleTest {
        public static void main(String[] args) {
            // 进制转换
            /**
             * toBinaryString() : 十进制转二进制,返回一个字符串
             * toHexString():十进制转十六进制,返回一个字符串
             * toOctalString():十进制转八进制,返回一个字符串
             */
    
            String binaryString = Integer.toBinaryString(20);
            String hexString = Integer.toHexString(20);
            String octalString = Integer.toOctalString(20);
    
    		// 20--->二进制:10100
            System.out.println("20--->二进制:" + binaryString);
            // 20--->十六进制:14
            System.out.println("20--->十六进制:" + hexString)
            // 20--->八进制:24
            System.out.println("20--->八进制:" + octalString);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5、比较的方法

    • 前面的数小于后面的数,返回-1,前面的数大于后面的数,返回1,两数相等,返回0
    public class CompareTest {
        public static void main(String[] args) {
            int c1 = Integer.compare(5, 10);
            int c2 = Double.compare(100, 3.55);
            int c3 = Integer.compare(1, 1);
            
            System.out.println(c1);	// -1
            System.out.println(c2);	// 1
            System.out.println(c3)	// 0
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7.6.4 包装类对象的特点

    1、包装类缓存对象

    在这里插入图片描述

    • Integer源码中存在一个IntegerCache静态内部类,其中包含一个静态代码块,将-128到127之间的数存到了一个叫cache的数组

    在这里插入图片描述

    • 包装类的注意点:
    public class Demo4 {
        public static void main(String[] args) {
            Integer a = new Integer(1) ;
            Integer b = new Integer(1) ;
            System.out.println(a == b);     // false
    
            // 缓冲的常量对象
            Integer a1 = 1 ;
            Integer b1 = 1 ;
            System.out.println(a1 == b1);   // true
    
            Integer a2 = 128 ;
            Integer b2 = 128 ;
            System.out.println(a2 == b2);   // false
    
            Integer a3 = new Integer(1) ;
            Integer b3 = 1 
            System.out.println(a3 == b3);   // false
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    Double d1 = 1.0;
    Double d2 = 1.0;
    System.out.println(d1==d2);//false 比较地址,没有缓存对象,每一个都是新new的
    
    • 1
    • 2
    • 3

    2、类型转换问题

    public class Demo5 {
        public static void main(String[] args) {
            Integer i = 1000 ;
            double d = 1000.0 ;
            Double d1 = 1000.0 ;
    
            // i会先进行自动拆箱,然后根据基本数据类型“自动类型转换”规则,转为double比较
            System.out.println(i == d); // true
    
            int a = 1000 ;
            System.out.println(i == a); // true
    
    //        System.out.println(i == d1);    // 报错,// 不可比较的类型: java.lang.Integer和java.lang.Doubl
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、包装类对象不可变

    示例:

    public class TestExam {
        public static void main(String[] args) {
            int i = 1;
            Integer j = new Integer(2);
            Circle c = new Circle();
            System.out.println("c.radius = " + c.radius);
            change(i,j,c);
            System.out.println("i = " + i);//1
            System.out.println("j = " + j);//2
            System.out.println("c.radius = " + c.radius);//10.0
        }
    
        /*
         * 方法的参数传递机制:
         * (1)基本数据类型:形参的修改完全不影响实参
         * (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
         * 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
         */
        public static void change(int a ,Integer b,Circle c ){
            a += 10;
    //		b += 10;//等价于  b = new Integer(b+10);
            c.radius += 10;
    		/*c = new Circle();
    		c.radius+=10;*/
        }
    }
    class Circle{
        double radius;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    7.7 注解

    7.7.1 什么是注解

    注解是以“@注释名”在代码中存在的,还可以添加一些参数值

    例如:

    @SuppressWarnings(value = "all")
    @Overrode
    @Deprecated
    
    • 1
    • 2
    • 3

    注解Annotation是从JDK5.0开始引入。

    虽然说注解也是一种注释,因为它们都不会改变程序原有的逻辑,只是对程序增加了某些注释性信息。不过它又不同于单行注释和多行注释,对于单行注释和多行注释是给程序员看的,而注解是可以被编译器或其他程序读取的一种注释,程序还可以根据注解的不同,做出相应的处理。所以注解是插入到代码中以便有工具可以对它们进行处理的标签。

    7.7.2 三个最基本的注解

    1、@Override

    • 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
      • 只能标记在方法上。
      • 它会被编译器程序读取。

    在这里插入图片描述
    在这里插入图片描述

    2、@Deprecated

    用于表示被标记的数据已经过时

    • 可以用于修饰 属性、方法、构造、类、包、局部变量、参数。

    它会被编译器程序读取。
    在这里插入图片描述

    3、@SuppressWarnings

    抑制编译警告

    • 可以用于修饰类、属性、方法、构造、局部变量、参数
      它会被编译器程序读取。

    没有加注解前是被警告的

    在这里插入图片描述

    加注解后警告消失

    在这里插入图片描述

  • 相关阅读:
    操作系统不等于 Linux,六问操作系统新时代 | 1024 程序员节
    南大通用GBase8s 常用SQL语句(286)
    力扣刷题19-删除链表的倒数第N个节点
    「 机器人/自动化控制 」“SCI检索论文与会议”小结
    如何在Linux服务器上安装Anaconda(超详细)
    【4.2 集中趋势&离散趋势】(描述性统计分析)——CDA
    【数据结构】排序3——交换排序(冒泡排序、快速排序)
    Gin_web教程
    LeetCode 刷题系列 -- 1254. 统计封闭岛屿的数目
    《C++设计模式》——行为型
  • 原文地址:https://blog.csdn.net/weixin_45890113/article/details/126293463