Java学习笔记Day03 面向对象基础

第三章 面向对象基础

文章目录

  • 第三章 面向对象基础
    • 3.1 面向对象思想
      • 3.1.1 面向对象简介
        • 对象
        • 类与对象之间的关系
        • 类的关系
      • 3.1.3 面向对象特征
    • 3.2 类与对象
      • 3.2.1 类的声明
        • 字段和方法(成员变量和成员方法)
      • 3.2.2 对象的创建和使用
        • 创建对象
        • 使用对象访问类中的成员
        • 对象存储图
        • 成员变量和局部变量的区别
    • 封装(补充)
        • 封装概述
        • 封装的步骤
        • 封装的操作 --private关键字
        • 封装优化1 --this关键字
          • this的含义
          • this的使用格式
        • 封装优化2 --构造方法
          • 构造方法的定义格式
        • 标准代码 --JavaBean
    • 3.3 继承
      • 3.3.1 概述
        • 定义
        • 好处
      • 3.3.2 继承的格式
      • 3.3.3 继承后的特点 --成员变量
        • 成员变量不重名
        • 成员变量重名
      • 3.3.4 继承后的特点 --成员方法
        • 成员方法不重名
        • 成员方法重名 --重写(Override)
        • 重写的应用
        • 注意事项
        • 方法的参数传递机制
      • 3.3.5 继承后的特点 --构造方法
      • 3.3.6 super和this
        • 父类有空间优先于子类对象产生
        • super和this的含义
        • super和this的用法
      • 3.3.7 继承的特点
    • 3.4包
      • 定义包
      • 导入包
    • 3.5 访问控制符
      • setter和getter
      • 内部类(private)【补充】
        • 概述
          • 什么是内部类
          • 成员内部类
          • 访问特点
        • 匿名内部类
          • 前提
          • 格式
          • 使用句式
    • 3.6 非访问修饰符
      • 3.6.1 static关键字
        • 概述
        • 定义和使用格式
          • 类变量
          • 静态方法
          • 调用格式
        • 静态原理图解
        • 静态代码块
      • 3.6.2 final关键字
        • 概述
        • 使用方式
          • 修饰类
          • 修饰方法
          • 修饰变量
      • 3.6.3 abstract关键字
        • 概述
          • 由来
          • 定义
          • abstract使用格式
          • 抽象类
          • 抽象的使用
        • 注意事项
    • 3.7 接口
      • 概述
      • 定义格式
        • 含有抽象方法
        • 含有默认方法和静态方法
        • 含有私有方法和私有静态方法
      • 基本的实现
        • 实现的概述
        • 抽象方法的使用
        • 默认方法的使用
        • 静态方法的使用
        • 私有方法的使用
      • 接口的多实现
        • 抽象方法
        • 默认方法
        • 静态方法
        • 优先级的问题
      • 接口的多继承*
      • 其他成员特点
    • 3.8 对象数组
    • 3.9 枚举
    • 模板
      • 完整的类定义
      • 完整的接口定义
      • 固定声明方式
      • 完整的java源文件
    • 练习
      • 案例分析
      • 案例实现
    • 本章总结

3.1 面向对象思想

3.1.1 面向对象简介

  • 面向对象的分析(OOA):确定需求或者业务的角度,按照面向对象的思想来分析业务。
  • 面向对象的设计(OOD):一个中间过渡环节,其主要作用在OOA的基础上进一步规范化整理,从而建立所要操作的对象以及相互之间的联系,以便能够被OOP直接接受。
  • 面向对象编程(OOP):在前两者的基础上,对数据模型进一步细化。OOP是根据真实的对象来构建应用程序模型。OOP是当今软件开发的主流设计范型,精通OOP是编写出高品质程序的关键。

对象

  • 一切事物皆对象,人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可以看作对象;
  • 一个对象可以通过使用数据值来描述自身所具有的状态。
  • 对象还具有行为,通过行为可以改变对象的状态。对象将数据和行为封装于一体,实现了两者之间的紧密结合

  • 类是具有相同或相似性质的对象的抽象。因此,对象的抽象是类;
  • 类由“特征”和“行为”两部分组成:
    • “特征”是对象状态的抽象,通常使用“变量”来描述类的特征,我们又称之“属性”;
    • “行为”是对象操作的抽象,通常使用“方法”来描述类的行为。

类与对象之间的关系

Java学习笔记Day03 面向对象基础_第1张图片

