面向对象——封装、继承、多态

1.类和对象

客观存在的事物皆为对象 ,即万物皆对象。

1.1类与对象的理解

类的理解:

​ 类是对现实生活中一类具有共同属性行为的事物的抽象。

​ 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合

​ 类就是对现实事物的一种描述。

类的组成:

​ 属性:指事物的特征,例如:学生(学号,姓名,年龄)。

​ 行为:指事物能执行的操作,例如:学生(学习,做题)。

类和对象的关系:

​ 类:类是对现实生活中一类具有共同属性行为的事物的抽象。

​ 对象:是能够看得到摸的着的真实存在的实体

​ 类是对事物的一种描述,对象则为具体存在的事物。

1.2 类的定义

类的组成是由属性和行为两部分组成。

​ 属性:在类中通过成员变量来体现(成员变量:类中方法外的变量)。

​ 行为:在类中通过成员方法来体现(成员方法:和前面的方法相比去掉static关键字即可)。

类的定义步骤:

①定义类。

②编写类的成员变量。

③编写类的成员方法。

/* 类的定义格式 */
public class 类名 {
	// 成员变量
	变量1的数据类型 变量1;
	变量2的数据类型 变量2;// 成员方法
	方法1;
	方法2;	
}

示例代码:

/*
    学生类:
        类名:
        学生(Student)
    
        成员变量:
        学号(id)
        姓名(name)
    
        成员方法:
        学习(study)
        做作业(doHomework)
*/
public class Student {

    //成员变量
    int id;
    String name;
    

    //成员方法
    public void study() {
        System.out.println("学习");
    }

    public void doHomework() {
        System.out.println("做作业");
    }
}

1.3 对象的使用

创建对象的格式:

​ 类名 对象名 = new 类名();

调用成员的格式:

​ 调用成员变量:

​ 对象名.成员变量;

​ 调用成员方法:

​ 对象名.成员方法();

示例代码:

package com.poiwens.notes;

/*
    创建对象
        格式:类名 对象名 = new 类名();
        范例:Phone p = new Phone();

    使用对象
        1:使用成员变量
            格式:对象名.变量名
            范例:p.brand
        2:使用成员方法
            格式:对象名.方法名()
            范例:p.call()
*/
public class StudentDemo {

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

        //使用成员变量
        System.out.println(p.id);
        System.out.println(p.name);

        p.id = 1001;
        p.name = "张三";

        System.out.println(p.id);
        System.out.println(p.name);

        //使用成员方法
        p.study();
        p.doHomework();
    }
}

2. 对象内存

​ 多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份。

3. 成员变量和局部变量

3.1 成员变量和局部变量的区别

​ 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)

​ 内存中位置不同:成员变量(堆内存)局部变量(栈内存)

​ 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)

​ 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)

4. 封装

4.1 封装思想

  1. 封装概述
    是面向对象三大特征之一(封装,继承,多态)

对象代表什么,就得封装对应的数据,并提供数据对应的行为。

多个类都有共同的属性,于是创建一个共同的父类去继承数据。

对象代表什么,就得封装对应的数据,并提供数据对应的行为

  1. 封装代码实现

    将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
    成员变量private,提供对应的getXxx()/setXxx()方法

4.2 private关键字

​ private是一个修饰符,可以用来修饰成员(成员变量,成员方法)。

​ 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作:

​ 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰。

​ 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰。

示例代码:

package com.poiwens.test;

/**
 * 类的描述:学生类
 */
public class Student {
    //成员变量
    private String name;
    private int age;

    // set/get方法
    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }
}

package com.poiwens.test;

/**
 * 类的描述:学生测试类
 */
public class StudentDemo {

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

        //set方法给成员变量赋值
        s.setAge(18);
        s.setName("张三");

        //get方法获取值并输出
        System.out.println(s.getAge() + "-" + s.getName());
    }
}

4.3 this关键字

​ this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)。

