Java知识点02——面向对象01(类、对象、方法、常量、变量、构造器、包、封装、继承、多态、static关键字)

声明:

  1. 该资料来自自己整理。
  2. 参考书籍 疯狂 Java讲义(第五版) 李刚©著

一、类和对象

1.1 面向对象、面向过程

1)面向过程

  面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面向过程, 强调的是功能行为,以函数为最小单位,考虑怎么做。

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。

2)面向对象

  面向对象思维方式是一种更符合人们思考习惯的思想,将复杂的问题简单化。面向对象是把构成问题的事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低。

1.2 类、对象

什么是类
类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物

现实中,描述一类事物:
    属性:就是该事物的状态信息
    行为:就是该事物能够做什么
  
举例:小猫
		属性:名字、体重、年龄、颜色...
  		行为(方法函数):跑、叫、睡、吃...

什么是对象
对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为。

现实中,一类事物的一个实例:一只小猫

举例:一只小猫。
		属性:tom、5kg、2 years、yellow...
  		行为(方法函数):慢悠悠的跑、温柔的叫、呼呼大睡...

类与对象的关系
对象是类的实例,类是对象的模板

1)类是对一类事物的描述,是抽象的
2)对象是一类事物的实例,是具体的
3)类是对象的模板,对象是类的实例

1.3 类的定义

在java中,我们可以用关键字class来定义一个类,一个java文件可以同时定义多个class

// 定义一个Person类
class Person {
	
}

// 定义一个Dog类
class Dog {
	
}

// 每一个源文件必须有且只有一个public class,并且类名和文件名保持一致!
public class Student {
	// 属性,此处也称为成员变量
	String name; // 姓名
	int age; // 年龄
	
	// 方法,此处也称为成员方法
	void eat(String food) {
		System.out.println(name + "在吃" + food);
	}
	void study() {
		System.out.println(name + "年龄" + age + "岁,在学习java");
	}
}

类的组成
属性 field,定义格式: [修饰符] 属性类型 属性名 = [默认值] ;
方法(行为) method,定义格式: [修饰符] 返回值类型 方法名(形参列表) {}

1.4 创建对象

在java中,对象也叫做 object 或 instance(实例),所以也称为实例

  要想创建一个对象,那么必须先有一个类,然后通过new 关键字来创建一个对象。(通过new关键字来调用构造器,从而返回该类的实例。如果一个类没有构造器,则这个类无法创建实例。

语法格式类名称 对象名称 = new 类名称();

// 测试类
public class ObjectTest {
	public static void main(String[] args) {
		// 实例化小明学生对象
		Student stu1 = new Student();
		
		// 成员变量赋值
		stu1.name = "小明"; // 设置名字
		stu1.age = 18; // 设置年龄
		System.out.println("name:" + stu1.name + " age:" + stu1.age);
		
		// 调用成员方法
		stu1.eat("小龙虾"); // 调用吃放方法
		stu1.study(); // 调用学习方法

		// 实例化小花学生对象
		Student stu2 = new Student();
		
		// 操作成员变量
		stu2.name = "小花"; // 设置名字
		stu2.age = 17; // 设置年龄
		System.out.println("name:" + stu2.name + " age:" + stu2.age);
		
		// 调用成员方法
		stu2.eat("冰淇淋"); // 调用吃放方法
		stu2.study(); // 调用学习方法
	}
}

注意事项
1、成员变量隶属于于对象,只能通过对象来调用,我们可以通过 对象.成员变量 来操作成员变量。
2、成员方法隶属于于对象,只能通过对象来调用,我们可以通过 对象.成员方法(实参列表) 来调用成员方法。

1.5 成员变量的默认值

数据类型默认值
基本类型整数(byte,short,int,long)0
浮点类型浮点数(float,double)0.0
字符型字符(char)‘\u0000’
布尔型布尔(boolean)false
引用类型数组,类,接口null

1.6 对象内存图

在栈内存中运行的方法,遵循 “先进后出,后进先出” 的原则。

Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。不管是数组还是对象,都只能通过引用来访问它们。

//定义一个Person类型的变量p;并将Person实例赋给p变量
Person p = new Person();
p.name = "李刚";
p.age;

二、方法

2.1 方法的定义

修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2,……){
	执行语句;
	return 返回值;
}
	
