JavaSE 零基础入门

JavaSE零基础入门

2、 什么是软件工程师?

是一种职位的名称。
通常是通过计算机的“某种编程语言”完成软件的开发。

2.1 什么是软件呢?

眼前的笔记本电脑就是一台计算机。

计算机包括两部分:

  • 硬件:鼠标、键盘、显示器、主机箱内部的CPU、内存条……

    ​ 注意:计算机只有硬件是无法工作的,需要软件驱动硬件才能工作。

  • 软件:

    • 系统包括:系统软件和应用软件
    • 系统软件:

    ​ 直接和硬件交互的软件,例如win7、win8、win10……

    ​ 应用软件:

    ​ 应用软件通常运行在系统软件中,例如:QQ运行在Wind7中。

    ​ QQ就是应用软件。

    ​ win7就是操作系统软件。

我们通常所说的软件开发一般都是指使用计算机编程语言完成“应用软件”的开发。

软件开发协议计算编程语言,计算机编程语言很多,例如:c语言、c++、Java、.net、C#、PHP……
通过协议java系列计算完成应用软件开发。

Java软件工程师:通过Java编程语言完成应用软件的开发,我们不是弄硬件,我们是弄软件开发的。

学习之前先安装一个好一点的文本编辑器:我们这里使用安装的是EditPlus3

  • 安装EditPlus之后取消文件自动备份。
  • 设置字体
    ……

3、常见DOC命令

  • cd命令

    • cd表示:change directory【改变目录】
    • cd命令怎么用?
      cd 目录的路径
    • 但是路径包含绝对路径和相对路径
    • 绝对路径:表示该路径从某个磁盘的盘符下作为出发点的路径。
    • 相对路径:表示该路径从当前所在的路径下作为出发点的路径。
  • cd \ 直接回到根目录

  • 切换盘符:

    c:回车
    d:回车
    g:回车

4、 计算机发展史

4.1 计算机语言发展史

什么是计算机“编程”语言?即-交流的规则

4.2 计算机语言发展史:
  • 第一代语言:机器语言
    主要辨析二进制码,直接编写10011这样的二进制。
    以打孔机为代表。

  • 第二代语言:低级语言

    ​ 主要以汇编语言为代表

    ​ 在低级语言当中已经引入了一些英语单词,例如变量赋值。

  • 第三代语言:高级语言

    ​ 几乎和人类的语言完全相同。

    ​ 如Java、C++……

4.3 Java语言发展史
  • Java语言诞生于1995年。

    其实在1995年之前SUN公司(太阳微电子公司:该公司目前给oracle(甲骨文:做数据库的)收购了),为了占领只能电子消费产品市场,派James Gosling领导团队开发了一个oak(橡树)语言。

  • 1996年:JDK1.0诞生

  • 什么是JDK?

    java开发工具包

    做java开发必须安装的一个工具包,该工具包需要从官网下载。

    目前SUN被oracle收购了,所有下载需要去oracle下载。http://www.oracle.com

    目前jdk最高版本Java8/JDK1.8/JDK8

  • java包括三大块

    JavaSE(java标准版)

    JavaEE(Java企业版)

    JavaME(Java微型版)
    其中JavaSE是基础,JavaEE是以后的主攻方向。

  • Java语言特性【开源、免费、纯面向对象、跨平台】

    • 简单性:
      相对而言的,例如java中不在支持多继承,C++是支持多继承的,多继承比较复杂。
      C++中有指针,Java中屏蔽了指针的概念。
      所以相对来说Java是简单的。
      Java语言底层是C++实现的,不是C语言。
    • 面向对象
      Java是纯面向对象的。更符合人的思维模式。更容易理解。
    • 可移植性
      什么是可移植性?
      Java程序可以做到一次编译,到处运行。
      也就是说java程序可以在Windows操作系统上运行,
      不做任何修改,同样的Java程序也能在Linux操作系统上运行,这个被称为java程序的可移植性,或者叫做跨平台。

​ Windows操作系统内核与Linux系统的内核肯定不相同,他们这两个操作系统执行指令的方式也是不一样的。

​ 结论:显然java程序不能直接和操作系统打交道,因为Java程序只有一份。操作系统执行原理都不同。

​ SUN的团队很聪明,他们想了一个办法,他们让java程序运行在一台虚拟的计算机当中,这个虚拟的计算机叫做java虚拟机,简称JVM。Java虚拟机在和底层的操作系统打交道。

JVM一般是在安装jdk的时候就自动安装了。

  • 多线程

  • 健壮性
    和自动垃圾回收机制有关,自动垃圾回收机制简称为GC机制。
    Java语言运行工程中产生的垃圾是自动回收的,不需要程序关心。

  • 安全性

……

5、Java的加载与执行

image-20211012212325503

5.1 java程序的运行的两个阶段
  • 编译阶段
  • 运行阶段
5.2 编译阶段

编译阶段主要任务是检查Java源程序是否符合Java语法,符合Java语法规则则生成长长的字节码文件(xxx.class),不符合则不会生成。

字节码文件中不是纯粹的二进制,这种文件无法在操作系统当中直接运行。

编译过程:

1. 程序员需要在任意位置新建一个.java扩展名的文件,这个文件成为Java源程序文件,源文件当中编写的是Java源代码。
2. Java程序员需要使用jdk当中自带的javac.exe命令进行java程序的编译。使用方法为:javac java源文件的路径。
3. 一个java源文件可以编译生成多个.class文件。
  1. 字节码文件/class文件是最终要执行的文件,所以说class文件生成之后,java源文件删除并不会影响java程序的执行。
  2. 编译结束后,可以将class文件拷贝到其他操作系统当中运行。即-跨平台
