【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员

学习面向对象的三条主线

        1、Java类及类的成员
                属性、方法、构造器、代码块、内部类;

        2、面向对象的三大特征
                封装性、继承性、多态性、(抽象性);

        3、抽象类、接口和其他关键字
                this、super、static、final、abstract、interface、package、import等

一、面向过程与面向对象

1.1 何谓编程思想

        思想是汉语词汇,本义是客观存在反映在人的意识中经过思维活动而产生的结果或形成的观点及观念体系。

        首先解释一下“思想”。
        先问你个问题:你想做个怎样的人?
        可能你会回答:我想做个好人,孝敬父母,尊重长辈,关爱亲朋……你看,这就是思想。这是你做人的思想,或者说,是你做人的原则。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第1张图片

        做人有做人的原则,编程也有编程的原则。这些编程的原则呢,就是编程思想。

        所谓编程思想,就是指用计算机来解决人们实际问题的思维方式。

        好比学习一门课程一样,首先我们应该对课程的基本概念熟悉掌握,然后学习了由定义得出的结论,等到一本书学完后,我们最重要的就是知识体系的构建,而这与编程思想有着极大的联系。

        我们在做一件事情的时候,这种方法是合理的:
                1、先将一个问题分为一个个小模块,就好比书到章的这一种关系;
                2、将一个小模块分为还要小的部分,就好比章到节的这种关系;
                3、最终将它们分为不可分割的部分,就好比节到定义与概念这种关系;
                4、这就好比我们实现一个程序的功能一样,先考虑大体方向,然后再逐步实现,做到不重不漏。

1.2 面向过程程序设计

        面向过程的程序设计(Procedure-Oriented Programming,简称POP)是一种以过程为中心的编程思想。这些都是以什么正在发生为目标进行编程,不同于面向对象的是谁在受影响。与面向对象明显的不同就是封装、继承、类。

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

        面向过程:根据业务逻辑从上到下写代码。面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑。

1.3 面向对象程序设计

        面向对象的程序设计(Object Oriented Programming,简称OOP)是按人们认识客观世界的系统思维方式,采用基于对象(实体)的概念建立模型,模拟客观世界分析、设计、实现软件的办法。通过面向对象的理念使计算机软件系统能与现实世界中的系统一一对应。

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

1.4 面向过程与面向对象的区别

        二者都是一种思想,面向对象是相对于面向过程而言的。

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

        面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为;面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

        面向过程程序的特点:强调实现功能、算法和一切细节

        面向对象程序的特点:强调具备了功能的对象

        面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。

案例说明:

	人把大象装进冰箱:

	面向过程:
		函数1:打开冰箱() {人站在冰箱前,打开冰箱,冰箱开到30度角的时候,冰箱的灯打开了....}
		函数2:储存大象() {大象先迈出左腿,再迈出右腿,考虑冰箱能不能装的下...}
		函数3:关闭冰箱() {人站在冰箱前,关闭冰箱,冰箱关到30度角的时候,冰箱的灯熄灭了....}

	面向对象:
		人{
          打开(冰箱) {
			冰箱.打开();
          }

          储存(大象) {
			大象.进入(冰箱);
          }

          关闭(冰箱) {
           冰箱.关闭();
          }
        }

        冰箱{
          打开() {}
          关闭() {}
        }

        大象{
          进入(冰箱) {}
        }
	
	面向过程---》面向对象,其实就是执行者到指挥者的一个过渡。

	二者相辅相成,并不是对立的。解决复杂的问题,通过面向对象方式方便我们从宏观上把握事物之间的复杂关系,方便我们分析整个系统,具体到微观操作,仍然使用面向过程方式来处理。

	怎么理解解决复杂的问题,面向对象更加高效?
		举例:现在有一家公司,刚起步;总共4-5个员工,公司业务相对来说也比较少,事情发生了,大家一起做;这样会更加高效,这就是面向过程的方式;但是如果公司规模越来越大,员工四五百人,如果还按照上述的方式来实现,那么显然会非常混乱;那么就势必也划分市场部、采购部、生产部、客服部等并且对每个部门进行职责划分(这就相当于类的设计),当事情发生了,根据业务去找对应部门的人(这就相当于根据类找对象)即可;

	也有人说,
		面向过程:编年体 (根据年份时间来说明谁谁谁做了什么)
		面向对象:纪传体(根据一个人说明他在不同时间做了什么)

1.5 面向对象分析问题的思路和步骤

        程序员从面向过程的执行者转化成了面向对象的指挥者。

        面向对象分析方法分析问题的思路和步骤:
                根据问题需要,选择问题所针对的‘现实世界中的实体’。
                从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了‘概念世界中的类’。
                把抽象的实体用计算机语言进行描述,‘形成计算机世界中类的定义’。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
                将‘类实例化成计算机世界中的对象’。对象是计算机世界中解决问题的最终工具。

思考:

        1、我要开车去丽江,这句话包含的类有什么?
        2、体会以下几个经典案例涉及到的类。
                人在黑板上画圆。
                列车司机紧急刹车。
                售货员统计收获小票的金额。
                你把门关上了。
        3、抽象出下面系统中的“类”及其关系。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第2张图片

二、Java基本元素:类和对象

        世界是由什么构成的?

        世界是由概念世界和物质世界组成。概念世界包含所有生命对客观世界的认知以及为记录认知而存在的事物的总和;物质世界,即客观存在,它是指不依赖人的意识并能为人的意识所反映的世界,它包括狭义的自然界和人类社会。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第3张图片

        Java是对现实世界的映射,在Java世界里面: 万事万物皆“对象”。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第4张图片

2.1 面向对象的思想概述

        类(Class)和对象(Object)是面向对象的核心概念。
                类是对一类事物的描述,是抽象的、概念上的定义。
                对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第5张图片

         可以理解为:类 = 汽车设计图;对象 = 实实在在的汽车;

        面向对象程序设计的重点是类的设计。

        类的设计,其实就是类的成员的设计。

2.1.1 从对象抽取出"类"

        抽取出下列对象的属性和方法的共同特征:

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第6张图片

        分类是人们认识世界的一个很自然的过程,在日常生活中会不自觉地进行分类。
        现实世界中的类:人类、猫类、汽车类。。。。。。
        现实世界中的对象:张三某人、一只白猫、一辆奔驰汽车。。。
        类:从一系列对象中抽取出来相同特征的抽象的概念。对象是类的具体化,即是该类事物实实在在存在的个体。
    
        所以类就是对现实世界事物的抽象定义, 这个抽象定义就可以基本把某事物描述清楚. 要想描述清楚事物, 必须要知道事物有哪些特征(数据, 用变量保存), 有哪些行为(用方法描述), 当某事物的特征和行为都描述清楚后, 我们就认为对这个事物有一个大概的把握.
        1、可以将复杂的事情简单化
        2、专业的事情交给专业的人去做
        3、现代社会,强调合作,面向对象正体现了这一思想(“木桶理论”新解,强调长板效应)
        4、计算机是对现实世界的模拟,只有采用面向对象的思想才能更好的模拟世界、服务世界、改造世界。

2.1.2 对象是类的具体化

        对象就是一个类的实实在在的实体, 也称为实例, 所以对象(object)也称为实例(instance), 实例就是对象, 对象就是实例.

        比如 “学生” 可以是一个类, 因为它描述了学生这一群体事物, 而具体的”3年级的小明” 就是一个对象, 相同的“4年级的小花” 也是一个学生对象.

        对比图:

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第7张图片

2.1.3 先有对象还是先有类

        在面向对象思想里,先有对象还是先有类,乍一看和先有鸡蛋还是先有鸡是一类问题。
    
        现实生活中,先有对象,由对象抽象成类,在程序设计中则是相反的,由抽象的类实例化成对象。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第8张图片

 2.1.4 理解“万事万物皆对象”

        1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
                >Scanner,String等
                >文件:File
                >网络资源:URL
        2.涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。

2.1.5 类和对象总结

        在Java世界中的类是指的一种数据类型。
                在Java中类是代码的载体,所有的代码都要写在类中。
                在Java中类包括了:JDK自带的、第三方开发的、自己开发的类。。。
                在Java世界中的对象是指的运行在内存(堆)中的类型的个体(实例)。

        具有相同特征的一组事物通常为一类,是存在于人脑中的一种抽象定义。
        类是一个模型,本身不具备任何的功能。比如蛋糕模子可以制作蛋糕,但不可以吃。
        对象是类的具体化,即是该类事物实实在在存在的个体。
        类是描述事物(对象)的, 一旦描述清楚, 就可以代表一类事物了, 但是类只是概念, 要想使用实体, 必须要有对象, 但是从时间的先后顺序来讲, 是先有类, 才有的对象, 因为类就像是一个模板, 而对象就像是用这个模板制造出来的产品, 如前面图示所描述的, 汽车设计图是一个模板, 一旦有了这个模板, 就可以使用设计图, 无限制地制造汽车了.

