工做了几年以后才发现,越是资深的JAVA工程师,应该越注重java基础知识,好比,整天和SpringBOOT、maven打交道,常常用apache提供的StringUtil类操做字符串,还有必要关心“String类为何是final”这样的问题,这是确定的哈。把基础夯实了,才不至于空中楼阁。java
对于应届生来书基础知识就更重要了,这决定你是否值得公司培养你,至于项目经验之类的若是有固然更好,没有也没必要苛求,毕竟绝大部分刚毕业的学生哪来的什么项目经验,基础扎实才是王道。c++
我的整理了一些资料,有须要的朋友能够直接点击领取。程序员
好了,话很少说,坐稳扶好,发车喽!面试
1,JDK:Java Development Kitjava的开发和运行环境,java的开发工具和jre。算法
2,JRE:Java Runtime Environmentjava程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。apache
3,配置环境变量:让java jdk\bin目录下的工具,能够在任意目录下运行,缘由是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮咱们去找指定的目录。数组
环境变量的配置:安全
1):永久配置方式:JAVA_HOME=%安装路径%\Java\jdk数据结构
path=%JAVA_HOME%\bin多线程
2):临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin
特色:系统默认先去当前路径下找要执行的程序,若是没有,再去path中设置的路径下找。
classpath的配置:
1):永久配置方式:classpath=.;c:\;e:\
2):临时配置方式:set classpath=.;c:\;e:\
注意:在定义classpath环境变量时,须要注意的状况
若是没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;
若是指定了classpath,那么会在指定的目录下查找要运行的类文件。
还会在当前目录找吗?两种状况:
CLASSPATH是什么?它的做用是什么?
它是javac编译器的一个环境变量。它的做用与import、package关键字有关。当你写下improt java.util.时,编译器面对import关键字时,就知道你要引入java.util这个package中的类;可是编译器如何知道你把这个package放在哪里了呢?
因此你首先得告诉编译器这个package的所在位置;如何告诉它呢?就是设置CLASSPATH啦 :) 若是java.util这个package在c:/jdk/ 目录下,你得把c:/jdk/这个路径设置到CLASSPATH中去!当编译器面对import java.util.这个语句时,它先会查找CLASSPATH所指定的目录,并检视子目录java/util是否存在,而后找出名称吻合的已编译文件(.class文件)。若是没有找到就会报错!CLASSPATH有点像c/c++编译器中的INCLUDE路径的设置哦,是否是?
当c/c++编译器遇到include 这样的语句,它是如何运做的?哦,其实道理都差很少!搜索INCLUDE路径,检视文件!当你本身开发一个package时,而后想要用这个package中的类;天然,你也得把这个package所在的目录设置到CLASSPATH中去!CLASSPATH的设定,对JAVA的初学者而言是一件棘手的事。因此Sun让JAVA2的JDK更聪明一些。你会发现,在你安装以后,即便彻底没有设定CLASSPATH,你仍然可以编译基本的JAVA程序,而且加以执行。
PATH环境变量
PATH环境变量。做用是指定命令搜索路径,在命令行下面执行命令如javac编译java程序时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序。咱们须要把jdk安装目录下的bin目录增长到现有的PATH变量中,bin目录中包含常常要用到的可执行文件如javac/java/javadoc等待,设置好PATH变量后,就能够在任何目录下执行javac/java等工具了。
4,javac命令和java命令作什么事情呢?
要知道java是分两部分的:一个是编译,一个是运行。
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm能够识别的字节码文件。也就是class文件,也就是java的运行程序。
java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.
一个文件要被执行,必需要有一个执行的起始点,这个起始点就是main函数.
2),不可使用关键字。
变量的做用域和生存期:
变量的做用域:
做用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期:
变量从定义的位置开始就在内存中活了;
变量到达它所在的做用域的时候就在内存中消失了;
1):基本数据类型:byte、short、int、long、float、double、char、boolean
4)、逻辑运算符。
& | ^ ! && ||
逻辑运算符除了 ! 外都是用于链接两个boolean类型表达式。
&: 只有两边都为true结果是true。不然就是false。
|:只要两边都为false结果是false,不然就是true
^:异或:和或有点不同。
两边结果同样,就为false。
两边结果不同,就为true.
& 和 &&区别: & :不管左边结果是什么,右边都参与运算。
&&:短路与,若是左边为false,那么右边不参数与运算。
| 和|| 区别:|:两边都运算。
||:短路或,若是左边为true,那么右边不参与运算。
5)、位运算符:用于操做二进制位的运算符。
& | ^
<< >> >>>(无符号右移)
练习:对两个变量的数据进行互换。不须要第三方变量。
int a = 3,b = 5;-->b = 3,a = 5;
方法一:
a = a + b; a = 8;
b = a - b; b = 3;
a = a - b; a = 5;
方法二:
a = a ^ b;//
b = a ^ b;//b = a ^ b ^ b = a
a = a ^ b;//a = a ^ b ^ a = b;
练习:高效的算出 2*8 = 2<<3;
重载的定义是:在一个类中,若是出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不一样,便可称之为该函数重载了。
如何区分重载:当函数同名时,只看参数列表。和返回值类型不要紧。
重写:父类与子类之间的多态性,对父类的函数进行从新定义。若是在子类中定义某方法与其父类有相同的名称和参数,咱们说该方法被重写 (Overriding)。
Java内存管理
Java内存管理:深刻Java内存区域
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
对于从事C和C++程序开发的开发人员来讲,在内存管理领域,他们既是拥有最高权力的皇帝,又是从事最基础工做的劳动人民—既拥有每个对象的"全部权",又担负着每个对象生命开始到终结的维护责任。
对于Java程序员来讲,在虚拟机的自动内存管理机制的帮助下,再也不须要为每个new操做去写配对的delete/free代码,并且不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是由于Java程序员把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,若是不了解虚拟机是怎样使用内存的,那排查错误将会成为一项异常艰难的工做。
Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些区域都有各自的用途,以及建立和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而创建和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将会包括如下几个运行时数据区域,以下图所示:
1. 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的做用能够看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各类虚拟机可能会经过一些更高效的方式去实现),字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。 因为Java虚拟机的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(对于多核处理器来讲是一个内核)只会执行一条线程中的指令。所以,为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,咱们称这类内存区域为"线程私有"的内存。 若是线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Natvie方法,这个计数器值则为空(Undefined)。此内存区域是惟一一个在**Java**虚拟机规范中没有规定任何OutOfMemoryError状况的区域。
1. Java虚拟机栈
与程序计数器同样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每一个方法被执行的时候都会同时建立一个栈帧(Stack Frame)用于存储局部变量表、操做栈、动态连接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
常常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的"堆"在后面会专门讲述,而所指的"栈"就是如今讲的虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
局部变量表存放了编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型),它不等同于对象自己,根据不一样的虚拟机实现,它多是一个指向对象起始地址的引用指针,也可能指向一个表明对象的句柄或者其余与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其他的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法须要在帧中分配多大的局部变量空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。** 在Java虚拟机规范中,对这个区域规定了两种异常情况:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常;若是虚拟机栈能够动态扩展(当前大部分的Java虚拟机均可动态扩展,只不过Java虚拟机规范中也容许固定长度的虚拟机栈),当扩展时没法申请到足够的内存时会抛出OutOfMemoryError异常。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的做用是很是类似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并无强制规定,所以具体的虚拟机能够自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈同样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
对于大多数应用来讲,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:全部的对象实例以及数组都要在堆上分配,可是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会致使一些微妙的变化发生,全部的对象都分配在堆上也渐渐变得不是那么"绝对"了。
Java堆是垃圾收集器管理的主要区域,所以不少时候也被称作"GC堆"(Garbage Collected Heap,幸亏国内没翻译成"垃圾堆")。若是从内存回收的角度看,因为如今收集器基本都是采用的分代收集算法,因此Java堆中还能够细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。若是从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过,不管如何划分,都与存放内容无关,不管哪一个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。在本章中,咱们仅仅针对内存区域的做用进行讨论,Java堆中的上述各个区域的分配和回收等细节将会是下一章的主题。
根据Java虚拟机规范的规定,Java堆能够处于物理上不连续的内存空间中,只要逻辑上是连续的便可,就像咱们的磁盘空间同样。在实现时,既能够实现成固定大小的,也能够是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(经过-Xmx和-Xms控制)。若是在堆中没有内存完成实例分配,而且堆也没法再扩展时,将会抛出OutOfMemoryError异常。
方法区(Method Area)与Java堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作Non-Heap(非堆),目的应该是与Java堆区分开来。
对于习惯在HotSpot虚拟机上开发和部署程序的开发者来讲,不少人愿意把方法区称为"永久代"Permanent Generation),本质上二者并不等价,仅仅是由于HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其余虚拟机(如BEA JRockit、IBM J9等)来讲是不存在永久代的概念的。即便是HotSpot虚拟机自己,根据官方发布的路线图信息,如今也有放弃永久代并"搬家"至Native Memory来实现方法区的规划了。
Java虚拟机规范对这个区域的限制很是宽松,除了和Java堆同样不须要连续的内存和能够选择固定大小或者可扩展外,还能够选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并不是数据进入了方法区就如永久代的名字同样"永久"存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,通常来讲这个区域的回收"成绩"比较难以使人满意,尤为是类型的卸载,条件至关苛刻,可是这部分区域的回收确实是有必要的。在Sun公司的BUG列表中, 曾出现过的若干个严重的BUG就是因为低版本的HotSpot虚拟机对此区域未彻底回收而致使内存泄漏。根据Java虚拟机规范的规定,当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池(**Runtime Constant Pool**)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(天然也包括常量池)的格式都有严格的规定,每个字节用于存储哪一种数据都必须符合规范上的要求,这样才会被虚拟机承认、装载和执行。但对于运行时常量池,Java虚拟机规范没有作任何细节的要求,不一样的提供商实现的虚拟机能够按照本身的须要来实现这个内存区域。不过,通常来讲,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具有动态性,Java语言并不要求常量必定只能在编译期产生,也就是并不是预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的即是String类的intern()方法。既然运行时常量池是方法区的一部分,天然会受到方法区内存的限制,当常量池没法再申请到内存时会抛出OutOfMemoryError异常。
介绍完Java虚拟机的运行时数据区以后,咱们就能够来探讨一个问题:在Java语言中,对象访问是如何进行的?对象访问在Java语言中无处不在,是最普通的程序行为,但即便是最简单的访问,也会却涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,以下面的这句代码:
Object obj = new Object();
假设这句代码出如今方法体中,那"Object obj"这部分的语义将会反映到Java栈的本地变量表中,做为一个reference类型数据出现。而"new Object()"这部分的语义将会反映到Java堆中,造成一块存储了Object类型全部实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不一样,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
因为reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并无定义这个引用应该经过哪一种方式去定位,以及访问到Java堆中的对象的具体位置,所以不一样虚拟机实现的对象访问方式会有所不一样,主流的访问方式有两种:使用句柄和直接指针。若是使用句柄访问方式,Java堆中将会划分出一块内存来做为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息**,以下图所示:
若是使用的是直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址,以下图所示:**
这两种对象的访问方式各有优点,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是很是广泛的行为)时只会改变句柄中的实例数据指针,而reference自己不须要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,因为对象的访问在Java中很是频繁,所以这类开销聚沙成塔后也是一项很是可观的执行成本。**就本书讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各类语言和框架使用句柄来访问的状况也十分常见。
匿名对象使用场景:
1:当对方法只进行一次调用的时候,可使用匿名对象。
2:当对象对成员进行屡次调用时,不能使用匿名对象。必须给对象起名字。
类中怎么没有定义主函数呢?
注意:主函数的存在,仅为该类是否须要独立运行,若是不须要,主函数是不用定义的。
主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。
成员变量和局部变量的区别:
1:成员变量直接定义在类中。
局部变量定义在方法中,参数上,语句中。
2:成员变量在这个类中有效。
局部变量只在本身所属的大括号内有效,大括号结束,局部变量失去做用域。
3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。
构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具备针对性,函数中的一种。
特色:
1:该函数的名称和所在类的名称相同。
2:不须要定义返回值类型。
3:该函数没有具体的返回值。
记住:全部对象建立时,都须要初始化才可使用。
注意事项:一个类在定义时,若是没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,为了方便该类建立对象,完成初始化。若是在类中自定义了构造函数,那么默认的构造函数就没有了。
一个类中,能够有多个构造函数,由于它们的函数名称都相同,因此只能经过参数列表来区分。因此,一个类中若是出现多个构造函数。它们的存在是以重载体现的。
构造代码块和构造函数有什么区别?
构造代码块:是给全部的对象进行初始化,也就是说,全部的对象都会调用一个代码块。只要对象一创建。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具备针对性。
执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每次建立对象是都会执行。
静态代码块的做用:好比咱们在调用C语言的动态库时会可把.so文件放在此处。
Person p = new Person();
建立一个对象都在内存中作了什么事情?
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),而后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)
封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离;便于使用;提升重用性;安全性。
封装原则:将不须要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。
this:表明对象。就是所在函数所属对象的引用。
this到底表明什么呢?哪一个对象调用了this所在的函数,this就表明哪一个对象,就是哪一个对象的引用。
开发时,何时使用this呢?
在定义功能时,若是该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
this 还能够用于构造函数间的调用。
调用格式:this(实际参数);
this对象后面跟上 . 调用的是成员属性和成员方法(通常方法);
this对象后面跟上 () 调用的是本类中的对应参数的构造函数。
注意:用this调用构造函数,必须定义在构造函数的第一行。由于构造函数是用于初始化的,因此初始化动做必定要执行。不然编译失败。
static:★★★ 关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。
特色:
一、static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另外一种是没有被static修饰的变量,叫实例变量。二者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程当中完成静态变量的内存分配,可用类名直接访问(方便),固然也能够经过对象来访问(可是这是不推荐的)。
对于实例变量,没建立一个实例,就会为实例变量分配一次内存,实例变量能够在内存中有多个拷贝,互不影响(灵活)。
二、静态方法
静态方法能够直接经过类名调用,任何的实例也均可以调用,所以静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。由于实例成员与特定的对象关联!这个须要去理解,想明白其中的道理,不是记忆!!!
由于static方法独立于任何实例,所以static方法必须被实现,而不能是抽象的abstract。
三、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,能够有多个,位置能够随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,若是static代码块有多个,JVM将按照它们在类中出现的前后顺序依次执行它们,每一个代码块只会被执行一次。
四、static和final一块用表示什么
static final用来修饰成员变量和成员方法,可简单理解为"全局常量"!
对于变量,表示一旦给值就不可修改,而且经过类名能够访问。
对于方法,表示不可覆盖,而且能够经过类名直接访问。
备注:
1,有些数据是对象特有的数据,是不能够被静态修饰的。由于那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。因此,在定义静态时,必需要明确,这个数据是不是被对象所共享的。
2,静态方法只能访问静态成员,不能够访问非静态成员。
(这句话是针对同一个类环境下的,好比说,一个类有多个成员(属性,方法,字段),静态方法A,那么能够访问同类名下其余静态成员,你若是访问非静态成员就不行)
由于静态方法加载时,优先于对象存在,因此没有办法访问对象中的成员。
3,静态方法中不能使用this,super关键字。
由于this表明对象,而静态在时,有可能没有对象,因此this没法使用。
4,主函数是静态的。
成员变量和静态变量的区别:
1,成员变量所属于对象。因此也称为实例变量。
静态变量所属于类。因此也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象建立而存在。随着对象被回收而消失。
静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。
静态变量能够被对象调用,也能够被类名调用。
因此,成员变量能够称为对象的特有数据,静态变量称为对象的共享数据。
静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。
做用:能够完成类的初始化。静态代码块随着类的加载而执行,并且只执行一次(new 多个对象就只执行一次)。若是和主函数在同一类中,优先于主函数执行。
final
根据程序上下文环境,Java关键字final有"这是没法改变的"或者"终态的"含义,它能够修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而须要阻止改变、设计或效率。
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但能够被继承。
final成员变量表示常量,只能被赋值一次,赋值后值再也不改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,所以private类型的方法默认是final类型的。
一、final类
final类不能被继承,所以final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,若是这个类不须要有子类,类的实现细节不容许改变,而且确信这个类不会载被扩展,那么就设计为final类。
二、final方法
若是一个类不容许其子类覆盖某个方法,则能够把这个方法声明为final方法。
使用final方法的缘由有二:
第1、把方法锁定,防止任何继承类修改它的意义和实现。
第2、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提升执行效率。
三、final变量(常量)
用final修饰的成员变量表示常量,值一旦给定就没法改变!
final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
从下面的例子中能够看出,一旦给final变量初值后,值就不能再改变了。
另外,final变量定义的时候,能够先声明,而不给初值,这中变量也称为final空白,不管什么状况,编译器都确保空白final在使用以前必须被初始化。可是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就能够实现依对象而有所不一样,却有保持其恒定不变的特征。
四、final参数
当函数参数为final类型时,你能够读取使用该参数,可是没法改变该参数的值。
生成Java帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java
/ //格式 类描述 @author 做者名 @version 版本号 / / 方法描述 @param 参数描述 @return 返回值描述 /
java中对于继承,java只支持单继承。java虽然不直接支持多继承,可是可实现多接口。
1:成员变量。
当子父类中出现同样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
若是想要调用父类中的属性值,须要使用一个关键字:super
This:表明是本类类型的对象引用。
Super:表明是子类所属的父类中的内存空间引用。
注意:子父类中一般是不会出现同名成员变量的,由于父类中只要定义了,子类就不用在定义了,直接继承过来用就能够了。
2:成员函数。
当子父类中出现了如出一辙的方法时,创建子类对象会运行子类中的方法。好像父类中的方法被覆盖掉同样。因此这种状况,是函数的另外一个特性:重写
3:构造函数。
发现子类构造函数运行时,先运行了父类的构造函数。为何呢?
缘由:子类的全部构造函数中的第一行,其实都有一条隐身的语句super();
super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。
为何子类对象初始化时,都须要调用父类中的函数?(为何要在子类构造函数的第一行加入这个super()?)
由于子类继承父类,会继承到父类中的数据,因此必需要看父类是如何对本身的数据进行初始化的。因此子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。
注意:子类中全部的构造函数都会默认访问父类中的空参数的构造函数,由于每个子类构造内第一行都有默认的语句super();
若是父类中没有空参数的构造函数,那么子类的构造函数内,必须经过super语句指定要访问的父类中的构造函数。
若是子类构造函数中用this来指定调用子类本身的构造函数,那么被调用的构造函数也同样会访问父类中的构造函数。
问题:
super()和this()是否能够同时出现的构造函数中?
两个语句只能有一个定义在第一行,因此只能出现其中一个。
super()或者this():为何必定要定义在第一行?
由于super()或者this()都是调用构造函数,构造函数用于初始化,因此初始化的动做要先完成。
在方法覆盖时,注意两点:
1:子类覆盖父类时,必需要保证,子类方法的权限必须大于等于父类方法权限能够实现继承。不然,编译失败。(举个例子,在父类中是public的方法,若是子类中将其下降访问权限为private,那么子类中重写之后的方法对于外部对象就不可访问了,这个就破坏了继承的含义)
2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是须要被继承,或者复写的。
这时如何解决问题呢?介绍一个关键字,final。
final特色:(详细解释见前面)
1:这个关键字是一个修饰符,能够修饰类,方法,变量。
2:被final修饰的类是一个最终类,不能够被继承。
3:被final修饰的方法是一个最终方法,不能够被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
抽象类: abstract
抽象类的特色:
1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(能够描述类和方法,不能够描述变量)。
2:抽象方法只定义方法声明,并不定义方法实现。
3:抽象类不能够被建立对象(实例化)。
4:只有经过子类继承抽象类并覆盖了抽象类中的全部抽象方法后,该子类才能够实例化。不然,该子类仍是一个抽象类。
抽象类的细节:
1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。
2:抽象类中是否能够定义非抽象方法?
能够。其实,抽象类和通常类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。因此抽象类和通常类在定义上,都是须要定义属性和行为的。只不过,比通常类多了一个抽象函数。并且比通常类少了一个建立对象的部分。
3:抽象关键字abstract和哪些不能够共存?final , private , static
4:抽象类中可不能够不定义抽象方法?能够。抽象方法目的仅仅为了避免让该类建立对象。
1:是用关键字interface定义的。
2:接口中包含的成员,最多见的有全局常量、抽象方法。
注意:接口中的成员都有固定的修饰符。
成员变量:public static final
成员方法:public abstract
interface
Inter{ public static final int x = 3; public abstract void show(); }
3:接口中有抽象方法,说明接口不能够实例化。接口的子类必须实现了接口中全部的抽象方法后,该子类才能够实例化。不然,该子类仍是一个抽象类。
4:类与类之间存在着继承关系,类与接口中间存在的是实现关系。
继承用extends ;实现用implements ;
5:接口和类不同的地方,就是,接口能够被多实现,这就是多继承改良后的结果。java将多继承机制经过多现实来体现。
6:一个类在继承另外一个类的同时,还能够实现多个接口。因此接口的出现避免了单继承的局限性。还能够将类进行功能的扩展。
7:其实java中是有多继承的。接口与接口之间存在着继承关系,接口能够多继承接口。
java类是单继承的。classB Extends classA
java接口能够多继承。Interface3 Extends Interface0, Interface1, interface……
不容许类多重继承的主要缘由是,若是A同时继承B和C,而b和c同时有一个D方法,A如何决定该继承那一个呢?
但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,因此接口能够继承多个接口。
抽象类与接口:
抽象类:通常用于描述一个体系单元,将一组共性内容进行抽取,特色:能够在类中定义抽象内容让子类实现,能够定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:通常用于定义对象的扩展功能,是在继承以外还需这个对象具有的一些功能。
抽象类和接口的共性:都是不断向上抽取的结果。
抽象类和接口的区别:
1:抽象类只能被继承,并且只能单继承。
接口须要被实现,并且能够多实现。
2:抽象类中能够定义非抽象方法,子类能够直接继承使用。
接口中都是抽象方法,须要子类去实现。
3:抽象类使用的是 is a 关系。
接口使用的 like a 关系。
4:抽象类的成员修饰符能够自定义。
接口中的成员修饰符是固定的。全都是public的。
多 态★★★★★(面向对象特征之一):函数自己就具有多态性,某一种事物有不一样的具体的体现。
体现:父类引用或者接口的引用指向了本身的子类对象。//**Animal a = new Cat();父类能够调用子类中覆写过的(父类中有的方法)
多态的好处:提升了程序的扩展性。继承的父类或接口通常是类库中的东西,(若是要修改某个方法的具体实现方式)只有经过子类去覆写要改变的某一个方法,这样在经过将父类的应用指向子类的实例去调用覆写过的方法就好了!
多态的弊端:当父类引用指向子类对象时,虽然提升了扩展性,可是只能访问父类中具有的方法,不能够访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性)
多态的前提:
1:必需要有关系,好比继承、或者实现。
2:一般会有覆盖操做。
若是想用子类对象的特有方法,如何判断对象是哪一个具体的子类类型呢?
能够能够经过一个关键字 instanceof ;//判断对象是否实现了指定的接口或继承了指定的类
格式:<对象 instanceof 类型> ,判断一个对象是否所属于指定的类型。
Student instanceof Person = true;//student继承了person类
-------------------------------------------------------------------------------------java.lang.Object
Object:全部类的直接或者间接父类,Java认为全部的对象都具有一些基本的共性内容,这些内容能够不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是全部对象都具有的功能。
具体方法:
1. boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
2,String toString():将对象变成字符串;默认返回的格式:类名@哈希值 = getClass().getName() + '@' + Integer.toHexString(hashCode())
为了对象对应的字符串内容有意义,能够经过复写,创建该类对象本身特有的字符串表现形式。
public String toString(){ return "person : "+age; }
3,Class getClass():获取任意对象运行时的所属字节码文件对象。
4,int hashCode():返回该对象的哈希码值。支持此方法是为了提升哈希表的性能。将该对象的内部地址转换成一个整数来实现的。
一般equals,toString,hashCode,在应用中都会被复写,创建具体对象的特有的内容。
内部类:若是A类须要直接访问B类中的成员,而B类又须要创建A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就能够了。A类就称为内部类。内部类能够直接访问外部类中的成员。而外部类想要访问内部类,必需要创建内部类的对象。
class Outer{ int num = 4; class Inner { void show(){ System.out.println("inner show run "+num); } } public void method(){ Inner in = new Inner();//建立内部类的对象。 in.show();//调用内部类的方法。 //内部类直接访问外部类成员,用本身的实例对象; } //外部类访问内部类要定义内部类的对象; }
当内部类定义在外部类中的成员位置上,可使用一些成员修饰符修饰 private、static。
1:默认修饰符。
直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer.new Inner();//这种形式不多用。
可是这种应用很少见,由于内部类之因此定义在内部就是为了封装。想要获取内部类对象一般都经过外部类的方法来获取。这样能够对内部类对象进行控制。
2:私有修饰符。
一般内部类被封装,都会被私有化,由于封装性不让其余程序直接访问。
3:静态修饰符。
若是内部类被静态修饰,至关于外部类,会出现访问局限性,只能访问外部类中的静态成员。
注意;若是内部类中定义了静态成员,那么该内部类必须是静态的。
内部类编译后的文件名为:"外部类名$内部类名.java";
为何内部类能够直接访问外部类中的成员呢?
那是由于内部中都持有一个外部类的引用。这个是引用是 外部类名.this
内部类能够定义在外部类中的成员位置上,也能够定义在外部类中的局部位置上。
当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。
匿名内部类(对象):没有名字的内部类。就是内部类的简化形式。通常只用一次就能够用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:须要前提,内部类必须继承一个类或者实现接口。
匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。
匿名内部类的使用场景:
当函数的参数是接口类型引用时,若是接口中的方法不超过3个。能够经过匿名内部类来完成参数的传递。
其实就是在建立匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个之内。
//面试 //1 new Object(){ void show(){ System.out.println("show run"); } }.show(); //写法和编译都没问题 //2 Object obj = new Object(){ void show(){ System.out.println("show run"); } }; obj.show(); //写法正确,编译会报错
1和2的写法正确吗?有区别吗?说出缘由。
写法是正确,1和2都是在经过匿名内部类创建一个Object类的子类对象。
区别:
第一个但是编译经过,并运行。
第二个编译失败,由于匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提高为了Object类型,而编译时会检查Object类中是否有show方法,此时编译失败。
--java.lang.Throwable:
Throwable:可抛出的。
|--Error:错误,通常状况下,不编写针对性的代码进行处理,一般是jvm发生的,须要对程序进行修正。
|--Exception:异常,能够有针对性的处理方式
这个体系中的全部类和对象都具有一个独有的特色;就是可抛性。
可抛性的体现:就是这个体系中的类和对象均可以被throws和throw两个关键字所操做。
throw与throws区别:
throws是用来声明一个方法可能抛出的全部异常信息,而throw则是指抛出的一个具体的异常类型。此外throws是将异常声明可是不处理,而是将异常往上传,谁调用我就交给谁处理。
throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。
throws用于抛出异常类,后面跟的异常类名,能够跟多个,用逗号隔开。throws用在函数上。
throws格式:方法名(参数)throws 异常类1,异常类2,.....
throw:就是本身进行异常处理,处理的时候有两种方式,要么本身捕获异常(也就是try catch进行捕捉),要么声明抛出一个异常(就是throws 异常~~)。
处理方式有两种:一、捕捉;二、抛出。
对于捕捉:java有针对性的语句块进行处理。
try {
须要被检测的代码;
}
catch(异常类 变量名){
异常处理代码;
}
fianlly{
必定会执行的代码;
}
定义异常处理时,何时定义try,何时定义throws呢?
功能内部若是出现异常,若是内部能够处理,就用try;
若是功能内部处理不了,就必须声明出来,让调用者处理。使用throws抛出,交给调用者处理。谁调用了这个功能谁就是调用者;
自定义异常的步骤:
1:定义一个子类继承Exception或RuntimeException,让该类具有可抛性(既可使用throw和throws去调用此类)。
2:经过throw 或者throws进行操做。
异常的转换思想:当出现的异常是调用者处理不了的,就须要将此异常转换为一个调用者能够处理的异常抛出。
try catch finally的几种结合方式:
1,
try
catch
finally
这种状况,若是出现异常,并不处理,可是资源必定关闭,因此try finally集合只为关闭资源。
记住:finally颇有用,主要用户关闭资源。不管是否发生异常,资源都必须进行关闭。
System.exit(0); //退出jvm,只有这种状况finally不执行。
注意:
若是父类或者接口中的方法没有抛出过异常,那么子类是不能够抛出异常的,若是子类的覆盖的方法中出现了异常,只能try不能throws。
若是这个异常子类没法处理,已经影响了子类方法的具体运算,这时能够在子类方法中,经过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不须要throws声明的。
返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread-编号定义的。编号从0开始。
线程要运行的代码都统一存放在了run方法中。
线程要运行必需要经过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
start方法:1)、启动了线程;2)、让jvm调用了run方法。
Thread类中run()和start()方法的区别:
start():用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。经过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并无运行,一旦获得cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
run():run()方法只是类的一个普通方法而已,若是直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径仍是只有一条,仍是要顺序执行,仍是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从CPU中申请另外一个线程空间来执行 run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,若是你直接调用线程对象的run()方法,固然也会执行,但那是 在当前线程中执行,run()方法执行完成后继续执行下面的代码.而调用start()方法后,run()方法的代码会和当前线程并发(单CPU)或并行 (多CPU)执行。因此请记住一句话:调用线程对象的run方法不会产生一个新的线程,虽然能够达到相同的执行结果,但执行过程和执行效率不一样
建立线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,经过建立Thread类的子类对象,建立线程对象;
4,调用线程的start方法,开启线程,并执行run方法。
线程状态:
被建立:start()
运行:具有执行资格,同时具有执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具有cpu的执行资格,没有cpu的执行权;
消亡:stop()
建立线程的第二种方式:实现一个接口Runnable。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,经过Thread类建立线程对象;
4,将实现了Runnable接口的子类对象做为实际参数传递给Thread类中的构造函数。
为何要传递呢?由于要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t = new Ticket();
/ 直接建立Ticket对象,并非建立线程对象。 由于建立对象只能经过new Thread类,或者new Thread类的子类才能够。 因此最终想要建立线程。既然没有了Thread类的子类,就只能用Thread类。 / Thread t1 = new Thread(t); //建立线程。 / 只要将t做为Thread类的构造函数的实际参数传入便可完成线程对象和t之间的关联 为何要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。 / t1.start();
为何要有Runnable接口的出现?
1:经过继承Thread类的方式,能够完成多线程的创建。可是这种方式有一个局限性,若是一个类已经有了本身的父类,就不能够继承Thread类,由于java单继承的局限性。
但是该类中的还有部分代码须要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
因此,一般建立线程都用第二种方式。
由于实现Runnable接口能够避免单继承的局限性。
2:实际上是将不一样类中须要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其余类进行功能扩展提供了前提。
因此Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口能够避免单继承的局限性。并且,继承Thread,是能够对Thread类中的方法,进行子类复写的。可是不须要作这个复写动做的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。因此Runnable接口将线程要执行的任务封装成了对象。
**//面试** new Thread(new Runnable(){ //匿名 public void run(){ System.out.println("runnable run"); } }) { public void run(){ System.out.println("subthread run"); } }.start(); //**结果:subthread run**
synchronized关键字(一)
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
5、以上规则对其它对象锁一样适用.
package ths; public class Thread1 implements Runnable { public void run() { synchronized(this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+"synchronized loop " + i); } } } }
synchronized关键字(二)
synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。
1. synchronized 方法:经过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法控制对类成员变量的访问:每一个类实例对应一把锁,每一个 synchronized 方法都必须得到调用该方法的类实例的锁方能执行,不然所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能得到该锁,从新进入可执行状态。这种机制确保了同一时刻对于每个类实例,其全部声明为 synchronized 的成员函数中至多只有一个处于可执行状态(由于至多只有一个可以得到该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要全部可能访问类成员变量的方法均被声明为 synchronized)。
在 Java 中,不光是类实例,每个类也对应一把锁,这样咱们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,因为在线程的整个生命期内它一直在运行,所以将致使它对本类任何 synchronized 方法的调用都永远不会成功。固然咱们能够经过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,可是 Java 为咱们提供了更好的解决办法,那就是 synchronized 块。
2. synchronized 块:经过 synchronized关键字来声明synchronized 块。语法以下:
synchronized(syncObject) { //容许访问控制的代码 }
synchronized 块是这样一个代码块,其中的代码必须得到对象 syncObject (如前所述,能够是类实例或类)的锁方能执行,具体机制同前所述。因为能够针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
对synchronized(this)的一些理解
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
5、以上规则对其它对象锁一样适用。
解决安全问题的原理:
只要将操做共享数据的语句在某一时段让一个线程执行完,在执行过程当中,其余线程不能进来执行就能够解决这个问题。
如何保障共享数据的线程安全呢?
java中提供了一个解决方式:就是同步代码块。
格式:
synchronized(对象) { //任意对象均可以。这个对象就是共享数据。
须要被同步的代码;
}
同步:★★★★★
好处:解决了线程安全问题。Synchronized
弊端:相对下降性能,由于判断锁须要消耗资源,产生了死锁。
同步的第二种表现形式: //对共享资源的方法定义同步
同步函数:其实就是将同步关键字定义在函数上,让函数具有了同步性。
同步函数是用的哪一个锁呢? //synchronized(this)用以定义须要进行同步的某一部分代码块
经过验证,函数都有本身所属的对象this,因此同步函数所使用的锁就是this锁。This.方法名
当同步函数被static修饰时,这时的同步用的是哪一个锁呢?
静态函数在加载时所属于类,这时有可能尚未该类产生的对象,可是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
因此静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
同步代码块和同步函数的区别?
同步代码块使用的锁能够是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步的话,可使用同步函数。若是有多同步,必须使用同步代码块,来肯定不一样的锁。因此同步代码块相对灵活一些。
★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?经过双重判断的形式解决。
//懒汉式:延迟加载方式。
当多线程访问懒汉式时,由于懒汉式的方法内对共性数据进行多条语句的操做。因此容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。可是却带来了效率下降。
为了效率问题,经过双重判断的形式解决。
class Single{ private static Single s = null; private Single(){} public static Single getInstance(){ //锁是谁?字节码文件对象; if(s == null){ synchronized(Single.class){ if(s == null) s = new Single(); } } return s; } }
等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的全部线程。
注意:
1:这些方法都须要定义在同步中。
2:由于这些方法必需要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就至关于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为何操做线程的方法定义在Object类中?
由于这三个方法都须要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又能够是任意对象,那么能被任意对象调用的方法必定定义在Object类中。
wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:
wait:能够指定时间也能够不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,并且线程会释放锁。
sleep:线程会释放执行权,但不是不释放锁。
线程的中止:经过stop方法就能够中止线程。可是这个方式过期了。
中止线程:原理就是:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?通常run方法里确定定义循环。因此只要结束循环便可。
第一种方式:定义循环的结束标记。
第二种方式:若是线程处于了冻结状态,是不可能读到标记的,这时就须要经过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具有执行资格的状态,让线程能够读到标记,并结束。
---------< java.lang.Thread >----------
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其余线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A何时执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
LOCK的出现替代了同步:lock.lock();………lock.unlock();
Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具有了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程当中,发现,获取锁,或者释放锁的动做应该是锁这个事物更清楚。因此将这些动做定义在了锁当中,并把锁定义成对象。
因此同步是隐示的锁操做,而Lock对象是显示的锁操做,它的出现就替代了同步。
在以前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是由于同步中的锁是任意对象,因此操做锁的等待唤醒的方法都定义在Object类中。
而如今锁是指定对象Lock。因此查找等待唤醒机制方式须要经过Lock接口来完成。而Lock接口中并无直接操做等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = **lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
集合框架:★★★★★,用于存储数据的容器。
对于集合容器,有不少种。由于每个容器的自身特色不一样,其实原理在于每一个容器的内部数据结构不一样。
集合容器在不断向上抽取过程当中。出现了集合体系。
在使用一个体系时,原则:参阅顶层内容。创建底层对象。
--< java.util >-- List接口:
List自己是Collection接口的子接口,具有了Collection的全部方法。如今学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特色。
List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素能够重复。
|--ArrayList:底层的数据结构是数组,线程不一样步,ArrayList替代了Vector,查询元素的速度很是快。
|--LinkedList:底层的数据结构是链表,线程不一样步,增删元素的速度很是快。
|--Vector:底层的数据结构就是数组,线程同步的,Vector不管查询和增删都巨慢。
可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。
Vector:是按照原数组的100%延长。
--< java.util >-- Set接口:
数据结构:数据的存储方式;
Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。
|--HashSet:底层数据结构是哈希表,线程是不一样步的。无序,高效;
HashSet集合保证元素惟一性:经过元素的hashCode方法,和equals方法完成的。
当元素的hashCode值相同时,才继续判断元素的equals是否为true。
若是为true,那么视为相同元素,不存。若是为false,那么存储。
若是hashCode值不一样,那么不判断equals,从而提升对象比较的速度。
|--LinkedHashSet:有序,hashset的子类。
|--TreeSet:对Set集合中的元素的进行指定顺序的排序。不一样步。TreeSet底层的数据结构就是二叉树。
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。
Map集合:
|--Hashtable:底层是哈希表数据结构,是线程同步的。不能够存储null键,null值。
|--HashMap:底层是哈希表数据结构,是线程不一样步的。能够存储null键,null值。替代了Hashtable.
|--TreeMap:底层是二叉树结构,能够对map集合中的键进行指定顺序的排序。
Map集合存储和Collection有着很大不一样:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
特色:要保证map集合中键的惟一性。
5,想要获取map中的全部元素:
原理:map中是没有迭代器的,collection具有迭代器,只要将map集合转成Set集合,可使用迭代器了。之因此转成set,是由于map集合具有着键的惟一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。
把map集合转成set的方法:
Set keySet();
Entry就是Map接口中的内部接口;
为何要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。
取出map集合中全部元素的方式一:keySet()方法。
能够将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再经过get方法对获取到的键进行值的获取。
Set keySet = map.keySet(); Iterator it = keySet.iterator(); while(it.hasNext()) { Object key = it.next(); Object value = map.get(key); System.out.println(key+":"+value); }
取出map集合中全部元素的方式二:entrySet()方法。
Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); while(it.hasNext()) { **Map.Entry** me = (Map.Entry)it.next(); System.out.println(me.**getKey**()+"::::"+me.**getValue**()); }
将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list); Map synchronizedMap(map); public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<K,V>(m); }
原理:定义一个类,将集合全部的方法加同一把锁后返回。
List list = Collections.synchronizedList(new ArrayList()); Map<String,String> synmap = Collections.synchronizedMap(map);
Collection 和 Collections的区别:
Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操做。
Collection是个java.util下的接口,它是各类集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操做,如插入、删除、判断一个元素是否其成员、遍历等。
自动拆装箱:java中数据类型分为两种 : 基本数据类型 引用数据类型(对象)
在 java程序中全部的数据都须要当作对象来处理,针对8种基本数据类型提供了包装类,以下:
int --> Integer byte --> Byte short --> Short long --> Long char --> Character double --> Double float --> Float boolean --> Boolean
jdk5之前基本数据类型和包装类之间须要互转:
基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();
1)、Integer x = 1; x = x + 1; 经历了什么过程?装箱 à 拆箱 à 装箱;
2)、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小 -128~127 一个字节的大小;
3)、String池:Java为了优化字符串操做 提供了一个缓冲池;
泛型:jdk1.5版本之后出现的一个安全机制。表现格式:< >
好处:
1:将运行时期的问题ClassCastException问题转换成了编译失败,体如今编译时期,程序员就能够解决问题。
2:避免了强制转换的麻烦。
泛型中的通配符:能够解决当具体类型不肯定的时候,这个通配符就是 ? ;当操做类型时,不须要使用类型的具体功能时,只使用Object类中的功能。那么能够用 ? 通配符来表未知类型。
反射技术:其实就是动态加载一个指定的类,并获取该类中的全部的内容。并将字节码文件中的内容都封装成对象,这样便于操做这些成员。简单说:反射技术能够对一个类进行解剖。
反射的好处:大大的加强了程序的扩展性。
反射的基本步骤:
一、得到Class对象,就是获取到指定的名称的字节码文件对象。
二、实例化对象,得到类的属性、方法或构造函数。
三、访问属性、调用方法、调用构造函数建立对象。
获取这个Class对象,有三种方式:
1:经过每一个对象都具有的方法getClass来获取。弊端:必需要建立该类对象,才能够调用getClass方法。
2:每个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必需要先明确该类。
前两种方式不利于程序的扩展,由于都须要在程序使用具体的类来完成。
3:使用的Class类中的方法,静态的forName方法。
指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入便可。
**// 1\. 根据给定的类名来得到 用于类加载** String classname = "cn.itcast.reflect.Person";// 来自配置文件 Class clazz = Class.forName(classname);// 此对象表明Person.class **// 2\. 若是拿到了对象,不知道是什么类型 用于得到对象的类型** Object obj = new Person(); Class clazz1 = obj.getClass();// 得到对象具体的类型 **// 3\. 若是是明确地得到某个类的Class对象 主要用于传参** Class clazz2 = Person.class;
反射的用法:
1)、须要得到java类的各个组成部分,首先须要得到类的Class对象,得到Class对象的三种方式:
Class.forName(classname) 用于作类加载
obj.getClass() 用于得到对象的类型
类名.class 用于得到指定的类型,传参用
2)、反射类的成员方法:
Class clazz = Person.class; Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2}); method.invoke();
3)、反射类的构造函数:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...}) con.newInstance(params...)
4)、反射类的属性:
Field field = clazz.getField(fieldName); field.setAccessible(true); field.setObject(value);
获取了字节码文件对象后,最终都须要建立指定类的对象:
建立对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1,调用空参数的构造函数:使用了Class类中的newInstance()方法。
2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,而后经过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
综上所述,第二种方式,必需要先明确具体的构造函数的参数类型,不便于扩展。因此通常状况下,被反射的类,内部一般都会提供一个公有的空参数的构造函数。
**// 如何生成获取到字节码文件对象的实例对象。** Class clazz = Class.forName("cn.itcast.bean.Person");**//类加载** // 直接得到指定的类型 clazz = Person.**class**; // 根据对象得到类型 Object obj = **new** Person("zhangsan", 19); clazz = obj.getClass(); Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给建立对象进行初始化。当指定类中没有空参数构造函数时,该如何建立该类对象呢?请看method_2(); public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 **//获取一个带参数的构造器。** Constructor constructor = clazz.**getConstructor**(String.class,int.class); **//想要对对象进行初始化,使用构造器的方法newInstance();** Object obj = constructor.newInstance("zhagnsan",30); **//获取全部构造器。** Constructor[] constructors = clazz.getConstructors();//只包含公共的 constructors = clazz.getDeclaredConstructors();//包含私有的 for(Constructor con : constructors) { System.out.println(con); } }
反射指定类中的方法:
**//获取类中全部的方法。** public static void method_1() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method[] methods = clazz.**getMethods**();**//获取的是该类中的公有方法和父类中的公有方法。** methods = clazz.**getDeclaredMethods**();**//获取本类中的方法,包含私有方法。** for(Method method : methods) { System.out.println(method); } }
//获取指定方法;
public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); **//获取指定名称的方法。** Method method = clazz.getMethod("show", int.class,String.class); **//想要运行指定方法,固然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法便可,可是方法运行必需要明确所属的对象和具体的实际参数。** Object obj = clazz.newInstance(); method.**invoke**(obj, 39,"hehehe");**//执行一个方法** } **//想要运行私有方法。** public static void method_3() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); **//想要获取私有方法。必须用getDeclearMethod();** Method method = clazz.getDeclaredMethod("method", null); **// 私有方法不能直接访问,由于权限不够。非要访问,能够经过暴力的方式。** **method.setAccessible(true);**//通常不多用,由于私有就是隐藏起来,因此尽可能不要访问。 } **//反射静态方法。** public static void method_4() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method method = clazz.getMethod("function",null); method.invoke(null,null); }
写到这里差很少就结束了,因为篇幅所限确定也是有些东西还没写到的,感兴趣的能够看一下我整理的
撤了xdm,下篇文章见,固然若是能够点个赞加个关注那真是感激涕零