5.3 运行阶段

jdk安装之后,除了自带javac.exe之外,还有另一个工具,叫做java.exe,我们主要是使用它来负责运行阶段。

用法:java 字节码文件/class文件(注意:不要跟后缀名.class,也不能是路径,否则会报错),如:java a 能够正常运行。java a.class 报错

运行阶段的过程:

  1. 打开DOS命令窗口
  2. 输入:java A
  3. java.exe命令会启动Java虚拟机(JVM),JVM会启动类加载器ClassLoader
  4. ClassLoader会去硬盘上搜索A.class文件,找到该文件则将该字节码文件装载到JVM当中。
  5. JVM将A.class字节码文件解释成二进制1000111001这样的数据。
  6. 然后操作系统执行二进制底层硬件平台进行交互。

6、第一个Java程序

jdk下载安装并配置环境变量:https://www.cnblogs.com/miaomiaoka/p/11198344.html

注意以下的名词:

jdk - Java开发工具包,用来开发的时候安装,一般会自带jre,有单独软件安装

jre - Java运行时环境,用来部署项目的时候安装,jre包括jvm,有单独软件安装

jvm - Java虚拟机,没有单独软件安装

三者关系:

image-20211012212418516

JDK目录的介绍:

JDK/bin:该目录下存放了很多命令,例如:javac.exe和java.exe

javac.exe 负责编译

java.exe负责运行

6.1 HelloWorld程序编写
  1. 编写源代码:

    public class HelloWorld{
    	public static void main(String[] args) {
            System.out.println("hello world");
        }
    }
    
  2. 编译源文件

    image-20211012212437152

  3. 运行字节码文件

    image-20211012212448458

Java源代码中的注释不会被编译到字节码文件中。

注:类体中不允许直接编写java语句【除声明变量之外】

如:一下语句为错误的定义方式:

public class Test{
    System.out.println("hello world!");  // 错误定义方式,语句必须放在函数体内。
    public static void main(String[] args) {
        System.out.println("hello world"); // 正确定义方法
    }
}
6.2 public class 和 class的区别
class A{
	public static void main(String[] arg){
		System.out.println("A");
	}
}

class B{
	public static void main(String[] arg){
		System.out.println("B");
	}
}

class C{
	public static void main(String[] arg){
		System.out.println("C");
	}
}

public class ClazzTest{
	
	public static void main(String[] arg){
		System.out.println("ClazzTest");
	}
}


  • 一个java源文件当中可以定义多个class。
  • 一个java源文件中不一定有public的class。
  • 一个class会对应生成一个xxx.class字节码文件。
  • 一个java源文件当中定义公开类,public的class只能有一个,并且该类名称必须和java源文件名称一致。
  • 每一个class当中都可以编写main方法,都可以设定程序的入口,想执行B.class中的main方法:java B
  • 注意:命令窗口中执行java Hello时,那么要求Hello.class当中必须有主方法。没有会出现运行阶段的错误。

image-20211012212502458

7、 JVM内存结构

image-20211012212514139

1. 一种非常经典的数据结构

栈数据结构:stack

2. 什么是数据结构?

数据结构通常是:存储数据的容器。而该容器可能存在不同的结构,数据结构和java语言实际上是没有关系的,数据结构是一门独立的学科,在大学计算机专业中,数据结构是必修的一门课程。

3. 常见的数据结构:

数组、链表、图、二叉树、栈、队列……,java语言把常用的数据结构都已经写好了,对于java程序员来说我们直接使用就好了。所以当前阶段是不需要精通数据结构的。如果期望有更高的造诣,建议数据结构还是需要精通的。

4. 和数据结构通常出现在一块的是:算法

算法:排序算法、查找算法、……算法

image-20211012212528575

7.1 内存变化过程:
/*
 局部变量:只在方法体重有效,方法结束,局部变量的内存就释放了。
 JVM三块主要的内存:栈内存、堆内存、方法区内存
 方法区中最先有数据:方法区中方代码片段,存放class字节码。
 堆内存:暂时不涉及
 对内存:方法调用的时候,方法需要的内存空间在栈中分配。方法不调用时不会在栈中分配空间的。

 方法只有在调用的时候才会在栈中分配空间,并且调用时就是压栈。
 方法调用结束后该方法所需要的空间就会被释放,此时发生弹栈动作。
*/
public class MethodTest08{
	public static void main(String[] args){
		System.out.println("main begin!");
		int x = 100;
		m1(x);
		System.out.println("main end!");
	}
	public static void m1(int i){
		System.out.println("m1 begin!");
		m2(i);
		System.out.println("m1 end!");
	}
	public static void m2(int i){
		System.out.println("m2 begin!");
		m3(i);
		System.out.println("m2 end!");
	}
	public static void m3(int i){
		System.out.println("m3 begin!");
		System.out.println(i);
		System.out.println("m3 end!");
	}
}

image-20211012212545999

8、面向对象