类的关系

  • 类和类之间具有一定的结构关系。

  • 类的关系主要有两种:

    • 或关系

      “或关系”也称为“is a”关系,是“一般~具体”的结构关系;

    • 与关系

      “与关系”也称为“has a”关系,是“整体~部分”的结构关系。

  • 消息能够使对象之间进行通信

  • 方法是类的行为实现,一个方法有方法名、参数以及方法体

3.1.3 面向对象特征

  • 唯一性

    每个对象都是唯一的,自身具有唯一的标识,系统通过该标识可以找到相应的对象。

    在对象的整个生命周期过程中,其标识都是不变的;

    不同的对象其标识也是不同的。

  • 分类性

    分类性是指将具有一致属性和行为的对象抽象成类,只反映与应用有关的重要性质,而忽略其他一些无关内容。

    任何类的划分都是主观的,但必须与具体的应用有关。

  • 继承性

    继承性是指子类自动继承父类的属性和方法,这是类之间的一种关系。在定义和实现一个子类的时候,可以在一个父类的基础之上加入若干新的内容即可,原来父类中所定义的内容将自动作为子类的内容。

  • 多态性

    多态性是指相同的操作、过程可作用于多种类型的对象上并获得不同的结果。

    不同的对象,收到同一消息可以产生不同的结果,即具有不同的表现行为,这种现象称为多态性。

3.2 类与对象

3.2.1 类的声明

  • 类是组成Java程序的基本要素

  • 是一类对象的原型

  • 封装了一类对象的状态和方法

    • 它将变量与函数封装到一个
  • 类的定义:

    public class ClassName {
        //成员变量
        //成员方法
    }
    

字段和方法(成员变量和成员方法)

  • 字段(field)是类的属性,使用变量来表示的。

    • 字段又称为域、域变量、属性、成员变量等
  • 方法(method)是类的功能和操作,是用函数来表示的

    class Student {
        String name;
        int age;
        void sayHello() {
            System.out.println("Hello",+name);
        }
    }
    

3.2.2 对象的创建和使用

创建对象

类名 对象名 = new 类名()

使用对象访问类中的成员

对象名.字段;
对象名.方法();
public class Demo {
    public static void main(String[] args) {
        // 1. 导包。
        // 2. 创建,格式:
        // 类名称 对象名 = new 类名称();
        // 3. 使用其中的成员变量,格式:
        // 对象名.成员变量名
        Studnt stu = new Student();
        // 改变对象当中的成员变量数值内容
        // 将右侧的字符串,赋值交给stu对象当中的name成员变量
        stu.name = "***";
        stu.age = 18;
        System.out.println(stu.name); 
        System.out.println(stu.age); 
        // 4. 使用对象的成员方法,格式:
        // 对象名.成员方法名()
        stu.sayHello();
    }
}

使用的好处:

  • 封装性
  • 安全性

成员变量的默认值

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

对象存储图

  • 一个对象,调用一个方法内存图

    Java学习笔记Day03 面向对象基础_第2张图片

    通过上图,我们可以理解,在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。变量p指向堆内存中 的空间,寻找方法信息,去执行该方法。
    但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存 了,因为所有对象的方法信息都是一样的。

  • 两个对象,调用同一个方法

    Java学习笔记Day03 面向对象基础_第3张图片

    对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间。

  • 一个引用,作为参数传递到方法中内存图

    Java学习笔记Day03 面向对象基础_第4张图片

    引用类型作为参数,传递的是地址值。

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

public class Car {
    String color;//成员变量
    public void driver() {
        int speed = 80;//局部变量
        System.out.println("时速"+speed);
    }
}
  • 在类中的位置不同
    • 成员变量:类中,方法外
    • 局部变量:方法中或者方法声明上(形式参数)
  • 作用范围不一样
    • 成员变量:类中
    • 局部变量:方法中
  • 初始化值的不同
    • 成员变量:有默认值
    • 局部变量:没有默认值。必须先定义,赋值,最后使用
  • 在内存中的位置不同
    • 成员变量:堆内存
    • 局部变量:栈内存
  • 生命周期不同
    • 成员变量:随着对象的创建而存在,随着对象的消失而消失
  • 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失

封装(补充)

封装概述

  • 概述:面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

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

封装的步骤

  1. 使用 private 关键字来修饰成员变量。
  2. 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。

封装的操作 --private关键字

  • private的含义

    • private是一个权限修饰符,代表最小权限。
    • 可以修饰成员变量和成员方法。
    • 被private修饰后的成员变量和成员方法,只在本类中才能访问。
  • private的使用格式

    private 数据类型 变量名;
    
    • 使用private修饰成员变量

      public class Student {
          private String name;
          private int age;
      }
      
    • 提供getxxx方法/setxxx方法,可以访问成员变量

      public class Student {
          private String name;
          private int age;
          public void setName(String n) {
          name = n;
      }
      
      public String getName() {
          return name;
      }
      
      public void setAge(int a) {
          age = a;
      }
      
      public int getAge() {
          return age;
      }
          
      

封装优化1 --this关键字

this的含义

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

记住 :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,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;
    }
}

