从零学Java:学习笔记06-面向对象的学习、认识垃圾回收机制、this及static

面向对象与面向过程:

  • 什么是面向对象?什么是面向过程?
    面向对象和面向过程都是对应用程序的分析、设计和开发的一种思想、它引导开发人员以最适合的角度去思考、分析程序的设计、面向过程更注重于过程、随着软件的规模扩大、程序复杂性的提高、面向过程的弊端越来越明显、后期面向对象的思想成为了目前主流的方式。
    • 面向对象(Object Oriented):
      面向对象相较于面向过程的思想、它更契合人的思维模式、面向对象的思想能够帮助我们从宏观上把握、从整体上分析整个应用程序的设计。
      面向对象的好处在于相当于在饭店里点的一份盖浇饭、盖浇饭的‘菜’,'饭’进行了分离、提高了制作盖浇饭的灵活度、可维护性高、两者之间的耦合度低。
    • 面向过程(Procedure Oriented):
      面向过程更注重于过程、它更适用于简单的、不需要互相协作的程序开发、以面向过程的思想设计应用程序、程序功能(散列)的完善进行一步一步的组合完善。

需要注意的是、程序的开发需要面向对象和面向过程两者的相辅相成、面向对象让应用程序具有宏观把握、而面向过程注重于微观的细节处理。

类和对象:

  • 什么是类?什么是对象?
    类实际是对对象的一种抽象表示、它相当于对象的模板、根据某些事物(哺乳动物、飞禽走兽)具有的共同特征、我们将这些共同的特征抽象以模板的形式抽象为类、以类的模板创建具有共同特征的每个对象。类与对象的关系相当于对象是实际的产品、而类是产品设计的蓝图、产品设计必然是需要蓝图将产品细节一 一进行说明、最后根据蓝图设计产品。类在java中称之为class、每个类都是被class关键字所修饰的。
    对象是根据类的模板而生产出的实例、每一个对象都代表着一个类的实例。比如手机、手机有最基本的发送短信、拨号功能及手机的配置、这些都是对一个对象的描述(类)、基本的功能参数每台手机都应该具备、每台手机的参数、价格都会有所不同、但它们都是以手机的类模板设计出来的。对象在Java中称之为Object、或是 instance(实例)、两者表达的都是对象。
/**
 * 这是创建一个类、类是对象的模板、创建对象实例的蓝图、类被class关键字所修饰、我们可以看作被class关键字所修饰的都是类
 */
public class Student {


    // 类的属性 field (实际上就是变量) 属性的定义我们根据需求而定
    // 如我们需要创建学生的对象实例、那么就需要在学生类中定义学生具备哪些属性特征。

    int id; // 学生的学号 每个学生都应该具备学号
    int name; // 学生的姓名 每个学生都应该具备姓名
    int age; // 学生的年龄 每个学生都应该具备年龄

    // 学生的属性特征我们定义好了 这时候需要考虑的是 学生可以有哪些行为?
    // 如吃饭、学习、玩游戏是每个学生都具备的行为、对象的行为我们以方法函数的形式去定义

    // 学生吃饭的行为
    public void eat(){
        System.out.println("吃饭!");
    }

    // 学生学习的行为
    public void learn(){
        System.out.println("学习!");
    }

    // 学生玩游戏的行为
    public void playGame(){
        System.out.println("玩游戏!");
    }

    // 对象的模板已经构建完成、我们如何根据类的模板创建对象呢?
    // Java中使用new的关键字来根据类的模板创建一个对象

    // 程序的主入口
    public static void main(String [] args){

        // 根据类的模板创建对象
        // 创建对象的语法格式:
        //          类名 变量 = new 类名();
        // 变量就是该类的实例对象 我们根据这个变量就可以操作这个对象
        Student student = new Student();
        // 方法调用的语法格式:
        //          变量.方法函数名(); // 与前面学习的方法调用是一样的 如果有参数则传递参数 如果没有则不用传递
        // 学生饿了 调用它的(eat)吃饭方法
        student.eat();
    }

面向对象的内存分析:

了解Java虚拟机如何加载、运行程序有助于我们理解Java语言的特点或其他语言、内存图大致都是差不多的。当然由于现在是入门的阶段、所以也不推荐学习者太过于去深究、目前只需要知道Java程序在运行的时候、存储数据的内存是如何去划分的就可以了。
我们知道、Java程序是运行在Java虚拟机上的、而Java虚拟机的内存大致可以分为两块内存区域:

  • 栈内存区域(stack):

    • 栈内存区域的特点:
      1.栈内存区域所描述的是一个方法执行的内存模型、每个方法被调用的时候都会在栈内存中创建一个栈帧(栈帧实际指的是还没有完成方法函数)、栈帧存储着局部变量、操作数、方法出口等。
      2.JVM虚拟机会为运行的程序的每个线程都创建独立的栈、线程是私有的、多个线程之间各自独立、不能共享、栈内存中存储实际的参数、局部变量等。
      3.栈内存区域的存储特点是“先进后出”,“后进先出”、枪械中的子弹夹概念。
      4.栈内存区域由系统自动分配、速度快、效率高、是一块连续的内存空间。
  • 堆内存区域(heap):

    • 堆内存区域的特点:
      1.堆内存区域用于存储创建好的对象
      2.JVM虚拟机中只有一个堆内存、它是被所有线程共享的
      3.堆内存不是一块儿连续的内存空间、分配内存灵活、但效率低。
  • 静态区(method area):
    静态区也称之为方法区、我们可以将静态内存区域划分到堆内存中。

    • 静态内存区域的特点:
      1.JVM虚拟机中只有一个静态内存区域、它是被所有线程共享的
      2.静态区存储类信息(Class信息)、静态变量、常量、字符串常量等相关的信息
public class Person {

    int id;  // 身份证号
    String name; // 姓名
    int age; // 年龄
    Clothing clothing; // 服装

    public void show(){
        System.out.println("我是"+name+",我今年"+age+"岁穿着"+clothing.brand+"的衣服");
    }

    public static void main(String [] args){
        // 创建person对象
        Person person = new Person();
        person.id=88485705;
        person.age=18;
        person.name="奥黛丽·赫本";
        Clothing c = new Clothing();
        c.brand="纪梵希";
        person.clothing = c;

        person.show();
    }
}


class Clothing {

    String brand;// 品牌
}
/**
 *
 * 1.javac编译java的源文件
 * 2.执行java命令将.class文件加载到JVM虚拟机
 * 3.JVM虚拟机开辟存储空间、执行main方法、在栈内存中开启栈帧、main方法(开启栈帧)执行完毕则关闭
 * 4.通过Person的无参构造器(开启栈帧)创建person对象、new出来的存放到堆内存中、由于属性没有进行初始化、系统默认为成员变量进行初始化
 * 5.通过person的变量指向堆内存的地址、修改对象的变量信息
 * 6.通过Clothing的无参构造器(开启栈帧)创建Clothing对象、与Person对象一样。
 * 7.为对象的属性赋值
 * 8.将对象的引用赋给person的clothing属性
 * 9.调用show方法、创建栈帧、方法结束关闭
 * 10.main方法执行完毕后栈帧销毁、JVM虚拟机关闭、内存数据荡然无存。
 */

简单的内存分析图:

在这里插入图片描述

构造器:

  • 什么是构造器?
    构造器也叫构造方法(Constructor)、它是被用于创建类模板的对象实例的特殊方法、Java语言创建某个对象实例的时候通常使用new关键字调用该类的构造器进行创建的。
  • [权限修饰符] 类名(形式参数列表){
    		// 构造器初始化代码
    }
    
  • 构造器的特点:
    1.构造器通过new的方式进行调用
    2.构造器虽然也是方法、但它比较特殊、它允许有return语句(因为return关键字单独使用也能够表示方法的结束)、但它不能够返回任何值、因为通常构造器返回的值都应该是该类、也因为如此定义构造器的时候也不需要写返回值类型、void也不需要。
    3.构造器也能够实现重载、重载的条件与普通方法函数的条件一样、方法名一致、但形式参数的列表个数或数据类型等不同。
    4.一个类的模板中如果没有构造器的话、编译器会自动为该类添加一个无参的构造器、如果已经定义了构造器、编译器就不会自动添加了。
    5.构造器的名字必须和类名保持一致、大小写也必须一致。
public class Main {