2.2 Java类及类的成员

        现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的‘细胞’构成的。同理,Java代码世界是由诸多个不同功能的‘类’构成的。

        Java中用类class来描述事物。事物有大小,颜色,好坏,高低,胖瘦等等静态特征, 而这些特征又可以用数据类型描述,在程序中保存数据的就是变量。事物也有行为动作, 比如吃,跑,跳,睡觉等等, 而这些行为的描述又比变量稍复杂一点, 用方法来描述事物的行为。

        常见的类的成员有:
        属性: 对象的静态特征
            属性——对象具有的各种特征;
            每个对象的每个属性都拥有特定值,例如:张浩和李明的年龄、姓名不一样;
            对应类中的成员变量;
        方法: 对象的动态特征
            方法——对象执行的操作,例如:收银员李明的方法:刷卡、打印订单、收银;
            对应类中的成员方法;

        Field = 属性 = 成员变量 = 实例变量,Method = 函数 = (成员)方法 = 实例方法;

        创建类的对象 = 类的实例化 = 实例化类;

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第9张图片

2.3 Java类的语法格式

        所有Java程序都以类class为组织单元

        关键字class定义类类型数据

        所有的类都是引用数据类型。

        定义一个类的步骤
                1、定义类
                2、编写类的属性 (成员变量)
                3、编写类的方法 (成员方法)

        定义类的语法:
                修饰符 class 类名{
                     //定义属性部分
                    属性1的类型 属性1;
                    属性2的类型 属性2;
                    …
                    属性n的类型 属性n;

                    //定义方法部分
                    方法1;
                    方法2;
                    …
                    方法m;

                }

        说明:
                修饰符public,类可以被任意访问
                类将现实世界中的概念模拟到计算机程序中

示例代码:

public class Person {

    String name; // 在类中声明的变量 name, 此时就是属性, 也称为成员变量, 描述人有名字
    int age; // 在类中声明的变量 age, 此时就是属性, 也称为成员变量, 描述人有年龄

    public void play() {    // 声明方法play(), 也称为成员方法, 表示人会玩
        System.out.println("玩的行为");
    }
}

2.4 Java类的成员构成

2.4.1 简易版本

public class Person {
    // 属性 或 成员变量 或 实例变量
    String name;
    int age;
    boolean isMarried;

    // 函数 或 成员方法 或 实例方法
    public void walk() {
        System.out.println("人走路的行为...");
    }

    public String disPlay() {
        return "名字是:" + name + ",年龄是:" + age + ",结婚否:" + isMarried;
    }
}

2.4.2 完整版本

public class Person {
    // 属性 或 成员变量 或 实例变量
    String name;
    int age;
    boolean isMarried;

    // 构造器 或 构造函数
    public Person() {
    }

    public Person(String name, int age, boolean isMarried) {
        this.name = name;
        this.age = age;
        this.isMarried = isMarried;
    }

    // 函数 或 成员方法 或 实例方法
    public void walk() {
        System.out.println("人走路的行为...");
    }

    public String disPlay() {
        return "名字是:" + name + ",年龄是:" + age + ",结婚否:" + isMarried;
    }

    // 构造代码块
    {
        name = "张三";
        age = 18;
        isMarried = true;
    }

    // 内部类
    class Pet {
        String name;
        int age;
        float weight;
    }
}

2.5 创建Java自定义类

        步骤:
                1、定义类(考虑修饰符、类名)
                2、编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
                3、编写类的方法(考虑修饰符、返回值类型、方法名、形参等)

        练习:
                定义Person、Animal、ClassRoom、Zoo等类,加以体会。

2.6 面向对象思想落地的实现

        1.创建类,设计类的成员
        2.创建类的对象
        3.通过“对象.属性”或“对象.方法”调用对象的结构

//1.创建类,设计类的成员
class Person {

    //属性
    String name;
    int age = 1;
    boolean isMale;

    //方法
    public void eat() {
        System.out.println("人可以吃饭");
    }

    public void sleep() {
        System.out.println("人可以睡觉");
    }

    public void talk(String language) {
        System.out.println("人可以说话,使用的是:" + language);
    }

}

//测试类
public class PersonTest {
    public static void main(String[] args) {
        //2. 创建Person类的对象
        Person p1 = new Person();
        //Scanner scanner = new Scanner(System.in);

        // 3. 调用属性和方法
        // 调用对象的结构:属性、方法
        // 调用属性:“对象.属性”
        p1.name = "Tom";
        p1.isMale = true;
        System.out.println(p1.name);

        // 调用方法:“对象.方法”
        p1.eat();
        p1.sleep();
        p1.talk("Chinese");

    }
}

三、Java对象的创建和使用

        类定义完成之后,肯定无法直接使用。如果要使用,必须依靠对象,那么由于类属于引用数据类型,所以对象的产生格式(两种格式)如下:

        格式一:声明并实例化对象
                类名称 对象名称 = new 类名称 () ;

        格式二:先声明对象,然后实例化对象:
                类名称 对象名称;
                对象名称 = new 类名称 () ;

        引用数据类型与基本数据类型最大的不同在于:引用数据类型需要内存的分配和使用。所以,关键字new的主要功能就是分配内存空间,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。

        当一个实例化对象产生之后,可以按照如下的方式进行类的操作: 
                对象.属性:表示调用类中的属性; 
                对象.方法():表示调用类中的方法。

3.1 使用对象操作类

3.1.1 创建对象语法一

public class TestDemo {
    public static void main(String args[]) {
        // 创建类对象
        Person per = new Person();
        //操作属性
        per.name = "张三";
        per.age = 30;

        //调用类中的get()方法
        per.get();
    }
}

class Person {
    String name;
    int age;

    public void get() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

运行结果:

        姓名:张三,年龄:30

3.1.2 创建对象语法二

        以上完成了一个类和对象的操作关系,下面换另外一个操作来观察一下:

public class TestDemo {
    public static void main(String args[]) {
        // 声明对象
        Person per = null;
        // 实例化对象
        per = new Person();
        // 操作属性内容
        per.name = "张三";
        per.age = 30;
        per.get();//调用类中的get()方法
    }
}

class Person {
    String name;
    int age;

    public void get() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

运行结果:

        姓名:张三,年龄:30

那么,问题来了,以上两种不同的实例化方式有什么区别呢? 

3.1.3 Java对象内存解析

        我们从内存的角度分析。首先,给出两种内存空间的概念: 
                (1)堆内存: 存放由new创建的对象和数组以及对象的实例变量(属性内容),堆内存需要用new关键字来分配空间。 
                (2)栈内存: 存放基本类型的变量数据、局部变量和对象的引用,但对象本身不存放在栈中。

对于null的理解:

        首先,null不是有效的对象实例,因此没有为它分配内存。它只是一个值,指示对象引用当前不引用对象。
    
        来自JVM规范:
                The Java Virtual Machine specification does not mandate a concrete value encoding null.(Java虚拟机规范并没有授权一个具体的值编码null。)

        任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。

        于是,上面两种对象实例化对象方式内存表示如下: 

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第10张图片

        两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。

        另外,如果使用了没有实例化的对象,结果如何? 

public class TestDemo {
    public static void main(String args[]) {
        // 声明对象
        Person per = null;
        // per = new Person() ;//实例化对象

        //操作属性和方法
        per.name = "张三";
        per.age = 30;
        per.get();
    }
}

class Person {
    String name;
    int age;

    public void get() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

运行结果:

        Exception in thread "main" java.lang.NullPointerException
                at com.wz.demo01.TestDemo.main(TestDemo.java:15)

        此时,程序只声明了Person对象,但并没有实例化Person对象(只有了栈内存,并没有对应的堆内存空间),则程序在编译的时候不会出现任何的错误,但是在执行的时候出现了上面的错误信息。这个错误信息表示的是“NullPointerException(空指向异常)”,这种异常只要是引用数据类型都有可能出现。

3.1.4 对象引用传递分析

        如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的),意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。

        引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。

        下面通过若干个程序,以及程序的内存分配图,来进行代码的讲解。

public class TestDemo {
    public static void main(String args[]) {
        Person per1 = new Person();         // 声明并实例化对象
        per1.name = "张三";
        per1.age = 20;

        // 这里不是创建新的对象,而是让per2指向per1的地址
        Person per2 = per1;  // 引用传递
        per2.name = "李四";
        per1.tell();
    }
}

class Person {
    String name;
    int age;

    public void tell() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

对应的内存分配图如下:

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第11张图片

 再来看另外一个:

public class TestDemo {
    public static void main(String args[]) {
        Person per1 = new Person();         // 声明并实例化对象
        Person per2 = new Person();

        per1.name = "张三";
        per1.age = 20;

        per2.name = "李四";
        per2.age = 30;

        per2 = per1;// 引用传递
        per2.name = "王五";
        per1.tell();
    }
}

class Person {
    String name;
    int age;

    public void tell() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第12张图片

        垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。

3.1.5 Java对象内存深入解析

对象的产生

// 类定义如下
class Person {
    int age;

    void shout() {
        System.out.println("oh, my god !I am "+age);
    }
}

        Person p1 = new Person();执行完后的内存状态。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第13张图片

对象的使用

class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.age = -30;
        p1.shout();
        p2.shout();
    }
}

//程序运行的内存布局如下图

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第14张图片

对象的生命周期

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第15张图片

JVM内存区域模型

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第16张图片

方法区:
        也称"永久代” 、“非堆”,  它用于存储‘虚拟机加载的类信息、常量、静态变量’、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
        运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

虚拟机栈:
        描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。
        局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部 变量表的大小空间。

本地方法栈:
        与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。

堆:
        也叫做Java 堆、GC堆是Java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。
        其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。
        由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。
        新生代:程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小。

        老年代:用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:
            1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默 认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。
            2、大的数组对象,且数组中无引用外部对象。

        老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。

程序计数器:
        它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