8.1 面向对象和面向过程的区别
  1. 从语言的角度出发

    对于c语言来说,是完全面向过程的。

    对于C++来说,是一半面向过程,一半面向对象的。

    对于java来说,是完全面向对象的。

  2. 什么是面向过程的开发方式?

    主要特点有:注重步骤,注重的是实现这个功能的步骤,第一步是干什么,第二部是干什么,……另外面向过程也注重实现功能的因果关系。

    缺点:耦合度太高,导致扩展性差。

    优点:对于小型项目(功能),采用面向对象过程的方式进行开发,效率较高。不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接写代码,编写因果关系。

  3. 什么是面向对象的开发方式?

    采用面向对象的方式进行开发,更符合人类的思维方式。人类就是以”对象”的方式去认识世界的,所有面向对象更容易让我们接受。

8.2 面向对象的三个术语
  • OOA:面向对象分析
  • OOD:面向对象设计
  • OOP:面向对象编程
8.3 面向对象三大特性

任何一个面向对象的编程语言都包括这三个特征。

  • 封装
  • 继承
  • 多态
8.4 类的定义

问题:为什么属性是以变量的形式存在?

答案:因为属性对应的是“数据”,数据在程序只能放在变量中。

变量的分类:变量根据出现的位置进行划分。

方法体当中声明的变量:局部变量

方法体外声明的变量:成员变量(也称为”属性“)。

数据类型包括两种:

基本数据类型:byte、short、int、long、float、double、boolean、char

引用数据类型:String、class……

9、static关键字

9.1 static
  1. static翻译为“静态”
  2. 所有的static关键字修饰的都是类相关的,类级别的。
  3. 所有的static修饰的类方法,都是采用“类名."的方式访问。
  4. static修饰的变量:静态变量
  5. static修饰的方法:静态方法
  6. 成员变量分为:实例变量和静态变量(带static关键字)
public class this_and_static {
    // 以下实例的,都是对象相关的,访问时采用“引用.”的方式访问。需要先new对象。
    // 实例相关的,必须先有对象,才能访问,可以能会出现空指针异常。
    // 成员变量中的实例变量
    int i;

    // 实例方法
    public void m2(){}

    // 以下静态的,都是类相关的,访问时采用“类名.”的方式访问,不需要new对象。
    // 不需要对象的参与即可访问。没有空指针异常的发生。
    // 成员变量中的静态变量
    static int k;

    // 静态方法
    public static void m1(){}
}
  • 变量什么时候声明为实例的,什么时候声明为静态的?

    如果这个类型的所有对象的某个属性值都是一样的,不建议定义为实例变量,浪费内存空间,建议定义为类级别特征,即静态变量,在方法区中只保留一份,节省内存开销。

    public class StaticTest02 {
        public static void main(String[] args) {
            Chinese c1 = new Chinese("522258199612031819", "张三");
            System.out.println("idCard: "+c1.idCard);
            System.out.println("name: "+c1.name);
            System.out.println("country: "+Chinese.country);  // country是静态变量,直接使用“类名.属性名”的方式访问
            Chinese c2 = new Chinese("622258199012035689", "李四");
            System.out.println("idCard: "+c2.idCard);
            System.out.println("name: "+c2.name);
            System.out.println("country: "+Chinese.country);
        }
    }
    
    // 定义一个类:中国人
    class Chinese{
        // 身份证号
        // 每一个人的身份证号不同,所有身份证号应该是实例变量
        String idCard;
    
        // 姓名
        // 每一个人的名字可能都是不同的,也定义为实例变量
        String name;
    
        // 国籍
        // 只要你是中国人,每个人的国籍都是一样,定义为静态变量
        static String country = "Chinese";
    
        public Chinese(){}
        public Chinese(String idCard, String name){
            this.idCard = idCard;
            this.name = name;
        }
    
    }
    
  • 实例变量内存分布图

    image-20211012212558981

  • 静态变量内存分布图

    image-20211012212610145

9.2 空指针异常
package com.zh0u.static关键字;

public class StaticTest03 {
    public static void main(String[] args) {
        // 通过“类名.静态变量名”的方式访问静态变量
        System.out.println(ChinaMobile.maker); // China Mobile
        // 创建对象
        ChinaMobile mobile = new ChinaMobile("15385895650");
        // 通过“引用.静态变量名”也可以正常访问,实际上是编译器自动将前面的“引用”转换为“类名”
        /* 总结:
            实例的变量:一定需要使用“引用.”来访问
            静态的变量:建议使用“类名.”来访问,但使用“引用.”也可以访问,但是不建议,容易使人困惑。
         */
        System.out.println(mobile.cardNo); // 15385895650
        System.out.println(mobile.maker); // China Mobile

        // 将mobile引用变成null引用
        mobile = null;
        // 分析这里会不会出现空指针异常?不会出现空指针异常,因为静态变量不需要对象的存在。
        // 实际上以下代码在运行的时候,还是:System.out.println(ChinaMobile.maker);
        System.out.println(mobile.maker);

        // 以下这个会出现空指针异常,因为cardNo是实例变量
        // 结论:空指针异常只有在“空引用”访问“实例”相关的时候,才会出现空指针异常。
        // System.out.println(mobile.cardNo);

    }
}

class ChinaMobile{
    // 卡号码,实例变量
    String cardNo;
    // 制造商,静态变量
    static String maker = "China Mobile";

    public ChinaMobile(){}
    public ChinaMobile(String cardNo){
        this.cardNo = cardNo;
    }
}

9.3 静态方法与实例方法
package com.zh0u.static关键字;