封装优化2 --构造方法

当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

小贴士:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法, 一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

构造方法的定义格式
修饰符 构造方法名(参数列表){ 
    // 方法体      
}
public class Student { 
    private String name;   
    private int age;   
    // 无参数构造方法   
    public Student() {}    
    // 有参数构造方法   
    public Student(String name,int age) {     
        this.name = name;
        this.age = age;    
    } 
}

注意事项

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

标准代码 --JavaBean

public class ClassName{   
    //成员变量   
    //构造方法   
    //无参构造方法【必须】  
    //有参构造方法【建议】  
    //成员方法     
    //getXxx()   
    //setXxx() 
}

3.3 继承

3.3.1 概述

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。

继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。

定义

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

好处

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

3.3.2 继承的格式

class 父类 { 
    ...     
}   

class 子类 extends 父类 { 
    ...      
}

3.3.3 继承后的特点 --成员变量

成员变量不重名

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

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 ExtendDemo02 { 
    public static void main(String[] args) {             
        // 创建子类对象 
        Zi z = new Zi();                 
        // 调用子类中的show方法    
        z.show();         
    }     
}   
演示结果:
    Fu num = 5 
    Zi num2 = 6

成员变量重名

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

class Fu { 
    // Fu中的成员变量。     
    int num = 5;    
} 

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

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

演示结果: 
    Fu num = 6 
    Zi num = 6

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

使用格式:

super.父类成员变量名

子类方法需要修改,代码如下:

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

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

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

3.3.4 继承后的特点 --成员方法

成员方法不重名

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

class Fu{ 
    public void show(){      
    System.out.println("Fu类中的show方法执行");         
    }     
} 

class Zi extends Fu{ 
    public void show2(){      
        System.out.println("Zi类中的show2方法执行");        
    }      
} 

public  class ExtendsDemo04{ 
    public static void main(String[] args) {      
        Zi z = new Zi();                
        //子类中没有show方法,但是可以找到父类方法去执行
        z.show();           
        z.show2();          
    }      
}

成员方法重名 --重写(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          
    }      
}

重写的应用

子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从 而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:

class Phone { 
    public void sendMessage(){     
        System.out.println("发短信");        
    }  
    
    public void call(){     
        System.out.println("打电话");       
    } 
    
    public void showNum(){     
        System.out.println("来电显示号码");     
    }   
}
//智能手机类 
class NewPhone extends Phone {      
    //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能   
    public void showNum(){      
        //调用父类已经存在的功能使用super       
        super.showNum();        
        //增加自己特有显示姓名和图片功能         
        System.out.println("显示来电姓名");       
        System.out.println("显示头像");         
    }   
}   public class ExtendsDemo06 {
    public static void main(String[] args) {   
        // 创建子类对象       
        NewPhone np = new NewPhone()// 调用父类继承而来的方法   
            np.call();       
        // 调用子类重写的方法    
        np.showNum();    
    }   
}

这里重写时,用到super.父类成员方法,表示调用父类的成员方法。

注意事项

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

方法的参数传递机制

方法可以带参数,通过参数可以给方法传递数据

  • 带参数

    public void setName(String name) {
    	this.name = name;
    }
    
  • 带多个参数

    public int add(int a, int b) {
    	return a+b;
    }
    

根据参数的使用场合,可以将参数分为:

  • 形参: “声明方法”时给方法定义的形式上的参数,此时形参没有具体的数值,形参前必须有数据类型,格式为:

    方法名(数据类型 形参)
    
  • 实参:“调用方法”时程序给方法传递的实际数据,实参前面没有数据类型,格式为:

    对象名.方法名(实参)
    

实参和形参之间传递数值的方式有两种:

  • 值传递(call by value)

    值传递时,实参和形参在内存中占不同的空间,当实参的值传递给形参后,两者之间将互不影响

    Java学习笔记Day03 面向对象基础_第5张图片

  • 引用传递(call by reference)

    引用传递是将实参的“地址”传递给形参,被调方法通过传递的地址获取其指向的内存空间,从而在原来内存空间直接进行操作。

    Java学习笔记Day03 面向对象基础_第6张图片

