Java核心-面向对象(下)
之前说完了类、对象、方法以及面向对象的三大特性封装、继承和多态,现在来了解一下接口、代码块和一些常见的类如抽象类、包装类等。
一、接口
1、概念
接口(Interface),是一种抽象类型,是抽象方法的集合,是对功能的抽象。接口本身不包含任何实现细节,只是定义了一种规范,规定了实现接口的类应该做什么,而不是怎么做。在前面讲 java数据类型时也提到接口,是一种引用类型。
注:interface关键字声明接口,implements关键字实现接口。
接口定义一种规范,规定某一批类里必须提供某些方法,要求这些类必须完全实现接口里所定义的全部抽象方法,从而实现接口中定义的功能。
要访问接口方法,接口必须由另一个具有implements关键字(而不是 extends)的类”实现”(类似于继承)。接口方法的主体由”implement”类提供。
2、语法
定义一个接口的语法如下。
1 | [修饰符] interface 接口名 extends 父接口1, 父接口2, ... { |
3、几点注意
1)接口无法被实例化,但是可以被实现,可以多实现。一个类实现了一个或多个接口之后,这个类必须完全实现(重写)这些接口里所定义的全部抽象方法,否则该类必须定义成抽象类。
2)接口没有构造方法,不能包含成员变量,除static 和 final 变量。
3)==接口支持多继承==。(Java类是单继承)
4)实现接口语法如下
1 | [修饰符] class 实现类名 extends 父类 implements 接口1, 接口2, ... { |
接口与接口之间有继承关系extends,支持多继承
类与接口之间有实现关系implements,可以多实现
实现接口方法时,必须使用 public 修饰
接口的方法默认就是public abstract,故可省略不写
4、接口和抽象类
接口和类是两个不同的概念,其中,类描述对象的属性和方法,而接口则包含类要实现的方法。==接口体现的是一种规范,抽象类体现的是一种模板式设计==。
5、总结
1)Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
2)接口也是数据类型,属于引用类型,适用于向上转型(upcasting)和向下转型(downcasting);
3)接口的所有方法都是抽象方法,接口不能定义实例字段;
4)接口可以定义default方法(JDK>=1.8)
6、面向接口编程(扩展)
面向接口编程是一种编程范式,强调的是在设计软件应用时,应该先定义接口,再实现接口。这种方式有很多优点,如可以提高代码的可读性、可维护性和可扩展性,以及降低代码之间的耦合度。
1)接口多态声明方式
1 | // 接口名 变量名 = new 该接口的实现类名(); |
2)简单案例
首先定义一个Animal接口,并在接口内定义eat和sleep方法。
1 | public interface Animal { |
再定义一个Dog类,用该类实现Animal接口,并在main方法中使用接口类型的引用myDog来调用eat和sleep方法。
1 | public class Dog implements Animal { |
运行结果
1 | Dog is eating |
分析
使用面向接口编程的好处就是,可以==轻松地更换实现接口的类,而不需要修改使用接口的代码==。例如,我们可以定义一个新的类Cat,这个类也实现了Animal接口,然后我们可以在不修改主函数的情况下,将myDog的引用更换为Cat的实例。这就是面向接口编程的基本思想:==编程针对接口,而不是针对实现==。这样可以使我们的代码更加灵活和可扩展,也更容易进行单元测试和重构。
二、代码块
代码块里的变量属于局部变量,只在自己所在区域(即前后的 {})内有效。
1、局部代码块
直接定义在方法内部的代码块,如条件执行体、循环体。
2、普通初始化块
即构造代码块:直接定义在类中(一般不这么用)。
3、静态代码块
即类初始化块:初始化块的修饰符只能是 static。使用 static 修饰的初始化块,通常用来对类变量做初始化操作、加载资源、加载配置文件等。
- 随着所在类的加载而执行(只执行一次)。
- 在同类中优先于 main 方法执行。
4、案例
1)编写一个程序示例如下
1 | public class Demo { |
2)程序执行结果为
1 | 普通初始化块 |
3)结果分析
代码执行顺序:父类静态代码块 -> 子类静态代码块 -> 父类非静态代码块 -> 父类构造方法 -> 子类非静态代码块 -> 子类构造方法
- 类变量的初始化 及 静态初始化块(执行顺序与它们在源代码中的排列顺序相同)
- 实例变量的初始化 及 普通初始化块(执行顺序与它们在源代码中的排列顺序相同)
- 构造器(先加载父类的字节码文件并调用父类的构造器)
- main 方法
三、一些类
1、内部类(Inner Classes)
说到类与类之间的关系,很多人首先想到的可能是并列关系,其实还有嵌套关系,比如内部类。顾名思义,内部类就是一个类定义在另一个类的内部。内部类可以分为以下几种。
注:包含内部类的最外面那个类称为外部类(outer class)
1.1 成员内部类
1)成员内部类是最普通的内部类,位于另一个类的内部。
1 | class Circle { //外部类 |
==2)成员内部类可以无条件访问外部类的所有成员属性和成员方法==(包括private成员和静态成员)。
1 | class Circle { //外部类 |
3)当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问。
外部类.this.成员变量
外部类.this.成员方法
4)外部类如果要访问成员内部类的成员,必须==先创建一个成员内部类的对象,再通过指向这个对象的引用来访问==。
1 | public class Circle { // 外部类 |
5)成员内部类是依附外部类而存在的,即如果==要创建成员内部类的对象,必须先存在一个外部类的对象==。创建成员内部类对象的两种方式如下。
- 通过外部类对象创建
- 通过getInstance方法创建
1 | public class Test { |
注:getInstance在单例模式 (保证一个类仅有一个实例,并提供一个访问它的全局访问点) 的类中常见,用来生成唯一的实例,getInstance往往是static的。
1.2 局部内部类
概念:定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于==局部内部类的访问仅限于方法内或者该作用域内==。
作用域:即一个变量或方法在程序中可以被访问的范围,可以是一个类、方法或一个代码块。
局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。eg:
1 | class People{ |
1.3 匿名内部类
匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。
注:在编写事件监听代码时使用匿名内部类不但方便,而且使代码更加容易维护。
1.语法
1 | new <类或接口>() { |
优点:这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。
2.匿名类的两种实现方式
1)继承一个类,重写其方法。
2)实现一个接口(可以是多个),实现其方法。
注:匿名内部类实现一个接口的方式与实现一个类的方式相同
3.案例
1 | public class Out { |
运行结果
1 | 调用匿名类中的 show() 方法 |
4.匿名类的特点
1)匿名类和局部内部类一样,可以访问外部类的所有成员。eg:
1 | public class Out { |
运行结果
1 | 调用了匿名类的 show() 方法10 |
2)匿名类中允许使用非静态代码块进行成员初始化操作。eg:
1 | Out anonyInter = new Out() { |
3)匿名类的非静态代码块会在父类的构造方法之后被执行。
5.补充
匿名内部类不能有访问修饰符和static修饰符。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,==大部分匿名内部类用于接口回调==。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
1.4 静态内部类
1)概念
定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
2)说明
静态内部类不需要依赖于外部类,这点和类的静态成员属性类似,并且它不能使用外部类的非static成员变量或者方法,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
3)eg:
1 | public class Test { // 测试类 |
2、抽象类
2.1 抽象方法
1)概念
一个方法使==用 abstract 来修饰,且只有声明没有实现==。
2)特征
- 使用 abstract 修饰且没有方法体(没有方法体与空方法体不同)。
- 抽象方法只能定义在抽象类或接口中。
- 不能使用 private、final 或 static 修饰。
注:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。
2.2 抽象类
与抽象方法一样,抽象类也要使用 abstract 关键字声明。几点注意如下:
==一个方法被声明为抽象的,那么这个类也必须声明为抽象的==。一个抽象类中,可以有 0~n 个抽象方法和具体方法。
有构造方法,但不能直接用来创建对象,只留给子类创建对象时调用。
==子类重写父类时,必须重写父类所有的抽象方法==。即抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
==抽象类不能实例化,即不能使用 new 关键字创建对象==。
==要访问抽象类,它必须从另一个类继承==。
3)代码示例
创建一个抽象类 Shape
1 | public abstract class Shape { |
定义一个正方形类Square,继承形状类 Shape,并重写了 area( ) 抽象方法。
1 | public class Square extends Shape { |
再定义一个三角形类Triangle,继承形状类 Shape,并重写父类中的抽象方法 area()。
1 | public class Triangle extends Shape { |
最后创建一个测试类。
1 | public class ShapeTest { |
运行结果
1 | 正方形的面积为:20.0 |
总结:Shape 类只是定义了计算图形面积的方法,而对于如何计算并没有任何限制,需要子类==重写==父类抽象方法来具体实现。即==抽象类 Shape 仅定义了子类的一般形式,所以是抽象的==。
3、基本类型包装类
1)装箱
把基本类型数据转成对应的包装类对象
2)拆箱
把包装类对象转成对应的基本类型数据
3)自动装箱
把一个基本类型变量直接赋给对应的包装类变量或 Object 变量
- 在底层依然是手动装箱,使用的是 Xxx.valueOf() 方法
4)自动拆箱
把包装类对象直接赋给对应的基本类型变量,或者包装类对象与基本数据类型变量使用“==”比较
- 在底层依然是手动拆箱,使用的是 xxxValue() 方法
注:八大基本数据类型的包装类都是最终类、不可变类(对应的储存数值的成员变量 value 值使用 private final 修饰)
5)对应
Byte、Short、Integer、Long、Float、Double、BigDecimal、BigInteger 类都是 Number 抽象类的子类,都是 Comparable 接口的实现类
6)案例
1 | public class Test { |