        JVM内存区域详解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen): https://blog.csdn.net/shiyong1949/article/details/52585256

        新生代GC、老年代GC:https://www.cnblogs.com/luao/p/10644124.html

3.2 类的访问机制

        在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)

        在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。

3.3 类中的匿名对象

        没名字的对象称为匿名对象;
    
        对象的名字按照之前的内存关系来讲,在栈内存之中,而对象的具体内容在堆内存之中保存,这样,没有栈内存指向堆内存空间,就是一个匿名对象。

        使用情况:
                如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
                我们经常将匿名对象作为实参传递给一个方法调用。

public class AnonymityTest {
    public static void main(String[] args) {
        // 有名对象: 通过变量指向堆内存中的对象的地址
        // 通过变量名指向new出来的对象,方法我们调用
        Animal animal1 = new Animal();

        // 多次使用
        animal1.name = "小脑斧";
        animal1.age = 6;

        animal1.show();


        // 匿名对象: 对象内存中的对象没有任何变量指向它
        // 两个匿名对象分别调用了一次show();
        // new Animal().show();
        // new Animal().show();

        // 场景一: 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
        new Animal().name = "大花猫";
        new Animal().show(); //null 0

        // 场景二: 我们经常将匿名对象作为实参传递给一个方法调用。
        /*Phone phone = new Phone();
        phone.name = "苹果";
        phone.price = 8888.88;*/

        People people = new People();
        people.talk(new Phone());
    }

}

class Animal {
    String name;
    int age;

    void show() {
        System.out.println("姓名是:" + name + "\t" + age);
    }
}

/*
 * 人类
 * */
class People {
    String name;
    int age;

    /*
     * 人类交流的方法
     * */
    void talk(Phone phone) {
        phone.call();
        phone.email();
        phone.social();
    }
}

/*
 * 手机类
 * */
class Phone {
    String name;
    double price;

    // 打电话
    void call() {
        System.out.println("用手机打电话");
    }

    // 发邮件
    void email() {
        System.out.println("用手机发邮件");
    }

    // 社交软件
    void social() {
        System.out.println("用手机里面的社交软件");
    }
}

运行结果:

        有俩个参数的构造方法
        图书的名称:Java开发 图书的价格:89.9

匿名对象由于没有对应的栈内存指向,所以只能使用一次,一次之后就将成为垃圾,并且等待被GC回收释放。

四、类的成员之一:属性

4.1 属性的语法格式

        修饰符 数据类型 属性名 = 初始化值 ; 

        成员变量也叫实例变量,是指对象的状态。
        成员变量定义在类中,在整个类中都可以被访问。
        成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
        成员变量有默认初始化值。

说明:

        修饰符:
                常用的权限修饰符有:private、缺省、protected、public
                其他修饰符:static、final (暂不考虑)
    
        数据类型:
                任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
    
        属性名:
                属于标识符,符合命名规则和规范即可。

举例:

public class Person {
    private int age;  // 声明private变量 age
    public String name;    // 声明public变量 name
    public String[] hobby; // 声明数组类型的变量
}

4.2 再谈变量的分类

        变量的分类:成员变量与局部变量;
        在方法体外,类体内声明的变量称为成员变量。在方法体内部或代码块内部声明的变量称为局部变量。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第17张图片

注意:二者在初始化值方面的异同:
        同:都有生命周期;都需要先声明,后使用;
        异:局部变量除形参外,均需显式初始化。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第18张图片

成员变量vs局部变量的内存位置

class Person {//人类
    // 1.属性
    String name;// 姓名
    int age = 1;// 年龄
    boolean isMale;// 是否是男性

    public void show(String nation) { // nation形参:局部变量
        String color;// color:局部变量
        color = "yellow";
    }
}

//测试类
class PersonTest {
    public static void main(String[] args) {
        Person p = new Person();
        p.show("USA");
    }
}

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第19张图片

4.3 对象属性的默认初始化赋值

        当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型,如上面的Person及前面讲过的数组。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第20张图片

五、类的成员之二:方法

        什么是方法(method、函数):
                为了解决代码重复编写的问题,可以将需要重复编写的代码提取出来放在一个{}(代码块)中,给这段代码起个名字,通过这个名字来调用这段代码就可以实现想要做的事情。提取出来的代码可以组成一个方法。

        方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
        将功能封装为方法的目的是,可以实现代码重用,简化代码
        Java里的方法不能独立存在,所有的方法必须定义在类里。

        方法存在的意义:
                1. 提高代码的复用性。
                2. 增强代码的可维护性。

示例代码:

public class Person {
    private int age;

    public int getAge() { //声明方法getAge()
        return age;
    }

    public void setAge(int i) { //声明方法setAge
        age = i; //将参数i的值赋给类的成员变量age
    }
}

5.1 方法的声明格式

        成员方法也叫实例方法,是指对象的行为。
        方法也可以称作函数。
        程序的作用体现在方法中,方法是对象功能的基本构件。

        修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, ….){
                方法体程序代码
                return 返回值;
        }

说明:

	修饰符:
		public 默认 protected private 等

    返回值类型:
		如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。
		如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。
		当不确定方法的返回值类型时,可以将返回值类型写成Object(因为Object是java中的根类,可以接收任意类型的返回值)[一般不要这样做]。如果存在分支,必须满足所有分支都有反馈;
		我们定义方法该不该有返回值?
			① 题目要求
			② 凭经验:具体问题具体分析

    方法名:
		属于标识符,命名时遵循标识符命名规则和规范,“见名知意”。
	
	参数列表:
		可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开。
    	参数类型:调用方法时传入的数据的数据类型,参数类型包括基本数据类型和引用数据类型。
		参数名:调用方法时传入的参数的参数的名字。
		我们定义方法时,该不该定义形参?
			① 题目要求
			② 凭经验:具体问题具体分析

    方法体:
		实现业务功能的代码。
	
	return关键字的使用:方法在执行完毕后返还给调用它的程序的数据。
		使用范围:使用在方法体中
        作用:结束方法,返回返回值。
            ① 结束方法 ② 针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据。
			注意点:return关键字后面不可以声明执行语句。

    注意事项:
		方法定义完成之后,在调用方法的时候才会执行方法,不调用的时候不会执行。
		方法的使用中,可以调用当前类的属性或方法。
		特殊的:方法A中又调用了方法A:递归方法。
		方法中,不可以定义方法。

方法组成三要素

        方法组成的三要素:返回值类型、方法名、参数列表

    1、不带参数,不带返回值的方法
        void say(){
            System.out.println("Hello World!!!");
        }

    2、带参数不带返回值的方法
        void fun2(int num){
            System.out.println(num);
        }

    3、带返回值不带参数的方法
        int fun3(){
            return 100;
        }

    4、带参数带返回值的方法。
        int fun4(int num){
            return num;
        }

    5、写出计算数组最大值下标的方法。
        int getMax(int[] arr){
            if(arr == null){
                return -1;
            }
            int maxValue = arr[0];
            int index = 0;
            for(int i = 0; i < arr.length; i++){
                if(arr[i]>maxValue){
                maxValue = arr[i];
                index = i;
                }
            }
            return index;
        }


        int getMax(double[] arr){
            if(arr == null){
                return -1;
            }
            int maxValue = arr[0];
            int index = 0;
            for(int i = 0; i < arr.length; i++){
                if(arr[i]>maxValue){
                maxValue = arr[i];
                index = i;
                }
            }
            return index;
        }

5.2 方法的分类

        方法的分类:按照是否有形参及返回值。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第21张图片

5.3 方法的调用

        方法通过方法名被调用,且只有被调用才会执行。

方法调用的过程分析

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第22张图片

 方法调用的原理和过程

        首先编写一个java类。
        在java类中找到main方法,并且调用main方法。
        在main方法中调用getArea方法。
        让形参接收实参。
        执行getArea方法内部的业务代码。
        方法内部的变量在方法结束时销毁,最后需要把返回值通过return返回方法外。
        结束。

注意

        方法被调用一次,就会执行一次。
        没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
        定义方法时,方法的结果应该返回给调用者,交由调用者处理。
        方法中只能调用方法或属性,不可以在方法内部定义方法。

方法练习题

1、创建一个Person类,其定义如下:
        (1)创建Person类的对象,设置该对象的name、age和sex属性,调用study方法,输出字符串“studying”,调用showAge()方法显示age值,调用addAge()方法给对象的age属性值增加2岁。
        (2)创建第二个对象,执行上述操作,体会同一个类的不同对象之间的关系。
        (3)通过画图分析内存存储模型。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第23张图片

public class Person {

    String name;
    int age;
    /**
     * sex:1 表明是男性
     * sex:0 表明是女性
     */
    int sex;

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

    public void showAge() {
        System.out.println("age:" + age);
    }

    public int addAge(int i) {
        age += i;
        return age;
    }
}

public class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();

        p1.name = "Tom";
        p1.age = 18;
        p1.sex = 1;

        p1.study();
        p1.showAge();

        int newAge = p1.addAge(2);
        System.out.println(p1.name + "的新年龄为:" + newAge);
        System.out.println(p1.age);//20

        System.out.println("----------------------------");
        Person p2 = new Person();
        p2.showAge();//0
        p2.addAge(10);
        p2.showAge();//10

        p1.showAge();

    }
}

2、利用面向对象的编程方法,设计类Circle计算圆的面积。

