一、面向过程和面向对象的区别

面向过程:当事件比较简单的时候,利用面向过程,注重的是事件的具体的步骤/过程,注重的是过程中的具体的行为,以函数为最小单位,考虑怎么做。

面向对象:注重找“参与者”,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

案例:人把大象装进冰箱:
面向过程:

  • 函数1:打开冰箱(){人站在冰箱前,打开冰箱,冰箱卡到30度角的时候,冰箱的灯打开了.........}
  • 函数2:储存大象(){大象先迈左腿,再迈右退,考虑冰箱能不能装下......}
  • 函数3:关闭冰箱(){人站在冰箱前,关闭冰箱,冰箱开到30度角的时候,冰箱的灯关闭了..........}

面向对象:

人{
  打开(冰箱){
  冰箱.打开();
  }

  存储(大象){
    大象.进入();
  }

  关闭(冰箱){
    冰箱.关闭();
  }
}
冰箱{

  打开(){ 1.2.3.}
  关闭(){}
}
大象{
  进入(冰箱){}
}

面向过程 到 面向对象 , 其实就是由执行者 到 指挥者的 一个过渡,二者相辅相成,并不是对立的。解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系、方便我们分析整个系统;具体到微观操作,仍然使用面向过程方式来处理。

二、类和对象的关系

对象:任何具有状态和行为的实体都称为对象。例如:椅子,钢笔,桌子,键盘,自行车等。它可以是物理和逻辑的。具体的事物,具体的实体,具体的实例,模板下具体的产品

对象的集合称为,它是一个逻辑实体。对对象向上抽取出像的部分,公共的部分,形成类,类是抽象的,是一个模板,

一般在写代码的时候先写类,然后在根据类创建对应的对象。类和对象的关系如模具和月饼的关系:

  • 一个模具可以产生多个月饼,对应面向对象中一个类可以产生多个对象
  • 根据同一个模具产生的月饼可以大小相同,但是用料不一样颜色和口味不同,对应面向对象中一个类可以产生多个实例,实例之间有一些属性和方法可以相同,也可以不同。

Java面向对象_局部变量

三、面向对象三个阶段

面向对象三个阶段:

1.面向对象分析OOA -- Object Oriented Analysis

  • 对象:张三、李四、王五、朱六
  • 抽取出一个类:人类
  • 类里面有什么:
    动词(唱歌、跳舞、打游戏、吃饭、睡觉):动态特性--》方法
    名词(年龄、性别、体重、身高):静态特性--》属性

2.面向对象设计OOD -- Object Oriented Design

  • 类里面有什么:
  •   动态特性:方法
  •   静态特性:属性
  • 所以定义类,其实就是定义类的方法和属性

3.面向对象编程OOP -- Object Oriented Programming

四、创建类

创建类主要是定义类中一下两部分:
(1)属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。属性定义格式:

[修饰符] 属性类型 属性名 = [默认值] ;

(2)方法
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。方法定义格式:

[修饰符] 方法返回值类型 方法名(形参列表) {
    // n条语句
}

void代表没有返回值;方法的作用:重用代码,封装功能,便于修改

案例代码如下:

package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 13:36
 * @Description: com.oop
 * @version: 1.0
 */
public class Person {
    //属性:作为成员变量,放在类中方法外部,下面定义,姓名、年龄和身高
    String name;
    int age;
    double height;

    //方法:定义的就是行为,也就是动作

    //吃饭
    public void eat(String foot){
        //注意,定位在方法中的变量,称之为局部变量
        int num1 = 21;
        System.out.println("最开心的就是去吃"+foot+"的时候");
    }

    //睡觉
    public void sleep(int hour){
        System.out.println("每天能够休息"+hour+"小时");
    }

    //自我介绍
    public String introduction(){
        return "我叫"+name+",今年"+age+"岁,身高是:"+height;
    }
}

五、创建对象

对象是根据类来创建的,创建对象的时候,可以给属性赋值,在对象创建后,可以访问该对象的属性,也可以调用该对象的方法

package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 13:52
 * @Description: com.oop
 * @version: 1.0
 */
public class Test {
    public static void main(String[] args) {
        //创建一个人类的具体实例对象,Person属于引用数据类型
        //第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化值
        Person p1 = new Person();
        //设置属性
        p1.name = "张无忌";
        p1.age = 23;
        p1.height = 180.6;

        //再次创建对象
        //再次创建类的时候,就不会进行类的加载了,类的加载只会在第一次需要的时候加载一次
        Person p2 = new Person();
        //设置属性
        p2.name = "赵敏";
        p2.age = 21;
        p2.height = 177.5;


        //访问对象的属性
        System.out.println(p1.name+"的年龄是"+p1.age+"岁,身高是"+p1.height);
        System.out.println(p2.name+"的年龄是"+p2.age+"岁,身高是"+p2.height);

        /**
         *对方法进行操作:不同的对象,属性有自己的特有的值,但是方法都是调用类中通用的方法。
         * 属性:各个对象的属性是独立的,
         * 方法:各个对象的方法是共享的。
         */

        p1.eat("面条");
        p1.sleep(8);
        String introduction1 = p1.introduction();
        System.out.println(introduction1);

        System.out.println("--------------------------------");
        p2.eat("米饭");
        p2.sleep(8);
        String introduction2 = p2.introduction();
        System.out.println(introduction2);

    }
}