​ this代表所在类的当前对象的引用(地址值),即代表当前对象。

​ 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量。

​ 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量。

package com.poiwens.test;

/**
 * 类的描述:学生类
 */
public class Student {
    //成员变量
    private String name;
    private int age;

    // set/get方法
    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }
}
4.3.1 this关键字的应用
4.3.1.1 用于普通的gettter与setter方法

this出现在实例方法中,谁调用这个方法(哪个对象调用这个方法),this就代表谁(this就代表哪个对象)

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

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

    public String getName() {
      	return name;
    }

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

    public int getAge() {
      	return age;
    }
}
4.3.1.2 用于构造方法中

this出现在构造方法中,代表构造方法正在初始化的那个对象。\

public class Student {
    private String name;
    private int age;
    
    // 无参数构造方法
    public Student() {} 
    
    // 有参数构造方法
    public Student(String name,int age) {
    	this.name = name;
    	this.age = age; 
    }
}

4.4 封装的步骤

1.使用 private 关键字来修饰成员变量。

2.使用public修饰getter和setter方法。

4.4.1 封装步骤的实现

1.private修饰成员变量

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

2.public修饰getter和setter方法

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

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

    public String getName() {
      	return name;
    }

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

    public int getAge() {
      	return age;
    }
}

5. 构造方法

5.1 构造方法概述

构造方法(又名构造器)是一种特殊的方法。

5.1.1作用

创建对象 Student stu = new Student(参数);

5.1.2格式

public class 类名{

    //构造方法
    修饰符 类名(形参列表) {
        // 构造体代码,执行代码
    }
}
5.1.3功能:

主要是完成对象数据的初始化

​ 示例代码:

public class Student {
    //成员变量
    private String name;
    private int age;

    //无参构造方法
    public Student() {
    }

    //含参构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

5.2 构造方法的注意事项

5.2.1 构造方法的创建

​ 如果没有定义构造方法,系统将给出一个默认的无参数构造方法。

​ 如果定义了构造方法,系统将不再提供默认的构造方法。

5.2.2 构造方法的重载

​ 如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法。

5.2.3 推荐的使用方式

​ 无论是否使用,都手动书写无参数构造方法。

5.2.4 重要功能

​ 可以使用带参构造,为成员变量进行初始化。

示例代码:

package com.poiwens.test;

/**
 * 类的描述:学生测试类
 */
public class StudentDemo {