public class StaticTest04 {
    public static void main(String[] args) {
        // 静态方法既可以使用“类名.”的方式来访问,也可以使用“引用.”的方式来访问,但是不建议使用后者。
        // 如果是静态方法,也可以直接写方法名即可。-- doSome();
        StaticTest04.doSome(); 
        // 实例方法只能通过“引用.”的方式来访问,而且当引用的对象为null时,此时引用会出现空指针异常。
        new StaticTest04().doOther();
    }

    public static void doSome(){
        System.out.println("静态方法doSome()执行了!");
    }

    public void doOther(){
        System.out.println("实例方法doOther()执行了!");
    }
}

对于方法来说,什么时候为实例方法?什么时候定义为静态方法?

参考标准:当这个方法体当中,直接访问了实例变量,这个方法一定是实例方法。

在今后的开发中,大部分情况下,如果是工具类的话,工具类当中的方法一般都是静态的(因为不需要new对象,直接采用类名调用,极其方便)。

9.4 静态代码块

使用static关键字可以定义“静态代码块”,语法如下:

static{
    java语句1;
    java语句2;
    ……
}

关键点:static定义的静态代码块在“类加载的时候执行,在main方法之前执行,并且只执行一次”。

public class StaticTest06 {
    static {
        System.out.println("a");
    }
    static {
        System.out.println("b");
    }
    public static void main(String[] args) {
        System.out.println("d");
    }
    static {
        System.out.println("c");
    }
}

以上程序运行结果:

a

b

c

d

静态代码块的作用:

第一:静态代码块不是很常用(不是每一个类都需要的)。

第二:可以放在具体业务中,例如:可以记录类加载的的时间。

代码执行顺序要求:

  1. 对于一个方法来说,方法体中代码执行是有顺序,遵循自上而下的顺序。
  2. 静态代码块1和静态代码块2是有先后顺序的。
  3. 静态代码块和静态变量是有先后顺序的。

10、 this关键字

this是一个关键字,全部小写。

10.1 this在内存中的存在形式

image-20211012212620778

一个对象一个this,this是一个变量(引用)。this保存当前对象的内存地址,指向的是对象本身。所以也可以说this就是“当前对象”。

this存在堆内存当中对象的内部。它只能使用在实例方法中而不能出现在静态方法中。谁调用这个实例方法,this就是谁,所以this代表的“当前对象”。

package com.zh0u.this关键字;

public class ThisTest01 {
    public static void main(String[] args) {
        Customer c1 = new Customer("张三");
        Customer c2 = new Customer("李四");
    }
}

class Customer{
    // 属性
    // 实例变量
    String name;

    // 构造方法
    public Customer(){}
    public Customer(String name){this.name = name;}

    // 顾客购物的方法
    // 实例方法
    public void shopping(){
        // c1调用shopping(),this是c1
        // c2调用shopping(),this是c2
        // 这里的this.name中,this可以省略,省略了默认访问“当前对象”的name。“this.”在大部分情况下都是可以省略的。
        System.out.println(this.name +"正在购物!");
    }

    public static void doSome(){
        // this代表的是当前对象,而静态方法的调用不需要对象。这里就会产生矛盾。
        // System.out.println(this);  // 错误:java: 无法从静态上下文中引用非静态 变量 this
    }
}

class Student{
    // 实例变量的访问,必须先new对象,通过“引用.”来访问。
    String name;

    // 静态方法
    public static void m1(){
        // 以下的两个输出语句都会报错
        // System.out.println(name);

        // this代表的是当前对象
        // System.out.println(this.name);
    }

}

10.2 this不能省略的情况

this可以使用在实例方法中,不能使用在静态方法中。this关键字大部分情况下可以省略,但有些时候却不能省略。

在实例方法或构造方法中,为了区分局部变量和实例变量,this是不能省略的,这是因为Java中变量存在一个“就近原则”问题。

class Robots{
    // 编号
    private int no;

    // 姓名
    private String name;

    // 构造方法
    public Robots(){}
    // 以下这些凡是有this的都是不能够省略的,因为Java变量赋值存在一个就近原则问题。
    public Robots(int no, String name){
        // 如果这里的this省略了,代表变成了:no = no;由于存在就近原则问题,这里的两个no都是局部变量,这样一来就没法给“属性变量no”赋值了。
        // 同样name也是一样的。
        this.no = no;
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }
}
10.3 this()

通过当前的构造方法去调用本类的另一个构造方法,可以使用一下的语法格式:

this(实际参数列表);

使用以上的方式可以提高代码的复用,但是需要注意的是,构造方法1和构造方法2都是在同一个类当中,this()方法在构造器中只能出现一次且this()方法之前不能有其它的语句。

class DefinedDate{
    // 年
    private int year;
    // 月
    private int month;
    // 日
    private int day;