举例:
public static void play(){
	System.out.println("这是一个方法");
}	

解释:

  • 修饰符:目前固定写法 public、private、
  • 返回值类型:void、基本类型、引用类型
  • 方法名:为我们定义的方法起名,满足标识符的规范,用来调用方法
  • 参数列表
  • return:方法结束,因为返回值类型是void,方法大括号内的return可以不写

2.2 方法的调用

  方法在定义完毕后,方法不会自己运行,必须被调用才能执行,我们可以在主方法main中来调用我们自己定义好的方法。
实际参数(简称实参):传递给形式参数的具体数值,对应着形式参数(简称形参)。

2.2.1 注意事项

注意事项

  • 1、形式参数(形参)和实际参数(实参)的类型和个数必须一一对应。
  • 2、在方法中只能调用方法,不可以在方法内部再定义方法。
  • 3、当方法有返回值类型时,可以用一个变量用于接收方法的返回值,该变量的类型必须和方法返回值类型保持一致。

案例:

public class MethodT01 {
    public static void main(String[] args) {
        MethodT01 m = new MethodT01();
        //静态方法调用普通方法只能使用该类的实例(对象)来调用!!!
        m.eat("晴天");
        //静态方法访问静态方法直接调用即可!!!
        fly("大波");
    }
    //普通方法01
    public void eat(String name){
        System.out.println(name+"吃饭。");
        //普通方法调用普通方法使用this作为调用者调用!!!
        this.play(name);
        //普通方法调用静态方法必须使用类名来作为调用者!!!
        MethodT01.fly("小波波");
    }
    //普通方法02
    public void play(String name){
        System.out.println(name+"正在玩。");
    }
    //普通方法03
    public void watch(String name){
        System.out.println(name+"正在看电视。");
    }
    //静态方法
    public static void fly(String name){
        System.out.println(name+"飞行。");
    }
}

2.2.2 方法调用的三种形式

直接调用

// 直接写方法名调用
public static void main(String[] args) {
		print();
}
public static void print() {
		System.out.println("方法被调用");
}

赋值调用

// 调用方法,在方法前面定义变量,接收方法返回值
public static void main(String[] args) {
    int sum = getSum(5,6);
    System.out.println(sum);
}
public static int getSum(int a,int b) {
		return a + b;
}

输出语句调用

// 在输出语句中调用方法, System.out.println(方法名());
public static void main(String[] args) {
		System.out.println(getSum(5,6));
}
public static int getSum(int a,int b) {
		return a + b;
}
// 不能用输出语句调用 void 类型的方法。因为方法执行后没有结果,也就打印不出任何内容
public static void main(String[] args) {
		System.out.println(printHello());// 错误,不能输出语句调用void类型方法
}
public static void printHello() {
		System.out.println("Hello");
}

2.3 方法的传参机制

  • Java里方法的基本类型的参数传递方式只有一种:值传递
  • Java里方法的引用类型的参数传递,一样是采用:值传递。(这个值是个引用(指针),它保存了对象的地址值)

⚠️所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。

2.3.1 基本类型的传参效果

public class PrimiriveTransferTest {
    public static void main(String[] args) {
        int a = 6;
        int b = 9;
        swap(a,b);
        System.out.println("交换结束后变量a,b的值:a="+a+",b="+b);
    }
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
        System.out.println("swap方法里a,b的值:a="+a+",b="+b);
    }
}


2.3.2 引用类型的参数传递

⚠️创建一个对象时,系统内存中有两个东西:堆内存中保存了对象本身,栈内存中保存了引用该对象的引用变量。

案例:

class DataWrap{
    int a;
    int b;
}
public class ReferenceTransferTest {
    public static void main(String[] args) {
        DataWrap dw = new DataWrap();
        dw.a = 6;
        dw.b = 9;
        System.out.println("交换前a,b成员变量的值:a="+dw.a+",b="+dw.b);
        swap(dw);//值传递 传递的是一个引用(也就是一个引用),它保存了DataWrap对象的地址值
        System.out.println("交换后a,b成员变量的值:a="+dw.a+",b="+dw.b);
    }
    private static void swap(DataWrap dw) {
        int tmp = dw.a;
        dw.a = dw.b;
        dw.b = tmp;
        System.out.println("swap方法里a,b成员变量的值:a="+ dw.a+",b="+dw.b);
        //把dw直接赋值为null,让它不在指向任何有效地址
        dw = null;
    }
}