3.3.5 继承后的特点 --构造方法

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

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构 造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代 码如下:
class Fu {   
    private int n;   
    Fu(){     
        System.out.println("Fu()");   
    }
}

class Zi extends Fu {   
    Zi(){    
        // super(),调用父类构造方法     
        super();     
        System.out.println("Zi()");  
    }   
} 

public class ExtendsDemo07{   
    public static void main (String args[]){     
        Zi zi = new Zi();  
    } 
} 

输出结果:
    Fu()
    Zi()

3.3.6 super和this

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

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

Java学习笔记Day03 面向对象基础_第7张图片)

super和this的含义

  • super :代表父类的存储空间标识(可以理解为父亲的引用)。
  • this :代表当前对象的引用(谁调用就代表谁)。

super和this的用法

  1. 访问成员

    this.成员变量     ‐‐    本类的     
    super.成员变量     ‐‐    父类的      
    this.成员方法名()   ‐‐    本类的        
    super.成员方法名()   ‐‐    父类的
    
  2. 访问构造方法

    this(...)     ‐‐    本类的构造方法 
    super(...)    ‐‐    父类的构造方法
    

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

3.3.7 继承的特点

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

  2. Java支持多层继承(继承体系)。

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

  3. 子类和父类是一种相对的概念。

3.4包

  • Java引入包(package)的机制,提供了类的多层命名空间,解决类的命名冲突、类文件管理等问题。
  • 借助于包可以将自己定义的类与其它类库中的类分开管理。

定义包

语法:

Person p = new Person();

1、package语句必须作为Java源文件的第一条非注释性语句;
2、一个Java源文件只能指定一个包,即只有一条package语句,不能有多条package语句;
3、定义包之后,Java源文件中可以定义多个类,这些类将全部位于该包下;
4、多个Java源文件可以定义相同的包。

在物理组织上,包的表现形式为目录,但并不等同于手工创建目录后将类拷贝过去就行,必须保证类中代码声明的包名与目录一致才行。为保证包名的规范性,建议以“公司域名反写.项目名.模块名”创建不同的子包,例如:com.qst.chapter03.comm包,“com.qst”是反写的公司域名,“chapter03”是项目名,“comm”是模块名。

导入包

Java中一个类可以访问其所在包中的其他所有的类,但是如果需要访问其他包中的类则可以使用import语句导入包。

  • 语法

    import 包名.*; //导入指定包中所有的类
    
    import 包名.类名; //导入指定包中指定的类
    

导入包之后,可以在代码中直接访问包中的这些类。

指明导入当前包的所有类,但不能使用“java.”或“java..”这种语句来导入以java为前缀的所有包的所有类。一个Java源文件只能有一条package语句,但可以有多条import语句,且package语句在import语句之前。

3.5 访问控制符

  • 封装是面向对象的特性之一
  • 封装实际上把该隐藏的隐藏,该暴露的暴露,这些都需要通过Java访问控制符来实现。

4种访问控制级别:

  • private(当前类访问权限):被声明为private的成员只能被当前类中的其他成员访问,不能在类外看到;
  • 缺省(包访问权限):如果一个类或类的成员前没有任何访问控制符,则获得缺省的访问权限,缺省的可以被同一包中的所有类访问;
  • protected(子类访问权限):被声明为protected的成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问;
  • public(公共访问权限):被声明为public的成员可被同一包或不同包中的所有类访问,即public访问修饰符可以使类的特性公用于任何类。

访问控制表

访问控制 private default protected public
同一类中成员 + + + +
同一包(子类与无关类) - + + +
不同包中子类 - - + +
不同包中非子类 - - - +

private、protected和public都是关键字,而friendly不是关键字,它只是一种缺省访问修饰符的称谓而已。

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

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

不加权限修饰符,其访问能力与default修饰符相同

setter和getter

将字段用private修饰,从而更好地将信息进行封装和隐藏

setXXXXgetXXXX方法对类的属性进行存取,分别称为setter与getter。

这种方法有以下优点

  • 属性用private更好地封装和隐藏,外部类不能随意存取和修改。
  • 提供方法来存取对象的属性,在方法中可以对给定的参数的合法性进行检验。
  • 方法可以用来给出计算后的值。
  • 方法可以完成其他必要的工作(如清理资源、设定状态,等等)。
  • 只提供getXXXX方法,而不提供setXXXX方法,可以保证属性是只读的
class Person2 {
    private int age;
    public void setAge(int age) {
        if(age > 0 && age < 200) 
    }
    
    public int getAge() {
        return age;
    }
}

内部类(private)【补充】

概述

什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。

成员内部类
  • 成员内部类 :定义在类中方法外的类。