六、局部变量和成员变量

局部变量指的是定义在方法体中,而成员变量指的是定义在方法体之外,成员变量指的就是类中的属性

局部变量和成员变量的区别在于:

区别1:代码中位置不同

  • 成员变量:类中方法外定义的变量
  • 局部变量:方法中定义的变量 代码块中定义的变量

区别2:代码的作用范围

  • 成员变量:当前类的很多方法
  • 局部变量:当前一个方法(当前代码块)

区别3:是否有默认值

  • 成员变量:有
  • 局部变量:没有

Java面向对象_System_02

区别4:是否要初始化

  • 成员变量:不需要,不建议初始化,后续使用的时候再赋值即可
  • 局部变量:一定需要,不然直接使用的时候报错

区别5:内存中位置不同

  • 成员变量:堆内存
  • 局部变量:栈内存

区别6:作用时间不同

  • 成员变量:当前对象从创建到销毁
  • 局部变量:当前方法从开始执行到执行完毕

案例代码如下:

package com.oop;

public class Student {
    //成员变量:在类中方法外
    byte e;
    short s;
    int c ;
    long num2;
    float f ;
    double d;
    char ch;
    boolean bo;
    String name;

    public void study(){
        //num就是局部变量,在方法中声明
        int num = 10 ;
        System.out.println(num);//10

        {
            int a;//局部变量:在代码块中
        }
        int a;
        if(1==2){
            int b;
        }
        System.out.println(c);
    }

    public static void main(String[] args) {
        //创建对象
        Student student = new Student();

        //访问成员变量,也就是属性
        System.out.println(student.c);
        System.out.println(student.bo);
        System.out.println(student.ch);
        System.out.println(student.d);
        System.out.println(student.e);
        System.out.println(student.f);
        System.out.println(student.name);
        System.out.println(student.num2);
        System.out.println(student.s);

        student.study();
    }
}

七、构造器

7.1.Java中构造器

java中的构造方法是一种特殊类型的方法,用于初始化对象。Java构造函数在对象创建时被调用。 它构造值,即提供对象的数据,这是为什么它被称为构造函数。

7.1.1.创建java构造函数的规则

构造函数基本上定义了两个规则。它们分别如下 :

  1. 构造函数名称必须与其类名称相同
  2. 构造函数必须没有显式返回类型

7.1.2. Java默认构造函数(无参数构造函数)

没有参数的构造函数称为默认构造函数。默认构造函数的语法如下:

package com.oop;

public class Tiger {
    //创建无参构造器
    Tiger(){
        System.out.println("Tiger is created");
    }

    public static void main(String[] args) {
        Tiger tiger = new Tiger();
    }
}

上面的示例代码运行结果如下 :

Tiger is created

注意:

  如果类中没有构造方法,编译器会自动创建一个默认构造方法。

默认构造函数根据类型为对象提供默认值,如:0null等。如下:

package com.oop;

public class Tiger {

    String name;
    int age;

    public void display(){
        System.out.println("昵称是"+name+"年龄是"+age);
    }

    public static void main(String[] args) {
        Tiger tiger1 = new Tiger();
        Tiger tiger2 = new Tiger();

        tiger1.display();
        tiger2.display();
    }
}

运行上面代码,得到如下结果 -

昵称是null年龄是0
昵称是null年龄是0

在上面的类中,代码中并没有创建任何构造函数,但编译器自动提供了一个默认构造函数。默认构造函数分别为字段:idname 分别提供了0null值。

7.1.3. Java参数化构造方法

package com.oop;

public class Student1 {
    int id;
    String name;

    //参数化构造方法
    Student1(int id, String name) {
        this.id = id;
        this.name = name;
    }

    void display() {
        System.out.println("ID的值为"+id + "姓名为" + name);
    }

    public static void main(String[] args) {
        Student1 s1 = new Student1(10001, "李明");
        Student1 s2 = new Student1(10002, "张飞");

        s1.display();
        s2.display();
    }
}

运行上面代码,得到如下结果

ID的值为10001姓名为李明
ID的值为10002姓名为张飞

7.2.构造器的重载

构造方法重载是Java中的一种技术,一个类可以有任何数量的参数列表不同的构造函数。编译器通过构造函数参数列表中的参数数量及其类型来区分这些构造函数。 

package com.oop;

public class Student1 {
    int id;
    String name;
    int age;

    //2个参数的构造方法
    public Student1(int id, String name) {
        //this代表的就是你创建的那个对象
        this.id = id;
        this.name = name;
    }

    //3个参数的构造方法
    public Student1(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    void display() {
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }

    public static void main(String[] args) {
        Student1 s1 = new Student1(10001, "李明");
        Student1 s2 = new Student1(10002, "张飞",32);

        s1.display();
        s2.display();
    }
}

上面示例代码,执行后输出结果如下

ID的值为10001姓名为李明,年龄为0
ID的值为10002姓名为张飞,年龄为32

7.3.java的构造函数和方法之间的区别

构造函数和方法之间的区别如下:

Java构造函数

Java方法

构造器用于初始化对象的状态(数据)。

方法用于暴露对象的行为。

构造函数不能有返回类型。

方法一般都有返回类型。

构造函数隐式调用。

方法要显式调用。

如果没有指定任何构造函数,java编译器提供一个默认构造函数。

在任何情况下编译器都不会提供默认的方法调用。

构造函数名称必须与类名称相同。

方法名称可以或可以不与类名称相同(随意)。


八、Java this关键字

java中,this关键字有很多种用法。 在java中,这是一个引用当前对象的引用变量。

Java面向对象_局部变量_03

java this关键字的用法如下:

  1. this关键字可用来引用当前类的实例属性。
  2. this关键字可用于调用当前类方法(隐式)。
  3. this()可以用来调用当前类的构造函数。
  4. this关键字可作为调用方法中的参数传递。
  5. this关键字可作为参数在构造函数调用中传递。
  6. this关键字可用于从方法返回当前类的实例。

(1)this可以修饰属性:
当属性名字和形参发生重名的时候,或者 属性名字局部变量 重名的时候,都会发生就近原则,所以如果我要是直接使用变量名字的话就指的是离的近的那个形参或者局部变量,这时候如果我想要表示属性的话,在前面要加上:this.修饰

如果不发生重名问题的话,实际上你要是访问属性也可以省略this.

package com.oop;

public class Student2 {
    int id;
    String name;
    int age;

    //2个参数的构造方法
    public Student2() {
        //this代表的就是你创建的那个对象
        this.id = id;
        this.name = name;
    }

    //3个参数的构造方法
    public Student2(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    void display() {
        int age = 10;
        System.out.println(age);//就近原则,age指的是距离最近的局部变量age
        System.out.println(this.age);//指的是属性age
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }

    public static void main(String[] args) {
        Student2 s = new Student2(10001, "魏无忌", 18);
        s.display();
    }
}

(2)this修饰方法:
在同一个类中,方法可以互相调用,this.可以省略不写。

package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 15:15
 * @Description: com.oop
 * @version: 1.0
 */
public class Student2 {
    int id;
    String name;
    int age;

    //2个参数的构造方法
    public Student2(int id,String name) {
        //this代表的就是你创建的那个对象
        this.id = id;
        this.name = name;
    }

    //3个参数的构造方法
    public Student2(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    void display() {
        int age = 10;
        System.out.println(age);//就近原则,age指的是距离最近的局部变量age
        System.out.println(this.age);//指的是属性age
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }

    public void eat(){
        //调用display(),this可以省略不写
        this.display();
        System.out.println(age);
        System.out.println("吃饭");
    }

    public static void main(String[] args) {
        Student2 s = new Student2(10001, "魏无忌", 18);
        s.eat();
    }
}

(3)this可以修饰构造器:
同一个类中的构造器可以相互用this调用,注意:this修饰构造器必须放在第一行

package com.oop;

public class Student2 {
    int id;
    String name;
    int age;

    //2个参数的构造方法
    public Student2(int id,String name) {
        //this代表的就是你创建的那个对象
        this.id = id;
        this.name = name;
    }

    //3个参数的构造方法
    public Student2(int id, String name, int age) {
        //这样写就相当于在调用前面的2个参数构造器
        this(id,name);
        this.age = age;
    }

    void display() {
        int age = 10;
        System.out.println(age);//就近原则,age指的是距离最近的局部变量age
        System.out.println(this.age);//指的是属性age
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }

    public void eat(){
        //调用display(),this可以省略不写
        this.display();
        System.out.println(age);
        System.out.println("吃饭");
    }

    public static void main(String[] args) {
        Student2 s = new Student2(10001, "魏无忌", 18);
        s.eat();
    }
}

九、static关键字

java中的static关键字主要用于内存管理。可以应用java static关键字在变量,方法,块和嵌套类中。 static关键字属于类,而不是类的实例。

静态(static)可以是:

  1. 变量(也称为类变量)
  2. 方法(也称为类方法)
  3. 代码块
  4. 内部类

9.1.static修饰变量(类属性)

package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 15:15
 * @Description: com.oop
 * @version: 1.0
 */
public class Student4 {
    int id;
    String name;
    //static修饰属性
    static int age;

    //3个参数的构造方法
    public Student4(int id, String name, int age) {
        this.id = id;
        this.name = name;
        //但是如果是给static修饰的属性赋值,建议使用 类名.属性名 的形式,下面这种不推荐
        this.age = age;
    }

    void display() {
        System.out.println(Student4.age);
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }
    
    public static void main(String[] args) {

        Student4 s1 = new Student4(10001, "李明",23);
        Student4 s2 = new Student4(10002, "张飞",32);
        Student4 s3 = new Student4(10003, "李媛",36);

        s1.display();
        s2.display();
        s3.display();
    }
}

上面的代码执行后结果如下:

ID的值为10001姓名为李明,年龄为36
ID的值为10002姓名为张飞,年龄为36
ID的值为10003姓名为李媛,年龄为36

可以发现上面static修改是类属性age,产生对象的时候赋值虽然不同,但是输出的值都是最后一个对象创建赋值的36,通过内存分析原因如下:

Java面向对象_System_04