    //入口
    public static void main(String[] args) {
        //创建对象
        //无参构造方法
        Student s1 = new Student();

        //set方法给成员变量赋值
        s1.setAge(18);
        s1.setName("张三");

        //get方法获取值并输出
        System.out.println(s1.getAge() + "-" + s1.getName());

        //有参构造方法
        Student s2 = new Student("李四", 20);

        //输出s2
        System.out.println(s2.getAge() + "-" + s2.getName());
    }
}

5.3 标准类制作

① 类名需要见名知意。

② 成员变量使用private修饰。

③ 提供至少两个构造方法 。

​ 无参构造方法。

​ 带全部参数的构造方法。

④ get和set方法

​ 提供每一个成员变量对应的setXxx()/getXxx()。

⑤ 如果还有其他行为,也需要写上。

6. Static关键字

6.1 概述

我们已经知道面向对象中,存在类和对象的概念,我们在类中定义了一些成员变量,例如name,age,sex ,结果发现这些成员变量,每个对象都存在(因为每个对象都可以访问)。

而像name ,age , sex确实是每个学生对象都应该有的属性,应该属于每个对象。

所以Java中成员(变量和方法)等是存在所属性的,Java是通过static关键字来区分的。static关键字在Java开发非常的重要,对于理解面向对象非常关键。

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量都是没有static修饰的,所以属于每个对象。

6.2 定义格式和使用

static是静态的意思。 static可以修饰成员变量或者修饰方法。(放在成员变量或方法名前)

6.2.1 静态变量及其访问

有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。

例如现在我们需要定义三年二班全部的学生类,那么这些学生类的对象的学校属性应该都是“三年二班”,这个时候我们可以把这个属性定义成static修饰的静态成员变量。

定义格式

修饰符 static 数据类型 变量名 = 初始值; 
public class Student {
    public static String schoolName = "三年二班"// 属于类,只有一份。
    // .....
}

静态成员变量的访问

格式:类名.静态变量

public static void  main(String[] args){
    System.out.println(Student.schoolName); // 三年二班
    Student.schoolName = "一年二班";
    System.out.println(Student.schoolName); // 一年二班
}
6.2.2 实例变量及其访问

无static修饰的成员变量属于每个对象的, 这个成员变量叫实例变量,之前我们写成员变量就是实例成员变量。

需要注意的是:实例成员变量属于每个对象,必须创建类的对象才可以访问。

格式:对象.实例成员变量

6.2.3 静态方法及其访问

有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用 类名访问即可。因为类只有一个,所以静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。

与静态成员变量一样,静态方法也是直接通过类名.方法名称即可访问。

public class Student{
    public static String schoolName = "三年二班"// 属于类,只有一份。
    // .....
    public static void study(){
    	System.out.println("我们都在山工院学习");   
    }
}

静态成员方法的访问:

格式:类名.静态方法

public static void  main(String[] args){
    Student.study();
}
6.2.4 实例方法及其访问

无static修饰的成员方法属于每个对象的,这个成员方法也叫做实例方法

需要注意的是:实例方法是属于每个对象,必须创建类的对象才可以访问。

格式:对象.实例方法

public class Student {
    // 实例变量
    private String name ;
    // 2.方法:行为
    // 无 static修饰,实例方法。属于每个对象,必须创建对象调用
    public void run(){
        System.out.println("学生可以跑步");
    }
	// 无 static修饰,实例方法
    public  void sleep(){
        System.out.println("学生睡觉");
    }
    public static void study(){
        
    }
}
public static void main(String[] args){
    // 创建对象 
    Student stu = new Student ;
    stu.name = "张三";
    // Student.sleep();// 报错,必须用对象访问。
    stu.sleep();
    stu.run();
}

6.3 小结

1.当 static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。

2.无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。

3.static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。

4.无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。

7. 继承

7.1 概述

继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

假如我们要定义如下类:
学生类,老师类和班主任类,分析如下。

1.学生类
属性:姓名,年龄
行为:吃饭,睡觉

2.老师类
属性:姓名,年龄,薪水
行为:吃饭,睡觉,教书

3.班主任
属性:姓名,年龄,薪水
行为:吃饭,睡觉,管理

如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?

假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。

其中,多个类可以称为子类,单独被继承的那一个类称为父类超类(superclass)或者基类

7.1.1 继承的好处

1.提高代码的复用性(减少代码冗余,相同代码重复利用)。

2.使类与类之间产生了关系。

7.2 继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}
注意:

Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

7.3 继承案例

7.3.1 案例

请使用继承定义以下类:

  1. 学生类
    属性:姓名,年龄
    行为:吃饭,睡觉
  2. 老师类
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,教书
  3. 班主任
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,管理

老师类,学生类,还有班主任类,实际上都是属于人类的,我们可以定义一个人类,把他们相同的属性和行为都定义在人类中,然后继承人类即可,子类特有的属性和行为就定义在子类中了。

7.3.2 代码实现

1.父类Human类

 public class Human {
    // 合理隐藏
    private String name ;
    private int age ;
	
    // 合理暴露
    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;
    }
 }

2.子类Teacher类

public class Teacher extends Human {
    // 工资
    private double salary ;
    
    // 特有方法
    public void teach(){
        System.out.println("老师在认真教技术!")}

    public double getSalary() {
        return salary;
    }

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

3.子类Student类

public class Student extends Human{
 
}

4.子类HeadTeacher类

public class HeadTeacher extends Human {
    // 工资
    private double salary ;
    