class 外部类 {
    class 内部类 {
        
    }
}
访问特点
  • 内部类可以直接访问外部类的成员,包括私有成员。
  • 外部类要访问内部类的成员,必须要建立内部类的对象。
外部类.内部类 对象名 = new 外部类型().new 内部类型();

定义类:

public class Person {     
    private  boolean live = true;     
    class Heart {         
        public void jump() {          
            // 直接访问外部类成员         
            if (live) {               
                System.out.println("心脏在跳动");    
            } else {            
                System.out.println("心脏不跳了");     
            }      
        }   
    }      
    
    public boolean isLive() {     
        return live;    
    }       
    
    public void setLive(boolean live) {     
        this.live = live;   
    }   
}

定义测试类:

public class InnerDemo {
    public static void main(String[] args) {         
        // 创建外部类对象          
        Person p  = new Person();        
        // 创建内部类对象         
        Heart heart = p.new Heart();        
        // 调用内部类方法     
        heart.jump();       
        // 调用外部类方法       
        p.setLive(false);    
        // 调用内部类方法       
        heart.jump();     
    }
} 

输出结果: 
	心脏在跳动 
    心脏不跳了

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名 和$符号 。

匿名内部类

  • 匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。
    开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法
    我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
前提

匿名内部类必须继承一个父类或者实现一个父接口。

格式
new 父类名或者接口名() {
    //方法重写
    @Override
    public void method() {
        //执行语句
    }
};
使用句式

定义接口:

public abstract class FlyAble {
    public abstract void fly();
}

创建匿名内部类,并调用:

public class InnerDemo {     
    public static void main(String[] args) {        
    /*         
    1.等号右边:是匿名内部类,定义并创建该接口的子类对象     
    2.等号左边:是多态赋值,接口类型引用指向子类对象      
    */         
    FlyAble  f = new FlyAble(){           
        public void fly() {      
            System.out.println("我飞了~~~");        
        }      
    };          
    //调用 fly方法,执行重写后的方法         
    f.fly();   
    }                   
}

当方法的形式参数是接口或者抽象类的时候,可以将匿名内部类作为参数传递:

public class InnerDemo2 {     
    public static void main(String[] args) {     
        /*         
        1.等号右边:定义并创建该接口的子类对象      
        2.等号左边:是多态,接口类型引用指向子类对象     
        */         
        FlyAble  f = new FlyAble(){       
            public void fly() {         
                System.out.println("我飞了~~~");         
            }      
        };         
        // 将f传递给showFly方法中         
        showFly(f);    
    }    
    
    public static void showFly(FlyAble f) {     
        f.fly();   
    }
}

以上两步,可以简化为一步:

public class InnerDemo03 {
   public static void main(String[] args) {       
       /*         创建匿名内部类,直接传递给showFly(FlyAble f)           */         showFly( new FlyAble(){      
           public void fly() {                 System.out.println("我飞了~~~");             }        
       });     
   }       
    
    public static void showFly(FlyAble f) {      
        f.fly();    
    } 
}

3.6 非访问修饰符

基本含义 修饰类 修饰成员 修饰局部变量
static 静态的、非实例的、类的 可以修饰内部类 +
final 最终的、不可改变的 + + +
abstract 抽象的、不可实例化的 Yes + +

3.6.1 static关键字

概述

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。

定义和使用格式

类变量

当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改 该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

  • 类变量:使用 static关键字修饰的成员变量。

定义格式:

static 数据类型 变量名
静态方法

当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要 创建类的对象。调用方式非常简单。

  • 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法。

定义格式

修饰符 static 返回值类型 方法名 (参数列表){ 
    // 执行语句       
}

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

  • 静态方法可以直接访问类变量和静态方法。
  • 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
  • 静态方法中,不能使用this关键字。
调用格式

被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属 于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

// 访问类变量 
类名.类变量名;  
// 调用静态方法 
类名.静态方法名(参数);

静态原理图解

static 修饰的内容:

  • 是随着类的加载而加载的,且只加载一次。
  • 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  • 它优先于对象存在,所以,可以被所有对象共享。

Java学习笔记Day03 面向对象基础_第8张图片

静态代码块

  • 静态代码块:定义在成员位置,使用static修饰的代码块{ }。
    • 位置:类中方法外。
    • 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。

格式:

public class ClassName {
    static {
        //执行语句
    }
}

作用:给类变量进行初始化赋值。

static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。

3.6.2 final关键字

概述

  • final: 不可改变。可以用于修饰类、方法和变量。
    • 类:被修饰的类,不能被继承。
    • 方法:被修饰的方法,不能被重写。
    • 变量:被修饰的变量,不能被重新赋值。