//测试类
public class CircleTest {
    public static void main(String[] args) {

        Circle c1 = new Circle();

        c1.radius = 2.1;

        //对应方式一:
        //		double area = c1.findArea();
        //		System.out.println(area);

        //对应方式二:
        c1.findArea();


        //错误的调用
        //		double area = c1.findArea(3.4);
        //		System.out.println(area);

    }
}

//圆
class Circle{

    //属性
    double radius;

    //求圆的面积
    //方式一:
    //	public double findArea(){
    //		double area = Math.PI * radius * radius;
    //		return area;
    //	}

    //方式二:
    public void findArea(){
        double area = Math.PI * radius * radius;
        System.out.println("面积为:" + area);
    }

    //错误情况:
    //	public double findArea(double r){
    //		double area = 3.14 * r * r;
    //		return area;
    //	}
    //	
}

    3、对象数组题。
        定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
        问题一:打印出3年级(state值为3)的学生信息。
        问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信
        
        提示:
            1) 生成随机数:Math.random(),返回值类型double; 
            2) 四舍五入取整:Math.round(double d),返回值类型long。

public class StudentTest {
    public static void main(String[] args) {

        //声明Student类型的数组
        Student[] stus = new Student[20];  //String[] arr = new String[10];

        for (int i = 0; i < stus.length; i++) {
            //给数组元素赋值
            stus[i] = new Student();
            //给Student对象的属性赋值
            stus[i].number = (i + 1);
            //年级:[1,6]
            stus[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
            //成绩:[0,100]
            stus[i].score = (int) (Math.random() * (100 - 0 + 1));
        }

        //遍历学生数组
        for (int i = 0; i < stus.length; i++) {
            //			System.out.println(stus[i].number + "," + stus[i].state 
            //					+ "," + stus[i].score);

            System.out.println(stus[i].info());
        }

        System.out.println("********************");

        //问题一:打印出3年级(state值为3)的学生信息。
        for (int i = 0; i < stus.length; i++) {
            if (stus[i].state == 3) {
                System.out.println(stus[i].info());
            }
        }

        System.out.println("********************");

        //问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
        for (int i = 0; i < stus.length - 1; i++) {
            for (int j = 0; j < stus.length - 1 - i; j++) {
                if (stus[j].score > stus[j + 1].score) {
                    //如果需要换序,交换的是数组的元素:Student对象!!!
                    Student temp = stus[j];
                    stus[j] = stus[j + 1];
                    stus[j + 1] = temp;
                }
            }
        }

        //遍历学生数组
        for (int i = 0; i < stus.length; i++) {
            System.out.println(stus[i].info());
        }

    }
}

class Student {
    int number;//学号
    int state;//年级
    int score;//成绩

    //显示学生信息的方法
    public String info() {
        return "学号:" + number + ",年级:" + state + ",成绩:" + score;
    }

}

对象数组的内存解析

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第24张图片

 4、自定义数组工具类
        定义类,定义求最大值、最小值、和、平均分、反转、复制、排序、遍历、查找等方法;

public class ArrayUtil {

    // 求数组的最大值
    public int getMax(int[] arr) {
        int maxValue = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (maxValue < arr[i]) {
                maxValue = arr[i];
            }
        }
        return maxValue;
    }

    // 求数组的最小值
    public int getMin(int[] arr) {
        int minValue = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (minValue > arr[i]) {
                minValue = arr[i];
            }
        }
        return minValue;
    }

    // 求数组的总和
    public int getSum(int[] arr) {

        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }

    // 求数组的平均值
    public int getAvg(int[] arr) {

        return getSum(arr) / arr.length;
    }

    // 反转数组
    public void reverse(int[] arr) {
        for (int i = 0; i < arr.length / 2; i++) {
            int temp = arr[i];
            arr[i] = arr[arr.length - i - 1];
            arr[arr.length - i - 1] = temp;
        }
    }

    public void reverse(String[] arr) {

    }

    // 复制数组
    public int[] copy(int[] arr) {
        int[] arr1 = new int[arr.length];
        for (int i = 0; i < arr1.length; i++) {
            arr1[i] = arr[i];
        }
        return arr1;
    }

    // 数组排序
    public void sort(int[] arr) {
        // 冒泡排序
        for (int i = 0; i < arr.length - 1; i++) {

            for (int j = 0; j < arr.length - 1 - i; j++) {

                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }

            }

        }
    }

    // 遍历数组
    public void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }

    // 查找指定元素
    public int getIndex(int[] arr, int dest) {
        // 线性查找:
        for (int i = 0; i < arr.length; i++) {
            if (dest == arr[i]) {
                return i;
            }
        }

        return -1;//返回一个负数,表示没有找到
    }

}

public class ArrayUtilTest {
    public static void main(String[] args) {

        ArrayUtil util = new ArrayUtil();
        int[] arr = new int[]{32, 34, 32, 5, 3, 54, 654, -98, 0, -53, 5};
        int max = util.getMax(arr);
        System.out.println("最大值为:" + max);

        System.out.println("排序前:");
        util.print(arr);

        util.sort(arr);
        System.out.println("排序后:");
        util.print(arr);

//		System.out.println("查找:");
//		int index = util.getIndex(arr, -5);
//		if(index >= 0){
//			System.out.println("找到了,索引地址为:" + index);
//		}else{
//			System.out.println("未找到");
//		}


//		util.reverse(arr);
    }
}

5.4 方法重载

    概述:
        在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型、参数顺序不同即可。
		方法重载的意义是可以减轻开发人员的记忆负担。

    特点:
        与方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。
        参数列表不一样,体现在参数的类型不一样和个数不一样,当个数和类型都一样时,顺序不一样也行。
        在调用时,jvm虚拟机通过参数列表的不同来区分同名方法。

	重载示例:
        //返回两个整数的和
        int add(int x,int y){return x+y;}
        
		//返回三个整数的和
        int add(int x,int y,int z){return x+y+z;}
        
		//返回两个小数的和
        double add(double x,double y){return x+y;}

		Arrays类中重载的sort() / binarySearch()

	口诀:
		判断是不是重载的依据:"两同一不同"
			同一个类、相同方法名
			参数列表不同:参数个数不同,参数类型不同,参数顺序不同

	在通过对象调用方法时,如何确定某一个指定的方法:	
		方法名 ---> 参数列表
public class OverLoadTest {
    public static void main(String[] args) {
        OverLoadTest test = new OverLoadTest();
        test.getSum(1, 2);
    }

    //如下的4个方法构成了重载
    public void getSum(int i, int j) {
        System.out.println("1");
    }

    public void getSum(double d1, double d2) {
        System.out.println("2");
    }

    public void getSum(String s, int i) {
        System.out.println("3");
    }

    public void getSum(int i, String s) {
        System.out.println("4");
    }

    //如下的3个方法不能与上述4个方法构成重载
    //	public int getSum(int i,int j){
    //		return 0;
    //	}

    //	public void getSum(int m,int n){
    //		
    //	}

    //	private void getSum(int i,int j){
    //		
    //	}


}

代码示例:

/*
 * 员工类:
 *   成员变量: 员工号、姓名、年龄、岗位、所属部门...
 *   成员方法: 吃饭、工作、睡觉...
 * */
public class Emp {
    // 成员变量
    int empId;
    String empName;
    String age;
    String job;
    String deptName;

    // 成员方法
    void eat() {
        System.out.println(empName + "的吃饭行为");
    }

    /*
     * 所有员工都具备工作的行为,但是不能一概而论
     *   同样都是销售工作行为
     *       有的员工工作不用任何帮助都可以完成;
     *       有的员工只需要某个人指导一下即可;
     *       有的员工不仅需要指导,还需要投入金钱请客户吃饭;
     * 方法重载:
     *   在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型、参数顺序不同即可。
     *       void work() / work(String name) 参数个数不同
     *       work(String name) / work(double money)  参数类型不同
     *       void work(double money,String name) / work(String name,double money) 参数顺序不同
     * */
    void work() {
        System.out.println("我是老司机,全凭一张嘴,完全搞定客户!");
    }

    void work(String name) {
        System.out.println("我是熟练工,但是我需要" + name + "的帮助才可以搞定客户!");
    }

    void work(double money) {
        System.out.println("我是熟练工,但是我需要" + money + "元带客户吃喝玩乐才可以搞定!");
    }

    void work(double money, String name) {
        System.out.println("我是新手,我需要" + money + "元带客户吃喝玩乐,并且需要" + name + "的帮助才可以搞定!");
    }

    void work(String name, double money) {
        System.out.println("我是新手,我需要" + money + "元带客户吃喝玩乐,并且需要" + name + "的帮助才可以搞定!");
    }

    void sleep() {
        System.out.println(empName + "的睡觉行为");
    }
}

        使用重载方法,可以为编程带来方便。
        例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下:
                public void println(byte x)
                public void println(short x)
                public void println(int x)
                public void println(long x)
                public void println(float x)
                public void println(double x)
                public void println(char x)
                public void println(double x)
                public void println()
        ……

5.5 可变个数的形参

        JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
        JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
                public static void test(int a ,String[] books);
        JDK 5.0开始:采用可变个数形参来定义方法,传入多个同一类型变量
               public static void test(int a ,String…books);
    
说明:
        1. 声明格式:方法名(参数的类型名 ...参数名)
        2. 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个或多个
        3. 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
        4. 可变参数方法的使用与方法参数部分使用数组是一致的
        5. 方法的参数部分有可变形参,需要放在形参声明的最后
        6. 在一个方法的形参位置,最多只能声明一个可变个数形参

