Java学习笔记Day03 面向对象基础
第三章 面向对象基础
文章目录
3.1 面向对象思想
3.1.1 面向对象简介
- 面向对象的分析(OOA):确定需求或者业务的角度,按照面向对象的思想来分析业务。
- 面向对象的设计(OOD):一个中间过渡环节,其主要作用在OOA的基础上进一步规范化整理,从而建立所要操作的对象以及相互之间的联系,以便能够被OOP直接接受。
- 面向对象编程(OOP):在前两者的基础上,对数据模型进一步细化。OOP是根据真实的对象来构建应用程序模型。OOP是当今软件开发的主流设计范型,精通OOP是编写出高品质程序的关键。
对象
- 一切事物皆对象,人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可以看作对象;
- 一个对象可以通过使用数据值来描述自身所具有的状态。
- 对象还具有行为,通过行为可以改变对象的状态。对象将数据和行为封装于一体,实现了两者之间的紧密结合
类
- 类是具有相同或相似性质的对象的抽象。因此,对象的抽象是类;
- 类由“特征”和“行为”两部分组成:
- “特征”是对象状态的抽象,通常使用“变量”来描述类的特征,我们又称之“属性”;
- “行为”是对象操作的抽象,通常使用“方法”来描述类的行为。
 
类与对象之间的关系
类的关系
- 
类和类之间具有一定的结构关系。 
- 
类的关系主要有两种: - 
或关系 “或关系”也称为“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 | 
对象存储图
- 
一个对象,调用一个方法内存图 通过上图,我们可以理解,在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。变量p指向堆内存中 的空间,寻找方法信息,去执行该方法。 
 但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存 了,因为所有对象的方法信息都是一样的。
- 
两个对象,调用同一个方法 对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间。 
- 
一个引用,作为参数传递到方法中内存图 引用类型作为参数,传递的是地址值。 
成员变量和局部变量的区别
public class Car {
    String color;//成员变量
    public void driver() {
        int speed = 80;//局部变量
        System.out.println("时速"+speed);
    }
}
- 在类中的位置不同
- 成员变量:类中,方法外
- 局部变量:方法中或者方法声明上(形式参数)
 
- 作用范围不一样
- 成员变量:类中
- 局部变量:方法中
 
- 初始化值的不同
- 成员变量:有默认值
- 局部变量:没有默认值。必须先定义,赋值,最后使用
 
- 在内存中的位置不同
- 成员变量:堆内存
- 局部变量:栈内存
 
- 生命周期不同
- 成员变量:随着对象的创建而存在,随着对象的消失而消失
 
- 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
封装(补充)
封装概述
- 
概述:面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。 
- 
原则:将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。 
封装的步骤
- 使用 private 关键字来修饰成员变量。
- 对需要访问的成员变量,提供对应的一对 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;    
    } 
}
注意事项
- 如果你不提供构造方法,系统会给出无参数构造方法。
- 如果你提供了构造方法,系统将不再提供无参数构造方法。
- 构造方法是可以重载的,既可以定义参数,也可以不定义参数。
标准代码 --JavaBean
public class ClassName{   
    //成员变量   
    //构造方法   
    //无参构造方法【必须】  
    //有参构造方法【建议】  
    //成员方法     
    //getXxx()   
    //setXxx() 
}
3.3 继承
3.3.1 概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。
定义
- 继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接 访问父类中的非私有的属性和行为。
好处
- 提高代码的复用性。
- 类与类之间产生了关系,是多态的前提。
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.父类成员方法,表示调用父类的成员方法。
注意事项
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
- 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
方法的参数传递机制
方法可以带参数,通过参数可以给方法传递数据
- 
带参数 public void setName(String name) { this.name = name; }
- 
带多个参数 public int add(int a, int b) { return a+b; }
根据参数的使用场合,可以将参数分为:
- 
形参: “声明方法”时给方法定义的形式上的参数,此时形参没有具体的数值,形参前必须有数据类型,格式为: 方法名(数据类型 形参)
- 
实参:“调用方法”时程序给方法传递的实际数据,实参前面没有数据类型,格式为: 对象名.方法名(实参)
实参和形参之间传递数值的方式有两种:
- 
值传递(call by value) 值传递时,实参和形参在内存中占不同的空间,当实参的值传递给形参后,两者之间将互不影响 
- 
引用传递(call by reference) 引用传递是将实参的“地址”传递给形参,被调方法通过传递的地址获取其指向的内存空间,从而在原来内存空间直接进行操作。 
3.3.5 继承后的特点 --构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构 造方法中默认有一个 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修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。理解图解如下:
)
super和this的含义
- super :代表父类的存储空间标识(可以理解为父亲的引用)。
- this :代表当前对象的引用(谁调用就代表谁)。
super和this的用法
- 
访问成员 this.成员变量 ‐‐ 本类的 super.成员变量 ‐‐ 父类的 this.成员方法名() ‐‐ 本类的 super.成员方法名() ‐‐ 父类的
- 
访问构造方法 this(...) ‐‐ 本类的构造方法 super(...) ‐‐ 父类的构造方法
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
3.3.7 继承的特点
- 
Java只支持单继承,不支持多继承。 
- 
Java支持多层继承(继承体系)。 顶层父类是Object类。所有的类默认继承Object,作为父类。 
- 
子类和父类是一种相对的概念。 
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修饰,从而更好地将信息进行封装和隐藏
用setXXXX和getXXXX方法对类的属性进行存取,分别称为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文件,但是前面冠以外部类的类名 和$符号 。
匿名内部类
- 匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。
 开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
 我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
前提
匿名内部类必须继承一个父类或者实现一个父接口。
格式
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 修饰的内容:
- 是随着类的加载而加载的,且只加载一次。
- 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
- 它优先于对象存在,所以,可以被所有对象共享。
静态代码块
- 静态代码块:定义在成员位置,使用static修饰的代码块{ }。
- 位置:类中方法外。
- 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
 
格式:
public class ClassName {
    static {
        //执行语句
    }
}
作用:给类变量进行初始化赋值。
static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。
3.6.2 final关键字
概述
- final: 不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,不能被重新赋值。
 
使用方式
修饰类
final class 类名 {
    
}
修饰方法
修饰符 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 class FinalDemo2 { public static void main(String[] args) { // 创建 User 对象 final User u = new User(); // 创建 另一个 User对象 u = new User(); // 报错,指向了新的对象,地址值改变。 // 调用setName方法 u.setName("张三"); // 可以修改 } }
- 
成员变量 - 
显示初始化 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();   
    }   
} 
输出结果: 小猫在墙头走~~~
注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义。
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 关键字。
非抽象子类实现接口:
- 必须重写接口中所有抽象方法。
- 继承了接口的默认方法,即可以直接调用,也可以重写。
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();     
    } 
} 
输出结果: 
    吃东西 
    晚上睡 
默认方法的使用
可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
- 
继承默认方法,代码如下: 定义接口: 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(); } } 输出结果: 天上飞
- 
重写默认方法,代码如下: 定义接口: 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 类名[长度];
对象数组在内存中的存储
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导入包 
- 
静态成员从属于类,可直接通过类名调用 
- 
对象数组就是一个数组中的所有元素都是对象 
- 
对象数组中的每个元素都需要实例化 
 
                
            
         
 
         浙公网安备 33010602011771号
浙公网安备 33010602011771号