当前位置: 首页 > news >正文

酒店网站报价方案酒店网站建设

酒店网站报价方案,酒店网站建设,福州网络推广建站,成都 网站 建设系列文章#xff1a; 《计算机底层原理专栏》#xff1a;欢迎大家订阅学习#xff0c;能够帮助到各位就是对我最大的鼓励#xff01; 文章目录 系列文章目录前言一、JVM是什么二、什么是继承三、什么是多态总结 前言 这篇文章聚焦JVM的实现原理#xff0c;我更专注于从一…系列文章 《计算机底层原理专栏》欢迎大家订阅学习能够帮助到各位就是对我最大的鼓励 文章目录 系列文章目录前言一、JVM是什么二、什么是继承三、什么是多态总结 前言 这篇文章聚焦JVM的实现原理我更专注于从一个语言的底层原理去剖析他的语法所实现的意义这篇文章我不会从太基础的语法层面讲起我会用我的方法我的视角带大家领略以下Java这个语言特有的魅力。 一、JVM是什么 JVMJava Virtual MachineJava虚拟机功能及其重要组成         Java为了代码能够实习跨越平台的特性也就是“一次编译到处执行”所以引入了Java虚拟机的概念Java的代码并不是直接运行在操作系统上而是运行在Java虚拟机上面的首先我们的代码首先会被Java当中的编译器翻译称为Java的字节码也就是我们所看到的 .class 文件这个JVM其实就是负责解释并且执行这些字节码的工具。         下面我将为大家详细地介绍JVM的各种功能请大家注意这里我认为是整个Java学习最重要的地方他将直接影响到我们后续的各种语法的学习例如继承和多态所以希望各位铁铁一定要重视起来 JVM——内存管理    JVM负责程序运行时的内存分配和垃圾回收确保程序运行有着足够的内存并且及时释放不需要的内存这里我们首先来谈一谈内存管理的几个重要组成部分。1虚拟机栈学过C语言的小伙伴可能听说过栈的存在那么Java当中的虚拟机栈其实就是我们平常所熟知的——栈后面为了方便我就不再使用虚拟机栈的概念了全部简称为栈。我面下面来了解以下栈当中都存放了哪些内容呢      栈当中主要为我们的程序存放三个重要的内容他们分别是局部变量表操作数栈、方法返回地址。注意了虚拟机栈上其实还存储了其他的内容比如像动态链接异常信息处理表等一系列的辅助信息以上三点是我们需要记忆的最重要的三点。   虚拟机栈 栈顶局部变量表操作数栈方法返回地址    在这里特别强调栈的创建主要针对于我们类当中方法的调用这个概念非常类似于C语言当中的函数栈帧的概念也就是每当有一个函数调用操作系统就会为这个函数在栈当中分配一个栈帧用于存储这个函数当中的一些关键信息那么Java当中的栈同样如此只不过我们叫做虚拟机栈而且这些内存并不是由操作系统直接分配的是由我们的JVM专门为我们分配的。      我们在Java一个类当中会定义一系列的方法而这些方法通过对象在调用的时候JVM就会为我们分配内存这也体现出了JVM内存管理的特性。     局部变量表就是一个方法内部定义的各种各样的局部变量会统一存放在一个局部变量表当中。方法在进行各种运算的时候必然会产生各种各样的操作数例如我在方法内部写了一段代码   public void Print(){int a 10;String c Hello World; } 那么这段代码当中变量a和c就要存放在局部变量表当中而操作数10和Hello World就要存放在操作数栈当中。那么这个方法运算的最后结果也会存放到操作数栈上面当我们执行return的时候这个时候结果就会从操作数栈上弹出。我们就获得了方法执行的最终结果。     那么方法返回地址是什么呢其实也很好理解就是一个方法执行完成后需要返回的地址这一点是针对CPU的这么说是不是不好理解好的我再写一段代码。 public class Person{public int age; public String name;//构造函数省略不写public void Print(){System.out.println(Hello World);}public void talk(){System.out.println(hello talk());} //当CPU执行到Print()方法的时候这个时候CPU的处理机资源就会跑到Print()方法体的内部那么这个 //时候当Print()方法执行完成以后CPU就会查看Print()的返回地址这个时候CPU就会返回到Print的地址 //处继续执行下一个talk()方法这就是方法返回地址存在的意义他让CPU知道执行完这个方法以后返回到这个方法原来的地址处继续执行这个方法后的下一行代码public static void main(String[] args){Person person new Person();//传参省略person.Print();//地址一person.talk();//地址二} } 温馨提示方法返回地址的作用在代码的注释处已经解释过了大家可以拖动滚动条进行查看 好了栈的知识我们就说到这里接下来为大家介绍JVM内存管理的第二个重要的内容方法区各位铁铁们这里的知识点都很重要哦千万别眨眼。2JVM内存管理——方法区   JVM方法区 类的元信息类头静态变量常量池方法代码方法被编译后的字节码字段描述符、方法描述符类的初始化状态涉及到多线程    这是方法区中我个人认为需要我们掌握、理解并且背诵的重要内容其中类的初始化状态涉及到了多线程的概念这个不作为我们这篇文章的重点去讲解。     首先我们首先来了解一下类的元信息这里的元信息也叫做类头需要我们重点掌握。   类的元信息类头 类名父类实现的接口访问修饰符构造函数MyClassMyBaseClass暂且不讲publicpublic MyClass(int,int)类头当中主要包含以上这些信息当我们在定义一个类的时候我们用代码将这些信息全部定义号之后只要我们一点击运行JRE将我们所写的代码进行编译好生成字节码文件JVM会将这些字节码文件加载到内存当中而类的这些关键信息包括类的元信息就会被JVM全部加载到内存的方法区当中不过我在这里提一句我们真是的物理内存是不会区分什么方法区、虚拟机栈之类的概念这些区域都是JVM帮我们进行在逻辑上的区分并且维护的这就是JVM内存管理工作的一部分内容。类头当中会涉及到实现的接口信息这个内容我会放到接口当中的内容去讲。大家可以看我后面发布的文章。     接下来我为大家讲解方法区当中的第二个内容——静态变量。   方法区静态变量 变量名称数据类型初始值访问修饰符staticVar1int0publicstaticVar2double 0.0privatestaticVar3Stringnullpublic当我们在类当中定义静态变量的时候JVM会将这些字节码存储到方法区当中我在这里还要提一句JVM的视角看到的全部都是字节码而CPU的视角看到的全都是机器码我给大家列出这样的表格仅仅为了方便大家理解而且我希望大家在看到Java代码的时候这些代码是“活着的”也就是我们要清楚地知道我们写的每一行代码它怎么来将会到哪里去JVM为我们做了什么我反复强调这一点因为它真的很重要啊     方法区当中的第三个内容——常量池这个是一个我们非常容易忽略的概念一说常量大家好像都听说过但是要细说常量的话好像又不太清楚今天我来为大家详细地介绍以下什么是常量方法区当中的常量主要分为两类一类是编译期间生成的各种字面常量另一类是各种符号引用。     说起字面常量比如我们所说的字符串常量那么符号引用呢也好理解比如说我们定义的类名接口名这是一个常量吧还有向方法名描述符等等这些都属于常量他们都会被存放在方法区当中常量还会分为编译期常量和运行期常量我写一段代码给大家看看。   public class ConstantExample {// 编译期常量public static final int COMPILE_TIME_CONSTANT 42;public static void main(String[] args) {// 运行期常量final int RUN_TIME_CONSTANT (int) (Math.random() * 100);System.out.println(Compile-time constant: COMPILE_TIME_CONSTANT);System.out.println(Run-time constant: RUN_TIME_CONSTANT);// 字符串常量String str1 Hello;String str2 World;String combinedString str1 , str2;System.out.println(Combined String: combinedString);} }对于编译期常量和运行期常量大家理解就好了。 接下来为大家介绍方法区当中的方法代码说到方法代码其实也就是字节码我为大家写一段代码来详细展示以下字节码。   public class Example {public int add(int a, int b){int result a b;return result;} }这样的代码翻译成字节码以后会变成什么样子呢   0: aload_0 1: iload_1 2: iload_2 3: iadd 4: istore_3 5: iload_3 6: ireturn嗯对的大概就长成这个样子大家看到这些代码有没有感觉到很熟悉的感觉是不是长得很像C语言的汇编代码是的我们可以理解字节码就是C语言当中的汇编代码但是也只是可以这么理解实际上它并不是Java当中的字节码更具有抽象性并不是完全的偏向计算机底层这个大家理解就好。     我为大家详细地介绍一下这些代码都表达了什么意思。 这些指令助记符的前缀例如i和a代表的是数据的类型i指代int类型的数据、a代表的是引用这里可能会有小伙伴不太清楚什么是引用稍后我会列一个拓展块为大家讲解。     aload_0将引用变量0通常代表this引用推送至栈顶随时准备出栈。     iload_1:将整形变量1推送至栈顶。     iadd:将栈顶的两个整数进行相加操作。     istore_3:将栈顶的整数型变量存储到本地变量3里面去。     ireturn:从当前方法返回整型值这个值一定是位于栈顶的。3)JVM内存管理——堆内存   JVM 堆内存 对象实例数组实例变量垃圾回收信息    我们先来解释数组当我们定义一个数组的时候JVM就会为它开辟出一个堆内存空间用于存放数组那么数组在堆内存当中时怎么样存放的呢首先我来为大家写一段代码来展示。   public class ArrayExample {public static void main(String[] args) {// 创建一个包含不同类型元素的数组Object[] array new Object[5];array[0] 42; // 整数array[1] 3.14; // 双精度浮点数array[2] Hello, World!; // 字符串array[3] true; // 布尔值array[4] new MyClass(); // 自定义对象} }数组在堆当中的存放形式 偏移量内存内容类型描述0referencereference对象引用4doubledouble双精度浮点数12referencereference对象引用字符串对象16booleanboolean布尔值20referencereference对象引用自定义对象    这里我要解释一下这里偏移量的大小就是这个数据所占内存的大小例如double类型的数据要占据8个字节那么他在内存当中的偏移量就是 这里的4代表起始地址4 8 12表格当中的第一个内容引用就是数组名数组名本身就是一个引用表格中的内容存放顺序时按照我们的定义顺序来存放的我们为数据定义的每一个内容都会按照这样的顺序存放因为想要为大家展示更多的内容所以我写的这个数组每一个区域都存放了不同类型的数据其实不只是这些内容堆当中还会存放每个数组元素当中的值或者初始化值我在这里就不给大家一一展示了列出这个表格只是为了大家理解在JVM视角下内存当中存储的都是字节码并不会有这么抽象的表格存在。     接下来为大家演示第二个内容对象的对象实例下面我依然为大家写一段代码。   public class MyClass {// 实例变量声明private int intValue;private double doubleValue;private String stringValue;// 构造方法public MyClass(int intValue, double doubleValue, String stringValue) {this.intValue intValue;this.doubleValue doubleValue;this.stringValue stringValue;}}实例变量 偏移量内存内容类型描述0intValueint整形变量4doubleValuedouble双精度浮点型变量12stringValuestring字符串引用变量    我在这里多说一句堆内存中存放有对象的实例和实例变量这二者有什么区别呢区别在于对象实例时包含实例变量的对象实例不仅有包括实例变量还有对象头信息不知道大家还记不记得在讨论方法区的时候我们提到了类的元信息类头对象也有对象头那么什么是对象头呢对象头包含对象的哈希码和一些锁信息锁涉及到了多线程的内容我们这里暂且不介绍哈希码呢如果有系统地学习过数据结构当中堆的知识的朋友应该知道哈希值是为了更好地检索堆当中的元素为了处理哈希冲突才引入的因为我们的对象本身就是存储在堆当中的所以每一个对象头都要存放一个哈希码用于检索对象我们在使用对象调用对象的方法的时候JVM首先要找到我们调用的对象他会怎么做呢他会把对象全都放在一个数据结构——堆当中然后使用对象头当中的哈希值来找到对象之后确定对象要调用的方法确定方法之后JVM在虚拟机栈上开辟空间类似于函数栈帧这个时候JVM会自动地将对象的虚拟地址并不是真实的物理地址Java不同于C语言程序员是不可以直接利用地址指针来访问内存的传递给位于虚拟机栈局部变量表当中的this引用同时开辟的栈帧还会接受来自堆当中的参数而堆当中的参数又是构造函数来赋值的这个时候this指针完成对局部变量的赋值最后的操作结果压入栈顶再由return语句返还给堆当中的对象实例这个时候一个完整的方法就算是结束了。     可能还会有小伙伴问return语句从计算机底层的视角来看的话传递返回值的过程是怎么完成的我在这里再多提一句我们的计算机是由CPU、内存、和系统总线来组成的而系统总线有是由数据总线、地址总线、控制总线共同构成的具体的运行逻辑我在之前的文章里面详细地介绍过这里就不讲了我直接说结果CPU通过总线堆内存的内容进行读写CPU把栈上的返回值直接写到堆当中的对象实例里面去return这个过程就算是彻底完成了但是Java这个语言我们并没有直接操作内存中间的很多过程都是由JVM来做的。CPU的世界里不存在什么堆和栈的区别大家都是一样的都是内存的一部分这些区域是JVM从逻辑上为我们划分的实际上并不存在所谓的数据结构也只是一个抽象的概念CPU的视角里也没有什么堆、栈、队列、树的这些概念、也只不过是逻辑上的概念我们把内存区域里的数据、按照地址的大小划分一个前后顺序、将数据按照先进后出的逻辑进行管理、那么这个区域就叫做队列、按照后进先出的逻辑进行管理那么这个区域就叫做堆。     然后是对象实例其实我在上文当中已经提到过了对象实例就是实例变量 对象头 一系列辅助信息垃圾回收信息由JVM来完成我就不为大家列表演示了。 3.JVM其它功能      上面为大家讲解了JVM最重要的功能——内存管理接下来为大家简要介绍以下JVM的其他功能。     1字节码执行     2垃圾回收     3即时编译     4线程管理 JVM的主要功能就这些具体的内容就不给大家展开讲解了。 JVM内存管理当中最重要的三个区域——虚拟机栈、堆、方法区的主要知识我就为大家讲解结束接下来我为大家准备一些拓展知识尽可能地做到我们以后写代码的时候这些代码在我们面前是透明的让我们距离技术大佬更进一步 拓展一什么是字段     直接说答案字段就是类当中定义的各种变量包括静态变量等各种变量有些时候这些成员变量也会被称为是类的属性。拓展二Java当中什么是引用引用通常有哪些     Java当中的引用这个概念是从C当中引入的而C的引用他的底层其实还是指针来实现的所以说C生万物这句话一点都不夸张学习Java之前系统地学习C语言真的是没有错。     那么我们可以理解Java当中的引用类似于C语言当中的指针但也有很多区别首先我直接说结论Java当中的引用就是存储着数据的地址的一个变量但是区别又在于Java当中的引用不允许我们直接访问内存并且对其进行修改和各种四则运算但是指针都是可以的而且Java当中的引用不像指针需要手动管理内存Java当中的引用内存开辟以及内存回收全部都由JVM来负责所以Java不用担心存在指针越界访问的问题Java的引用比起指针要安全的多。     提到了引用的作用我不知道各位是否记得C语言当中一个经典的问题两数交换在C语言当中形参的改变是无法改变实参的代码如下。 #include stdio.hvoid swap(int a,int b){int tmp a;a b;b tmp; }int main(){int x 3;int y 5;int ret swap(x,y);printf(%d\n,ret); }     请问上面的代码能够达到我们想要的目的吗显然是不能的因为形参只要他的生命周期并不长只要一出函数的作用域形参就会被立刻销毁无法改变实参所以在C语言当中要想交换两数要么使用指针要么使用返回值return将swap函数栈帧当中的数据结果传递给main函数栈帧当中的ret变量。Java当中也是一样的道理如果我们也使用简单的变量进行传参是不行的所以我们可以使用数组或者直接使用对象因为他们不单纯是数据还是引用所以就可以达到我们的目的因为Java的引用存储的是数据的地址。     那么引用通常包括哪些呢引用主要包括以下几点。   引用 对象引用数组引用接口引用泛型引用枚举引用自定义类型引用 常见的引用包括这些因为这不是我这篇文章要讲的重点所以就不给大家展开讲解了。 拓展三我们来谈一谈内存管理三大区调用以及初始化的顺序     首先当我们定义好一个类的时候类的元信息类头方法信息以及父类信息、接口信息包括方法的代码字节码字节码包括方法的指令、操作数、异常处理信息方法的指令其实我在上面已经展示过了类似于汇编代码。     上述提到的关于类的信息包括静态成员会被一次性全部加载到内存当中的方法区注意只会加载一次不会频繁地加载而且这些信息属于是共享区每一个线程都可以访问。     当我们使用new关键字创建对象的时候JVM会再堆当中为对象创建关键字会将这个对象的实例全部存储并且这个时候会调用构造函数对对象的实例进行初始化。     当对象调用方法的时候JVM又会在栈上开辟栈帧用于方法的实现。拓展四浅谈JRE与JVM     我们之前详细地介绍了JVM的功能组成现在还涉及到了另外一个很重要的组件——JRE那么什么是JRE呢      JREJava Runtime Environment包含以下主要内容 Java虚拟机JVM JVM是Java应用程序运行的虚拟计算机负责解释和执行Java字节码。 Java类库Java API 包含了大量的Java标准类和接口供开发者使用。这些类库提供了各种功能如文件操作、网络通信、图形界面等。 Java插件和部署工具 用于在浏览器中运行Java Applet等Web应用程序。 Java Web Start 一个用于部署和启动Java应用程序的工具可以通过Web浏览器启动独立的Java应用。 其他运行时支持文件 包括配置文件、属性文件等用于支持Java应用程序的运行和配置。       说到这里了我给大家提供一个比喻 JRE是一 个 提供Java应用程序运行环境的仓库而JVM就是这个仓库的管理员负责管理、执行和监督仓库中的各种物资(Java应用程序)JVM确保这个仓库中的物资能够被正确运行处理各种运行时的任务例如内存管理、垃圾回收、线程管理等。JRE提供了运行环境和各种资源而JVM是负责执行和管理这些资源的核心组件。     其实说到了JRE还有一个重要的概念就是JDK我为大家详细地介绍以下JDK首先我们来解决第一个问题什么是JDK。      JDK的全称是Java Development Kit即Java开发工具包。JDK是用于Java应用程序开发的软件开发工具集包含了以下主要组件 1. JREJava Runtime Environment包含Java虚拟机JVM和Java类库用于支持Java应用程序的运行。 2. 编译器javac用于将Java源代码编译成Java字节码。 3. 调试器jdb用于调试Java应用程序帮助开发者查找和修复错误。 4. Java文档生成工具Javadoc用于从Java源代码中生成文档。 5. 各种开发工具和实用程序包括用于性能分析、图形界面设计、版本控制等的工具。 说到这里大家就因该已经明白了JDK是一个负责运行Java程序的企业这个企业里面有着JRE这个仓库以及JVM这个仓库的管理员负责Java的程序运行。 总结上面的这些知识我为大家详细地讲解了JVM的重要功能相信大家已经对这一部分有了一个清晰的认识吗还是要提示一下大家我个人认为以上的这些知识是整个Java体系当中最重要的组成部分下面我要开始为大家讲解面向对象的重要特性——继承和多态以上这些关于JVM的知识是非常重要的铺垫请大家一定要掌握以后再学习后续的内容否则会有些吃力。 二、继承 1.继承概念以及语法 1.为什么需要继承         1代码复用         2扩展功能         3多态性         4类层次结构         我们定义一个类让他继承一个现有的类就可以实现代码的复用这样可以避免反复多次地定义同样功能的代码从而减轻程序员的负担。而且继承还是多态实现的一个重要前提多态必须是在继承的前提下才能够实现后续我为大家讲解多态的时候会专门提到。         提到类的层次结构这样可以更好地利用代码表现对象之间的关系使我们的代码更有层次感提高代码的可读性。2.继承是什么         从语法的角度上来讲继承就是一个类使用关键字extends来实现继承关系这篇文章我主要从JVM的角度来给大家分析所以最基础的简单语法我就不为大家展开讲解了基础性的东西我们一笔带过。3.什么是super         1在此之前我们必须先提一下什么是thisthis是指向一个对象的引用一个对象在调用方法的时候JVM会为这个方法在栈上开辟空间我在上文当中提到过虚拟机栈上分为三个主要的空间局部变量表操作数栈。方法返回地址这个this引用就存放在虚拟机栈上的局部变量表当中。文字的力量还是有限接下来我为大家写一段代码。   package Yangon;public class Base {public int a;public int b;//请大家务必注意这里的Base thispublic void Print(Base this){System.out.println(Hello World!);}public static void main(String[] args) {Base base new Base();base.Print();} }         大家请看这段代码我定义了一个Print()方法这个方法的第一个参数就是this引用注意了这个参数可不是我们自己定义的是JVM帮我们定义的很多小伙伴就要疑惑了我平常可是从来没有这么写过我怎么不知道这回事这个参数其实是一个隐式参数我们程序员也可以自己定义出来但是即便我们自己不定义编译器也会帮我们生成这个隐式的this引用其实就是方法当中默认的第一个参数这个参数是用来干什么的呢接下来的内容非常重要能否理解关乎到你后续是否能够真正的学会继承和多态以及对于super()引用的理解。         当我们写好了代码之后经过JRE当中的编译器编译生成字节码我们的类 Base 当中的类头信息方法信息静态成员我这里没有定义、字段信息不知道什么是字段的往前翻等这个类的所有信息会全部被加载入方法区。         当我们使用new来定义这个对象的时候JVM会为我们在堆当中开辟一块空间用于存放我们这个对象的信息这些信息包括对象头以及对象的成员变量等等关键信息new 执行完成之后会自动返回这个对象的引用也就是JVM提供的对象的虚拟地址 base 变量就是用于接受这个对象的引用。         当我们使用对象的引用调用方法的时候对象的引用也就是对象的地址会传递给方法当中的第一个参数也就是 this 引用也就是说这个时候指向对象的引用其实有两个一个是base一个是 this当base 赋值给 this 之后我们就可以调用这个对象的方法并且对对象的成员变量进行赋值例如像构造函数。   public Base(Base this,int a,int b){this.a a;this.b b; } //注意这里的this是隐式参数只不过我这里为大家写出来了而已编译器也允许我们这么写但是没必要 这个时候我们就完成了对对象的一系列操作这些操作其实都是借助 this 引用来完成的。         拓展说到这里我还要多提一嘴我想说的是静态方法静态成员这些方法是无法通过对象来进行调用的为什么呢非常简单因为这些成员是不接收 this 引用的也就是没有隐式的 this 参数况且静态成员是存储在方法区当中的是所有线程共享的在方法区中类的结构信息也存放在方法区当中所以我们调用静态成员一般都是通过类来进行调用的。2那么 super 是什么呢和这个 this 有什么关系呢         前面其实都是铺垫我真正要将的东西其实是这个那么什么是super呢我先说结论super 是子类访问父类的成员变量以及方法的时候我们需要使用的一个关键字。比如我为大家写一段代码。   public class Base {public int a;public int b;public Base(){}public Base(int a,int b){this.a a;this.b b;} } class Derived extends Base{public int c;public Derived(int c) {this.c c;}public Derived(int a, int b, int c) {super(a, b);this.c c;}public void Print(){super.a 40;super. b 50;} }         大家可以看到 super 这里是子类想要访问父类的成员信息索要使用的关键字但是这里关于super 的用法也埋藏着一个“陷阱”这个陷阱非常容易误导很多的初学者为了防止大家掉入这个陷阱我在这里要提问一句请问子类通过调用 super 关键字能够访问并修改父类当中的属性请问这句话对还是不对呢         我直接告诉各位这句话大错特错我之前看过很多的视频也看过很多的文章没有一个能够解释清楚的明明很简单的一句话就能够说明白的非要搞得很复杂。         super修改的是子类从父类那里继承过来的属性所以super 修改的是子类自己的属性只不过这些属性是从父类那里继承过来的一定要注意子类是没有权限对于父类的成员进行修改的子类要修改也只能修改自己所以这行代码。          public void Print(){super.a 40;super. b 50;}         这行代码修改的是父类的成员变量吗不是的它修改的是子类自己的变量只不过这些变量是从父类那里继承来的我这么解释大家能够明白吗拓展二我们来谈一谈 super 和 this 之间的相同点以及不同点         相同点this 和 super 都是Java当中的关键字用于处理类和对象之间的关系都可以用来解决同名变量和方法的歧义。如果大家觉得文字不好理解的话我可一给大家写一段代码。          public class Base {public int age;public String name;public Base(int age, String name) {this.age age;this.name name;} }         大家请看上面的这段代码为什么构造函数当中为成员变量赋值的时候需要调用this这个关键字呢可不可以直接这么写呢代码如下。 public class Base {public int age;public String name;public Base(int age, String name) {age age;name name;} }         答案是当然不可以了所以this这个关键字出现的意义一方面是为了使对象能够调用方法this是如何帮助对象调用方法的我在上文当中已经讲过了就不再重复了。其实this还有一个非常重要的作用就是解决命名冲突和方法歧义在这里就可以体现出来了如果不使用this我怎么知道你这个name 和 age 是参数当中的age 还是类的成员变量呢         我们提到的关于子类对象调用与父类同名或者不同名的属性的内容文章下面会详细谈到。         接下来我们谈一谈super 和 this之间的不同在哪里他们最主要的区别就是super是用来访问子类当中从父类那里继承来的属性的而 this 则是用来访问子类本身的属性的。         到这里我来讲一下super 与 this 最本质的区别各位要注意了这个内容非常重要看仔细了我之前提到过this其实是类的非静态方法所提供的一个隐式参数用于接受对象的引用所以this本质上是一个引用从操作系统或者JVM的视角来看this其实是一个地址只不过JVM为我们提供的this是一个虚拟地址经过JVM的一系列操作这个虚拟地址会映射到内存的物理地址上去对象要通过这个地址我们程序员拿到的是一个引用来调用对象所属类的方法那么super是不是也是一个引用呢不是的从JVM的角度来看super经过编译以后会变成一个字节码指令我上文当中提到过字节码指令还为大家列出表格类似于汇编指令super再经过操作系统就会变成一个机器指令最终会送入到CPU的IR寄存器当中经过一系列的解析CPU会根据翻译过后的super指令进行一系列的操作例如操纵子类对象访问从父类当中继承来的属性。super经过一系列的编译之后super会定位到子类对象的父类代码当中来指挥CPU进行相关操作。         好了 super 和 this 最底层的区别我就讲到这里希望大家能够理解。4.父类成员的访问         1父类成员变量子类访问父类成员变量的时候通常会遇到两个问题一个是子类当中与父类当中的不同名变量其次是父子类当中的同名变量其实这个时候我们只需要记住一点那就是当子类当中出现了与父类当中的同名变量那么JVM优先访问子类当中的变量代码如下 public class Base {public int a;public int b; } class Derived extends Base{public int c 10;public void Print(){System.out.println(a);} }         这个时候Print()函数访问的就是 父类当中的a变量不过这里我还是要提一句我说这里的Print函数访问的是父类当中的a变量严格意义上来说是错的这里访问的是从父类当中继承来的子类的成员变量。但是如果子类当中也有这样的成员变量那么这个时候就会优先访问子类当中的变量代码如下。   public class Base {public int a;public int b; } class Derived extends Base{public int a 50;public int c 10;public void Print(){System.out.println(a);} }         那么这个时候的 a 变量就不会再去访问父类当中的成员了子类从父类继承来的成员本质上还是子类的成员子类没有权限去访问父类的成员我反复强调这一点因为这一点很容易误导初学者。 5.继承当中的构造方法          首先我讲第一点什么是默认构造函数这一点非常重要直接说答案Java当中的默认构造分为两类一类是无参构造第二类是我们不显示定义构造函数由编译器为我们默认生成的构造函数叫做默认构造函数。         其次我之前反复强调子类调用super的时候其实并没有直接访问父类当中的属性成员变量 方法我害怕有小伙伴不知道属性是什么东西在这里强调一下而是访问子类当中从父类继承来的属性这一点到这里有一个特例就是构造函数子类调用构造函数对字段进行初始化的时候必须调用super来对子类当中从父类继承来的字段进行初始化字段我文章上面讲过了这里就不重复了所以这个时候子类调用super()来对初始化子类当中从父类继承来的字段的时候其实子类的的确确是使用super访问了父类的构造函数。因为这里我要特别强调一点构造函数是不会被子类继承的切记切记         我还要强调一点如果父类当中有默认构造函数那么子类当中调用构造函数的时候不需要显示调用super对从父类那里继承来的成员进行初始化但是如果父类没有构造函数那么我们调用子类的构造函数的时候就必须显示调用super为子类当中从父类那里继承来的字段进行赋值。具体代码如下。   package Yangon;public class Base {public int a;public int b;public Base(){} //默认构造或者干脆不写让编译器直接为我们定义} class Derived extends Base{public int c;public Derived(int c){} }         因为父类当中存在默认构造函数所以我们再初始化子类的时候就不需要再进行super调用了这里要注意如果我们已经显示定义了父类的构造函数这个时候编译器就不会再为我们默认生成默认构造函数了所以这个时候我们必须手动定义无参默认构造函数否则编译器会直接报错。   package Yangon;public class Base {public int a;public int b;public Base(int a, int b) {this.a a;this.b b;} } class Derived extends Base{public int c;public Derived(int a, int b, int c) {super(a, b);this.c c;} }这个时候因为父类没有默认构造函数所以子类就必须显示调用当我们为父类定义了构造函数编译器就不会为我们自动生成了可是我们又没有定义无参默认构造函数所以必须调用super可是如果代码是下面这样的话。   package Yangon;public class Base {public int a;public int b;public Base(){} } class Derived extends Base{public int c;public Derived(int c) {this.c c;} }         因为我们显示定义了默认构造函数所以也就不需要调用super了。 2.类当中的执行顺序 接下来为大家讲解一下父子类当中常见的代码运行顺序下面请看代码   class Person { public String name; public int age; public Person(String name, int age) { this.name name; this.age age; System.out.println(构造方法执行); } { System.out.println(实例代码块执行); } static { System.out.println(静态代码块执行); } } public class TestDemo { public static void main(String[] args) { Person person1 new Person(bit,10); System.out.println(); Person person2 new Person(gaobo,20); } }         这段代码定义了两个对象请问这几个方法当中谁最先调用呢我在上文当中也提到过静态代码块是存储在方法去当中的这部分代码早在JRE编译期间就已经被写成字节码并且被JVM调入到了内存中的方法区里面去而且只调入一次即便创建了多个对象也是只调用一次所以最先执行的一定是静态代码块。         我再解释一下实例代码块这些代码块是在对象创建的时候就已经调用了对象创建之后才会调用构造函数对这个对象的实例进行初始化所以先后顺序我想大家也应该知道了。大家只要记住凡是类当中的非静态代码块统统都叫实例代码块静态的叫静态代码块。         最后继承当中还有很多的基础性知识我没有去讲例如想protected继承方式还有访问限定符还有组合的概念但我觉得这些都不是什么难点只要稍微查阅一些资料都可以学会所以我就不在这里进行赘述了。之后我就要开始为大家讲解多态的知识了。 三、多态 1.什么是多态 多态polymorphism是面向对象编程中的一个重要概念它允许一个对象能够以多种形态存在。具体而言多态性有两种主要形式编译时多态性静态多态性和运行时多态性动态多态性。         其实这里的编译时多态就是我们平常所熟知的重载也就是方法的参数列表类型、个数、顺序有一项不同即可不同。这里其实是广义上的多态的概念那么从狭义上的多态来理解的话我们所说的多态其实就是运行时多态也就是再编译阶段我们无法确定对象的类型只有在运行的时候我们才能确定类型并且调用相应类型的方法。         说到这里可能还是又很多的小伙伴不能够理解到底什么是多态这些文字感觉不像是人话那么我就来举个例子好了这个例子非常重要直接贯穿了我们多态的整条故事线请大家一定记住。这个例子是这样的。         相信大家都有过买高铁票的经历那么请问在卖高铁票的时候同样的经过但是当不同的人买票的时候会产生不同的结果比如如果你是一个大学生那么你买票就会是学生票如果你是一个军人那么你买票也会享受相应的优惠如果你什么都不是那很抱歉你只能购买成人票了。你发现了吗同样都是买高铁票同样都是使用了“铁路12306”这个购票软件但是不同的人参与却产生了完全不同的结果这就是多态。         说到这里我相信大家可能已经理解了简单地说多态就是同一个对象做同一件事情但是产生了不同的结果这就是多态运行时多态。 2.怎么样使用多态 1使用多态的三个条件 必须在继承体系之下子类必须对父类当中的方法进行重写必须通过父类的引用调用重写的方法向上转型         第一个继承体系之下我们之前已经介绍过了就不再进行重复了我这里主要将后两个知识点。         首先什么是重写我直接上代码。   public class Base {public int a;public int b;public Base(){}public void Print(){System.out.println(Hello World!);} } class Derived extends Base{public int c;public Derived(int c) {this.c c;}public void Print(){System.out.println(Hello Father!);}public static void main(String[] args) {Base base new Derived(20);base.Print();} }         这段代码里的子类当中的Print()函数就叫做重写说白了重写就是子类当中方法签名与父类完全一致的方法。方法签名就是一个方法的声明部分也就是方法名参数列表、返回值类型都相同。         还有很重要的一点就是子类重写父类的方法子类重写的方法访问限定符的权限只能大于等于父类的方法不能够比子类重写方法的权限还要小。例如父类方法的权限是public子类重写方法权限只能是public不能是private 或者 default 因为这些都比public 的权限要小。         那么这是为什么呢请看代码 class Parent {// 父类方法访问权限为publicpublic void someMethod() {System.out.println(Method in Parent);} }class Child extends Parent {// 子类重写父类方法访问权限设为private比public小private void someMethod() {System.out.println(Method in Child);} }public class Main {public static void main(String[] args) {Parent obj new Child(); // 使用父类引用指向子类对象// 外部的代码无法直接调用子类重写的private方法// obj.someMethod(); // 编译错误无法访问} }道理很简单一个权限为public的父类方法被一个private的子类方法重写了意味着这个重写方法的权限缩小了也就意味着想要访问这个方法的难度更大了门槛更高了编译器是不允许这样的事情发生的。                  现在我来解释一下什么叫做向上转型。向上转型是指将一个子类类型的引用转换为其父类类型的引用的过程。在 Java 中这是一种自动的类型转换不需要显式声明。 class Animal {void eat() {System.out.println(Animal is eating);} }class Dog extends Animal {void bark() {System.out.println(Dog is barking);} }public class Main {public static void main(String[] args) {Dog dog new Dog(); // 创建 Dog 对象Animal animal dog; // 向上转型自动发生animal.eat(); // 调用父类方法// animal.bark(); // 无法调用子类特有的方法因为 animal 引用的是 Animal 类型} }在这个例子中dog 是一个 Dog 类型的引用通过向上转型我们将其转换为 Animal 类型的引用 animal。这是安全的因为 Dog 是 Animal 的子类子类的对象可以被视为父类的对象。         向上转型通常发生在多态的场景中当我们有一个父类类型的引用指向一个子类对象时可以通过这个引用调用父类中定义的方法但无法调用子类特有的方法。向上转型是一种常见的操作它允许代码更加灵活可以处理父类和子类对象的引用。         拓展向上转型有着三种方法这三种方法都需要我们掌握接下来我为大家一一列举。         1隐式转换自动转型         2静态方法传参         3方法返回值         具体代码如下   package Yangon;public class Animal {public int a;public int b;public void Print(){System.out.println(Hello World!);} } class Dog extends Animal{public void Print(){System.out.println(Hello Yangyi!);} }class Main{static void eat(Animal animal){animal.Print();}static Dog getDog(){return new Dog();}public static void main(String[] args) {//隐式类型向上转型Animal animal new Dog();animal.Print();//这里可以通过函数传参来实现向上转型Dog dog new Dog();eat(dog);//方法返回值实现向上转型Animal animal1 getDog();animal1.Print();} }以上代码已经完整地为大家展示了三种实现向上转型希望大家能够很好的理解。不过在这里我还要特别地强调一下即便是向上转型将子类的引用赋值给父类对象那么这个父类对象也只能访问子类从父类当中继承来的方法不可以访问子类当中特有的方法这是为什么呢以为我们多态的实现是在编译期间动态绑定实现的也就是说只有在运行期间才能够确定父类所引用的子类类型是什么在编译期间我们只知道父类的类型是什么所以这也就导致了向上转型之后父类对象也只能访问子类当中从父类继承来的方法不能够访问子类特有的方法。         说到这里的时候有很多的小伙伴就有疑问了代码如下   Animal animal new Dog();animal.Print();         你在写代码的时候不是已经明确地指出了子类的类型是Dog吗怎么会不确定子类的类型呢很简单虽然将子类的引用赋值给了父类对象可是别忘了这个父类的对象仍然是Animal类型的在Animal类型当中Dog类的字段信息以及方法信息都没有定义所以编译器并不能确定子类当中的特有方法所以向上转型之后就只能访问子类当中的继承方法不能访问子类的特有方法希望我的这次解答能够帮你解答疑惑。         向上转型可以让我们的实现多态让我们的代码更加的灵活以适应这个世界当中对象之间复杂的关系所以我们需要向上转型。但是向上转型也有一个巨大的缺陷那么就是父类的引用无法调用子类当中特有的方法这一点是比较可惜的那么针对这个问题我们有提出了向下转型这样就可以使得我们父类引用方便地访问我们子类当中特有的方法。         下面我为大家讲解一下什么是向下转型请看代码。   package Yangon;public class Animal {public int age;public String name;public void eat() {System.out.println(吃东西);} } class Dog extends Animal{public void eat(){System.out.println(小狗正在吃东西);}public void play(){System.out.println(小狗正在玩耍);} } class Bird extends Animal{public void eat(){System.out.println(小鸟正在吃东西);} } class Main{public static void main(String[] args) {Animal animal new Dog();if(animal instanceof Dog){Dog myRealDog (Dog)animal;myRealDog.play();//这里是可以调用子类的特有方法的因为对父类进行了强制类型转换//也就是向下转型}Animal animal1_bird new Bird();if(animal1_bird instanceof Bird){//编译就会报错不允许将Bird 赋值给 Dog类这就是instanceof的意义所在Dog bird_dog (Bird)animal1_bird;}} }3.为什么使用多态 针对我们为什么要使用多态我们下来看以下这段代码。   package Yangon;public class Animal {public void Print(){System.out.println(Hello World!);} } class Dog extends Animal{public void Print(){System.out.println(Hello YangYi);} } class Main{public static void main(String[] args) {Animal animal new Dog();animal.Print();} }多态在这里的作用是什么呢我们可以看到通过多态我们调用了子类当中的继承方法可是我不知道会不会有人跟我有同样的疑问这段代码明明可以这么写。如下   class Main{public static void main(String[] args) {Dog dog new Dog();dog.Print();} }那么问题来了明明可以直接定义子类的对象调用子类的方法为什么还要多此一举地使用多态呢其实调用多态是非常便利的一个做法只不过是我们这段代码不需要调用多态而已我敢才所写的这份代码压根就不需要使用多态因为我们从一开始就知道我们要调用的对象是什么类型那么什么时候应该使用多态呢当然是我们不知道对象的具体类型的时候了。我不知道大家是否还记得我最开始举的一个例子就是高铁购票的案例你作为一个售票员请问你坐在那里的时候你知道要来买票的客户他要买的是成人票是学生票还是军人优先票你不知道对不对那我们是怎么确定对象的类型呢当然是要核对他们的证件了我写一段代码给大家看一看。   import java.util.Random;// 抽象票类 abstract class Ticket {abstract double getPrice(); }// 普通票类 class NormalTicket extends Ticket {Overridedouble getPrice() {return 100.0; // 普通票价} }// 学生票类 class StudentTicket extends Ticket {Overridedouble getPrice() {return 80.0; // 学生票八折优惠} }// 军人票类 class MilitaryTicket extends Ticket {Overridedouble getPrice() {return 50.0; // 军人票半价优惠} }// 票工厂类用于生成随机类型的票 class TicketFactory {static Ticket getRandomTicket() {Random random new Random();int randomNum random.nextInt(3); // 0, 1, 2 分别对应三种票类型switch (randomNum) {case 0:return new NormalTicket();case 1:return new StudentTicket();case 2:return new MilitaryTicket();default:throw new IllegalArgumentException(Invalid ticket type);}} }// 购票系统 public class TicketSystem {public static void main(String[] args) {// 示例购票purchaseTicket(TicketFactory.getRandomTicket(), 20); // 普通人购买随机类型的票purchaseTicket(TicketFactory.getRandomTicket(), 16); // 学生购买随机类型的票purchaseTicket(TicketFactory.getRandomTicket(), 30); // 军人购买随机类型的票}// 购票方法根据年龄和特殊身份选择不同的票static void purchaseTicket(Ticket ticket, int age) {System.out.println(购票信息);System.out.println(年龄 age 岁);// 根据年龄和特殊身份选择不同的票if (age 18 || age 18 age 25) {System.out.println(购买学生票优惠价 ticket.getPrice());} else if (age 18 age 60) {System.out.println(购买普通票票价 ticket.getPrice());} else {System.out.println(购买军人票优惠价 ticket.getPrice());}System.out.println(------------);} }         这段代码就很好地诠释了多态的意义想要实现这样的业务就必须使用多态因为在这样的代码逻辑之下我们是不确定对象的具体类型的所以这个时候我们必须使用多态。         好了多态就给大家讲解到这里希望大家有所收获 总结 这篇文章为大家详细地展示了JVM的工作内容以及继承和多态因为我本人大学期间主修方向是C、C语言因为工作原因需要用到Java所以最近开始转换语言由于之前学C以及嵌入式的相关知识的时候我留下来了一个特点就是我非常喜欢探查一门语言他的底层运行逻辑包括他与操作系统的工作逻辑。这篇文章是我从11月中旬开始准备的从知识的积累到总结最后到整理我用了将近半个月的时间因为战线拉得比较长所以中间的内容难免有些地方衔接的不够好或者说是有遗漏的地方希望大家多多包涵我会用我的方式站在JVM的角度站在计算机底层原理的角度为大家带来不同的Java学习的体验谢谢大家。你们能够从我的文章当中学到东西这就是对我最大的鼓励。
http://www.zqtcl.cn/news/470650/

