想要学好一门语言,一定要了解它的历史,了解它存在的价值和意义。
1991年成立了一个称为Green的项目小组,帕特里克、詹姆斯·高斯林、麦克·舍林丹和其他几个工程师一起组成的工作小组在加利福尼亚州门洛帕克市沙丘路的一个小工作室里面研究开发新技术,专攻计算机在家电产品上的嵌入式应用。
由于C++所具有的优势,该项目组的研究人员首先考虑采用C++来编写程序。但对于硬件资源极其匮乏的单片式系统来说,C++程序过于复杂和庞大。
为了解决困难,他们首先着眼于语言的开发, 对于新语言的设计,Sun公司研发人员并没有开发一种全新的语言,而是根据嵌入式软件的要求,对C++进行了改造,去除了留在C++的一些不太实用及影响安全的成分,并结合嵌入式系统的实时性要求,开发了一种称为Oak的面向对象语言。
1994年将Oak语言更名为Java。
1996年1月,Sun公司发布了Java的第一个开发工具包(JDK 1.0),这是Java发展历程中的重要里程碑,标志着Java成为一种独立的开发工具。
1998年12月8日,第二代Java平台的企业版J2EE发布。
1999年4月27日,HotSpot虚拟机发布。
2005年6月,在Java One大会上,Sun公司发布了Java SE 6。此时,Java的各种版本已经更名,已取消其中的数字2,如J2EE更名为JavaEE,J2SE更名为JavaSE,J2ME更名为JavaME。
2009年,甲骨文公司宣布收购Sun。
2014年3月Oracle发布正式版JDK8,JDK8改进比较多,最大的改进是Lambda表达式(以及因之带来的函数式接口,很多原有类都做了变更,但能够与以往版本兼容,堪称奇功!),还有Stream API流式处理,joda-time等等一些新特性。
JDK:java development kit (java开发工具)
JRE:java runtime environment (java运行时环境)
JVM:Java Virtual Machine (java虚拟机)
1、jdk--开发环境(核心)
Java development kit的缩写,意思是Java开发工具,我们写文档做PPT需要office 办公软件,开发当然需要开发工具了,说到开发工具大家肯定会想到Eclipse,但是如果直接安装Eclipse你会发现它是运行不起来 是会报错的,只有安装了JDK,配置好了环境变量和path才可以运行成功。这点相信很多人都深有体会。
jdk主要包含三个部分:
第一部分是Java运行时环境,JVM
第二部分是Java的基础类库,这个类库的数量还是相当可观的
第三部分是Java的开发工具,它们都是辅助你更好地使用Java的利器jre--运行环境
2、jre--运行环境
① jdk中的jre
如下图:jdk中包含的jre,在jre的bin目录里有个jvm.dll,既然JRE是运行时环境,那么运行在哪?肯定是JVM虚拟机上了。另,jre的lib目录中放的是一些JAVA类库的class文件,已经打包成jar文件。
② 第二个JRE(独立出来的运行时环境)
如下图,不管是JDK中的JRE还是JRE既然是运行时环境必须有JVM。所以JVM也是有两个的。
3、JVM——转换环境
Java Virtual Machine (java虚拟机)的缩写。
大家一提到JAVA的优点就会想到:一次编译,随处运行,说白了就是跨平台性好,这点JVM功不可没。
Java的程序也就是我们编译的代码都会编译为class文件,class文件就是在jvm上运行的文件,只有JVM还不能完全支持class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。
JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改的运行。
JVM也是一门很深的学问,感兴趣的同学可以深入研究,只有好处,没有坏处。
其实有时候面试官问JDK和JRE的区别的目的不是想让你解释什么名词的,而是想看看你的基础和研究Java的深浅,还有另一方面就是你是不是经常喜欢问为什么。
面向对象编程的最大挑战就是如何在问题空间的元素和解决方案空间的对象之间建立一对一的关联。
依赖关系表示一个类依赖于另一个类的定义。例如,一个人(Person)可以买车(car)和房子(House),Person类依赖于Car类和House类的定义,因为Person类引用了Car和House。与关联不同的是,Person类里并没有Car和House类型的属性,Car和House的实例是以参量的方式传入到buy()方法中去的。一般而言,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。
关联(Association)关系是类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。
聚合(Aggregation) 关系是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。例如,汽车类与引擎类、轮胎类,以及其它的零件类之间的关系便整体和个体的关系。与关联关系一样,聚合关系也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。
组合(Composition) 关系是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,组合关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生组合关系,由后者排他地负责生命周期。部分和整体的生命周期一样。
封装就是把对象的属性和行为结合为一个独立的整体,并尽可能多的隐藏对象的内部实现细节。
对象用来封装数据和功能,但我们要创建一个新类,然而它又与已存在的类具有部分相同的属性或功能,此时,为了代码复用原则,可以使用继承来实现。
继承通过基类和子类的概念来表达,基类的所有特征和行为都可以与子类共享。也就是说,你可以通过基类呈现核心思想,从基类继承的子类则为核心思想提供不同的实现方式。
有时基类和子类的方法都是一样的,这时你就可以直接用子类的对象代替基类的对象,这种纯替代关系通常叫做替换原则。
有时,子类会添加一些新的方法,此时就是不完美替换。
通过将子类对象引用赋给父类对象引用来实现动态方法调用。
List<String> list = new ArrayList<String>();
后续 【Java基础知识 3】为何要配置环境变量? 会详细介绍。
package com.guor; public class Test{ public static void main(String[] args) { System.out.println("hello world!"); }}
数据类型 | 内存 |
byte | 8位 |
short | 16位 |
int | 32位 |
long | 64位 |
float | 32位 |
double | 64位 |
char | 16位 |
boolean | 1个字节或4个字节 |
后续 【Java基础知识 5】装箱和拆箱 详细介绍。
java中的方法可以传递参数,参数的传递方法就是值传递。
参数有形参和实参,定义方法时写的参数叫形参,真正调用方法时,传递的参数叫实参。
调用方法时,会把实参传递给形参,方法内部其实是在使用形参。
所谓值传递就是当参数是基本类型时,传递参数的值,比如传递i=10,真实传参时,把10赋值给了形参。
当参数是对象时,传递的是对象的值,也就是对象的首地址。就是把对象的地址赋值给形参。
类的创建者负责在创建新的类时,只暴露必要的接口给客户程序员,同时隐藏其它所有不必要的信息。
为什么这么做呢?
(1)因为如果这些信息对于客户程序员而言是不可见的,那么类的创建者就可以任意修改隐藏信息,而无需担心对其它任何人造成影响。隐藏的代码通常代表着一个对象内部脆弱的部分,如果轻易暴露给粗心或经验不足的客户程序员,就可能在顷刻之间被破坏殆尽。所以,隐藏代码的具体实现可以有效减少程序bug。
(2)让类库的设计者在改变类的内部工作机制时,不用担心影响到使用该类的客户程序员。
Java提供了三个显示关键字来控制访问权限。
(1)接口
(2)抽象类
(1)在类中的位置不同
成员变量:类中方法外;
局部变量:方法定义中或者方法声明上;
(2)在内存中的位置不同
成员变量:在堆中
局部变量:在栈中
(3)生命周期不同
成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
(4)初始化值不同
成员变量:有默认值
局部变量:没有默认值,必须定义,赋值,然后才能使用
对象的创建需要消耗一些资源,尤其是内存资源。
当我们不再需要一个对象时,就要及时清理它,这样占用的资源才能被释放并重复使用。
如果要最大化运行时效率,可以通过栈区(局部变量)来保存对象,或者将对象保存在静态区里,这样在编写程序时就可以明确的知道对象的内存分配和生命周期,这种做法会优先考虑分配和释放内存的速度。但是代价就是牺牲了灵活性,因为你必须在编写代码时就明确对象的数量、生命周期以及类型,但是这种写法的限制性很大。
还有一种方案是在内存池中动态创建对象,这个内存池也就堆。如果使用这个方案,直到运行时你才能知道需要多少对象,以及它们的生命周期和确切的类型是什么。如果需要创建一个新对象,可以直接通过堆来创建。因为堆是在运行时动态管理内存的,所以堆分配内存所花费的时间通常会比栈多一些。栈通常利用汇编指令向下或向上移动栈指针来管理内存,而堆何时分配内存则取决于内存机制的实现方式。
Java只允许动态分配内存,每当要创建一个对象时,都需要使用new来创建一个对象的动态实例。
如果在栈中创建对象,编译器会判断对象存在时间以及负责自动销毁该对象。
如果在堆中创建对象,编译器就无法得知对象的生命周期。
Java支持垃圾回收机制,它会自动找到没用的对象将其销毁。
都可以修饰类、方法、成员变量
static可以修饰类的代码块,final不可以
static不可以修饰方法内局部变量,final可以
static修饰表示静态或全局
static修饰的代码块表示静态代码块,当JVM加载类时,只会被创建一次
static修饰的变量可以重新赋值
static方法中不能用this和super关键字
因为this代表的是调用这个函数的对象的引用,而静态方法是属于类的,不属于对象,静态方法成功加载后, 对象还不一定存在。 this代表对本类对象的引用,指向本类已创建的对象。 super代表对父类对象的引用,指向父类对象。 静态优先于对象存在,方法被static修饰之后,方法先存在,所需的父类引用对象晚于该方法的出 现,也就是super所指向的对象还没出现,当然就会报错。
static方法必须被实现,而不能是抽象的abstract
static方法只能被static方法覆盖
final修饰表示常量、一旦创建不可被修改
final标记的成员变量必须在声明的同时赋值,或在该类的构造方法中赋值,不可重新赋值
final方法不能被子类重写
final类不能被继承,没有子类,final类中的方法默认是final的
final不能用于修饰构造方法
private类型的方法默认是final类型的
final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写
finally用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。
finalize方法用于垃圾回收。
一般情况下不需要我们实现finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法,关闭这个链接。
但是当调用finalize方法后,并不意味着gc会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用finalize方法。
父类的功能无法满足子类的需求时,则需要用到重写;
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写的注意事项:
(1)重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。
(2)重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。
(3)简单总结:
接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。
接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类就不能同时是 Runnable 和 Clonable( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。
在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。
(1)什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。
(2)如何实现对象克隆?
实现Cloneable接口,重写clone方法;
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。
BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆。
(3)深拷贝和浅拷贝区别是什么?
浅拷贝:仅仅克隆基本类型变量,不克隆引用类型变量;
深克隆:既克隆基本类型变量,又克隆引用类型变量;
(1)javac是什么
(2)javac编译器编译程序的步骤
① 词法分析
首先是读取源代码,找出这些字节中哪些是我们定义的语法关键词,如Java中的if、else、for等关键词。
语法分析的结果:从源代码中找出一些规范化的token流。
注:token是一种认证机制
② 语法分析
检查关键词组合在一起是不是Java语言规范,如if后面是不是紧跟着一个布尔表达式。
语法分析的结果:形成一个符合Java语言规范的抽象语法树
③ 语义分析
把一些难懂的、复杂的语法转化为更加简单的语法。
语义分析的结果:完成复杂语法到简单语法的简化,如将foreach语句转化成for循环结果,还有注解等。最后形成一个注解过后的抽象语法树,这颗语法树更接近目标语言的语法规则。
④ 生成字节码
通过字节码生成器生成字节码,根据经过注解的抽象语法树生成字节码,也就是将一个数据结构转化成另一个数据结构。
代码生成器的结果:生成符合Java虚拟机规范的字节码。
注:抽象语法树
在计算机科学中,抽象语法树是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
(3)javac编译器的基本结构
按照上述编译步骤来看,可以将javac编译器分为4个模块:词法分析器、语法分析器、语义分析器和代码生成器。
后续【Java集合 4】Java.lang.Character类详解 详细介绍
后续【Java基础知识 7】toString()、String.valueOf、(String)强转 详细介绍
后续【Java基础知识 8】String、StringBuilder、StringBuffer详解 详细介绍
同一种数据类型的集合,数组是一个容器。
数组能够对存储的元素进行自动排号,编号从0开始,方便操作。
int[] arr = new int[10];
//方式1int[] arr = { 1, 2, 3, 4, 5 }; //方式2int[] arr3=new int[3];arr3[0]=1;arr3[1]=2;arr3[2]=3;
public class Test{ public static void main(String[] args) { String[] arr = new String[3]; arr[0] = "CSDN"; arr[1] = "哪吒"; arr[2] = "博客专家"; for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } for (String str:arr) { System.out.println(str); } }}
后续【Java基础知识 4】秒懂数组拷贝,感知新境界 详细介绍
寄存器是速度最快的数据存储方式,数据直接保存在中央处理器,然而寄存器的数量是有限的,所以只能按需分配。
JVM中有4种常见的寄存器:
所有寄存器都为32位。
pc用于记录程序的执行,optop,frame和vars用于记录指向Java栈区的指针。
(1)堆内存用于存放new创建的对象
在堆中分配的内存,由JVM自动垃圾回收器管理。
在堆中产生一个对象后,会在栈中定义一个特殊变量,这个变量的取值等于数组或对象在堆内存中的首地址,在栈中的这个特殊的变量就编程了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的对象。
对象在没有引用变量指向它的时候,才会编程垃圾,不能再被使用,但是仍然占着内存,在随后一个不确定的时间点被垃圾回收器释放掉。这也是Java程序比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针。
(2)在函数中定义的一些基本类型的变量和对象的引用变量都是在栈内存中分配的。
当一段代码定义一个变量时,java就在栈中分配一个内存,当超过变量的作用域后,java会自动释放掉该变量分配的内存空间。
简而言之,堆是用来存放对象的,栈是运行程序的。
堆:为成员分配和释放,由程序员自己申请、自己释放。否则发生内存泄露。典型为使用 new 申请的堆内容。
栈为编译器自动分配和释放,如函数参数、局部变量、临时变量等等。
如果想迅速进行垃圾回收,可以将对象置为null。
常量通常会直接写在程序代码中,不可变。
不保存在应用程序里的数据,最典型的例子就是序列化对象,它指的是转换为字节流并可以发送到其它机器的对象。另一个例子则是持久化对象,它指的是保存在磁盘上的对象。也支持使用数据库存储对象信息。
大多数微处理芯片有额外的缓存内存,只不过缓存内容使用的是传统的内存管理方式,而非寄存器。
一个例子是字符串常量池,所有字符串和字符串常量都会被自动放置到这个特殊的存储空间中。
特殊情况 -> 原始类型
原始类型是直接创建一个“自动变量”,不是引用,该变量直接在栈上保存它的值,运行效率更高。
后续 【Java基础知识 6】Java异常详解
后续【Java基础知识 9】序列化与反序列化
后续【Java基础知识 10】Java IO流详解
在Java5之前,JAVA语言的集合所支持的通用类型是Object。因为单根结构决定了所有对象都属于Object类型,所以一个持有Object的集合就可以持有任何对象,这就使得集合十分易于复用。
其实并不能保存原始数据类型,不过自动装箱机制在一定程度上解决了这个问题。
当集合中持有Object类型时,要添加一个对象到集合中,该对象会向上转型为Object,从而失去了原本的类型。当你需要将其取出时,会获得一个Object类型的对象引用,这就不是当初的类型了,需要进行向下转型。但除非明确知道对象的具体类型,否则向下转型是不安全的,转型失败会抛出异常。
这个问题的解决方式是“参数化类型”,一个被参数化的类型是一种特殊的类,可以让编译器自动适配特定的类型,参数化类型也叫泛型,通过尖括号中间加上类名来定义泛型,比如List<String>。
后续【Java 基础知识 11】Java泛型详解
后续【Java基础知识 12】Java枚举详解
后续【Java基础知识 13】Java注解详解
后续【Java基础知识 14】Java动态代理
后续【Java基础知识 15】Java反射机制