Java核心-面向对象(下)

2024-02-21 11:58:51 浏览数 (1)

之前说完了类、对象、方法以及面向对象的三大特性封装、继承和多态,现在来了解一下接口、代码块和一些常见的类如抽象类、包装类等。

一、接口

1、概念

接口(Interface),是一种抽象类型,是抽象方法的集合,是对功能的抽象。接口本身不包含任何实现细节,只是定义了一种规范,规定了实现接口的类应该做什么,而不是怎么做。在前面讲 java数据类型时也提到接口,是一种引用类型。

注:interface关键字声明接口,implements关键字实现接口。

接口定义一种规范,规定某一批类里必须提供某些方法,要求这些类必须完全实现接口里所定义的全部抽象方法,从而实现接口中定义的功能。

要访问接口方法,接口必须由另一个具有implements关键字(而不是 extends)的类"实现"(类似于继承)。接口方法的主体由"implement"类提供。

2、语法

定义一个接口的语法如下。

代码语言:javascript复制
[修饰符] interface 接口名 extends 父接口1, 父接口2, ... {
    0-N 常量
    0-N 抽象方法
    0-N 内部类、接口、枚举
    0-N 默认方法或静态方法 // Java 8 以上才允许
}
3、几点注意

1)接口无法被实例化,但是可以被实现,可以多实现。一个类实现了一个或多个接口之后,这个类必须完全实现(重写)这些接口里所定义的全部抽象方法,否则该类必须定义成抽象类。 2)接口没有构造方法,不能包含成员变量,除static 和 final 变量。 3)接口支持多继承。(Java类是单继承) 4)实现接口语法如下

代码语言:javascript复制
[修饰符] 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)接口多态声明方式

代码语言:javascript复制
// 接口名 变量名 = new 该接口的实现类名();
// 面向接口编程
Animal myDog=new Dog();

2)简单案例 首先定义一个Animal接口,并在接口内定义eat和sleep方法。

代码语言:javascript复制
public interface Animal {
    // 定义一个接口
    void eat(); // 接口的方法默认就是public abstract,故可省略
    void sleep();
}

再定义一个Dog类,用该类实现Animal接口,并在main方法中使用接口类型的引用myDog来调用eat和sleep方法。

代码语言:javascript复制
public class Dog implements Animal {
    // 重写Animal接口内所定义的全部抽象方法
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
    @Override
    public void sleep() {
        System.out.println("Dog is sleeping");
    }
    // 在主函数中使用接口类型的引用来调用方法
    public static void main(String[] args) {
        // 接口名 变量名 = new 该接口的实现类名();
        // 面向接口编程
        Animal myDog=new Dog();
        myDog.eat();
        myDog.sleep();
    }
}

运行结果

代码语言:javascript复制
Dog is eating
Dog is sleeping

分析

使用面向接口编程的好处就是,可以轻松地更换实现接口的类,而不需要修改使用接口的代码。例如,我们可以定义一个新的类Cat,这个类也实现了Animal接口,然后我们可以在不修改主函数的情况下,将myDog的引用更换为Cat的实例。这就是面向接口编程的基本思想:编程针对接口,而不是针对实现。这样可以使我们的代码更加灵活和可扩展,也更容易进行单元测试和重构。

二、代码块

代码块里的变量属于局部变量,只在自己所在区域(即前后的 {})内有效。

1、局部代码块

直接定义在方法内部的代码块,如条件执行体、循环体。

2、普通初始化块

即构造代码块:直接定义在类中(一般不这么用)。

3、静态代码块

即类初始化块:初始化块的修饰符只能是 static。使用 static 修饰的初始化块,通常用来对类变量做初始化操作、加载资源、加载配置文件等。

  • 随着所在类的加载而执行(只执行一次)。
  • 在同类中优先于 main 方法执行。
4、案例

1)编写一个程序示例如下

代码语言:javascript复制
public class Demo {
    private static Demo demo=new Demo(); // 单例模式
    static {
        System.out.println("静态初始化");  //静态代码块=>static修饰
    }
    Demo(){
        // 编译后,“还原”到这里 System.out.println("普通初始化块");
        System.out.println("构造器...");
    }
    {
        System.out.println("普通初始化块");  //普通初始化块=>直接定义在类中
    }
    public static void main(String[] args) { //局部代码块=>直接定义在方法内部
        System.out.println("进入 main 方法");
        new Demo();
        new Demo();
    }
}

2)程序执行结果为

代码语言:javascript复制
普通初始化块
构造器...
静态初始化
进入 main 方法
普通初始化块
构造器...
普通初始化块
构造器...

3)结果分析 代码执行顺序:父类静态代码块 -> 子类静态代码块 -> 父类非静态代码块 -> 父类构造方法 -> 子类非静态代码块 -> 子类构造方法

  • 类变量的初始化 及 静态初始化块(执行顺序与它们在源代码中的排列顺序相同)
  • 实例变量的初始化 及 普通初始化块(执行顺序与它们在源代码中的排列顺序相同)
  • 构造器(先加载父类的字节码文件并调用父类的构造器)
  • main 方法