2.4 方法重载(overload)

// 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关
// 特点:
    1)参数列表:个数不同 、数据类型不同 、顺序不同 
    2)重载方法调用:JVM通过方法的参数列表,调用不同的方法
        
//方法重载与修饰符和返回值类型无关!!!
public void Info(String s,int a){}
public void Into(int a,String s){} //方法名相同,参数个数形同、参数类型相同、但参数顺序不同
public void Info(String s,String s1){} //方法名相同,参数个数形同、参数类型不同
public void Into(int a,String s,double d){} //方法名相同,参数个数不同

注意:方法修饰符不同,不构成方法的重载。

2.5 可变参数

  可变参数:适用于参数个数不确定,但类型确定的情况,java把可变参数当做数组处理。我们使用...表示可变长参数,…位于参数类型和参数名之间,前后有无空格都可以。

【示例】

public static void main(String[] args) {
	System.out.println(add(1, 2));   // 输出:3
	System.out.println(add(1, 2, 3));  // 输出:6
}
// 可变参数的方法	
public static int add(int a, int b, int ... arr) {
	int sum = a + b;
	for(int i = 0; i < arr.length; i++) {
		sum += arr[i];
	}
	return sum;
}

可变参数的特点:

  • 一个方法中可变参数最多只能有一个,并且只能出现在参数列表的最后面
  • 调用可变参数的方法时可以给出任意个参数,在方法体中以数组的形式访问可变参数

三、常量、成员变量、局部变量

3.1 常量

常量;指在Java程序中不变的量
当使用常量的时候,前缀 0 表示 8进制,而前缀 0x 代表16进制
int a = 01;
int b = 0x12;

常量的分类

类型含义举例
整数常量所有的整数-20、-1、0、24、101
小数常量所有的小数0.1、-2.7、6.66
字符常量单引号引起来,只能写一个字符,也可以不写‘a’、‘’、‘牛’
字符串常量双引号引起来,可以写多个字符,也可以不写“a”、“666”、“你好吗?”、“”
布尔常量 只有两个值:true、false(流程控制中讲解)true、false
空常量只有一个值(引用数据类型中讲解)null

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

!!成员变量有默认值;局部变量没有默认值必须显示初始化;

!!如果局部变量和成员变量同名,局部变量将覆盖成员变量,如果想要在这个方法里引用被覆盖的成员变量,则可以使用 this(对于实例变量)类名(对于类变量)作为调用者来限定访问成员变量。

// 变量根据定义位置的不同,我们给变量起了不同的名字
1)在类中的位置不同(重点)
    成员变量:类中,方法外
    局部变量:方法中或者方法声明上(形式参数)
    
2)作用范围不一样(重点)
    成员变量:类中
    局部变量:方法中
    
3)初始化值的不同(重点)
    成员变量:有默认值
    局部变量:没有默认值。必须先定义,赋值,最后使用
    
4)在内存中的位置不同(了解)
    成员变量:堆内存
    局部变量:栈内存
    
5)生命周期不同(了解)
    成员变量:随着对象的创建而存在,随着对象的消失而消失
    局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
    
6)可用修饰符不同
	成员变量可以被publicprotectedprivatestaticfinal等修饰符修饰;
	局部变量只能被final修饰。

3.3 成员变量内存图

class Person{
    //定义一个实例变量
    public String name;
    //定义一个类变量(属于类而不属于对象) 类初始化时为eyeNum分配内存
    public static int eyeNum;
}

public class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.name = "张三";
        p1.eyeNum = 2;//最好不要使用对象来调用类变量!!!!
        p2.name = "孙悟空";
        p2.eyeNum = 3;//最好不要使用对象来调用类变量!!!!
        //使用类来调用类变量
        System.out.println(Person.eyeNum); // 3 
    }
}


3.4 局部变量内存图

局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。

定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才回为局部变量分配内存,并将初始值保存到这块内存中。