       // 特有方法
    public void admin(){
        System.out.println("班主任强调纪律问题!")}
    
    public double getSalary() {
        return salary;
    }

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

5.测试类

  public class Test {
      public static void main(String[] args) {
          Teacher zhangSan = new Teacher();
          zhangSan.setName("张三");
          zhangSan.setAge("31");
          zhangSan.setSalary(1000.99);
          System.out.println(zhangSan.getName());
          System.out.println(zhangSan.getAge());
          System.out.println(zhangSan.getSalary());
          zhangSan.teach();
          
          BanZhuRen liSi = new HeadTeacher();
          liSi.setName("李四");
          liSi.setAge("28");
          liSi.setSalary(1000.99);
          System.out.println(liSi.getName());
          System.out.println(liSi.getAge());
          System.out.println(liSi.getSalary());
          liSi.admin();

          Student wangWu = new Student();
          wangWu.setName("王五");
          wangWu.setAge("18");
          //wangWu.setSalary(1000.99); // xugan没有薪水属性,报错!
          System.out.println(wangWu.getName());
          System.out.println(wangWu.getAge());
      }
  }

7.4 小结

1.继承实际上是子类相同的属性和行为可以定义在父类中,子类特有的属性和行为由自己定义,这样就实现了相同属性和行为的重复利用,从而提高了代码复用。

2.子类继承父类,就可以直接得到父类的成员变量和方法。

7.5 子类不能继承的内容

并不是父类的所有内容都可以给子类继承的:

子类不能继承父类的构造方法。

值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。

public class Demo {
    public static void main(String[] args) {
        Zi z = new Zi();
        System.out.println(z.num1);
//		System.out.println(z.num2); // 私有的子类无法使用
        // 通过getter/setter方法访问父类的private成员变量
        System.out.println(z.getNum2());

        z.show1();
        // z.show2(); // 私有的子类无法使用
    }
}

class Fu {
    public int num1 = 10;
    private int num2 = 20;

    public void show1() {
        System.out.println("show1");
    }

    private void show2() {
        System.out.println("show2");
    }

    public int getNum2() {
        return num2;
    }

    public void setNum2(int num2) {
        this.num2 = num2;
    }
}

class Zi extends Fu {
}

7.6 继承后的特点—成员变量

7.6.1 成员变量不重名

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的

class Fu {
	// Fu中的成员变量
	int num = 5;
}
class Zi extends Fu {
	// Zi中的成员变量
	int num2 = 6;
  
	// Zi中的成员方法
	public void show() {
		// 访问父类中的num
		System.out.println("Fu num="+num); // 继承而来,所以直接访问。
		// 访问子类中的num2
		System.out.println("Zi num2="+num2);
	}
}
class Demo04 {
	public static void main(String[] args) {
        // 创建子类对象
		Zi z = new Zi(); 
      	// 调用子类中的show方法
		z.show();  
	}
}

演示结果:
Fu num = 5
Zi num2 = 6
7.6.2 成员变量重名

如果子类父类中出现重名的成员变量,这时的访问是有影响的

class Fu1 {
	// Fu中的成员变量。
	int num = 5;
}
class Zi1 extends Fu1 {
	// Zi中的成员变量
	int num = 6;
  
	public void show() {
		// 访问父类中的num
		System.out.println("Fu num=" + num);
		// 访问子类中的num
		System.out.println("Zi num=" + num);
	}
}
class Demo04 {
	public static void main(String[] args) {
      	// 创建子类对象
		Zi1 z = new Zi1(); 
      	// 调用子类中的show方法
		z1.show(); 
	}
}
演示结果:
Fu num = 6
Zi num = 6

子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量可以使用super关键字。

7.6.3 super访问父类成员变量

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this

需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。

使用格式:

super.父类成员变量名;
class Fu {
	// Fu中的成员变量。
	int num = 5;
}

class Zi extends Fu {
	// Zi中的成员变量
	int num = 6;
  