public class MethodArgsTest {

    public static void main(String[] args) {

        MethodArgsTest test = new MethodArgsTest();
        test.show(12);
        // test.show("hello");
        // test.show("hello","world");
        // test.show();

        test.show(new String[]{"AA", "BB", "CC"});
    }

    public void show(int i) {

    }

    public void show(String s) {
        System.out.println("show(String)");
    }

    public void show(String... strs) {
        System.out.println("show(String ... strs)");

        for (int i = 0; i < strs.length; i++) {
            System.out.println(strs[i]);
        }
    }
    //不能与上一个方法同时存在
    //	public void show(String[] strs){
    //		
    //	}

    //The variable argument type String of the method 
    //show must be the last parameter
    //	public void show(String ...strs,int i){
    //		
    //	}

}

5.6 方法中的参数传递机制

        概述:
                方法,必须由其所在类或对象调用才有意义。当我们要调用一个方法时,我们会把指定的数据,传递给方法中的参数,这样方法中的参数就拥有了这个指定的数据。这种传递方式,被称为参数传递。

        参数传递的特点:
                声明方法时,参数列表中的变量,是形式参数,简称形参。
                调用方法时,传给方法的数值,是实际参数,简成实参。

        参数传递的分类:
                参数是基本数据类型时,将实参基本数据类型变量的“数据值”传递给形参,简称值传递。
                参数是引用数据类型时,将实参引用数据类型变量的“地址值”传递给形参,简称引用传递。

5.6.1 值传递

public class ValueTransferTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println("a的值:" + a + ",b的值:" + b); // a的值:10,b的值:20
        // 交换两个变量的值
       /* int temp = a;
        a = b;
        b = temp;
        System.out.println("a的值:" + a + ",b的值:" + b);*/

        // 交换两个变量的值,是经常要执行的操作,那么我们就可以封装为方法
        ValueTransferTest test = new ValueTransferTest();
        // 参数是基本数据类型时,将实参基本数据类型变量的“数据值”传递给形参,简称值传递。
        test.swap(a, b);
        System.out.println("a的值:" + a + ",b的值:" + b); // a的值:10,b的值:20
    }

    void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    }
}

5.6.2 引用传递

        概述:
                方法中的参数是引用数据类型时,方法中传递的不是具体的数据,而是传递的是引用的地址,因此不需要返回数据。
                参数指向的是堆内存中引用数据类型的对象的地址。把引用数据类型的参数(堆内存中的地址)比喻成一把钥匙,把引用数据类型的对象比喻成一个房子,只要有相同钥匙的人,房间对于这些人是共享的,这些人都可以对这个房间进行操作。

代码示例:

public class ArgsTransferTest3 {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;

        /*int[] arr = new int[]{a,b};
        System.out.println("a的值:" + arr[0] + ",b的值:" + arr[1]); // a的值:10,b的值:20
        ArgsTransferTest3 test = new ArgsTransferTest3();
        test.swap(arr);
        System.out.println("a的值:" + arr[0] + ",b的值:" + arr[1]); // a的值:20,b的值:10*/

        DataSwap dataSwap = new DataSwap();
        dataSwap.a = a;
        dataSwap.b = b;
        System.out.println("a的值:" + dataSwap.a + ",b的值:" + dataSwap.b); // a的值:10,b的值:20

        ArgsTransferTest3 test = new ArgsTransferTest3();
        test.swap(dataSwap);
        System.out.println("a的值:" + dataSwap.a + ",b的值:" + dataSwap.b); // a的值:20,b的值:10
    }

    void swap(int[] arr) {
        int temp = arr[0];
        arr[0] = arr[1];
        arr[1] = temp;
    }

    void swap(DataSwap dataSwap) {
        int temp = dataSwap.a;
        dataSwap.a = dataSwap.b;
        dataSwap.b = temp;
    }
}

class DataSwap {
    int a;
    int b;
}

值传递和引用传递示例代码:

public class Compare {
    public static void main(String[] args) {

        int x = 1000;
        int values[] = {111, 222, 333, 444, 555};

        Compare com = new Compare();
        // void test(int num,int arr[])
        // 参数传递: 把x的值传递给num; 把values的值传递给arr
        // x是基本数据类型,那么发生了值传递,相当于把1000复制了一份给num,那么修改num的值不会影响到原数据;
        // values是引用数据类型,那么发生了引用传递,相当于把arr指向了values的地址,那么修改arr会影响到原数据;
        com.test(x, values); // 我们在test()里面对传递进来的值进行了修改

        // 在方法里面对传递进来的值进行修改,影不影响原数据?
        System.out.println("\n=========================");
        System.out.println("原数据x:" + x);
        System.out.print("原数据values:");
        for (int n : values) {
            System.out.print(n + "\t");
        }

    }

    // num是基本数据类型; arr是引用数据类型
    void test(int num, int arr[]) {
        System.out.println("num传递进来的值是:" + num);
        System.out.print("arr传递进来的值是:");
        for (int n : arr) {
            System.out.print(n + "\t");
        }
        // 给num重新赋值
        num = 9999;

        // 给arr下标2重新赋值
        arr[2] = 666;

        System.out.println("\n方法里面修改过后num的值是:" + num);
        System.out.print("方法里面修改过后arr的值是:");
        for (int n : arr) {
            System.out.print(n + "\t");
        }
    }

}

5.6.3 值传递和引用传递内存解析

基本数据类型的参数传递

public static void main(String[] args) {
    int x = 5;
    System.out.println("修改之前x = " + x);// 5
    // x是实参
    change(x);
    System.out.println("修改之后x = " + x);// 5
}

public static void change(int x) {
    System.out.println("change:修改之前x = " + x);
    x = 3;
    System.out.println("change:修改之后x = " + x);
}

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第25张图片

 引用数据类型的参数传递

public static void main(String[] args) {
    Person obj = new Person();
    obj.age = 5;
    System.out.println("修改之前age = " + obj.age);// 5
    // x是实参
    change(obj);
    System.out.println("修改之后age = " + obj.age);// 3
}

public static void change(Person obj) {
    System.out.println("change:修改之前age = " + obj.age);
    obj.age = 3;
    System.out.println("change:修改之后age = " + obj.age);
}

// 其中Person类定义为:
class Person{
    int age; 
}

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第26张图片

public static void main(String[] args) {
    Person obj = new Person();
    obj.age = 5;
    System.out.println("修改之前age = " + obj.age);// 5
    // x是实参
    change(obj);
    System.out.println("修改之后age = " + obj.age);// 5
}
public static void change(Person obj) {
    obj = new Person();
    System.out.println("change:修改之前age = " + obj.age);
    obj.age = 3;
    System.out.println("change:修改之后age = " + obj.age);
}
// 其中Person类定义为:
class Person{
    int age; 
}

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第27张图片

 方法参数传递练习题:

1、下面程序的输出结果,并分析内存存储模型。

public class TransferTest1 {
    public void swap(int a, int b) {
        int tmp = a;
        a = b;
        b = tmp;
        System.out.println("swap方法里,a的值是" + a + ";b的值是" + b);
    }

    public static void main(String[] args) {
        TransferTest1 test = new TransferTest1();
        int a = 5;
        int b = 10;
        test.swap(a, b);
        System.out.println("交换结束后,变量a的值是" + a + ";变量b的值是" + b);
    }
}

swap方法里,a的值是10;b的值是5
交换结束后,变量a的值是5;变量b的值是10

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第28张图片

class DataSwap {
    public int a;
    public int b;
}

public class TransferTest2 {
    public static void swap(DataSwap ds) {
        int temp = ds.a;
        ds.a = ds.b;
        ds.b = temp;
        System.out.println("swap方法里,a Field的值是" + ds.a + ";b Field的值是" + ds.b);
    }

    public static void main(String[] args) {
        DataSwap ds = new DataSwap();
        ds.a = 5;
        ds.b = 10;
        swap(ds);
        System.out.println("交换结束后,a Field的值是" + ds.a + ";b Field的值是" + ds.b);
    }
}

swap方法里,a Field的值是10;b Field的值是5
交换结束后,a Field的值是10;b Field的值是5

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第29张图片

public class TransferTest3 {
    public static void main(String args[]) {
        TransferTest3 test = new TransferTest3();
        test.first();
    }

    public void first() {
        int i = 5;
        Value v = new Value();
        v.i = 25;
        second(v, i);
        System.out.println(v.i);
    }

    public void second(Value v, int i) {
        i = 0;
        v.i = 20;
        Value val = new Value();
        v = val;
        System.out.println(v.i + " " + i);
    }
}

class Value {
    int i = 15;
}

15 0
20

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第30张图片

5.7 递归(recursion)方法