⚠️局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中。如果局部变量是基本类型的变量,则直接把这个变量的值保存在该变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。

栈内存中的变量无须系统垃圾回收,通常随方法或代码块的运行结束而结束。

3.5 static 关键字


⚠️static 修饰的内容:

  1. static修饰变量的时候:存在静态区,只有一份,随着类的加载而加载,且只加载一次;
  2. static修饰方法时:静态方法只能访问静态方法;非静态方法可通过类名.方法来访问静态方法;
  3. 它优先于对象存在,所以,可以被所有对象共享。

⚠️静态方法调用的注意事项:

  1. 静态方法只能访问类静态变量和静态方法
  2. 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类静态变量或静态方法。
  3. 静态方法中,不能使用this关键字!!
// 静态变量
// 使用 static关键字修饰的成员变量
  static 数据类型 变量名;
  static int numberID;

// 静态方法
// 当 static 修饰成员方法时,该方法称为【类方法】,习惯称为【静态方法】。
// 静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。
  修饰符 static 返回值类型 方法名 (参数列表){
  		// 执行语句
  }
// 静态方法示例
public static void showNum() {
  	System.out.println("num:" + numberOfStudent);
}

调用格式

// 被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息
// 格式
    // 访问类变量
    类名.类变量名;
      
    // 调用静态方法
    类名.静态方法名(参数)// 示例
public class StuDemo2 {
    public static void main(String[] args) {
        // 访问类变量
        System.out.println(Student.numberOfStudent);
        // 调用静态方法
        Student.showNum();
    }
}

四、构造器、静态代码块

4.1 构造器

构造方法也叫构造器(constructor),用于给对象进行初始化操作,即为对象成员变量赋初始值。

声明格式: [修饰符] 类名(形参列表) { }

注意事项
1、构造方法名必须和类名相同,采用大驼峰命名规范。
2、构造方法不用定义返回值类型,在方法体中不允许return具体的数据。
3、构造方法必须通过new关键字调用,是一种特殊的方法
4、无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法, Java自动提供的默认无参数构造方法就会失效

// 示例
public class Demo01 {
    private String name;
    private int age;
    private long tel;
    
    //无参构造器
    public Demo01(){
    }
    
    //一个参数的构造器
    public Demo01(String name){
        this.name = name;
    }
    
    //两个参数的构造器 形成构造器重载
    public Demo01(String name,int age,long tel){
        //this调用另一个重载的构造器;this调用构造器时必须放在构造器的第一条语句!!!
        this(name);
        this.age = age;
        this.tel = tel;
    }
}

⚠️注意事项:
		1. 如果你不提供构造方法,系统会给出无参数构造方法。
		2. 如果你提供了构造方法,系统将不再提供无参数构造方法。
		3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

4.2 构造方法和成员方法区别

区别一:定义格式区别

  • 构造函数的方法名要与类名一样,并且不用定义返回值类型。
  • 成员方法的方法名只需符合标识符的规范,必须定义返回值类型。

区别二:调用时期区别

  • 构造方法在实例化对象的时候调用。
  • 成员方法在对象创建成功之后调用。

区别三:调用方式区别

  • 构造方法通过new关键字来调用。
  • 成员方法通过对象来调用。

区别四:调用次数区别

  • 构造方法只能调用一次,在创建对象的时候调用。
  • 成员方法可以调用任意多次!

注意构造方法中可以调用成员方法,成员方法中不能调用构造方法

4.2 静态代码块

静态代码块:定义在成员位置,使用static修饰的代码块{ },即static {}

  • 位置:类中方法外(不在乎在哪一行,只要是类中方法外就行)
  • 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行
// 格式:
public class ClassName{
	static {
		// 执行语句
	}
}
	
// 作用:进行初始化赋值
public class Game {
    public static int number;
    public static ArrayList<String> list;
  
    static {
        // 给类变量赋值
        number = 2;
        list = new ArrayList<String>();
        // 添加元素到集合中
        list.add("张三");
        list.add("李四");
    }
}

⚠️static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况下,去调用方法。

五、包