	public void show() {
        int num = 1;
      
        // 访问方法中的num
        System.out.println("method num=" + num);
        // 访问子类中的num
        System.out.println("Zi num=" + this.num);
        // 访问父类中的num
        System.out.println("Fu num=" + super.num);
	}
}

class Demo04 {
	public static void main(String[] args) {
      	// 创建子类对象
		Zi1 z = new Zi1(); 
      	// 调用子类中的show方法
		z1.show(); 
	}
}

演示结果:
method num=1
Zi num=6
Fu num=5

Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量.访问父类的私有成员变量可以在父类中提供公共的getXxx方法和setXxx方法。

7.7 继承后的特点—成员方法

7.7.1 成员方法不重名

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。

class Fu {
	public void show() {
		System.out.println("Fu类中的show方法执行");
	}
}
class Zi extends Fu {
	public void show2() {
		System.out.println("Zi类中的show2方法执行");
	}
}
public  class Demo05 {
	public static void main(String[] args) {
		Zi z = new Zi();
     	//子类中没有show方法,但是可以找到父类方法去执行
		z.show(); 
		z.show2();
	}
}
7.7.2 成员方法重名

如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

class Fu {
	public void show() {
		System.out.println("Fu show");
	}
}
class Zi extends Fu {
	//子类重写了父类的show方法
	public void show() {
		System.out.println("Zi show");
	}
}
public class ExtendsDemo05{
	public static void main(String[] args) {
		Zi z = new Zi();
     	// 子类中有show方法,只执行重写后的show方法
		z.show();  // Zi show
	}
}

7.8 继承后的特点—构造方法

7.8.1 概念

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。**子类的构造方法中默认有一个super() ,**表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子

继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

7.8.2 案例演示

按如下需求定义类:

  1. 人类
    成员变量: 姓名,年龄
    成员方法: 吃饭
  2. 学生类
    成员变量: 姓名,年龄,成绩
    成员方法: 吃饭
class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("父类无参");
    }

    // getter/setter省略
}

class Student extends Person {
    private double score;

    public Student() {
        //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
     public Student(double score) {
        //super();  // 调用父类无参,默认就存在,可以不写,必须再第一行
        this.score = score;    
        System.out.println("子类有参");
     }

}

public class Demo07 {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student(99.9);
    }
}

输出结果:
父类无参
子类无参
----------
父类无参
子类有参
7.8.3 小结

子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。

子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。

7.9 继承的特点

Java只支持单继承,不支持多继承。

// 一个类只能有一个父类,不可以有多个父类。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // error

一个类可以有多个子类。

// A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends A {}

可以多层继承。

class A {}
class C1 extends A {}
class D extends C1 {}

顶层父类是Object类。所有的类默认继承Object,作为父类。

8. 方法重写

8.1 概念

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

8.2 使用场景

发生在子父类之间的关系。
子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法。

8.2.1 动物类
public class Animal  {
    public void run(){
        System.out.println("动物跑");
    }
    public void cry(){
        System.out.println("动物叫");
    }
}

然后定义一个猫类,猫可能认为父类cry()方法不能满足自己的需求。

8.2.2 猫类
public class Cat extends Animal {
    public void cry(){
        System.out.println("猫叫!");
    }
}

public class Test {
	public static void main(String[] args) {
      	// 创建子类对象
      	Cat ddm = new Cat()// 调用父类继承而来的方法
        ddm.run();
      	// 调用子类重写的方法
      	ddm.cry();
	}
}

8.3 @Override重写注解

@Override:注解,重写注解校验!

这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。

建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

public class Cat extends Animal {
     // 声明不变,重新实现
    // 方法名称与父类全部一样,只是方法体中的功能重写写了!
    @Override
    public void cry(){
        System.out.println("猫叫");
    }
}