    // 构造函数
    public DefinedDate(){
        // 这里的三行代码和有参构造函数中的代码在一定程度上是重复的,为了提高代码的复用,可以使用this()方法来解决。
        // this.year = 1970;
        // this.month = 1;
        // this.day = 1;

        // this()方法解决以上代码重复问题
        this(1970, 1, 1);
    }
    public DefinedDate(int year, int month, int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

11、 继承extends

继承的本质: 子类继承父类后,是将父类继承过来的方法归为自己所有。子类调用父类的方法时,实际上是调用继承过来的方法。

继承的时候应该遵循 “is a” 的规范。如Cat is a Animal。

11.1 继承的作用

基本作用:子类继承父类,代码可以得到复用。

主要作用:因为有了继承关系,才有了后面的方法覆盖和多态机制。

package com.zh0u.extends关键字;

public class ExtendsTest01 {
    public static void main(String[] args) {
        Account account = new Account("65245892144456266", "400000");
        System.out.println("账号: "+account.getActNo()+", 余额: "+account.getBalance());
        CreditAccount creditAccount = new CreditAccount("65245892144456266","600000",0.9999);

        System.out.println("账号: "+creditAccount.getActNo()+", 余额: "+creditAccount.getBalance()+", 信誉度: "+creditAccount.getCredit());
    }
}

class Account{
    // 账号
    private String actNo;
    // 余额
    private String balance;

    // 构造方法
    public Account(){}
    public Account(String actNo, String balance){
        this.actNo = actNo;
        this.balance = balance;
    }

    public String getActNo() {
        return actNo;
    }

    public void setActNo(String actNo) {
        this.actNo = actNo;
    }

    public String getBalance() {
        return balance;
    }

    public void setBalance(String balance) {
        this.balance = balance;
    }
}

class CreditAccount extends Account{
    // 信誉度
    private double credit;

    public CreditAccount(){}
    public CreditAccount(String actNo, String balance, double credit){
        this.setActNo(actNo);
        this.setBalance(balance);
        this.credit = credit;
    }

    public double getCredit() {
        return credit;
    }

    public void setCredit(double credit) {
        this.credit = credit;
    }
}

11.2 继承的相关特性
  1. B类继承A类,则A类成为超类(superclass)、父类、基类。B类称为子类(subclass)、派生类、扩展类。

  2. Java中的继承制仅支持单继承,不支持多继承,C++中支持多继承。

  3. 虽然Java总不支持多继承,但有时候会产生间接继承的效果。

    class A{}
    class B extends A{}
    class C extends B{} // 这种方法在某种程度上来说也是多继承
    
  4. Java中规定,子类继承父类,除构造方法不能继承,剩余的都可以继承。但是私有(private)的属性无法在子类中直接访问。可以通过间接方式(setter与getter)访问。

  5. 如果Java中的类没有显示的继承任何类,但会默认继承Object类,Object类时java语言提供的根类,也就是说,一个对象与生俱来就有Object类型总所有的特性。

  6. 继承也存在一些缺点,如父类中发生改变之后便会影响到子类,即耦合度较高。

11.3 Object类

要想成为牛人,先看看牛人写的源代码。多模仿,后超越。

注意: 当源码当当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字,表示底层调用C++写的dll(动态链接库)程序。

如:

private static native void registerNatives();
  • Object的toString()方法

当System.out.println()打印对象而没有指定使用“toString()”方法的时候,println()会自动的调用toString()并将其结果作为输出结果。

public class ObjectTest02 {
    public static void main(String[] args) {
        // 分析下面的第一行代码能不能正常执行?
        // 答案是不能正常执行的,我们通过分析源代码可以知道toString()方法在Object类中不是静态方法,只有静态的才使用“类名.”的方式进行访问。
        // 非静态方法只能使用“引用(也称为对象).”的形式访问
        // ObjectTest02.toString();

        // 下面使用“引用.”的方式进行访问
        ObjectTest02 test02 = new ObjectTest02();
        // 在以下的代码中输出为:com.zh0u.Object类.ObjectTest02@6d6f6e28。在这个返回结果中,com.zh0u.Object类是包名,ObjectTest02为类名。
        // 而6d6f6e28是该对象在堆内存中的地址经过“哈希算法”返回的十六进制结果。
        System.out.println(test02.toString());  // com.zh0u.Object类.ObjectTest02@6d6f6e28

        System.out.println(test02);  // com.zh0u.Object类.ObjectTest02@6d6f6e28

    }

}

12、方法覆盖与多态机制

12.1 方法覆盖

子类继承父类,有一些“行为”可能不需要改进,有一些“行为”可能面临着必须改进,因为父类中继承过来的方法已经无法满足子类的业务需求。

什么时候我们会考虑使用“方法覆盖”呢?

子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。方法覆盖又叫做:方法重写,Override,Overrite。

需要注意的是应该要将“方法覆盖(Override/Overrite)”与“方法重载(Overload)”正确的区分开来。

方法覆盖满足一下条件:

  1. 两个类必须要有继承关系
  2. 重写之后的方法和之前的方法具有:相同的返回值类型、相同的方法名、相同的形式参数列表。
  3. 访问权限只能更高,不能更低。权限级别:public > protected > private
  4. 重写之后的方法不能比之前的方法抛出更多的异常,可以更少。

那么什么是时候使用方法重载?

当在一个类当中,如果功能相似的话,建议将名字定义一样,这样代码美观,并且方便编程,满足一下条件之后便能构成方法重载:

  1. 在同一个类当中
  2. 方法名相同
  3. 参数列表不同(个数、顺序、类型)
  4. 返回值类型(了解,没有实际意义)
    1. 返回值是基本数据类型的时候,必须一致。
    2. 返回值是引用数据类型的时候,如果是向下(变小)转型,Java是允许的。如果是向上(变大)转型,Java是不允许的。

重要结论:当子类对父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。

package com.zh0u.Overwride;

public class OverwriteTest01 {
    public static void main(String[] args) {
        new Bird().move();  // 输出: 鸟儿在飞翔!!!
    }
}

class Animal{
    public void move(){
        System.out.println("动物在移动!!!");
    }
}

class Bird extends Animal{
    // 方法覆盖
    public void move(){
        System.out.println("鸟儿在飞翔!!!");
    }
}

注意点:

  1. 方法覆盖只是针对于方法,和属性无关。

  2. 私有方法无法覆盖。(可以理解为子类无法继承,所以没法覆盖)

  3. 构造方法不能被继承,所以构造方法也不能被覆盖。

  4. 方法覆盖知识针对于实例方法,静态方法覆盖没有意义。

    package com.zh0u.多态.静态方法不存在Overrite;
    
    /*
     * 1. 方法覆盖需要和多态进制联合起来使用才有意义。
     * Animal a = new Cat();
     * a.move();
     * 要的是什么效果?
     *      编译的时候move()方法是Animal的。运行的时候自动调用到子类重写move()的方法上去。
     *
     * 假设没有多态机制,只有方法覆盖机制,这样是没有意义的。
     *      没有多态机制,方法覆盖可有可无。方法覆盖和多态不能分开。
     *
     * 静态方法存在方法覆盖吗?
     *      多态自然和对象有关,而静态方法的执行不需要对象,所有,一般情况下,我们会说静态方法“不存在”方法覆盖。
     *
     */
    
    public class OverwriteTest {
        public static void main(String[] args) {
            // 静态方法可以使用“引用.”来调用嘛?答案是:可以的。
            // 虽然使用“引用.”来调用,但是实际运行的时候还是:Animal.doSome()
            Animal a = new Cat();
            // 静态方法和对象无关
            // 虽然使用“引用.”来调用。但是实际运行的时候还是:Animal.doSome()
            a.doSome(); // Animal的doSome()方法执行!!!
    
            Animal.doSome();  // Animal的doSome()方法执行!!!
            Cat.doSome(); // Cat的doSome()方法执行!!!
        }
    }
    
    class Animal{
        // 父类的静态方法
        public static void doSome(){
            System.out.println("Animal的doSome()方法执行!!!");
        }
    }
    
    class Cat extends Animal{
        // 尝试在子类当中对父类的静态方法进行重写
        public static void doSome(){
            System.out.println("Cat的doSome()方法执行!!!");
        }
    }
    
12.2 多态

父类型引用指向子类对象。分析程序的时候包括编译阶段和运行阶段。

编译阶段:绑定父类型的方法。

运行阶段:动态绑定子类型对象的方法

这个过程被称为“多态”。

12.2.1 多态的基础语法
  1. 两个重要的概念:

    第一:向上转型,子 ----> 父,即父类型指向子对象。

    第二:向下转型,父 ----> 子,需要加强制类型转换符。

  2. 注意点:

    Java中允许向上转型,也允许向下转型。无论是向上转型还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。

  3. 什么时候必须进行向下转型?

    当父类引用执行子类中“特有的方法”时,这个时候必须使用向下转型。

Animal

package com.zh0u.多态;

public class Animal {
    public void move(){
        System.out.println("动物在移动!!!");
    }
}

Bird

package com.zh0u.多态;

public class Bird extends Animal{
    public void move(){
        System.out.println("鸟儿在飞翔!!!");
    }
}

Cat

package com.zh0u.多态;

public class Cat extends Animal{
    public void move(){
        System.out.println("Cat走猫步!!!");
    }

    // 猫抓老鼠,应该是猫特有的属性
    public void catchMouse(){
        System.out.println("猫正在抓老鼠!!!");
    }
}

Test01

package com.zh0u.多态;

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

        // 鸟
        Bird bird = new Bird();
        bird.move();

        // 猫
        Cat cat = new Cat();
        cat.move();

        // 多态,父引用指向子对象
        Animal a1 = new Bird();
        Animal a2 = new Cat();

        /*
            什么是多态?
                多种形态,多种状态。
            分析a2.move();
                Java程序分为编译阶段和运行阶段。
                先来编译阶段:
                    对于编译器来说,编译器只知道a2的类型是Animal,所以编译器再检查语法的时候。
                    会去Animal.class字节码文件中找move()方法,找到了,绑定move()方法,编译通过,静态绑定成功。编译阶段属于“静态绑定”。
                在来分析运行阶段:
                    运行阶段的时候,实际上在堆内存中创建的Java对象是Cat对象,所以move的时候,真正参与move的对象是猫对象。所以运行阶段
                    对动态执行Cat对象的move()方法,这个过程属于运行阶段。(运行阶段绑定输入动态绑定)
                多态表示多种形态:
                    编译的时候是一种形态。
                    运行的时候是另一种形态。
                    即 “多态”
         */
        a1.move();  //  鸟儿在飞翔!!!
        a2.move();  //  Cat走猫步!!!

        // =================================
        Animal a3 = new Cat();
        /*
            分析下面的语句是否能编译和运行?
            分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
            只有编译通过的代码才能运行,以下代码编译是不能通过的。因为编译器只知道a3的类型是Animal,去Animal.class文件中找catchMouse()
        方法,结果没有找到,所以静态绑定失败,编译报错。无法运行。
        */

        // a3.catchMouse();

        /*
            假设代码写到了这里,我非要让a3调用catchMouse()方法咋办?
            这个时候就必须使用“向下转型”了,即“强制类型转换”,即下面的代码。
            分析以下代码:为啥能够正常运行?
            因为a3是Animal类型,转成Cat,Animal和Cat之间存在继承关系,所有没有报错!!!
         */
        Cat c4 = (Cat) a3;  // 这里也可以称为“向下转型”
        c4.catchMouse(); // 猫正在抓老鼠!!!
    }
}

  1. 在进行强制类型转换时存在着一定的风险,例如:将一个类强制转换为另外一个类的类型的时候,如果这两个类没有继承关系,则会抛出“ java.lang.ClassCastException”异常。

    Animal b = new Bird();
    Cat cat1 = (Cat) b;  // 没有继承关系的两个类之间是不能进行强制类型的转换的。
    cat1.catchMouse();  
    