5.1 为什么需要包?

  包机制是java中管理类重要手段,给类提供了多层命名空间。 开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。 包对于类,相当于,文件夹对于文件的作用。

  在java中我们通过package实现对类的管理,我们将相同功能的类放到一个包中,并且日常项目的分工也是以包作为边界。

注意同一个包中,不允许有相同的类名。

5.2 如何定义和使用包?

定义包

  一般是公司域名反写,再加上模块名,便于内部管理类。可以有多层包,包名采用全部小写字母,多层包之间用“.”连接。

【示例】包的命名举例
com.java.object
com.java.object.demo

注意:com.java.object 和 com.java.object.demo,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。

使用包

在程序有效代码的第一行(注释不算)声明包。

package com.java.object; // 包的声明,必须在有效代码的第一行(注释不算)

// 以下是业务逻辑所需要的代码
class Person {}

5.3 常用包

java中的常用包说明
java.lang包含一些Java语言的核心类,如String、Math、System等。
java.awt包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
java.net包含执行与网络相关的操作的类。
java.io包含能提供多种输入/输出功能的类。
java.util包含一些实用工具类,如定义系统特性、与日期日历相关的类。

5.4 类的访问

1)类的简化访问
  当我们要使用一个类时,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是 java.lang 包中的类时通常可以省略掉包名,直接使用该类。

package com.java.object; 

class Person {}

public class Test {
	public static void main(String[] args) {
		// 访问同一包中的类
		Person p = new Person();
		// 访问java.lang包中的类
		String str = "hello world";
	}
}

2)类的带包名访问
  在访问类时,为了能够找到该类,必须使用含有包名的类的全限定名(包名.类名),例如:java.util.Date。

使用带包的类来创建对象格式:包名.类名 变量名 = new包名.类名();

// 类的带包名访问举例
java.util.Date date = new java.util.Date();

5.5 import 导入类

  我们每次使用类时,都需要写很长的包名。很麻烦,我们可以通过 import 导包的方式来简化。可以通过导包的方式使用该类,可以避免使用全类名编写(包类.类名),以便于编写代码,提高可维护性。

导包的格式import 包名.类名;

import 导包代码书写的位置:在声明包 package 后,定义所有类 class 前。

//【示例】import的使用举例(一)
package com.java.object; 
// 导入位置:在声明包package后,定义所有类class前。
import java.util.Date; // 导入日期类
public class Test {
	public static void main(String[] args) {
		// 使用Date类
		Date date = new Date();
	}
}

//【示例】import的使用举例(二)
package com.java.object; 
// 导入位置:在声明包 package后,定义所有类 class前。
import java.util.*; // 导入该包下所有的类。会降低编译速度,但不会降低运行速度。
class Test {
	public static void main(String[] args) {
		// 使用java.util包下的类
		Date date = new Date();
		Scanner s = new Scanner(System.in);
	}
}
//注意:如果导入两个同名的类中的一个,只能用包名+类名来显式调用相关类。

//【示例】导入同名类的处理
import java.sql.Date; 
import java.util.*; 
public class Test {
	public static void main(String[] args) {
		java.util.Date date = new java.util.Date();
	}
}

  静态导入(static import)是在JDK1.5新增加的功能,其作用是用于导入指定类的静态变量或静态方法,这样我们可以直接使用静态变量或静态方法。

// 以下两种静态导入的方式二选一即可
import static java.lang.Math.*; // 导入Math类的所有静态变量和静态方法
import static java.lang.Math.PI; // 导入Math类的PI属性
public class Test {
	public static void main(String[] args) {
		System.out.println(PI);
		System.out.println(random());
	}
}

六、封装、隐藏

6.1 访问控制符


6.2 封装概述

  封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

原则将属性隐藏起来,若需要访问某个属性,提供公共方法对其访

/** 封装的步骤 */
1)使用 private 关键字来修饰成员变量
2)对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法


// 示例
public class Student {
  
  	// 隐藏属性
    private String name;
    private int age;
  
  	// 提供 getXxx 方法(取值) / setXxx(赋值) 方法,可以访问成员变量
    public void setName(String n) {
    		name = n;
    }
    public String getName() {
    		return name;
    }
    public void setAge(int a) {
    		age = a;
    }
    public int getAge() {
   		 return age;
    }
}

6.3 this 关键字