使用方式

修饰类
final class 类名 {
    
}
修饰方法
修饰符 final 返回值类型 方法名(参数列表) {
    //方法体
}
修饰变量
  1. 局部变量 --基本类型

    public class FinalDemo1 {     
        public static void main(String[] args) {        
        // 声明变量,使用final修饰         
        final int a;        
        // 第一次赋值          
        a = 10;        
        // 第二次赋值        
        a = 20; // 报错,不可重新赋值           
        // 声明变量,直接赋值,使用final修饰      
        final int b = 10;        
        // 第二次赋值       
        b = 20; // 报错,不可重新赋值    
        } 
    }
    
  2. 局部变量 --引用类型

    public class FinalDemo2 {     
        public static void main(String[] args) {         
            // 创建 User 对象         
            final   User u = new User();         
            // 创建 另一个 User对象         
            u = new User(); // 报错,指向了新的对象,地址值改变。          
            // 调用setName方法         
            u.setName("张三"); // 可以修改    
        } 
    }
    
  3. 成员变量

    • 显示初始化

      public class User {
          final String USERNAME = "zzz";
          private int age;
      }
      
    • 构造方法初始化

      public class User {
          final String USERNAME ;
          private int age;
          public User(String username,int age) {
              this.USERNAME = username;
              this.age = age;
          }
      }
      

      被final修饰的常量名称,一般都有书写规范,所有字母都大写。

3.6.3 abstract关键字

概述

由来

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法 的类就是抽象类

定义
  • 抽象方法 : 没有方法体的方法。
  • 抽象类:包含抽象方法的类。
abstract使用格式

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表)
抽象类

如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:

abstract class 类名字 {

}
抽象的使用

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父 类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

public class Cat extends Animal {     
    public void run (){       
        System.out.println("小猫在墙头走~~~")}
}   

public class CatTest {   
    public static void main(String[] args) {          
        // 创建子类对象      
        Cat c = new Cat();           
        // 调用run方法       
        c.run();   
    }   
} 

输出结果: 小猫在墙头走~~~

注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  1. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  1. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义。

3.7 接口

概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并 不是类,而是另外一种引用数据类型。

引用数据类型:数组,类,接口。

接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做 是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象 类。

定义格式

public interface 接口名称 {
    //抽象方法
    //默认方法
    //静态方法
    //私有方法
}

含有抽象方法

抽象方法:使用 abstract 关键字修饰,可以省略,没有方法体。该方法供子类实现使用。

public interface InterfaceName {
    public abstract void method();
}

含有默认方法和静态方法

默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。

静态方法:使用 static 修饰,供接口直接调用。

public interface InterfaceName {
    public default void method() {
       //执行语句 
    }
    
    public static void method2() {
        //执行语句
    }
}

含有私有方法和私有静态方法

私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。

public interface InterfaceName {
    private void method() {
        //执行语句
    }
}

基本的实现

实现的概述

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类 似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。
非抽象子类实现接口:

  1. 必须重写接口中所有抽象方法。
  2. 继承了接口的默认方法,即可以直接调用,也可以重写。
class 类名 implements 接口名 {
    //重写接口中的方法【必须】
    //重写接口中默认方法【可选】
}

抽象方法的使用

定义接口

public interface LiveAble {
    //定义抽象方法
    public abstract void eat();
    public abstract void sleep();
}

定义实现类:

public class Animal implements LiveAble {     
    @Override     
    public void eat() {         
        System.out.println("吃东西");     
    }    
    
    @Override     
    public void sleep() {         
        System.out.println("晚上睡");    
    } 
}

定义测试类

public class InterfaceDemo {     
    public static void main(String[] args) {         
        // 创建子类对象           
        Animal a = new Animal();         
        // 调用实现后的方法         
        a.eat();         
        a.sleep();     
    } 
} 

输出结果: 
    吃东西 
    晚上睡 

默认方法的使用