 一般官方的推荐访问方式:可以通过 类名.属性名   的方式去访问static修改的静态属性:

Java面向对象_System_05

static修饰属性总结:
(1)在类加载的时候一起加载入方法区中的静态域中
(2)先于对象存在
(3)访问方式: 对象名.属性名 类名.属性名(推荐)

static修饰属性的应用场景:某些特定的数据想要在内存中共享这个情况下,就可以用static修饰的属性,比较节省内存

所以Java中属性分两种:

  • 静态属性: static关键字修饰(类变量)
  • 非静态属性(实例变量)

9.2.static修饰方法

Java中在任何方法上使用static关键字修饰,此方法称为静态方法。

  • 可以直接调用静态方法,而无需创建类的实例(实例方法需要通过实例区调用)。
  • 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。
案例代码如下:
package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 15:15
 * @Description: com.oop
 * @version: 1.0
 */
public class Student5 {
    int id;
    String name;
    //static修饰属性
    static int age;

    //3个参数的构造方法
    public Student5(int id, String name, int age) {
        this.id = id;
        this.name = name;
        //但是如果是给static修饰的属性赋值,建议使用 类名.属性名 的形式,
        //this.age = age;
        Student5.age=age;
    }

    public void display() {
        System.out.println("ID的值为"+id + "姓名为" + name+",年龄为"+age);
    }

    //定义静态方法
    //static和public都是修饰符,先后顺序其实不影响,
    public static void eat(){
        //在静态方法中不能使用this关键字
        //System.out.println(this.name);
        //在静态方法中不能调用非静态方法,也就是实例方法
        //display();

        //在静态方法中只能方法静态属性
        System.out.println(age);
        
    }


    public static void main(String[] args) {

        Student5 s1 = new Student5(10001, "李明",23);
        /*Student5 s2 = new Student5(10002, "张飞",32);
        Student5 s3 = new Student5(10003, "李媛",36);*/

        /*s1.display();
        s2.display();
        s3.display();*/

        //访问静态属性
        System.out.println(Student5.age);//通过 类名.属性名 调用静态属性 推荐
        System.out.println(s1.age); // 通过 实例对象.属性名 调用静态属性
        
        //调用静态方法
        s1.eat();//通过实例对象调用静态方法
        Student5.eat();//通过 类名调用静态方法
        
        
    }
}

静态方法有两个主要限制。它们分别是:

  • 静态方法不能直接使用非静态属性或调用非静态方法。
  • thissuper两个关键字不能在静态上下文中使用。

为什么java main方法是静态的?

这是因为对象不需要调用静态方法,如果它是非静态方法,jvm首先要创建对象,然后调用main()方法,这将导致额外的内存分配的问题。

9.3.Java静态块

Java中的静态块主要有两个作用:

  • 用于初始化静态属性。
  • 它在类加载时在main方法之前执行。
package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/22 17:51
 * @Description: com.oop
 * @version: 1.0
 */
public class A1 {
    //静态块
    static {
        System.out.println("这是static代码块");
    }

    //构造块
    {
        System.out.println("这是构造块");
    }

    public static void main(String[] args) {
        System.out.println("这是main方法");
    }
}

执行结果如下:

这是static代码块
这是main方法

注意:

  • Java中类的组成:属性,方法,构造器,代码块和内部类
  • 代码块分类:普通块,构造块,静态块,同步块(多线程)

代码块执行顺序:

  • 最先执行静态块,只在类加载的时候执行一次,所以一般以后实战写项目:创建工厂,数据库的初始化信息都放入静态块。一般用于执行一些全局性的初始化操作。
  • 再执行构造块(不常用)
  • 再执行构造器,
  • 再执行方法中的普通块。

十、面向对象三大特征

10.1.继承

10.1.1.继承的概念

继承是允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。生活中继承的案例:

Java面向对象_构造函数_06

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。

食草动物和食肉动物又是属于动物类。

所以继承需要符合的关系是:is-a,父类更通用,子类更具体。

虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特征

10.1.2.类的继承格式

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
}
 
class 子类 extends 父类 {
}

10.1.2.为什么需要继承

开发动物类,其中动物分别为老虎以及羊,要求如下:

  • 老虎:属性(姓名,id),方法(吃,睡,自我介绍)
  • 羊:属性(姓名,id),方法(吃,睡,自我介绍)

老虎类

public class Tiger{ 
    private String name; 
    private int id; 
    public Penguin(String myName, int  myid) { 
        name = myName; 
        id = myid; 
    } 
    public void eat(){ 
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
        System.out.println(name+"正在睡");
    }
    public void introduction() { 
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

羊类

public class Sheep{ 
    private String name; 
    private int id; 
    public Mouse(String myName, int  myid) { 
        name = myName; 
        id = myid; 
    } 
    public void eat(){ 
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
        System.out.println(name+"正在睡");
    }
    public void introduction() { 
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

从这两段代码可以看出来,代码存在重复臃肿,而且维护性不高(维护性主要是后期需要修改的时候,就需要修改很多的代码,容易出错),所以要从根本上解决这两段代码的问题,就需要继承,将两段代码中相同的部分提取出来组成 一个父类:

公共父类:

public class Animal { 
    private String name;  
    private int id; 
    public Animal(String myName, int myid) { 
        name = myName; 
        id = myid;
    } 
    public void eat(){ 
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
        System.out.println(name+"正在睡");
    }
    public void introduction() { 
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

这个Animal类就可以作为一个父类,然后老虎类和羊类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:

老虎类:

public class Tiger extends Animal { 
    public Penguin(String myName, int myid) { 
        super(myName, myid); 
    } 
}

羊类:

public class Sheep extends Animal { 
    public Mouse(String myName, int myid) { 
        super(myName, myid); 
    } 
}

10.1.3.继承类型

注意Java 不支持多继承,但支持多重继承。

Java面向对象_构造函数_07

10.1.4.继承的特性

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

10.1.5.继承关键字

继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。

extends关键字

在 Java 中,类的继承是单继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal { 
    private String name;   
    private int id; 
    public Animal(String myName, int myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
 
public class Tiger  extends  Animal{ 
}
implements关键字

使用 implements 关键字可以变相的使java具有多继承的特性,一般在类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}
super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

package com.oop;

public class Animal {
    public void eat(){
        System.out.println("animal:eat");
    }
}

class Dog extends Animal{
    public void eat(){
        System.out.println("dog:eat");
    }
    
    void dogEatTest(){
        this.eat();//this调用自己的eat方法
        super.eat();//调用父类的eat方法
    }
}

测试代码:

package com.oop;

public class Test1 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.eat();

        Dog dog = new Dog();
        //调用方法
        dog.dogEatTest();
    }
}

执行结果如下:

animal:eat
dog:eat
animal:eat
final 关键字

final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。

final 含义为 "最终的"。

使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写:

Java面向对象_局部变量_08

  • 声明类:
final class 类名 {//类体}
  • 声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}

注: final 定义的类,其中的属性、方法不是 final 的。

private关键字
  • private关键字是一个权限修饰符
  • 可以修饰成员
  • 被private修饰的成员只能在本类中才能访问
  • 针对private修饰的成员变量,如果需要被其它类使用,需要提供相应的操作
  • 提供“setXxx(参数)”方法,用于给成员变量赋值,方法用public修饰
  • 提供"getXxx()"方法,用于获取成员变量的值,方法用public修饰

案例代码:

package com.ood;

public class Student {
    //属性
    private String name;
    private int age;
    private String gender;

    //针对于每一个私有化的成员变量,都要提供get和set方法
    //set方法:给成员变量赋值
    //get方法:对外提供成员变量的值


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    //方法
    public void sleep(){
        System.out.println(name+"今年"+age+"岁,行为"+gender+"喜欢睡觉");
    }

    public void eat(){
        System.out.println(name+"今年"+age+"岁,行为"+gender+"喜欢吃汉堡包");
    }
}

测试代码:Test.java

package com.ood;

public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        //给成员变量赋值
        s.setName("李明");
        s.setAge(23);
        s.setGender("男");

        //查看成员变量的属性
        System.out.println(s.getName());
        System.out.println(s.getAge());
        System.out.println(s.getGender());

        //调用方法
        s.eat();
        s.sleep();
    }
}

10.1.6.构造器

子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器

package com.ood;

public class A {
    private int id;
    public A(){
        System.out.println("A类无参构造器");
    }


    public A(int id) {
        System.out.println("A类有参构造器");
        this.id = id;
    }

}

class B extends A{
    private int n;

    //自动调用父类的无参构造器
    B(){
        System.out.println("B类无参构造器");
    }

    public B(int n) {
        super(300);//调用父类的中带有参数的构造器
        this.n = n;
        System.out.println("B类有参构造器");
    }
}

测试代码如下:

package com.ood;

public class Test1 {
    public static void main(String[] args) {
        System.out.println("-------无参构造器-------");
        B b1 = new B();
        System.out.println("-------有参构造器-------");
        B b2 = new B(200);
    }
}

10.2.封装

面向对象设计方法中,封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

封装的优点

  • 良好的封装能够减少耦合。
  • 类内部的结构可以自由修改。
  • 可以对成员变量进行更精确的控制。
  • 隐藏信息,实现细节。

实现Java封装的步骤

1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:

public class Person {
    private String name;
    private int age;
}

上面代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

2. 对每个属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:

public class Person{
    private String name;
    private int age;

    public int getAge(){
      return age;
    }

    public String getName(){
      return name;
    }

    public void setAge(int age){
      this.age = age;
    }

    public void setName(String name){
      this.name = name;
    }
}

使用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。案例代码如下:

package com.ood;

public class Student {
    //属性
    private String name;
    private int age;
    private String gender;

    //针对于每一个私有化的成员变量,都要提供get和set方法
    //set方法:给成员变量赋值
    //get方法:对外提供成员变量的值


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    //方法
    public void sleep(){
        System.out.println(name+"今年"+age+"岁,行为"+gender+"喜欢睡觉");
    }

    public void eat(){
        System.out.println(name+"今年"+age+"岁,行为"+gender+"喜欢吃汉堡包");
    }
}

测试方法如下:

上面案例中public方法是外部类访问该类成员变量的入口。通常情况下,这些方法被称为getter和setter方法。因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

package com.ood;

public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        //给成员变量赋值
        s.setName("李明");
        s.setAge(23);
        s.setGender("男");

        //查看成员变量的属性
        System.out.println(s.getName());
        System.out.println(s.getAge());
        System.out.println(s.getGender());

        //调用方法
        s.eat();
        s.sleep();
    }
}

10.3.多态

多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作,如图所示

Java面向对象_构造函数_09

多态的优点

消除类型之间的耦合关系、可替换性、可扩充性、灵活性、简化性

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

案例代码:

package com.ood;

/**
 * @author : Augus
 * @date: 2023/5/23 15:42
 * @Description: com.ood
 * @version: 1.0
 */
public class Animal {
    public void eat(){
        System.out.println("吃鱼");
    }