12.2.2 instanceof

为了解决在强制类型转化是的风险,可以使用instanceof来解决这个问题。

在对类型进行向下转型(强制类型转换)时一定要使用instanceof运算符进行判断。这样可以很好的避免:ClassCastException

instanceof - 运行阶段动态判断

  1. instanceof可以在运行阶段动态判断引用指向的对象的类型。

  2. instanceof 语法: (引用 instanceof 类型)

  3. instanceof运算符的运算结果只能是true/false。

  4. 假设c是一个引用,c变量保存了内存地址指向对中的对象。

    如果(c instanceof Cat) 为true,则表示:c引用指向的堆内存中的java对象是一个Cat,反之则不是。

则存在以下代码:

Animal b = new Bird();
if (b instanceof Cat){  // 通过返回值来判断是否能强制类型转换
    Cat cat1 = (Cat) b;
    cat1.catchMouse();
}
12.2.3 多态在开发中的作用(理解)

尝试完成以下练习:

编写程序模拟“主人”喂养“宠物”的场景:

提示1:

​ 主人类:Master

​ 宠物类:Pet

​ 宠物子类:Dog、Cat、YingWu

提示2:

​ 主人应该有喂养的方法:feed()

​ 宠物应该有吃的方法:eat()

​ 只要主人喂养宠物,宠物就吃。