可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。

  1. 继承默认方法,代码如下:

    定义接口:

    public interface LiveAble {     
        public default void fly(){         
            System.out.println("天上飞");     
        } 
    }
    

    定义实现类:

    public class Animal implements LiveAble { 
        // 继承,什么都不用写,直接调用    
    }
    

    定义测试类:

    public class InterfaceDemo {     
        public static void main(String[] args) {      
            // 创建子类对象          
            Animal a = new Animal();        
            // 调用默认方法        
            a.fly();     
        }
    } 
    
    输出结果: 
        天上飞
    
  2. 重写默认方法,代码如下:

    定义接口:

    public interface LiveAble {     
        public default void fly(){         
            System.out.println("天上飞");   
        }
    }
    

    定义实现类:

    public class Animal implements LiveAble {  
        @Override    
        public void fly() {        
            System.out.println("自由自在的飞");   
        } 
    }
    

    定义测试类:

    public class InterfaceDemo {     
        public static void main(String[] args) {        
            // 创建子类对象         
            Animal a = new Animal();       
            // 调用重写方法        
            a.fly();   
        }
    } 
    
    输出结果: 
        自由自在的飞
    

静态方法的使用

静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用,代码如下:

定义接口:

public interface LiveAble {     
    public static void run(){       
        System.out.println("跑起来~~~");   
    } 
}

定义实现类:

public class Animal implements LiveAble { 
    // 无法重写静态方法     
}

定义测试类:

public class InterfaceDemo {    
    public static void main(String[] args) {    
        // Animal.run(); // 【错误】无法继承方法,也无法调用      
        LiveAble.run(); //    
    } 
} 

输出结果: 
    跑起来~~~

私有方法的使用

  • 私有方法:只有默认方法可以调用。
  • 私有静态方法:默认方法和静态方法可以调用。

如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法 去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。同学们在已学技术的基础上,可以自行测 试。
定义接口:

public interface LiveAble {     default void func(){         func1();         func2();     }       private void func1(){         System.out.println("跑起来~~~");     }       private void func2(){         System.out.println("跑起来~~~");     } }

接口的多实现

之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接 口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

实现格式

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {     
    // 重写接口中抽象方法【必须】    
    // 重写接口中默认方法【不重名时可选】  
}

[ ]: 表示可选操作。

抽象方法

接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。代码如 下:

定义多个接口:

interface A {     
    public abstract void showA();     
    public abstract void show();
}   

interface B {     
    public abstract void showB();     
    public abstract void show(); 
}

定义实现类:

public class C implements A,B{
    @Override     
    public void showA() {         
        System.out.println("showA");    
    }           
    @Override     
    public void showB() {   
        System.out.println("showB");  
    }      
    @Override     
    public void show() {   
        System.out.println("show");  
    }
}

默认方法

接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。代码如下:

定义多个接口:

interface A {     
    public default void methodA(){
        
    }     
    public default void method(){
        
    }
}   

interface B {    
    public default void methodB(){
        
    }  
    public default void method(){
        
    } 
}

定义实现类:

public class C implements A,B{     
    @Override     
    public void method() {        
        System.out.println("method");   
    } 
}

静态方法

接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

优先级的问题

当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执 行父类的成员方法。代码如下:

定义接口:

interface A {     
    public default void methodA(){    
        System.out.println("AAAAAAAAAAAA");    
    } 
}

定义父类:

class D {     
    public void methodA(){       
        System.out.println("DDDDDDDDDDDD"); 
    } 
}

定义子类:

class C extends D implements A {   
    // 未重写methodA方法  
}

定义测试类:

public class Test {     
    public static void main(String[] args) {      
        C c = new C();        
        c.methodA();      }
} 

输出结果: 
DDDDDDDDDDDD

接口的多继承*

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继 承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。代码如下:

定义父接口:

interface A {    
    public default void method(){   
        System.out.println("AAAAAAAAAAAAAAAAAAA");  
    } 
}   

interface B {   
    public default void method(){    
        System.out.println("BBBBBBBBBBBBBBBBBBB");   
    } 
}

定义子接口:

interface D extends A,B{    
    @Override     
    public default void method() {   
        System.out.println("DDDDDDDDDDDDDD");    
    } 
}

子接口重写默认方法时,default关键字可以保留。 子类重写默认方法时,default关键字不可以保留

其他成员特点

  • 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
  • 接口中,没有构造方法,不能创建对象。
  • 接口中,没有静态代码块

3.8 对象数组

对象数组就是一个数组中的所有元素都是对象,声明对象数组与普通基本数据类型的数组一样

语法:

类名[] 数组名 = new 类名[长度];

对象数组在内存中的存储

Java学习笔记Day03 面向对象基础_第9张图片

3.9 枚举

  • 从JDK1.5起,可以使用枚举

    enum Light{Red,Yellow,Green}
    Light light = Light.Red;
    switch(light){case Red;;break;}
    
  • Java中的枚举是用class来实现的,可以复杂地使用

模板

完整的类定义