    public void work(){
        System.out.println("捉老鼠");
    }
}

class Dog extends Animal{
    //这里的eat方法是对应父类eat方法的重写
    public void eat(){
        //super.eat();//调用父类的eat方法
        System.out.println("吃骨头");
    }

    public void work(){
        System.out.println("看家护院");
    }
}

测试代码:

package com.ood;

public class Test2 {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        a.work();

        System.out.println("-----------------");
        Dog dog = new Dog();
        dog.eat();
        dog.work();

        System.out.println("-----------------");
        //父类应用指向子类对象
        Animal d = new Dog();
        d.eat();
        d.work();
    }
}

代码执行结果如下:

吃鱼
捉老鼠
-----------------
吃骨头
看家护院
-----------------
吃骨头
看家护院

注意:

Java中子类能够重写父类的方法。当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super

十一、Java 重写(Override)与重载(Overload)

11.1.重写(Override)

方法重写

重写指的是面向对象中子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

案例代码:

package com.ood;

/**
 * @author : Augus
 * @date: 2023/5/23 15:42
 * @Description: com.ood
 * @version: 1.0
 */
public class Animal {
    public void eat(){
        System.out.println("吃鱼");
    }

    public void work(){
        System.out.println("捉老鼠");
    }
}

class Dog extends Animal{
    //这里的eat方法是对应父类eat方法的重写
    public void eat(){
        //super.eat();//调用父类的eat方法
        System.out.println("吃骨头");
    }

    public void work(){
        System.out.println("看家护院");
    }
}

测试代码:

package com.ood;

public class Test2 {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        a.work();

        System.out.println("-----------------");
        Dog dog = new Dog();
        dog.eat();
        dog.work();

        System.out.println("-----------------");
        //父类应用指向子类对象
        Animal d = new Dog();
        d.eat();//执行的是Dog类中的方法
        d.work();//执行的是Dog类中的方法
    }
}

代码执行结果如下:

吃鱼
捉老鼠
-----------------
吃骨头
看家护院
-----------------
吃骨头
看家护院

在上面的例子中可以看到,尽管 d 属于 Animal 类型,但是它运行的是 Dog 类的 eat和work方法。

原因在于在编译阶段,只是检查参数的引用类型。然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 eat和work方法,然而运行时,运行的是特定对象的方法。

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。

11.2.重载(Overload)

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

案例代码如下:

package com.ood;

public class OverLoadTest {
    public int test(){
        System.out.println("hi test1");
        return 1;
    }

    public void test(int a){
        System.out.println("hi test2");
    }

    //下面两个方法参数类型相同,顺序不同,构成重载
    public void test(int a, String s){
        System.out.println("hi test3");
    }

    public void test(String s,int a){
        System.out.println("hi test3");
    }

    public static void main(String[] args) {
        OverLoadTest overLoadTest = new OverLoadTest();
        //调用方法
        overLoadTest.test();//hi test1
        overLoadTest.test(1);//hi test2
        overLoadTest.test(2,"hello");//hi test3
        overLoadTest.test("hello",2);//hi test4
    }
}

11.3.重写与重载之间的区别

区别点

重载方法

重写方法

参数列表

必须修改

一定不能修改

返回类型

可以修改

一定不能修改

异常

可以修改

可以减少或删除,一定不能抛出新的或者更广的异常

访问

可以修改

一定不能做更严格的限制(可以降低限制)

总结:方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  1. 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
  2. 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
  3. 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

十二、Java 抽象类

12.1.什么是抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

12.2.为什么需要抽象类

在抽象类中定义抽象方法,目的是为了为子类提供一个通用的模板,子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容。抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制。使子类更加的通用。

12.3.抽象类的定义

在 Java 语言中使用 abstract class 来定义抽象类。如下实例:

package com.ood;

/**
 * @author : Augus
 * @date: 2023/5/23 16:53
 * @Description: com.ood
 * @version: 1.0
 */
public abstract class Employee {
    private String name;
    private String address;
    private int number;

    //构造方法
    public Employee(String name, String address, int number) {
        this.name = name;
        this.address = address;
        this.number = number;
    }

    //get set方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    //toString方法通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法
    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", number=" + number +
                '}';
    }

    //输出薪水
    public String personalSalary(){
        return getName()+"的地址是"+getAddress()+",销售数量为:"+getNumber();
    }
}

class salary extends Employee{
    //薪水
    private double salary;