5.7.1 引子

        大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine。中文译为:人理解迭代,神理解递归。毋庸置疑地,递归确实是一个奇妙的思维方式。对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代码的简洁,但要想真正领悟递归的精髓、灵活地运用递归思想来解决问题却并不是一件容易的事情。在正式介绍递归之前,我们对递归和循环的生动解释:

        递归:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开它。若干次之后,你打开面前的门后,发现只有一间屋子,没有门了。然后,你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这你把钥匙打开了几扇门。

        循环:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门(若前面两扇门都一样,那么这扇门和前两扇门也一样;如果第二扇门比第一扇门小,那么这扇门也比第二扇门小,你继续打开这扇门,一直这样继续下去直到打开所有的门。但是,入口处的人始终等不到你回去告诉他答案。

5.7.2 递归的内涵

1、定义 (什么是递归?)

        在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。
    
        方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。

        递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。

2、递归思想的内涵(递归的精髓是什么?)

        正如上面所描述的场景,递归就是有去(递去)有回(归来),如下图所示。“有去”是指:递归问题必须可以分解为若干个规模较小,与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决,就像上面例子中的钥匙可以打开后面所有门上的锁一样;“有回”是指:这些问题的演化过程是一个从大到小,由近及远的过程,并且会有一个明确的终点(临界点),一旦到达了这个临界点,就不用再往更小、更远的地方走下去。最后,从这个临界点开始,原路返回到原点,原问题解决。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第31张图片

        更直接地说,递归的基本思想就是把规模大的问题转化为规模小的相似的子问题来解决。特别地,在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况,这也正是递归的定义所在。格外重要的是,这个解决问题的函数必须有明确的结束条件,否则就会导致无限递归的情况。

3、用归纳法来理解递归

        数学都不差的我们,第一反应就是递归在数学上的模型是什么,毕竟我们对于问题进行数学建模比起代码建模拿手多了。观察递归,我们会发现,递归的数学模型其实就是数学归纳法,这个在高中的数列里面是最常用的了,下面回忆一下数学归纳法。

   数学归纳法适用于将解决的原问题转化为解决它的子问题,而它的子问题又变成子问题的子问题,而且我们发现这些问题其实都是一个模型,也就是说存在相同的逻辑归纳处理项。当然有一个是例外的,也就是归纳结束的那一个处理方法不适用于我们的归纳处理项,当然也不能适用,否则我们就无穷归纳了。总的来说,归纳法主要包含以下三个关键要素:
                步进表达式:问题蜕变成子问题的表达式;
                结束条件:什么时候可以不再使用步进表达式;
                直接求解表达式:在结束条件下能够直接计算返回值的表达式;

        事实上,这也正是某些数学中的数列问题在利用编程的方式去解决时可以使用递归的原因,比如著名的斐波那契数列问题。

4、递归的三要素

        在我们了解了递归的基本思想及其数学模型之后,我们如何才能写出一个漂亮的递归程序呢?主要是把握好如下三个方面:

        1、明确递归终止条件;
                我们知道,递归就是有去有回,既然这样,那么必然应该有一个明确的临界点,程序一旦到达了这个临界点,就不用继续往下递去而是开始实实在在的归来。换句话说,该临界点就是一种简单情境,可以防止无限递归。

        2、给出递归终止时的处理办法;
                我们刚刚说到,在递归的临界点存在一种简单情境,在这种简单情境下,我们应该直接给出问题的解决方案。一般地,在这种情境下,问题的解决方案是直观的、容易的。

        3、提取重复的逻辑,缩小问题规模。
                我们在阐述递归思想内涵时谈到,递归问题必须可以分解为若干个规模较小、与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决。从程序实现的角度而言,我们需要抽象出一个干净利落的重复的逻辑,以便使用相同的方式解决子问题。

5、递归的应用场景

        在我们实际学习工作中,递归算法一般用于解决三类问题:
                1、问题的定义是按递归定义的(斐波拉契数列,阶乘,…);
                2、问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);
               3、数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)。

5.7.3 递归与循环

        递归与循环是两种不同的解决问题的典型思路。

        递归通常很直白地描述了一个问题的求解过程,因此也是最容易被想到解决方式。循环其实和递归具有相同的特性,即做重复任务,但有时使用循环的算法并不会那么清晰地描述解决问题步骤。

        单从算法设计上看,递归和循环并无优劣之别。然而,在实际开发中,因为函数调用的开销,递归常常会带来性能问题,特别是在求解规模不确定的情况下;而循环因为没有函数调用开销,所以效率会比递归高。递归求解方式和循环求解方式往往可以互换,也就是说,如果用到递归的地方可以很方便使用循环替换,而不影响程序的阅读,那么替换成循环往往是好的。

        问题的递归实现转换成非递归实现一般需要两步工作:
                1、自己建立“堆栈(一些局部变量)”来保存这些内容以便代替系统栈,比如树的三种非递归遍历方式;
                2、把对递归的调用转变为对循环处理。

        在下文中我们将给出递归算法的一些经典应用案例,对于这些案例的实现,我们一般会给出递归和非递归两种解决方案,以便大家体会。

5.7.4 经典递归问题实战

阶乘

public class Factorial {
    /**     
     * @description 阶乘的递归实现
     */
    public static long f(int n){
        if(n == 1)   // 递归终止条件 
            return 1;    // 简单情景

        return n*f(n-1);  // 相同重复逻辑,缩小问题的规模
    }

    /**     
     * @description 阶乘的非递归实现
     */
    public static long f_loop(int n) {
        long result = n;
        while (n > 1) {
            result = result * n;
            n--;
        }
        return result;
    }
}

斐波纳契数列

        斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……;在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>2)。

public class FibonacciSequence {

    /**
     * @description 经典递归法求解
     * 斐波那契数列如下:
     * 1,1,2,3,5,8,13,21,34,...
     * 那么,计算fib(5)时,需要计算1次fib(4),2次fib(3),3次fib(2),调用了2次fib(1)*,即:
     * fib(5) = fib(4) + fib(3)
     * fib(4) = fib(3) + fib(2) ;fib(3) = fib(2) + fib(1)
     * fib(3) = fib(2) + fib(1)
     * 这里面包含了许多重复计算,而实际上我们只需计算fib(4)、fib(3)、fib(2)和fib(1)各一次即可,
     * 后面的optimizeFibonacci函数进行了优化,使时间复杂度降到了O(n).
     */
    public static int fibonacci(int n) {
        if (n == 1 || n == 2) {     // 递归终止条件
            return 1;       // 简单情景
        }
        return fibonacci(n - 1) + fibonacci(n - 2); // 相同重复逻辑,缩小问题的规模
    }

    /**
     * @description 非递归解法:有去无回
     */
    public static int fibonacci_loop(int n) {

        if (n == 1 || n == 2) {
            return 1;
        }

        int result = -1;
        int first = 1;      // 自己维护的"栈",以便状态回溯
        int second = 1;     // 自己维护的"栈",以便状态回溯

        for (int i = 3; i <= n; i++) { // 循环
            result = first + second;
            first = second;
            second = result;
        }
        return result;
    }


}

回文字符串的判断

public class PalindromeString {
    /**
     * @description 递归判断一个字符串是否是回文字符串
     */
    public static boolean isPalindromeString_recursive(String s) {
        int start = 0;
        int end = s.length() - 1;
        if (end > start) {   // 递归终止条件:两个指针相向移动,当start超过end时,完成判断
            if (s.charAt(start) != s.charAt(end)) {
                return false;
            } else {
                // 递归调用,缩小问题的规模
                return isPalindromeString_recursive(s.substring(start + 1).substring(0, end - 1));
            }
        }
        return true;
    }


    /**
     * @description 循环判断回文字符串
     */
    public static boolean isPalindromeString_loop(String str){
        boolean b = true;
        /*
        * 0 4
        * 1 3
        * */
        for(int i = 0;i < str.length() / 2;i++){
            if(str.charAt(i) != str.charAt(str.length() - 1 - i)){
                b = false;
                break;
            }
        }
        return b;
    }
}

六、类的成员之三:构造器

6.1 构造器语法格式

        上述我们提到,以下语法用来创建对象
                类名称 对象名称 = new 类名称 () ; 
        其实本质是调用了构造函数:
                关键字 new 通常称为创建运算符,用于分配对象内存,并将该内存初始化为缺省值。
                一旦 new 完成分配和初始化内存,它就将调用构造函数来执行对象初始化。
        注:当创建一个类时,只是创建了一种新的数据类型。对象是类的一个实例。创建类不是创建对象。

    构造函数是对象的一种特殊的方法(有歧义),主要表现:
        构造函数的方法名与类名相同。
        构造函数没有返回类型。(与声明为void不同)
        构造函数可以不写,系统会默认赋予一个无参构造函数;
        构造函数的主要作用:
			1、创建对象。
			2、完成对对象的初始化工作。
		构造函数不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

	语法格式:
		修饰符 类名 (参数列表) {
        	初始化语句;
        } 

	根据参数不同,构造器可以分为如下两类:
		隐式无参构造器(系统默认提供)
		显式定义一个或多个构造器(无参、有参)

示例代码:

        我们人出生的时候,有些人一出生之后再起名字的,但是有些人一旦出生就已经起好名字的。那么我们在 java 里面怎么在对象一旦创建就赋值呢?

public class Person {

    String name; // 姓名
    int age; // 年龄

    public static void main(String[] args) {
        // 创建了Person类型的p对象,这里的Person()其实就是构造器;而且是系统默认提供的;
        Person p = new Person();

        System.out.println("姓名: " + p.name + " 年龄: " + p.age); // name = null, age = 0;
        // 这个小孩刚出生的时候没有姓名和年龄
    }
}    
public class Person {

    String name; // 姓名
    int age; // 年龄

    // 构造方法
    Person(String pname, int page) {
        name = pname; // 给对象赋予name值
        age = page; // 给对象赋予age值
    }