8.4 注意事项

  1. 方法重写是发生在子父类之间的关系。
  2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
  3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

9. super()和this()

class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("父类无参");
    }

    // getter/setter省略
}

class Student extends Person {
    private double score;

    public Student() {
        //super(); // 调用父类无参构造方法,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
     public Student(double score) {
        //super();  // 调用父类无参构造方法,默认就存在,可以不写,必须再第一行
        this.score = score;    
        System.out.println("子类有参");
     }
      // getter/setter省略
}

public class Demo07 {
    public static void main(String[] args) {
        // 调用子类有参数构造方法
        Student s2 = new Student(99.9);
        System.out.println(s2.getScore()); // 99.9
        System.out.println(s2.getName()); // 输出 null
        System.out.println(s2.getAge()); // 输出 0
    }
}

可以发现,子类有参数构造方法只是初始化了自己对象中的成员变量score,而父类中的成员变量name和age依然是没有数据的,可以借助与super(…)去调用父类构造方法,以便初始化继承自父类对象的name和age。

9.1 super和this的用法格式

一般用法:

this.成员变量    	--    本类的
super.成员变量    	--    父类的

this.成员方法名()  	--    本类的    
super.成员方法名()   --    父类的

调用构造方法:

super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...)  -- 调用本类的其他构造方法,根据参数匹配确认

9.2 super(…)用法演示

class Person {
    private String name ="李二";
    private int age = 20;

    public Person() {
        System.out.println("父类无参");
    }
    
    public Person(String name, int age){
        this.name = name ;
        this.age = age ;
    }

    // getter/setter省略
}

class Student extends Person {
    private double score = 100;

    public Student() {
        //super(); // 调用父类无参构造方法,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
     public Student(String name, int age, double score) {
        super(name ,age);// 调用父类有参构造方法Person(String name , int age)初始化name和age
        this.score = score;    
        System.out.println("子类有参");
     }
      // getter/setter省略
}

public class Demo07 {
    public static void main(String[] args) {
        // 调用子类有参数构造方法
        Student s2 = new Student("张三"2099);
        System.out.println(s2.getScore()); // 99
        System.out.println(s2.getName()); // 输出 张三
        System.out.println(s2.getAge()); // 输出 20
    }
}

注意:

子类的每个构造方法中均有默认的super(),用于调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

super(…)是根据参数去确定调用父类哪个构造方法的。

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造调用时,一定先调用父类的构造。

9.3 this(…)用法演示

默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。

为了借用其他构造方法的功能。

public class ThisDemo {
    public static void main(String[] args) {
        Student zhangSan = new Student();
        System.out.println(xuGan.getName()); // 输出:张三
        System.out.println(xuGan.getAge());// 输出:21
        System.out.println(xuGan.getSex());// 输出: 男
    }
}

class Student{
    private String name ;
    private int age ;
    private char sex ;

    public Student() {
  // 调用其他构造方法:Student(String name, int age, char sex),赋予初值
        this("张三",21,'男');
    }

    public Student(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    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 char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }
}

9.4 小结

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。

super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。

this(…)可以调用本类中的其他构造方法。

10. 多态

10.1 多态的形式

多态是继封装、继承之后,面向对象的第三大特性。

多态是出现在继承或者实现关系中的

多态体现的格式

父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。

10.2 多态的使用场景

如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

public void register(Student s) {
	//代码
}

有了多态之后,方法的形参就可以定义为共同的父类Person。

public void register(Person p) {
	//代码
}

要注意的是:

​ 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。

​ 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。

​ 而且多态还可以根据传递的不同对象来调用不同类中的方法。

public void register(Person p) {
	p.show; //根据传递对象的不同,调用不同的show方法
}
父类:
public class Person {
    private String name;
    private int age;

    空参构造
    带全部参数的构造
    get和set方法

    public void show(){
        System.out.println(name + ", " + age);
    }
}

子类1public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}