[public][abstract|final] class className [extends superclassName]
[implements InterfaceNameList] {//类声明
    [public|protected|private] [static] [final] [transient] [volatile] type variableName;
    //成员变量声明,可为多个
    [public|protected|private] [static] [final|abstract] [native] [synchronized]
    //方法定义及实现,可为多个
    returnType methodName([paramList])
    [throws exceptionList]{
        statements
    }
} 

完整的接口定义

//接口声明
[public] interface InterfaceName [extends superInterface]{
    //常量声明,可为多个
    type constantName = Value;
    //方法声明,可为多个
    returnType metodName([paramList]);
}

固定声明方式

  • 构造方法

    className([paramList]){
        
    }
    
  • main()方法

    public static void main(String[] args){
        
    }
    
  • finalize()方法

    protected void finalize() throws throwable {
        
    }
    

完整的java源文件

package packageName; //指定文件中的类所在的包,0个或1个
import packageName.[className|*]; //指定引入的类,0个或多个
public classDefinition //属性为public的类定义,0个或1个
interfaceDefinition and classDefinition //接口或类定义,0个或多个。
  • 源文件的名字必须与属性为public的类的类名完全相同
  • 在一个.java文件中,package语句和public类最多只能有1个。

练习

案例分析

编写一个小的程序,其中定义一些接口、类、抽象类,定义它们的成员(字段及方法), 要求使用setter/getter, static, final, abstract,@Override等语法要素,并写一个main函数来使用它们。这些类、接口可以是围绕以下选题之一

飞翔世界:来一次飞翔接力(即多个可飞翔的对象依次调用);

动物世界:来一次吃西瓜大赛;

图书馆:模拟一天的借阅过程;

学校:模拟选课过程;

等等

要求写个简要说明。

案例实现

在本次练习中,我选择了飞翔世界作为练习。

定义接口Fly

public interface Fly {
    void start();
    void fly();
    void stop();
}

定义抽象类AbstractFilter

abstract class AbstractFlier implements Fly {
    @Override
    public void start() {
        System.out.println("START");
    }
    @Override
    public void stop() {
        System.out.println("END");
    }
}

定义飞行员类:

class Flier extends AbstractFlier {
    private String flier;
    protected static final int DISTANCE = 1;

    public void setName(String flier) {
        this.flier = flier;
    }
    public void flying() {
        System.out.println(this.flier + "\thas flown "+DISTANCE+"km");
    }
    @Override
    public void start(){
        System.out.println("========================");
        System.out.println(this.flier+"\tstart flying");
    }

    @Override
    public void fly() {
        System.out.println(this.flier+"\tflying");
    }

    @Override
    public void stop() {
        System.out.println(this.flier+"\tstop flying");
    }
}

主函数

public class FlyRelyDemo {
    public static void main(String[] args) {
        System.out.println("FlyRely START");
        String flier1 = "pilot1";
        String flier2 = "pilot2";
        String flier3 = "pilot3";
        Flier pilot1 = new Flier();
        pilot1.setName(flier1);
        pilot1.start();
        pilot1.fly();
        pilot1.flying();
        pilot1.stop();
        Flier pilot2 = new Flier();
        pilot2.setName(flier2);
        pilot2.start();
        pilot2.fly();
        pilot2.flying();
        pilot2.stop();
        Flier pilot3 = new Flier();
        pilot3.setName(flier3);
        pilot3.start();
        pilot3.fly();
        pilot3.flying();
        pilot3.stop();
        System.out.println("END");
    }
}

效果:

FlyRely START
========================
pilot1	start flying
pilot1	flying
pilot1	has flown 1km
pilot1	stop flying
========================
pilot2	start flying
pilot2	flying
pilot2	has flown 1km
pilot2	stop flying
========================
pilot3	start flying
pilot3	flying
pilot3	has flown 1km
pilot3	stop flying
END

本章总结

  • 面向对象具有唯一性、分类性、继承性以及多态性四个特征

  • 类是具有相同属性和方法的对象的抽象定义

  • 对象是类的一个实例,拥有类定义的属性和方法

  • Java中通过关键字new创建一个类的实例对象

  • 构造方法可用于在new对象时初始化对象属性

  • 方法的参数传递有值传递和引用传递两种

  • 类的方法和构造方法都可以重载定义

  • 访问控制符用来限制类内部的信息(属性和方法)被访问的范围

  • Java中的访问修饰符有:public、protected、缺省、private

  • 包可以使类的组织层次更鲜明

  • Java中使用package定义包,使用import导入包

  • 静态成员从属于类,可直接通过类名调用

  • 对象数组就是一个数组中的所有元素都是对象

  • 对象数组中的每个元素都需要实例化

你可能感兴趣的:(java)