    public static void main(String[] args) {
        // 创建了Person类型的p对象,并且调用构造方法赋予该对象属性值
        Person p = new Person("张三", 1);
        System.out.println("姓名: " + p.name + " 年龄: " + p.age); // name = 张三, age = 1;
        // 这个小孩刚出生的时候已经有了姓名和年龄
    }
}

说明:

        Java语言中,每个类都至少有一个构造器。
        默认构造器的修饰符与所属类的修饰符一致。
        一旦显式定义了构造器,则系统不再提供默认构造器。
        一个类可以创建多个重载的构造器。
        父类的构造器不可被子类继承。

public class Person {

    String name; // 姓名
    int age; // 年龄

    // 全参构造方法
    Person(String pname, int page) {
        name = pname; // 给对象赋予name值
        age = page; // 给对象赋予age值
    }

    // 无参构造方法
    Person() {

    }

    public static void main(String[] args) {
        Person p = new Person("张三", 1); 
        /*
          根据创建对象的实参个数,jvm回去寻找合适的构造方法,
          两个实参所有会调用含有两个参数的构造方法.Person(String name,int age)
         */
        System.out.println("姓名: " + p.name + " 年龄: " + p.age); // name = 张三, age = 1;
    }
}

构造函数与普通函数的区别:

        (1)普通函数是用于定义对象应该具备的功能。而构造函数定义的是,对象在调用功能之前,在建立时,应该具备的一些内容。也就是对象的初始化内容。

        (2)构造函数是在对象建立时由 jvm 调用, 给对象初始化。普通函数是对象建立后,当对象调用该功能时才会执行。

        (3)普通函数可以使用对象多次调用,构造函数就在创建对象时调用。

        (4)构造函数的函数名要与类名一样,而普通的函数只要符合标识符的命名规则即可。

        (5)构造函数没有返回值类型。

6.2 构造器重载

        构造器一般用来创建对象的同时初始化对象。如:
        class Person{
            String name;
            int age;
            public Person(String n , int a){ name=n; age=a;}
        }

        构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
        构造器重载,参数列表必须不同

示例代码:

public class Person {
    private String name;
    private int age;
    private Date birthDate;

    public Person(String n, int a, Date d) {
        name = n;
        age = a;
        birthDate = d;
    }

    public Person(String n, int a) {
        name = n;
        age = a;
    }

    public Person(String n, Date d) {
        name = n;
        birthDate = d;
    }

    public Person(String n) {
        name = n;
        age = 30;
    }
}

构造器练习题:

(1)定义Student类,有4个属性:
        String name; 
        int age; 
        String school; 
        String major;
(2)定义Student类的3个构造器:
        第一个构造器Student(String n, int a)设置类的name和age属性;
        第二个构造器Student(String n, int a, String s)设置类的name, age 和school属性;
        第三个构造器Student(String n, int a, String s, String m)设置类的name, age ,school和major属性;
(3)在main方法中分别调用不同的构造器创建的对象,并输出其属性值。

6.3 总结:属性赋值过程

        截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位置,并指明赋值的先后顺序。

        赋值的位置:
                ① 默认初始化
                ② 显式初始化
                ③ 构造器中初始化
                ④ 通过“对象.属性“或“对象.方法”的方式赋值
    
        赋值的先后顺序:
                ① - ② - ③ - ④

public class UserTest {
    public static void main(String[] args) {
        User u = new User();

        System.out.println(u.age);

        User u1 = new User(2);

        System.out.println(u1.age);

        u1.setAge(3);
        u1.setAge(5);

        System.out.println(u1.age);
    }
}

class User {
    String name;
    int age = 1;

    public User() {

    }

    public User(int a) {
        age = a;
    }

    public void setAge(int a) {
        age = a;
    }
}

七、关键字:this

        this是什么?
        在Java中,this关键字比较难理解,它的作用和其词义很接近。
                它在方法内部使用,即这个方法所属对象的引用;
                它在构造器内部使用,表示该构造器正在初始化的对象。
    
        this 可以调用类的属性、方法和构造器。
        什么时候使用this关键字呢?
                当在方法内需要用到调用该方法的对象时,就用this。
                具体的:我们可以用this来区分属性和局部变量。
                比如:this.name = name;

使用this,调用属性、方法

        1、在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
        2、当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。
        3、使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。

class Person { // 定义Person类
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void getInfo() {
        System.out.println("姓名:" + name);
        this.speak();
    }

    public void speak() {
        System.out.println("年龄:" + this.age);
    }
}
class Person { // 定义Person类
    String name;

    Person(String name) {
        this.name = name;
    }

    public void getInfo() {
        System.out.println("Person类 --> " + this.name);
    }

    public boolean compare(Person p) {
        return this.name == p.name;
    }
}

// 当前正在操作本方法的对象称为当前对象。
public class PersonTest {
    public static void main(String args[]) {
        Person per1 = new Person("张三");
        Person per2 = new Person("李四");
        per1.getInfo(); // 当前调用getInfo()方法的对象是per1
        per2.getInfo(); // 当前调用getInfo()方法的对象是per2
        boolean b = per1.compare(per2);
    }
}

使用this调用本类的构造器

        4.this可以作为一个类中构造器相互调用的特殊格式。

class Person { // 定义Person类
    private String name;
    private int age;

    public Person() { // 无参构造器
        System.out.println("新对象实例化");
    }

    public Person(String name) {
        this(); // 调用本类中的无参构造器
        this.name = name;
    }

    public Person(String name, int age) {
        this(name); // 调用有一个参数的构造器
        this.age = age;
    }

    public String getInfo() {
        return "姓名:" + name + ",年龄:" + age;
    }
}

思考:
        当我们初始化对象的时候,想要执行某些重复的代码,怎么办?
                1、把重复代码定义在一个成员方法中,调用成员方法;
                2、把重复代码定义在一个构造方法中,调用构造方法;

注意:
        可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
        明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器。
        如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"。
        "this(形参列表)"必须声明在类的构造器的首行!
        在类的一个构造器中,最多只能声明一个"this(形参列表)"。

        JDK示例:
                HashMap、String等

八、关键字:package、import

8.1 关键字—package

	起因:在我们设计一个程序的时候(尤其是多人合作),会写一些类来实现功能,但是往往会有重名的现象发生,为了解决这个问题,则专门设计了包。(还有其他作用)

	简单理解:不同的城市之间存在相同名字的小区,用城市名则可以区分这些重名小区,城市名就可以理解为上面的包,小区则可以看做是重名的类,通过这个前缀,解决了重名问题。

	为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。其实可以理解为就是文件夹,并且使用了树形目录的存储方式。

	package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。

	它的格式为:
		package 顶层包名.子包名 ;

	举例:
		文件系统结构:pack1\pack2\PackageTest.java
		package pack1.pack2; //指定类PackageTest属于包pack1.pack2
        public class PackageTest{
            public void display(){
            	System.out.println("in method display()");
            }
        }

	包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;
	包通常用小写单词标识。通常使用所在公司域名的倒置:

包的作用:

        包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式。
        包可以包含类和子包,划分项目层次,便于管理。
        解决类命名冲突的问题。
        控制访问权限。

JDK中主要的包介绍

        1. java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
        2. java.net----包含执行与网络相关的操作的类和接口。
        3. java.io ----包含能提供多种输入/输出功能的类。
        4. java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
        5. java.text----包含了一些java格式化相关的类
        6. java.sql----包含了java进行JDBC数据库编程的相关类/接口
        7. java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。

8.2 关键字—import

	为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。
	语法格式:import 包名.类名;

	应用举例:
		import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
        public class PackTest{
            public static void main(String args[]){
                Test t = new Test(); //Test类在pack1.pack2包中定义
                t.display();
            }
        }

	注意:
        1. 在源文件中使用import显式的导入指定包下的类或接口。
        2. 声明在包的声明和类的声明之间。
        3. 如果需要导入多个类或接口,那么就并列显式多个import语句即可。
        4. 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
        5. 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
        6. 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
        7. 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
        8. import static组合的使用:调用指定类或接口下的静态的属性或方法
import static java.lang.System.*;
import static java.lang.Math.*;

public class PackageImportTest {
    public static void main(String[] args) {

        out.println("hello");

        long num = round(123.434);
    }
}

九、课后练习题

程序题

1、编写教师类和学生类,并通过测试类创建对象进行测试。

【Java基础系列教程】第六章 Java面向对象详解(一)_类和类的成员_第32张图片

 2、根据代码,画出内存图。

class Car{
    String color = "red";
    int num = 4;
    void show(){
        System.out.println("color="+color+"..num="+num);
    }
}

class CarTest {
    public static void main(String[] args) {
        Car c1 = new Car(); //建立对象c1
        Car c2 = new Car(); //建立对象c2
        c1.color = "blue"; //对对象的属性进行修改
        c1.show(); //使用对象的方法
        c2.show();
    } 
}

class Person{//人类
    //1.属性
    String name;//姓名
    int age = 1;//年龄
    boolean isMale;//是否是男性
}

class PersonTest{
    main(){
        Person p = new Person();
        Person p1 = new Person();
        p1.name = "Tom";
        Person p2 = p1;
    }
}

3、方法练习;
        编写程序,声明一个method方法,在方法中打印一个10*8 的*型矩形,在main方法中调用该方法。
        修改上一个程序,在method方法中,除打印一个10*8的*型矩形外,再计算该矩形的面积,并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
        修改上一个程序,在method方法提供m和n两个参数,方法中打印一个m*n的*型矩形,并计算该矩形的面积, 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。