子类2public class Student extends Person{

    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}

子类3public class Teacher extends Person{

    @Override
    public void show() {
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}

测试类:
public class Test {
    public static void main(String[] args) {
        //创建三个对象,并调用register方法

        Student s = new Student();
        s.setName("张三");
        s.setAge(18);


        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);

        register(s);
        register(t);
        register(admin);
    }
    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类型的父类
    public static void register(Person p){
        p.show();
    }
}

10.3 多态的定义和前提

多态: 是指同一行为,具有多个不同表现形式。

从上面案例可以看出,Cat和Dog都是动物,都有吃这一行为,但是出现的效果(表现形式)是不一样的。

前提【重点】

  1. 有继承或者实现关系

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

10.4 多态的运行特点

调用成员变量时:编译看左边,运行看左边

调用成员方法时:编译看左边,运行看右边

Fu f = new Zi()//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

10.5 多态的弊端

我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

class Animal{
    public  void eat()System.out.println("动物吃东西!")}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        //a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
}

10.6 引用数据类型转换

10.6.1 为什么要转型

多态的写法无法访问子类独有功能。

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。所以,想要调用子类特有的方法,必须做向下转型。

基本数据类型转换

​ 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;

​ 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14 ;

​ 多态的转型分为**向上转型(自动转换)向下转型(强制转换)**两种。

10.6.2 向上转型(自动转换)

向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
使用格式:

父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

**原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。**所以子类范围小可以直接自动转型给父类类型的变量。

10.6.3 向下转型(强制转换)

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

使用格式:

子类类型 变量名 = (子类类型) 父类变量名;:Aniaml a = new Cat();
   Cat c =(Cat) a;  
10.6.4 案例演示

定义类:

abstract class Animal {  
    abstract void eat();  
}  

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat

        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
    }  
}
10.6.5 转型的异常
public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常。这是因为,明明创建的是Cat类型对象,运行时,当然不能转换成Dog对象的。

10.6.6 instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验

变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false

在类型转换前,可以先对 要转换的类型做一个判断,代码如下:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}
10.6.7 instanceof新特性(JDK14)
//新特性
//先判断a是否为Dog(cat)类型,如果是,则强转成Dog(cat)类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
    d.lookHome();
}else if(a instanceof Cat c){
    c.catchMouse();
}else{
    System.out.println("没有这个类型,无法转换");
}

11. 包

11.1 包

包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。

包名的命名规范

路径名.路径名.xxx.xxx
// 例如:com.poiwens.a

​ 包名一般是公司域名的倒写。例如:百度是www.baidu.com,包名就可以定义成com.baidu.技术名称。

​ 包名必须用”.“连接。

​ 包名的每个路径名必须是一个合法的标识符,而且不能是Java的关键字。

2.2 导包

什么时候需要导包?

​ 情况一:在使用Java中提供的非核心包中的类时

​ 情况二:使用自己写的其他包中的类时

什么时候不需要导包?

​ 情况一:在使用Java核心包(java.lang)中的类时

​ 情况二:在使用自己写的同一个包中的类时

2.3 使用不同包下的相同类

假设demo1和demo2中都有一个Student该如何使用?

//使用全类名的形式即可。
//全类名:包名 + 类名
//拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference
com.poiwens.homework.demo1.Student s1 = new com.poiwens.homework.demo1.Student();
com.poiwens.homework.demo2.Student s2 = new com.poiwens.homework.demo2.Student();

12. 权限修饰符

​ 在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。

​ public:公共的,所有地方都可以访问。

​ protected:本类 ,本包,其他包中的子类都可以访问。

​ 默认(没有修饰符):本类 ,本包可以访问。

​ 注意:默认是空着不写,不是default

​ private:私有的,当前类可以访问。

访问权限排列(由大到小):

public > protected > 默认 > private

12.1 不同权限的访问能力

publicprotected默认private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