⚠️this 关键字总是指向调用该方法的对象

  1. 构造器中引用该构造器正在初始化的对象
  2. 在方法中引用调用该方法的对象

!!只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁。

⚠️在构造器中this关键字可以调用另一个构造器,但是必须写在构造器内部第一条语句位置。!!!

public class Demo01 {
    private String name;
    private int age;
    private long tel;
    
    //无参构造器(如果有有参构造器的话无参构造器会被隐藏,如果不显示声明则用不了无参构造器!!!)
    public Demo01(){
    }
    
    //一个参数的构造器
    public Demo01(String name){
        this.name = name;
    }
    
    //两个参数的构造器 形成构造器重载
    public Demo01(String name,int age,long tel){
        //this调用另一个重载的构造器;this调用构造器时必须放在构造器的第一条语句!!!
        this(name);
        this.age = age;
        this.tel = tel;
    }
}

七、类的继承(extends)

  继承(extends)就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为,并能拓展新的能力。子类可以直接访问父类中的非私有的属性和行为。

继承的优点:

  1. 提高代码的复用性。
  2. 类与类之间产生了关系,是多态的前提。

⚠️继承的注意点

  1. java 只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类。
  2. java 支持多层(重)继承,即一个类的父类可以再去继承另外的父类(继承体系)。
  3. 如果定义一个类时,没有使用extends,则它的父类默认是:java.lang.Object。
  4. 只能继承非私有的成员变量或方法!!!
  5. 子类如果有和父类同名的方法则覆盖父类同名的方法!(相当于重写)调用父类被重写的同名方法:super.父类方法名
  6. 子类如果有和父类同名的属性则覆盖父类同名的属性!!调用父类同名变量: super.父类成员变量名
  7. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的
  8. 子类可以继承接口中的默认方法;例如:default void defaultMethod(){}

格式:

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

注意

  1. 子类又被称为派生类,父类又被称为超类(Super Class)

7.1 成员变量重名

class Fu{
    //父类私有成员
    private int id;//私有变量不能被继承
    //父类普通成员
    String name = "许褚";
    //父类中的show方法
    public void show(){
        System.out.println("Fu--show();");
    }
}

class Zi extends Fu{
    //子类私有成员
    private int id;
    //子类与父类同名的成员
    String name = "刘备";
    //子类与父类同名的show方法(同名则覆盖父类方法)
    public void show(){
        //调用父类的show方法
        super.show(); //Fu--show();
        //访问父类中的name
        System.out.println("Fu--name="+super.name);//Fu--name=许褚
        //访问子类中的name
        System.out.println("Zi--name="+this.name);//Zi--name=刘备
        System.out.println("Zi--name="+name);//Zi--name=刘备 //默认省略this关键字
    }
}

public class ExtendsTest02 {
    public static void main(String[] args) {
        Zi z = new Zi();
        //调用子类的成员变量
        System.out.println(z.name);//刘备
        //调用子类中的show方法
        z.show();
    }
}

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

7.2 成员方法重名(重写 Override)

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)

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

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
    }
}		

重写的应用
  子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。


注意

  1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
  2. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

7.3 重载和重写(重点)

重载(Overload)

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

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

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

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

7.4 初始化顺序(重点)

class Father{
    static{
        System.out.println("父类 静态代码块");
    }
    {
        System.out.println("父类 代码块");
    }
    Father(){
        System.out.println("父类 构造器");
    }
}

class Son extends Father{
    static {
        System.out.println("子类 静态代码块");
    }
    {
        System.out.println("子类 代码块");
    }
    Son(){
        System.out.println("子类 构造器");
    }
}

public class ExtendsTest03 {
    public static void main(String[] args) {
        Son s = new Son();
    }
}

父类与子类的执行顺序如下
父类静态变量 🔽
父类静态代码块 🔽
子类静态变量 🔽
子类静态代码块 🔽
父类非静态变量 🔽
父类非静态代码块 🔽
父类构造函数 🔽
子类非静态变量 🔽
子类非静态代码块 🔽
子类构造函数 🔚

八、多态(polymorphism)

8.1 定义

多态: 是指同一行为,具有多个不同表现形式。多态是封装、继承之后,面向对象的第三大特性 。