public class RectTest {
    public static void main(String[] args) {
        RectTest test = new RectTest();

        //3.1测试
        //		test.method();

        //3.2测试
        //方式一:
        //		int area = test.method();
        //		System.out.println("面积为:" + area);

        //方式二:
        //		System.out.println(test.method());

        //3.3测试
        int area = test.method(12, 10);
        System.out.println("面积为:" + area);

    }

    //3.1 
    //	public void method(){
    //		for(int i = 0;i < 10;i++){
    //			for(int j = 0;j < 8;j++){
    //				System.out.print("* ");
    //			}
    //			System.out.println();
    //		}
    //	}

    //3.2 
    //	public int method(){
    //		for(int i = 0;i < 10;i++){
    //			for(int j = 0;j < 8;j++){
    //				System.out.print("* ");
    //			}
    //			System.out.println();
    //		}
    //		
    //		return 10 * 8;
    //	}
    //3.3
    public int method(int m,int n){
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                System.out.print("* ");
            }
            System.out.println();
        }

        return m * n;
    }
}

4、声明一个日期类型MyDate:有属性:年year,月month,日day。创建2个日期对象,分别赋值为:你的出生日期,你对象的出生日期,并显示信息。

public class MyDateTest {
    public static void main(String[] args) {
        MyDate myDate = new MyDate();
        myDate.year = 2000;
        myDate.month = 5;
        myDate.day = 10;
        myDate.show();

        MyDate myGirlDate = new MyDate();
        myGirlDate.year = 2001;
        myGirlDate.month = 10;
        myGirlDate.day = 1;
        myGirlDate.show();
    }
}

class MyDate {
    int year;
    int month;
    int day;

    public void show() {
        System.out.println("出生年月日是:" + year + "年" + month + "月" + day + "日");
    }
}

5、定义一个int型的数组:int[] arr = new int[]{12,3,3,34,56,77,432};让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。

public class ArrayTest {
    public static void main(String[] args) {
        //正确写法1
        for(int i = arr.length – 1;i >= 0;i--){
            arr[i] = arr[i] / arr[0];
        }

        //正确写法2
        int temp = arr[0];
        for(int i= 0;i < arr.length;i++){
            arr[i] = arr[i] / temp;
        }
    }
}

6、完成如下要求:
        (1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。
        (2)定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:public void printAreas(Circle c, int time)
            在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
            例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
        (3)在main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示。

public class Circle {
	double radius;//半径
	
	//求圆的面积
	public double findArea(){
		return Math.PI * radius * radius;
	}
}

public class PassObject {

    public static void main(String[] args) {
        PassObject test = new PassObject();

        Circle c = new Circle();

        test.printAreas(c, 5);

        System.out.println("now radius is " + c.radius);
    }

    public void printAreas(Circle c, int time){

        System.out.println("Radius\t\tArea");
        int i = 1;
        for(;i <= time;i++){
            //设置圆的半径
            c.radius = i;
            double area = c.findArea();
            System.out.println(c.radius + "\t\t" + area);
        }

        //
        //		c.radius = time + 1;
        c.radius = i;

    }
}

7、自定义数组工具类
        定义类,定义求最大值、最小值、和、平均分、反转、复制、排序、遍历、查找等方法;

8、完成如下题目要求:
        public class Test {
            public static void main(String[] args) {
                int a = 10;
                int b = 10;
                // 需要在method方法被调用之后,仅打印出 a = 100,b = 200,请写出method方法的代码
                method(a,b);
                System.out.println("a = " + a);
                System.out.println("b = " + b);
            }

            // 代码编写处

        }

public static void method(int a,int b) {
    System.out.print("a=100,b=200");
    System.exit(0);
}

public static void method(int a,int b) {
    PrintStream stream = new PrintStream(System.out){
        @Override
        public void println(String x) {
            if ("a = 10".equals(x)) {
                x = "a=100";
            } else if ("b = 10".equals(x)) {
                x = "b=200";
            }
            super.println(x);
        }
    };
    System.setOut(stream);
}

9、如下程序的输出结果?
        int[] arr = new int[10];
        System.out.println(arr);// 
        
        char[] arr1 = new char[10];
        System.out.println(arr1); // 

public class ArrayPrintTest {
	public static void main(String[] args) {
		int[] arr = new int[]{1,2,3};
		System.out.println(arr);//地址值

		char[] arr1 = new char[]{'a','b','c'};
		System.out.println(arr1); //abc,分析println()

	}
}

10、输入一个数据n,计算斐波那契数列(Fibonacci)的第n个值
        1 1 2 3 5 8 13 21 34 55
        规律:一个数等于前两个数之和
        要求:计算斐波那契数列(Fibonacci)的第n个值,并将整个数列打印出来

public int Fibonacci(int n) {
    if(n == 0) {
        return 0;
    }
    if(n == 1) {
        return 1;
    }
    return Fibonacci(n-1) + Fibonacci(n-2);
}

简答题

1、面向对象思想编程内容的三条主线分别是什么?
        1、Java类及类的成员:属性、方法、构造器、代码块、内部类;
        2、面向对象的三大特征:封装性、继承性、多态性、(抽象性);
        3、抽象类、接口和其他关键字:this、super、static、final、abstract、interface、package、import等

2、面向对象的编程思想?
        面向对象的编程产生的历史原因:由于面向过程编程在构造系统时,无法解决重用,维护,扩展的问题,而且逻辑过于复杂,代码晦涩难懂,因此,人们开始想能不能让计算机直接模拟现实的环境,以人类解决问题的方法,思路,习惯和步骤来设计相应的应用程序。于是,面向对象的编程思想就产生了。
        面向对象的编程的主要思想是把构成问题的各个事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述一个事物在解决问题的过程中经历的步骤和行为。对象作为程序的基本单位,将程序和数据封装其中,以提高程序的重用性,灵活性和可扩展性。
        类是创建对象的模板,一个类可以创建多个对象。对象是类的实例化。
        类是抽象的,不占用存储空间;而对象具体的,占用存储空间。
        面向对象有三大特性:封装,继承,多态。
    ...

3、谈谈你对面向对象中类和对象的理解,并指出二者的关系?
        类是一个模型,本身不具备任何的功能,是从一系列对象中抽取出来相同特征的抽象的概念。
        对象是类的具体化,即是该类事物实实在在存在的个体。

4、类的组成部分(类的成员)?
        成员变量/属性/实例变量/域/字段
        成员方法/方法/实例方法/函数
        构造方法/构造器/构造函数
        代码块
        内部类

5、类的方法内是否可以定义变量?是否可以调用属性?是否可以定义方法?是否可以调用方法?
        是 是 否 是

6、如何理解万事万物皆对象?
        1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
        >Scanner,String等
        >文件:File
        >网络资源:URL
        2.涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。

7、方法存在的意义?方法组成的三要素?方法参数个数最多不宜超过几个(*)?一个方法中写多少行代码比较合适呢(*)?
        方法存在的意义:1. 提高代码的复用性。2. 增强代码的可维护性。
        方法组成的三要素:返回值类型、方法名、参数列表。
        方法参数个数最多不宜超过4个。
        一个方法中写 90 行代码比较合适。

8、什么是方法的重载?
        方法的重载分为:成员方法重载和构造方法重载。
        在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型、参数顺序不同即可。

9、说明Java方法中的参数传递机制的具体体现?
        参数传递的特点:
                声明方法时,参数列表中的变量,是形式参数,简称形参。
                调用方法时,传给方法的数值,是实际参数,简成实参。
        参数传递的分类:
                参数是基本数据类型时,将实参基本数据类型变量的“数据值”传递给形参,简称值传递。
                参数是引用数据类型时,将实参引用数据类型变量的“地址值”传递给形参,简称引用传递。

10、谈谈return关键字的使用?
        ① 结束方法  ② 针对于有返回值的方法,return + 返回数据

11、this关键字的理解?
        在Java中,this关键字比较难理解,它的作用和其词义很接近。
        它在方法内部使用,即这个方法所属对象的引用;
        它在构造器内部使用,表示该构造器正在初始化的对象。
        当在方法内需要用到调用该方法的对象时,就用this。
        我们还可以用this来区分属性和局部变量。

12、创建对象的两种方式以及区别?
        1、声明并实例化对象;
        2、先声明对象,然后实例化对象;

        区别:
                第一种方式: 声明变量并在堆内存分配空间,变量指向该堆内存;
                第二种方式: 先声明变量,再在堆内存分配空间,然后让变量指向该对内存;

13、new关键字的作用?
        1、在堆内存中给对象分配空间;
        2、给成员变量进行默认初始化;

14、构造函数的特点?
        1、构造函数是特殊的成员方法,构造函数的方法名与类名相同
        2、构造函数没有返回类型
        3、构造函数可以不写,系统会默认赋予一个无参构造函数;
        4、构造函数的主要作用是完成对对象成员的初始化工作

15、构造函数和成员方法的区别(*)?
        1、构造函数名要和类名一致,成员方法是自定义名称,最好是动词;
        2、构造函数没有返回值,成员方法需要定义返回值
        3、构造函数是完成对对象成员的初始化工作,成员方法代表对象的某一个行为;
        4、构造函数是当使用new关键字的时候,由jvm来调用; 成员方法是由实例化出来的对象来调用;

你可能感兴趣的:(Java基础系列教程,java,开发语言,后端,JavaSE,Java基础)