    int id; // id
    String name; // 名字
    int age; // 年龄

    // 每个类都有构造方法、无非是有参构造方法和无参构造方法而已 如果我们没写构造方法、编译器会帮我们自动添加无参数的构造方法、如果写了构造方法、编译器则不会帮我们自动添加了。
    // 建议初学者写类的时候无论是否只需要有参构造方法就足够 都将无参构造方法加上 加上也不会报错 后期的学习许多技术都需要类的无参构造方法 没有加会出现很多奇怪错误
    // 无参构造方法
    public Main(){

    }

    // 有参构造方法
    public Main(int id,String name,int age){
        this.id = id;
        this.name=name;
        this.age=age;
    }

    // 重载的有参构造方法
    public Main(int id,String name){
        this.id=id;
        this.name=name;
    }

    // main方法
    public static void main(String []args){

        // 以无参构造器的方式创建Main类的对象实例
        Main main1 = new Main();
        // 以有参构造器的方式创建Main类的对象实例
        Main main2 = new Main(3,"张三");
        // 以有参构造器的方式创建Main类的对象实例(重载)
        Main main3 = new Main(4,"李四",18);

    }
}

垃圾回收机制(Garbage Conllection):

Java语言引入了垃圾回收机制、解决了让开发人员头疼的内存管理的问题、如c++语言做程序开发需要时刻注意内存的回收、一旦掉以轻心程序会出现非常严重的问题。而Java语言开发程序的时候由于有垃圾回收器的存在、可以将更多的精力放在程序的业务逻辑上而不是内存管理上、极大的提高程序开效率。

  • 为什么需要垃圾回收器?
    垃圾回收器实际就是对内存的管理、对于Java来说、内存管理很大程度指的是对对象的管理、其中就包括了对对象内存的分配及资源释放。Java根据一些垃圾回收的算法对“不可达”的对象所占用的内存进行释放、提高程序的运行效率。
    垃圾回收器能够做的基本是两件事情、一是发现无用的对象、二是对无用的对象所占用的内存空间进行回收、无用的对象指的是没有任何变量引用该对象、垃圾回收器通过算法发现无用的对象、而后进行清理。
    做个比喻、c++和java都各自开了一家饭店、c++饭店的饭桌需要顾客自行收拾、而如果顾客一旦忘记收拾饭桌、那饭桌就一直没人管理、久而久之对c++饭店的生意是非常有影响的。而java饭店聘请了服务员(GC)、当某饭桌没人使用的时候、服务员会对饭桌进行清理、方便后续顾客使用。
  • 如何对对象进行内存空间分配?如果对对象进行内存空间的释放?
    通常来说、使用new关键字创建对象的实例就是在对对象分配内存空间。
    将对象实例的引用置为null、垃圾回收器就会根据垃圾回收算法负责回收所有“不可达”对象所占用的内存空间。
  • 垃圾回收器的相关算法:
    • 引用计数法:
      创建的对象都在堆内存中、堆中的每个对象都有一个引用的计数器、该对象被引用一次、计数器+1、当引用变量的值为null、计数器-1、直到计数器为0、则表示该对象是一个无用的对象、该引用计数法的优点是算法简单、缺点是循环引用无用的对象的话无法进行识别。
    • 引用可达法:
      程序的对象引用关系看作是一张联系图、从第一个节点CG ROOT开始寻找相应的节点、找到相应的节点后、继续寻找该节点的引用节点、当所有节点找遍后、剩余没有被引用到的节点都认为是无用的节点。
  • 分代的垃圾回收机制:
    分代的垃圾回收机制、不同的对象的生命周期不同、因此不同生命周期的对象也可以采取不同的回收算法、以便于提高垃圾回收的效率问题。Java将堆内存划为Eden区、Survivor 区及Old区、对象也分为三种状态:
    • 年轻代:
      所有新的对象都放置在Eden区域、当Eden区中的新生代对象达到一定数量限制的时候、则会触发垃圾回收(Minor GC)、将无用的对象进行清理、意味着剩余的对象生命周期会更长、将剩余的对象采用复制算法复制到Survivor区域、同时将Eden区域进行清空。
    • 老年代:
      重复多次(Java默认15次)Survivor区域中没有被清理的对象(都是常被引用的对象)、则会采取复制算法将这些对象复制到Old区域、这些对象的状态也变为老年代、如果老年代的对象越来越多、就需要启用Mojor GC 和Full GC对内存空间进行全面清理。
    • 持久代:
      用于存储类的信息、静态变量或方法、
  • 垃圾回收的过程:
    1.大部分新创建的对象都是存储在Eden区域中
    2.当Eden区域达到一定比例的时候、触发Mojor GC进行垃圾回收、根据算法将无用的对象进行清理、将清理后还剩余的对象根据复制算法移植到Survivor区域(有两个区域、两个区域内存大小相同、同一时刻两个区域只有一个在被使用、一个为空)中、同时将Eden区域的对象进行清空。
    3.当Eden区域再次达到一定比例的时候、根据垃圾回收的算法对Survivor区域无用的对象进行清空、剩余还存活的对象则被移植到Survivor2中、Survivor区域被清空、重复2
    4.默认重复15次之后Survivor区域中还没有清空的对象、则会被认为是很常用的对象、则会被移植到老年代Old区域。
    5.当Old区域满了、就触发一个完整的垃圾回收机制(Full GC)、对整个程序的内存空间进行大清扫。

this关键字:

类的构造器、是创建java对象的重要途径、它通过new关键字对构造器进行调用、调用完成后该构造器也确实返回了该类的对象实例、但实际上、对象的创建工作并不是完全由该类的构造器完成的、所以在学习this关键字之前、我们需要对一个对象的创建过程进行一次总结:

  1. 在堆内存中对对象进行内存空间的分配、并将对象的成员变量进行初始化
  2. 执行属性值的显式初始化
  3. 执行构造器(也就是说实际上在调用构造器之前、该类对象的就已创建完成了、执行构造器实际是对对象进一步的初始化)
  4. 返回对象的地址给相关的变量

this关键字的本质实际就是指“创建好的对象的地址值”、通过上述对对象创建的过程的总结、我们得出的结论是在调用构造器的时候、实际对象就已经被创建出来了、而这个创建出来的对象就能够使用this关键字进行表示、this关键字表示的是“当前的对象”。
this关键字在构造器表示“正在初始化的对象”、因为执行构造器的时候必然是正在对该对象进行初始化工作、所以我们能够这样去理解。
this关键字在方法函数表示“调用该方法的对象”、谁调用该方法、这个this所代表的对象就是谁。

public class Main {

    int id; // 身份证号码
    String name; // 姓名
    int age; // 年龄

    // 有参的构造器
    public Main(int id,String name){
        // 这里程序会有问题 形式参数的变量和需要被赋值的变量实际是同一个变量 因为就近原则 谁离得近该变量指的就是谁
        // 而我们想要在调用该构造器的时候、形式参数的变量应该赋值给该对象的成员变量 这才是我们的目的
        //id = id;
        //name = name;
        // 我们可以将形式参数的变量名与成员变量名进行区分再赋值即可
        // id = _id;
        // name = _name;
        // 还可以使用this关键字进行区分、this表示的是当前对象
        this.id = id; //  这时候就能够成功赋值了
        this.name = name;
    }

    // 重载的构造器
    public Main(int id,String name,int age){
        // 在构造器中、我们可以通过this关键字调用当前对象的构造器 避免了相同多余的代码。
        this(id,name);
        this.age = age;
    }

    public static void main(String [] args){
        Main main = new Main(1001,"张三");
    }

}

static关键字:

被static关键字修饰的变量是静态变量、也称之为类变量、被static关键字修饰的方法是静态方法、无论是静态变量或是静态方法、它们都是从属于类的、它们的生命周期伴随类的始终、在整个应用程序执行期间都有效。
1.静态变量也是应用程序的公用变量、它从属于类、能够被该类的对象所有实例共享、静态变量在类被加载的时候就进行显式初始化。
2.静态变量和静态方法都是从属于类的、在类被加载的时候就被进行初始化、由于这个特点、调用该类的变量或方法的时候、我们也不需要一定创建该类的对象实例才能get到该类的变量或方法、而是通过类名.属性名/方法名。
3.在静态方法中无法直接访问非静态的变量。

public class Main {