要求:主人类中提供一个喂养方法feed(),要求达到可以喂养各种类型的宠物。

编写测试程序:

​ 创建主人对象

​ 创建各种宠物对象

​ 调用主人的喂养方法feed(),喂养不同的宠物,观察执行结果。

通过该案例,理解多态在开发中的作用。

重要提示:feed()方法是否需要一个参数,参数选什么类型!!!

Master:

package com.zh0u.多态在开发中的作用;

// 主人类
public class Master {
    /*
    // 假设主人起初的时候知识喜欢养宠物狗狗
    // 喂养宠物狗狗
    public void feed(Dog dog){
        dog.eat();
    }

    // 由于心得需求产生,导致我们“不得不”去修改Master这个类的代码
    public void feed(Cat cat){
        cat.eat();
    }
    */
    // 能不能让Master主人这个类以后不再修改了,即使主人有喜欢养其他宠物了,Master也不需要修改。
    // 这个时候就需要使用“多态机制”。
    // 最好不要写具体的宠物类型,这样会影响程序的扩展性。可以编写一个更为抽象的父类,让这些具体的类去继承这个抽象的父类,实现多态机制。
    public void feed(Pet pet){
        // 编译的时候,编译器发现pet是Pet类,回去Pet类中找eat()方法,结果找到了,编译通过。
        // 运行的时候,底层实际的对象是什么,就会自动调用到该实际对象对应的eat()方法上去。
        // 这就是多态的使用。
        pet.eat();
    }

    /*
        注意这里的分析:
            主人起初的时候只喜欢养宠物狗狗,随着时间的推移,主人有喜欢上养“猫咪”。
            在实际的开发中这就是表示客户产生了新的需求,作为软件开发人员来说,必须满足客户的需求。
            我们怎么去满足客户的需求呢?
                在不使用多态进制的前提下,目前我们只能在Master类中添加一个新的方法。

        思考:软件在扩展新需求过程当中,修改Master这个类有什么问题?
            一定要记住:软件在扩展过程当中,修改的越少越好,修改的越多,你系统当前的稳定性就越差,未知的风险就越多。

            其实这里涉及到一个软件的开发原则:
                软件开发有七大原则,其中有一条最基本的原则:OCP(开闭原则)

            什么是开闭原则?
                对扩展开放(你可以额外添加,没问题),对修改关闭(尽量很少的修改现有程序)。在软件的扩展过程当中,修改的越少越好。

        高手开发项目不是仅仅为了实现客户的需求,还需要考虑软件的扩展性。

        面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。因为面向具体编程会让软件的扩展力很差。
     */
}

Pet

package com.zh0u.多态在开发中的作用;

public class Pet {
    public void eat(){
        System.out.println("宠物能吃东西!");
    }
}

Dog

package com.zh0u.多态在开发中的作用;

public class Dog extends Pet{
    public void eat(){
        System.out.println("宠物狗狗喜欢啃骨头,啃的很香!!!");
    }
}

Cat

package com.zh0u.多态在开发中的作用;

public class Cat extends Pet{
    public void eat(){
        System.out.println("宠物猫猫喜欢吃鱼,吃的很香!!!");
    }
}

Test

package com.zh0u.多态在开发中的作用;

public class Test {
    public static void main(String[] args) {
        // 创建一个主人
        Master master = new Master();
        // 创建一个主人喜欢的宠物狗狗
        Dog dog = new Dog();
        // 再创建一个主人喜欢的宠物猫猫
        Cat cat = new Cat();
        // 主人喂
        master.feed(dog);
        master.feed(cat);
    }
}

多态在开发中的作用:降低程序的耦合度,提高程序的扩展力。

如以下代码:

public class Master{
    public void feed(Dog d){}
    public void feed(Cat c){}
}

以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。

public class Master{
    public void feed(Pet pet){}
}