不加权限修饰符,就是默认权限

13. final关键字

13.1 概述

​ 学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。

如果有一个方法不想别人去改写里面内容,可以使用final关键字。

Java提供了final 关键字,表示修饰的内容不可变。

  • final: 不可改变,最终的含义。可以用于修饰类、方法和变量。
    • 类:被修饰的类,不能被继承。
    • 方法:被修饰的方法,不能被重写。
    • 变量:被修饰的变量,有且仅能被赋值一次。

13.2 使用方式

13.2.1 修饰类

final修饰的类,不能被继承

格式如下:

final class 类名 {
}
final class Fu {
}
// class Zi extends Fu {} // 报错,不能继承final的类

查询API发现像 public final class Stringpublic final class Mathpublic final class Scanner 等,很多类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。

13.2.2 修饰方法

final修饰的方法,不能被重写

格式如下:

修饰符 final 返回值类型 方法名(参数列表){
    //方法体
}

代码:

class Fu2 {
	final public void show1() {
		System.out.println("Fu2 show1");
	}
	public void show2() {
		System.out.println("Fu2 show2");
	}
}

class Zi2 extends Fu2 {
//	@Override
//	public void show1() {
//		System.out.println("Zi2 show1");
//	}
	@Override
	public void show2() {
		System.out.println("Zi2 show2");
	}
}
13.2.3 修饰变量-局部变量

局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。

代码如下:

public class FinalDemo1 {
    public static void main(String[] args) {
        // 声明变量,使用final修饰
        final int a;
        // 第一次赋值 
        a = 10;
        // 第二次赋值
        a = 20; // 报错,不可重新赋值

        // 声明变量,直接赋值,使用final修饰
        final int b = 10;
        // 第二次赋值
        b = 20; // 报错,不可重新赋值
    }
}

| public | protected | 默认 | private |
| ---------------- | ------ | --------- | ---- | ------- |
| 同一类中 | √ | √ | √ | √ |
| 同一包中的类 | √ | √ | √ | |
| 不同包的子类 | √ | √ | | |
| 不同包中的无关类 | √ | | | |

public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

不加权限修饰符,就是默认权限

13. final关键字

13.1 概述

​ 学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。

如果有一个方法不想别人去改写里面内容,可以使用final关键字。

Java提供了final 关键字,表示修饰的内容不可变。

  • final: 不可改变,最终的含义。可以用于修饰类、方法和变量。
    • 类:被修饰的类,不能被继承。
    • 方法:被修饰的方法,不能被重写。
    • 变量:被修饰的变量,有且仅能被赋值一次。

13.2 使用方式

13.2.1 修饰类

final修饰的类,不能被继承

格式如下:

final class 类名 {
}
final class Fu {
}
// class Zi extends Fu {} // 报错,不能继承final的类

查询API发现像 public final class Stringpublic final class Mathpublic final class Scanner 等,很多类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。

13.2.2 修饰方法

final修饰的方法,不能被重写

格式如下:

修饰符 final 返回值类型 方法名(参数列表){
    //方法体
}

代码:

class Fu2 {
	final public void show1() {
		System.out.println("Fu2 show1");
	}
	public void show2() {
		System.out.println("Fu2 show2");
	}
}

class Zi2 extends Fu2 {
//	@Override
//	public void show1() {
//		System.out.println("Zi2 show1");
//	}
	@Override
	public void show2() {
		System.out.println("Zi2 show2");
	}
}
13.2.3 修饰变量-局部变量

局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。

代码如下:

public class FinalDemo1 {
    public static void main(String[] args) {
        // 声明变量,使用final修饰
        final int a;
        // 第一次赋值 
        a = 10;
        // 第二次赋值
        a = 20; // 报错,不可重新赋值

        // 声明变量,直接赋值,使用final修饰
        final int b = 10;
        // 第二次赋值
        b = 20; // 报错,不可重新赋值
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值