    int id; // 身份证号
    static String name = "张三"; // 姓名(被static关键字所修饰的)
    int age; // 年龄

    // 被static关键字修饰的方法
    public static void showName(){
        // 静态方法中调用静态变量 不会有错误
        System.out.println(name);
    }

    // 非静态方法
    public void showName2(){
        // 非静态方法中调用静态变量也不会有错误
        System.out.println(name);
    }

    // 被static关键字修饰的方法
    public static void showAge(){
        // 静态方法中调用非静态变量 编译错误

        System.out.println(age);
    }

    public static void main(String [] args){
        Main.showAge();// 类名.方法名直接可以调用静态方法
        Main.showName2();// 编译错误 类名.方法名无法调用普通方法
        Main main = new Main();
        main.showName2();
        main.showName();
        main.showAge();
    }
}
static变量、方法的内存图结构:

在这里插入图片描述

本节常见问题:

  1. 每个.java源文件可以有多少个类?
    一个java的源文件可以有多个类、但有且只能有一个同时被public class 关键字修饰的类、并且被public class关键字所修饰的类名和java源文件名必须是一致的。
  2. 一个最基本的类需要具备什么?
    通常来说、类的定义是为了创建对象实例做准备的、根据实际需要为类定义一些具体的信息如成员变量、成员方法、和构造器、而如果只是创建一个类的模板但里面什么信息内容也没有、那这个类没有任何的实际意义。
    就像去生活超市买了张A4图纸、但这张图纸什么都不画不写、那这张图纸就没有任何的实际意义。
  3. 构造器有什么作用?可以有返回值吗?构造器能够重载吗?
    它是被用于创建类模板的对象实例的特殊方法。
    构造器无法定义返回值的类型、因为构造器返回的类型往往都必须是本类。
    构造器能够重载、重载的条件与普通函数相同、形似参数列表个数、数据类型不同即可构成重载。
  4. 垃圾回收机制是什么?它的作用是什么?该如何去理解?
    垃圾回收器是Java语言非常重要的知识点、c++语言并没有垃圾回收的概念、需要开发人员手动对程序的内存进行管理、一旦疏忽后果不堪设想。而在Java语言中则不需要担心内存管理的问题、因为有垃圾回收器的存在、Java程序的开发人员能够更专注于程序的业务逻辑上、而不是耗费心思在内存管理。
    垃圾回收器能够发现内存中无用的对象、进而对这些无用的对象进行清理回收、极大提高程序的容错性。
    java的垃圾回收器基于这样的一个概念、它认为不同对象的生命周期也不一样、因此不同生命周期的对象也应该采取不同的回收算法进行管理、提高回收的效率。java将对象的状态分为三种、年轻代、老年代及持久代、它们分别对应的是不同对象的生命周期。
  5. this关键字是什么?它有什么作用?
    this关键字在构造器中表示“正在进行初始化的对象”、在方法函数中表示“当前调用该方法的对象”。
    在应用程序中的两义性之处、使用this关键字能够区分是否需要指定当前的对象。
    在构造器中使用this关键字能够对构造器进行重载、避免相同多余的初始化代码、但只能够使用在构造器中、并且使用this关键字重载构造器的方法代码必须要在构造器的第一行的位置、否则编译出错。
    this关键字无法使用在被static修饰的方法函数中的、原因很简单、static修饰的方法函数存放在内存的静态区、而this关键字所要表示的是当前的对象、静态区只有类的信息并没有对象、编译会出错的。
    6.static关键字是什么?它有什么作用?
    被static修饰的变量和方法都是静态的、它们的生命周期从属于类、随着类的加载而创建、随着类的销毁而销毁、需要注意的是、对于该类的所有对象来说、它们共享同一份静态变量、也因为静态变量的生命周期过长、需要谨慎使用、否则会非常消耗内存资源。
    静态变量或方法常被用于一些工具类的方法调用、或者是需要统计某些数的时候、多个对象实例需要共享一份变量、这些都是灵活多变的、根据需求选择。

猜你喜欢

转载自blog.csdn.net/weixin_38858343/article/details/83451571