以上的代表中表示:Master和Dog以及Cat的关系就脱离了,Master关心的是Pet类,这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。

面向抽象编程,不建议面向具体编程。

13、super

super是一个关键字,全部小写。可以将super和this对比着学习。

this:

  1. this能出现在实例方法和构造方法中。
  2. this的语法是:“this.”或“this()”。
  3. this不能使用在静态方法中。
  4. this.大部分情况下都可以省略。
  5. this.在区分局部变量和实例变量的时候不能省略。

super:

  1. super能出现在实例方法和构造方法中。
  2. super的语法是:“super.”或“super()”。
  3. super不能使用在静态方法中。
  4. super.大部分情况下都可以省略。
  5. this.在什么时候不能省略????后面讲
  6. super()只能出现在构造方法第一行,通过当前的构造方法去调用“父类”中的构造方法,目的是:“创建子类对象的时候,先初始化父类型特征”。

重要结论:

当一个构造方法第一行,既没有this()有没有super()的话,默认会有一个super(),表示通过当前子类的构造方法调用父类的参数构造方法。所以必须保证父类的无参数构造方法是存在的。

13.1 super的原理(内存图)

super和this都不能出现在static修饰的代码中。

image-20211012212647020

package com.zh0u.super关键字;

/*
 * 1. 在恰当的时间使用:super(实际参数列表);
 * 2. 注意:在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法有继续向上调用它父类的构造方法。但是实际上对象只创建了一个。
 *
 * 3. 思考:“super(实参)”到底是干啥的?
 * super(实参)的作用是:初始化当前对象的父类型特征,并不是创建新对象,实际上对象只创建1个。
 * 
 * 4. super关键字代表什么?
 * super关键字代表的就是“当前对象”的那部分父类型特征。
 */

public class SuperTest03 {
    public static void main(String[] args) {
        Account account = new Account("65245892144456266", "400000");
        System.out.println("账号: "+account.getActNo()+", 余额: "+account.getBalance());
        CreditAccount creditAccount = new CreditAccount("65245892144456266","600000",0.9999);

        System.out.println("账号: "+creditAccount.getActNo()+", 余额: "+creditAccount.getBalance()+", 信誉度: "+creditAccount.getCredit());

    }
}

class Account{
    // 账号
    private String actNo;
    // 余额
    private String balance;

    // 构造方法
    public Account(){}
    public Account(String actNo, String balance){
        this.actNo = actNo;
        this.balance = balance;
    }

    public String getActNo() {
        return actNo;
    }

    public void setActNo(String actNo) {
        this.actNo = actNo;
    }

    public String getBalance() {
        return balance;
    }

    public void setBalance(String balance) {
        this.balance = balance;
    }
}

class CreditAccount extends Account {
    // 信誉度
    private double credit;

    public CreditAccount(){}
    public CreditAccount(String actNo, String balance, double credit){
        // 私有属性只能在本类中使用
        /*
        this.actNo = actNo;
        this.balance = balance;
         */
        // 通过以上两行代码,得出在恰当的位置使用super(),即通过父类构造方法调用父类构造方法
        super(actNo, balance);
        this.setBalance(balance);
        this.credit = credit;
    }

    public double getCredit() {
        return credit;
    }

    public void setCredit(double credit) {
        this.credit = credit;
    }
}

this、super

image-20211012212656377

package com.zh0u.super关键字;

public class SuperTest04 {
}

class Customer{
    String name;
    public Customer(){};
    public Customer(String name){
        // super(); 默认存在
        this.name = name;
    }
}

class Vip extends Customer{
    public Vip(){};

    public Vip(String name){
        super(name);
    }

    public void shopping(){
        // this表示当前对象
        System.out.println(this.name +"正在购物!");  // 张三正在购物!
        // super表示当前对象的父类型特征
        System.out.println(super.name +"正在购物!"); // 张三正在购物!
        System.out.println(name +"正在购物!"); // 张三正在购物!
    }
}

13.2 super不能省略的情况

当子类中的属性和父类中的属性(方法名)相同时,访问对应属性(方法)的时候应该加上this.或super.,即这个时候的super是不能省略的。

image-20211012212704371

package com.zh0u.super关键字;

public class SuperTest04 {
}

class Customer{
    String name;
    public Customer(){};
    public Customer(String name){
        // super(); 默认存在
        this.name = name;
    }
}

class Vip extends Customer{
    public Vip(){};

    public Vip(String name){
        super(name);
    }

    public void shopping(){
        // this表示当前对象
        System.out.println(this.name +"正在购物!");  // NULL正在购物!
        // super表示当前对象的父类型特征
        System.out.println(super.name +"正在购物!"); // 张三正在购物!
        System.out.println(name +"正在购物!"); // NULL正在购物!
    }
}

13.3 super. 的问题
package com.zh0u.super关键字;

public class SuperTest06 {

    public void doSome(){
        // com.zh0u.super关键字.SuperTest06@6d6f6e28
        System.out.println(this);
        // 输出引用的时候会自动调用引用的toString()方法,System.out.println(this.toString());

        // 以下语句会出现“需要'.'”的错误
        //System.out.println(super);
        /*
            通过上面的语句得出结论:
            super不是引用。super也不保存内存地址,super也不指向任何任何对象。
            super只是代表当前对象内部的那一块父类型的特征。
            this和super都不能出现在static静态方法中。
         */
    }

    public static void main(String[] args) {
        SuperTest06 test06 = new SuperTest06();
        test06.doSome();
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值