    //子类的构造方法
    public salary(String name,String address,int number, double salary) {
        super(name,address,number);//调用父类的成员变量
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    //输出薪水
    public String personalSalary(){
        return getName()+"的地址是"+getAddress()+",销售数量为:"+getNumber()+",薪资为:"+getSalary();
    }
}

上面的 Employee 类和之前的类相比虽然该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法构成,但是这个类是无法创建对象的如下:

package com.ood;

public class Test3 {
    public static void main(String[] args) {
        //抽象类是不允许实例化的,会报错
        Employee employee = new Employee();
        employee.setName("张无忌");
        employee.setAddress("冰火岛");
        employee.setNumber(23);

        System.out.println(employee);

    }
}

执行会出现如下异常:

Java面向对象_构造函数_10

12.4.继承抽象类

之前的案例不能实例化一个 Employee 类的对象,但是让 Salary 类继承employee类,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。 

package com.ood;

/**
 * @author : Augus
 * @date: 2023/5/23 17:32
 * @Description: com.ood
 * @version: 1.0
 */
public class Test4 {
    public static void main(String[] args) {
        salary s1 = new salary("展宏","北京市朝阳区1271号",23,1112.5);
        //调用方法
        System.out.println(s1.personalSalary());


        Employee s2 = new salary("李敏","上海市黄浦区71号",63,3112.5);
        //调用方法
        System.out.println(s2.personalSalary());

    }
}

以上程序编译运行结果如下

展宏的地址是北京市朝阳区1271号,销售数量为:23,薪资为:1112.5
李敏的地址是上海市黄浦区71号,销售数量为:63,薪资为:3112.5

12.5.抽象方法

想设计一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract 关键字可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

package com.oop;

public abstract class Employee {
    private String name;
    private String address;
    private int number;

    //定义抽象方法
    public abstract double computePay();

    //构造方法
    public Employee(String name, String address, int number) {
        this.name = name;
        this.address = address;
        this.number = number;
    }
}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,最初的父类到最终的子类都不能用来实例化对象。

如果Salary类继承了Employee类,那么它必须实现computePay()方法:

 

package com.oop;

/**
 * @author : Augus
 * @date: 2023/5/23 18:13
 * @Description: com.oop
 * @version: 1.0
 */
public class Salary extends Employee{
    
    //薪水
    private double salary;

    public Salary(String name, String address, int number,double salary) {
        super(name, address, number);//调用父类的构造方法
        this.salary = salary;
    }

    //实现抽象方法
    @Override
    public double computePay() {
        System.out.println("这是Salary类中的computePay方法");
        return salary/2;
    }
}

12.6.抽象类总结规定

  • 1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  • 2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  • 3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  • 4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
  • 5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

十三、Java 接口

13.1.什么是接口?

接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

13.2.接口与类相同点:

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

13.3.接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

13.4.接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

13.5.抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  •  一个类只能继承一个抽象类,而一个类却可以实现多个接口。

  • JDK 1.8 以后,接口里可以有静态方法和方法体了。
  • JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
  • JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。

13.6.接口的声明

声明接口的语法格式如下:

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

Interface关键字用来声明一个接口,案例如下:

package com.augus;

public interface Animal {
    //定义两个方法
    public void eat();
    public void sleep();
}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。所以上面的方法前面的public可以取消。

13.7.接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。语法如下:

...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

案例代码:

package com.augus;

//使用Tiger类来实现接口Animal
public class Tiger implements Animal{
    @Override
    public void eat() {
        System.out.println("我是Tiger类中的eat");
    }

    @Override
    public void sleep() {
        System.out.println("我是Tiger类中的sleep");
    }

    //在实现类中也可以定义方法
    public int getNumber(){
        return 1;
    }

    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        //调用方法
        tiger.eat();
        tiger.sleep();
    }
}

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

13.8.接口的继承

一个接口能继承另一个接口,和类之间的继承方式相似。接口的继承使用extends关键字,子接口继承父接口的方法。案例如下:

这个是动物接口:Animal如下:

package com.augus;

public interface Animal {
    //定义两个方法
    public void eat();
    public void sleep();
}

这是食肉动物carnivore接口继承了Animal接口,如下:

package com.augus;

public interface carnivore extends Animal{
    //继承了接口Animal,同时自己也可以定义接口
    public void predation();//捕食
    public void play();//玩
}

采用狮子这个类,实现carnivore接口如下:

package com.augus;

public class lion implements carnivore{
    //由于lion实现了carnivore接口中,但是由于carnivore接口做了继承Animal接口,所以需要实现四个接口
    @Override
    public void predation() {
        System.out.println("我是lion类中的predation");
    }

    @Override
    public void play() {
        System.out.println("我是lion类中的play");
    }

    @Override
    public void eat() {
        System.out.println("我是lion类中的eat");
    }

    @Override
    public void sleep() {
        System.out.println("我是lion类中的sleep");
    }
}

carnivore接口自己声明了2个方法,从Animal接口继承了两个方法,这样,实现carnivore接口的类需要实现4个方法。

13.9.接口的多继承

在Java中,类的多继承是不合法,但接口允许多继承。

在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface GiantPanda extends carnivore, herbivore

与类不同的是,接口允许多继承,而 carnivore及 herbivore可以定义或是继承相同的方法

13.10.标记接口

没有任何方法的接口被称为标记接口,标记接口是没有任何方法和属性的接口,它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。

标记接口作用:就是给某个对象打个标(盖个章),使对象拥有某个或某些特权。