三、一些类

1、内部类(Inner Classes)

说到类与类之间的关系,很多人首先想到的可能是并列关系,其实还有嵌套关系,比如内部类。顾名思义,内部类就是一个类定义在另一个类的内部。内部类可以分为以下几种。

注:包含内部类的最外面那个类称为外部类(outer class)

1.1 成员内部类

1)成员内部类是最普通的内部类,位于另一个类的内部。

代码语言:javascript复制
class Circle { //外部类
    double radius=0; //成员变量
    public Circle(double radius){ //成员方法
        this.radius=radius;
    }
    class Draw{ //成员内部类
        public void drawShape(){
            System.out.println("drawshape");
        }
    }
}

2)成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

代码语言:javascript复制
class Circle { //外部类
    private double radius = 0; //成员变量
    public static int count =1; //静态成员
    public Circle(double radius) { //成员方法
        this.radius = radius;
    }
    class Draw {     //成员内部类
        public void drawShape() {
            System.out.println(radius); //外部类的private成员
            System.out.println(count);  //外部类的静态成员
        }
    }
}

3)当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问。

外部类.this.成员变量 外部类.this.成员方法

4)外部类如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。

代码语言:javascript复制
public class Circle { // 外部类
    private double radius = 0;
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawShape();   //必须先创建成员内部类的对象,再进行访问
    }  //Draw d=new Draw(),上面代码相当于d.drawShape(),因为getDrawInstance()方法返回了Draw对象
    private Draw getDrawInstance() {
        return new Draw();  //返回成员内部类的对象
    }
    class Draw {     //成员内部类
        public void drawShape() {
            System.out.println(radius);  //外部类的private成员
        }
    }
    public static void main(String[] args) {
        Circle c=new Circle(1);
    }
}
// 输出结果:1.0

5)成员内部类是依附外部类而存在的,即如果要创建成员内部类的对象,必须先存在一个外部类的对象。创建成员内部类对象的两种方式如下。

  • 通过外部类对象创建
  • 通过getInstance方法创建
代码语言:javascript复制
public class Test {
    public static void main(String[] args)  {
        Outter outter = new Outter(); //第一种方式
        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
        Outter.Inner inner1 = outter.getInnerInstance(); //第二种方式,getInstance方法
    }
}
class Outter { // 外部类
    private Inner inner = null;
    public Outter() { //无参构造
    }
    public Inner getInnerInstance() { //getInnerInstance()方法
        if(inner == null)
            inner = new Inner();
        return inner;
    }
    class Inner { // 成员内部类
            public Inner() { //无参构造
        }
    }
}

注:getInstance在单例模式 (保证一个类仅有一个实例,并提供一个访问它的全局访问点) 的类中常见,用来生成唯一的实例,getInstance往往是static的。

1.2 局部内部类

概念:定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

作用域:即一个变量或方法在程序中可以被访问的范围,可以是一个类、方法或一个代码块。

局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。eg:

代码语言:javascript复制
class People{
    public People() { // 无参构造
    }
}
class Man{
    public Man(){ // 无参构造
    }
    public People getWoman(){
        class Woman extends People{   //局部内部类,定义在getWoman这个方法内
            int age =0;
        }
        return new Woman();
    }
}
1.3 匿名内部类

匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。

注:在编写事件监听代码时使用匿名内部类不但方便,而且使代码更加容易维护。

1.语法

代码语言:javascript复制
new <类或接口>() {
    // 类的主体
};

优点:这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。

2.匿名类的两种实现方式 1)继承一个类,重写其方法。 2)实现一个接口(可以是多个),实现其方法。

注:匿名内部类实现一个接口的方式与实现一个类的方式相同

3.案例

代码语言:javascript复制
public class Out {
    void show(){
        System.out.println("调用 Out 类的 show() 方法");
    }
}
class testAnonyInter { // 测试
    private void show() {
        // 在这个方法中构造一个匿名内部类
        Out anonyInter = new Out() {
            // 获取匿名内部类的实例
            void show() {
                System.out.println("调用匿名类中的 show() 方法");
            }
        };
        anonyInter.show();
    }
    public static void main(String[] args) {
        testAnonyInter tA = new testAnonyInter();
        tA.show();
    }
}

运行结果

代码语言:javascript复制
调用匿名类中的 show() 方法

4.匿名类的特点 1)匿名类和局部内部类一样,可以访问外部类的所有成员。eg:

代码语言:javascript复制
public class Out {
    void show(){
        System.out.println("调用 Out 类的 show() 方法");
    }
}
class testAnonyInter {
    public static void main(String[] args) {
        int a = 10;
        final int b = 10;
        Out anonyInter = new Out() { // 构造一个匿名类
            void show() {
                System.out.println("调用了匿名类的 show() 方法" a);    // 编译出错
                System.out.println("调用了匿名类的 show() 方法" b);    // 编译通过
            }
        };
        anonyInter.show();
    }
}

运行结果

代码语言:javascript复制
调用了匿名类的 show() 方法10
调用了匿名类的 show() 方法10

2)匿名类中允许使用非静态代码块进行成员初始化操作。eg:

代码语言:javascript复制
Out anonyInter = new Out() {
    int i; {    // 非静态代码块
        i = 10;    //成员初始化
    }
    public void show() {
        System.out.println("调用了匿名类的 show() 方法" i);
    }
}

3)匿名类的非静态代码块会在父类的构造方法之后被执行。

5.补充

匿名内部类不能有访问修饰符和static修饰符。

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

1.4 静态内部类

1)概念 定义在另一个类里面的类,只不过在类的前面多了一个关键字static。 2)说明

静态内部类不需要依赖于外部类,这点和类的静态成员属性类似,并且它不能使用外部类的非static成员变量或者方法,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

3)eg:

代码语言:javascript复制
public class Test { // 测试类
    public static void main(String[] args){
        Outter.Inner inner=new Outter.Inner();
    }
}
class Outter{
    int a=10;
    static int b=5;
    public Outter(){
    }
    static class Inner{ //静态内部类
        public Inner(){
            //System.out.println(a); 编译出错,java: 无法从静态上下文中引用非静态变量 a
            System.out.println(b); //输出结果:5
        }
    }
}
2、抽象类
2.1 抽象方法

1)概念 一个方法使用 abstract 来修饰,且只有声明没有实现。 2)特征

  • 使用 abstract 修饰且没有方法体(没有方法体与空方法体不同)。
  • 抽象方法只能定义在抽象类或接口中。
  • 不能使用 private、final 或 static 修饰。

注:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。

2.2 抽象类

与抽象方法一样,抽象类也要使用 abstract 关键字声明。几点注意如下:

一个方法被声明为抽象的,那么这个类也必须声明为抽象的。一个抽象类中,可以有 0~n 个抽象方法和具体方法。

有构造方法,但不能直接用来创建对象,只留给子类创建对象时调用。

子类重写父类时,必须重写父类所有的抽象方法。即抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

抽象类不能实例化,即不能使用 new 关键字创建对象。

要访问抽象类,它必须从另一个类继承。

3)代码示例 创建一个抽象类 Shape

代码语言:javascript复制
public abstract class Shape {
    public int width; // 几何图形的长
    public int height; // 几何图形的宽
    public Shape(int width, int height) { //带全部参数的构造方法
        this.width = width;
        this.height = height;
    }
    public abstract double area(); // 定义抽象方法,计算面积
}

定义一个正方形类Square,继承形状类 Shape,并重写了 area( ) 抽象方法。

代码语言:javascript复制
public class Square extends Shape {
    public Square(int width, int height) {  //带全部参数的构造方法
        super(width, height);
    }
    // 重写父类中的抽象方法,实现计算正方形面积的功能
    @Override
    public double area() {
        return width * height;
    }
}

再定义一个三角形类Triangle,继承形状类 Shape,并重写父类中的抽象方法 area()。

代码语言:javascript复制
public class Triangle extends Shape {
    public Triangle(int width, int height) {  //带全部参数的构造方法
        super(width, height);
    }
    // 重写父类中的抽象方法,实现计算三角形面积的功能
    @Override
    public double area() {
        return 0.5 * width * height;
    }
}

最后创建一个测试类。

代码语言:javascript复制
public class ShapeTest {
    public static void main(String[] args) {
        Square s = new Square(5, 4); // 创建正方形类对象
        System.out.println("正方形的面积为:"   s.area());
        Triangle t = new Triangle(2, 5); // 创建三角形类对象
        System.out.println("三角形的面积为:"   t.area());
    }
}

运行结果

代码语言:javascript复制
正方形的面积为:20.0
三角形的面积为:5.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)案例

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        // 装箱
        Integer intObj1 = new Integer(123);
        Integer intObj2 = Integer.valueOf(123); // 推荐,带有缓存
        // 拆箱
        int num3 = intObj1.intValue(); // int num3 = new Integer(123);
        Integer intObj4 = 123;  // 底层 Integer intObj4 = Integer.valueOf(123)
        int num5 = intObj4; // 底层 int num5 = intObj4.intValue()
        boolean b = intObj4 == 123;// true,底层 intObj4.intValue() == 123
        Object boolObj = true;
        if (boolObj instanceof Boolean) {
            boolean b1 = (Boolean)boolObj; // 大转小(Object->boolean),强转
            System.out.println(b1);
        }
    }
}
// 结果:true

0 人点赞