相关文章:

  • 建个网站要花多少钱WordPress密码重设怎么改
  • 招商网站建设免费网站改版 升级的目的
  • 安徽圣力建设集团网站如何自己开发微信小程序
  • 学院网站板块盘多多搜索引擎入口
  • 网站seo内部优化wordpress建站网站报错
  • 网站建设科技国外网站入口
  • 怎样用网站做淘宝推广免费的项目管理软件
  • 共青城网站建设微网站开发报价
  • 网站建设选超速云建站网站建设公司比较
  • 芜湖网络科技有限公司沈阳网站推广优化公司哪家好
  • 自己制作图片文字图片网站建设和优化内容最重要性
  • 邢台做网站优化建筑行业新闻资讯
  • 站长统计app最新版本2023网站标题是关键词吗
  • 中山精品网站建设市场wordpress登陆phpadmin
  • 泸县手机网站建设佛山城市建设工程有限公司
  • 长沙网站推广排名优化wordpress主题字体更改
  • 深圳网站建设软件定制公司房地产开发公司注册资金要求
  • 个人如何在企业网站做实名认证房地产平面设计主要做什么
  • 网站做字工具WordPress搜索功能增强
  • 慢慢来做网站多少钱wordpress优化搜索引擎
  • 网页 网站 区别现在装宽带要多少钱
  • 黄金网站下载免费建设个人网站需要什么条件
  • 网站开发人员岗位职责网站维护报价单
  • 免费正能量不良网站推荐自建网站视频教程
  • 厦门物流网站建设南京宜电的网站谁做的
  • vps 网站备案手机界面设计素材
  • seo排名影响因素主要有灯塔seo
  • 济南哪家做网站小勇cms网站管理系统
  • sns社交网站注册做网站 提交源码 论坛
  • wordpress网站编辑semir是什么牌子