例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

标记接口主要用于以下两种目的:

  1. 建立一个公共的父接口:上面的EventListener接口,是由几十个其他接口扩展的Java API,可以使用一个标记接口来建立一组接口的父接口。当一个接口继承了EventListener接口,Java虚拟机、就知道该接口将要被用于某个事件的代理方案。
  2. 向一个类添加数据类型:此种情况是标记接口最初的实现目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

14.Java 枚举(enum)

14.1.枚举的定义和使用

Java 枚举是一个特殊的类,一般表示一组常量,有以下几种场景适合来使用枚举类,如下:

  • 星期: Monday(星期一)、Tuesday(星期二)、Wednesday(星期三)、Thursday(星期四)、Firday(星期五)、Saturday(星期六)、Sunday(星期日)
  • 性别: Man(男)、Woman(女)
  • 季节: Spring(春天)、Summer(夏天)、Autumn(秋天)、Winter(冬天)
  • 支付方式: Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)
  • 订单状态: Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配货)、Delivered(已发货)、Return(退货)、Checked(已确认)
  • 线程状态: Establish(创建)、Ready(就绪)、Run(运行)、Obstruct(阻塞)、Die(死亡)

Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。案例如下:

 

package com.augus;

public enum Color {
    RED, GREEN, BLUE,YELLOW;
}

class Test{
    public static void main(String[] args) {
        Color c = Color.RED;
        System.out.println(c);
    }
}

 

以上枚举类 Color 颜色常量有 RED, GREEN, BLUE,YELLOW,分别表示红色,绿色,蓝色,黄色。

14.2.内部类中使用枚举

枚举类也可以声明在内部类中:

package com.augus;

public class Test {
    //内部类中使用枚举
    enum Color{
        RED, GREEN, BLUE;
    }

    public static void main(String[] args) {
        Color c = Color.RED;
        System.out.println(c);
    }
}

每个枚举都是通过 Class 在内部实现的,且所有的枚举值都是 public static final 的。

14.3.迭代枚举元素

可以使用 for 语句来迭代枚举元素:

package com.augus;

public class Test {
    //内部类中使用枚举
    enum Color{
        RED, GREEN, BLUE;
    }

    public static void main(String[] args) {
        //通过.values方法和增强for循环实现
        for (Color value : Color.values()) {
            System.out.println(value);
        }
    }
}

14.4.在 switch 中使用枚举类

枚举类常应用于 switch 语句中:

package com.augus;

public enum Color {
    RED, GREEN, BLUE,YELLOW;
}

class Test{
    public static void main(String[] args) {
        Color c = Color.RED;

        switch (c){
            case RED:
                System.out.println("红色");
                break;
            case BLUE:
                System.out.println("蓝色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
            case YELLOW:
                System.out.println("黄色");
                break;
        }
    }
}

14.5.values(), ordinal() 和 valueOf() 方法

enum 定义的枚举类默认继承了 java.lang.Enum 类,实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。

values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:

  • values() 返回枚举类中所有的值。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。
package com.augus;

public enum Color {
    RED, GREEN, BLUE,YELLOW;
}

class Test{
    public static void main(String[] args) {
        //调用 values()方法
        Color[] values = Color.values();

        //迭代枚举
        for (Color value : values) {
            //查看索引和值
            System.out.println(value+"索引为:"+value.ordinal());
        }

        //使用valueOf()返回枚举常量。不存在会报异常:IllegalArgumentException
        System.out.println(Color.valueOf("RED"));
    }
}

上述代码执行后如下:

RED索引为:0
GREEN索引为:1
BLUE索引为:2
YELLOW索引为:3
RED

14.6.枚举类成员

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

package com.augus;

enum Color {
    RED,GREEN,BLUE,YELLOW;

    private Color() {
        System.out.println("hi: "+this.toString());
    }

    public void colorInfo(){
        System.out.println("这是colorInfo");
    }
}

class Test{
    public static void main(String[] args) {
        /*//调用 values()方法
        Color[] values = Color.values();

        //迭代枚举
        for (Color value : values) {
            //查看索引和值
            System.out.println(value+"索引为:"+value.ordinal());
        }

        //使用valueOf()返回枚举常量。不存在会报异常:IllegalArgumentException
        System.out.println(Color.valueOf("RED"));*/

        Color c = Color.RED;
        System.out.println(c);
        c.colorInfo();
    }
}

执行后内容如下:

hi: RED
hi: GREEN
hi: BLUE
hi: YELLOW
RED
这是colorInfo

15.Java 包(package)

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

15.1.包的作用

  • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

例如:

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

那么它的路径应该是 java/util/EventListener.java 这样保存的。 package(包) 的作用是把不同的 java 程序分类保存,更方便的被其他 java 程序调用。一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。以下是一些 Java 中的包:

  • java.lang-打包基础的类
  • java.io-包含输入输出功能的函数

开发人员可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

15.2.创建包

创建包的时候,指定该包的名字。如果其他的一个源文件中使用了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。

包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。

如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

15.3.import 关键字

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能。

在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1[.package2…].(classname|*);

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

package com.augus;

import com.ood.A;

public class Test2 {
    public static void main(String[] args) {
        A a = new A();
    }
}