多态的必要条件

  1. 继承(extends)或实现(implements)是多态的前提。【二选一】
  2. 子类重写父类方法。【不重写没意义】
  3. 父类引用指向子类对象。【格式提现】

多态的使用场合

  1. 使用父类做方法的形参,实参可以是任意子类类型。
  2. 使用父类做方法的返回值类型,返回值可以是任意子类的对象。

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

【格式】父类类型 变量名 = new 子类对象;

// 示例
Fu f = new Zi();

【案例】

public abstract class Animal {
	public abstract void eat();
}

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

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

public class Test {
    public static void main(String[] args) {
        // 多态形式,创建对象
        Animal a1 = new Cat();
        // 调用的是 Cat 的 eat
        a1.eat();
        // 多态形式,创建对象
        Animal a2 = new Dog();
        // 调用的是 Dog 的 eat
        a2.eat();
    }
}

⚠️注意

  1. 多态中若子类中与父类中普通成员变量同名则 执行子类的成员变量(实例变量不具备多态性)。
  2. 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后法。

8.2 引用类型转换

向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

优点:隐藏了子类类型,提高了代码的扩展性,多态本身就是向上转型的过程。
缺点:只能使用父类共性的内容,不能调用子类特有的方法!

// 示例:当父类引用指向一个子类对象时,便是向上转型
    父类类型 变量名 = new 子类类型();
    
    Animal a = new Cat();

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

优点:向下转型之后,可以调用子类特有的方法。
缺点:向下转型有风险,容易发生ClassCastException异常!

// 示例:一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型
    子类类型 变量名 = (子类类型) 父类变量名;
    
    Cat c = (Cat) a;


【案例】

// 父类
class Person {
	public void eat() {
		System.out.println("person eat ...");
	}
}

// 子类
public class Student extends Person {
	public void study() {
		System.out.println("Student study ...");
	}
}

// 测试类
public class PolymorphismDemo {
	public static void main(String[] args) {
		// 向上转型:父类引用指向子类对象
		Person p = new Student();
		p.eat(); // 调用Person的eat()方法
		// p.study(); // 编译失败,不能调用Student的study()方法
		
		// 向下转型:子类引用指向父类对象
		Student stu = (Student)p;
		stu.eat(); // 调用Person的eat()方法
		stu.study(); // 调用Student的study()方法
	}
}

8.3 instanceof 运算符

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

格式如下boolean result = object instanceof class

  • 如果变量属于该数据类型,返回 true。
  • 如果变量不属于该数据类型,返回 false。

注意:格式中 class可以是类,也可以是接口!

在编译状态中
  class是object对象的父类、自身类、兄弟类和子类时,这四种情况下编译都不会报错。

在运行转态中
  class是object对象的父类和自身类时,返回的结果为true;class是object对象的子类和兄弟类时,返回的结果为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
        }
    }
}

8.4 多态中成员变量特点

无论编译和运行,都看等号左边(引用类型变量所属的类)。

class Father {
	int num = 10;
}

class Son extends Father {
	int num = 20;
	String name = "小明";
}

public class InstanceDemo {
	public static void main(String[] args) {
		Father father = new Son();
		// 多态中的成员变量:无论编译和运行,都参考等号左边的类
		System.out.println("num:" + father.num); // 编译通过,输出父类中的num值
		// System.out.println("name:" + father.name); 编译错误
	}
}

8.5 多态中成员方法特点

编译看左边,检查等号左边引用变量所属的类是否有该方法。

运行看右边,在执行期间判断引用变量所指向对象的实际类型,然后根据其实际的类型调用其相应的方法,又称为动态绑定。如果右边对象没有该方法则执行父类中的方法。(接口中只有默认方法可以供实现类调用)

【案例】

class Father {
	public void eat() {
		System.out.println("父类中的eat方法");
	}
}

class Son extends Father {
	public void study() {
		System.out.println("子类中的show方法");
	}
	public void eat() {
		System.out.println("子类中的eat方法");
	}
}

public class InstanceDemo {
	public static void main(String[] args) {
		Father father = new Son();
		father.eat(); // 编译通过,调用子类中的eat方法
		// father.study(); 编译错误
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值