java基础关键知识点总结
1 类
1)在java中,类文件是以.java为后缀的文件,若.java文件中存在public类,则文件名必须与public的类名一致。否则,文件名随意。
2)类中的成员变量若没有被显式地赋值初始化,java会保证每个成员变量得到合适的初始化。
a)基本数据类型:byte, short, int, long, float, double, char 默认值为0,boolean默认值为false。
b)对象类型,例如string,默认值为null。
3)类中若没有显式地定义构造方法,则编译器会自动创建一个无参构造方法;若显式地定义了构造方法,则不会创建无参构造方法。
4)初始化顺序
a)程序执行时,需要生成某个类的对象,java执行引擎会先检查是否加载了这个类,若没有加载,则先执行类的加载再创建对象。
若已经加载,则直接生成对象。
b)在类的加载过程中,类的static成员变量会被初始化,若有static语句块,也会执行。两者的执行顺序同程序中的顺序一致。
c)类是按需加载,只有当需要这个类的时候,才会加载。并且只会加载一次,这说明static成员变量与static语句块也只会执行一次。
d)在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造方法。
2 继承
继承就是对类的扩展,有父类和子类。每创建一个类都是在继承,若没有显式地extends,则默认都是继承自Object。
子类继承父类的成员变量、成员方法(包括静态成员变量与方法),但是不能继承父类的构造方法。但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
继承的局限:1)子类只能继承父类中的public和protected修饰的成员变量和方法,不能继承private修饰的成员变量与方法。但是可以通过公共的get和set访问器来访问private属性。2)对于父类的包访问权限(default)的成员变量与方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;3)一个子类只能继承一个父类。但是一个父类可以有多个子类。4)允许多层继承。A extends B, B extends C ---->推出----> A extends C。
3 子类会调用父类的构造方法吗?
答:会,且一定会调用。(构造函数链的概念)每创建一个子类的实例时,在子类的构造方法执行前,都会先执行父类的构造方法,若父类还有父类,则在执行父类的构造方法前,要先执行父类的父类中的构造方法。如此,直到最上层的构造方法被执行。父类的初始化过程以及构造器调用一定在子类的前面(先执行初始化再执行构造器)。
4 多态
多态的实现技术就是动态绑定。是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
父类引用可以指向子类对象。当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的该同名方法。
多态存在的三个必要条件
一、要有继承;
二、要有重写;
三、父类引用指向子类对象。
下面代码输出是什么?
class A{ public String show(D obj){ return ("A and D"); } public String show(A obj){ return ("A and A"); } } class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ //覆盖了父类 A 中的show(A obj) return ("B and A"); } } class C extends B{ } class D extends B{ }
测试类:
A a1 = new A(); A a2 = new B(); //多态的调用,首先在A类中查找相关方法show,若存在,再去查看是否在B类中覆盖了。若覆盖了,则调用B中的方法,否则调用A中的方法。 B b = new B(); C c = new C(); D d = new D(); System.out.println(a1.show(b)); ① System.out.println(a1.show(c)); ② System.out.println(a1.show(d)); ③ System.out.println(a2.show(b)); ④ System.out.println(a2.show(c)); ⑤ System.out.println(a2.show(d)); ⑥ System.out.println(b.show(b)); ⑦ System.out.println(b.show(c)); ⑧
结果:
① A and A
② A and A
③ A and D
④ B and A
⑤ B and A
⑥ A and D
⑦ B and B
⑧ B and B
这里涉及方法调用的优先问题 ,优先级由高到低依次为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
5 覆盖与隐藏的区别
下面这段代码的输入结果是什么?
public class Test { public static void main(String[] args) { Shape shape = new Circle(); System.out.println(shape.name); shape.printType(); shape.printName(); } } class Shape { public String name = "shape"; public Shape(){ System.out.println("shape constructor"); } public void printType() { System.out.println("this is shape"); } public static void printName() { System.out.println("shape"); } } class Circle extends Shape { public String name = "circle"; public Circle() { System.out.println("circle constructor"); } public void printType() { System.out.println("this is circle"); } public static void printName() { System.out.println("circle"); } }
结果:
hape constructor circle constructor shape this is circle shape
覆盖只针对非静态方法(终态方法不能被继承,所以就不存在覆盖一说了),而隐藏是针对成员变量和静态方法的。这2者之间的区别是:覆盖受RTTI(Runtime type identification)约束的,而隐藏却不受该约束。也就是说只有覆盖方法才会进行动态绑定,而隐藏是不会发生动态绑定的。在Java中,除了static方法和final方法,其他所有的方法都是动态绑定。
6 类的封装
答:
封装的定义:将对象的信息隐藏在对象内部,禁止外部程序直接访问对象内部的属性和方法。
封装的作用:
(1)隐藏类的实现细节。
(2)通过公开的方法访问数据。
(3)数据检查保证对象信息的完整。
(4)提高代码的可维护性。
如何实现封装:
(1)修改属性的可见性,限制访问。
(2)设置属性的读取方法。
(3)在读取属性的方法中,添加对属性读取的限制。
7 抽象类与接口
1)抽象类:用abstract修饰的类叫作抽象类,抽象类中含有抽象方法,抽象方法即只有声明没有实现的方法。
2)抽象类就是为了被继承,如果一个方法在父类中实现出来没有任何的意义,必须根据子类的实际需求来进行不同的实现,则可以将这个方法定义为抽象方法,这个类就定义为抽象类。
3)抽象类与普通类的区别:a)抽象类中也可以有成员变量和普通的成员方法。b)抽象方法必须为public或protected,默认为public。c)抽象类不能用来创建对象,不管是否含有抽象方法。d)如果一个类继承了一个抽象类,则子类必须实现父类的抽象方法,若子类没有实现父类的抽象方法,则子类也必须定义为abstract类。
4)接口是对行为的抽象,接口中可以含有变量和方法,但是一般情况下不在接口中定义变量。接口中的变量默认为public static final类型,接口中的方法全部为abstract方法。
5)接口和抽象类在语法层面上的区别:a)抽象类中可以含有方法的具体实现,接口中的所有方法都是abstract。b)抽象类中可以含有各种类型的成员变量,接口的成员变量只能是public static final。c)接口中不能含有静态方法与静态代码块,而抽象类中可以。d)一个类只能继承一个抽象类,却可以实现多个接口。
6)接口与抽象类在设计层面的区别:a)抽象类是对事物的抽象,即对类整体的抽象,而接口是对行为的抽象。b)继承是一个"是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
7)设计层面不同。对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
8 final关键字
答:final关键字可以修饰类、方法与变量(包括成员变量与局部变量)。1)对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但是可以修改其内容。2)final修饰的成员方法不能被重写。3)final修饰的类不可被继承。
9 java命名规范
尽量使用完整的有意义的单词;不使用缩写;以字母或下划线开头,后跟字母、数字或下划线。
包:全部小写;
类/接口:首字母大写,每个单词的首字母均大写;
变量:首字母小写,之后的每个单词首字母大写;
final变量:全部大写;
方法:第一个应该是动词,且首字母小写;
参数:参数的命名与变量的命名规则相同,最好使用与被赋值的字段一样的名称。
10 java中的equals与==的区别
1)== 比较的是变量的值是否相等。对于8种基本数据类型来说,变量直接存储的是“值”。==进行比较的就是“值”本身。而对于引用类型的变量,其直接存储的不是“值”本身,而是关联的对象在内存的地址。2)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址。3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量,如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
11 java集合框架
Collection是最基本的集合接口,Set,List,Map,Queue
12 HashSet与TreeSet、HashMap与TreeMap
HashSet是无序的,TreeSet是有序的,
13 set与map的区别,内部实现
答:
14 arraylist,hashmap,linkedlist是线程安全的吗?他们内部分别是如何实现的
答:
2 JVM的工作原理,java程序是如何实现跨平台的?
答:java代码之所有能够实现跨平台,是由于JVM的作用。JVM并不是物理机器,而是完成特定功能的程序。java源文件经过编译生成.class的字节码文件,字节码文件不能直接运行,必须翻译成机器码才能运行。
jvm将.class字节码文件翻译成特定平台下的机器码。注意:跨平台的是Java程序,不是JVM。JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM。
JVM是一个”桥梁“,是一个”中间件“,是实现跨平台的关键,Java代码首先被编译成字节码文件,再由JVM将字节码文件翻译成机器语言,从而达到运行Java程序的目的。
注意:编译的结果不是生成机器码,而是生成字节码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。
3 RESTful API,有什么特点? 如何设计好的RESTful API?
答:(1)这个API应该是对浏览器友好的,能够很好地融入Web,而不是与Web格格不入。
浏览器是最常见和最通用的REST客户端。好的RESTful API应该能够使用浏览器+HTML完成所有的测试(不需要使用编程语言)。
这样的API还可以很方便地使用各种自动化的Web功能测试、性能测试工具来做测试。
(2)这个API中所包含的资源和对于资源的操作,应该是直观和容易理解的,并且符合HTTP协议的要求。
REST开发又被称作“面向资源的开发”,这说明对于资源的抽象,是设计RESTful API的核心内容。RESTful API建模的过程与面向对象建模类似,是以名词为核心的。这些名词就是资源,任何可命名的抽象概念都可以定义为一个资源。而HTTP协议并不是一种传输协议,它实际提供了一个操作资源的统一接口。对于资源的任何操作,都应该映射到HTTP的几个有限的方法(常用的有GET/POST/PUT/DELETE四个方法,还有不常用的PATCH/HEAD/OPTIONS方法)上面。所以RESTful API建模的过程,可以看作是具有统一接口约束的面向对象建模过程。
按照HTTP协议的规定,GET方法是安全且幂等的,POST方法是既不安全也不幂等的(可以用来作为所有写操作的通配方法),PUT、DELETE方法都是不安全但幂等的。将对资源的操作合理映射到这四个方法上面,既不过度使用某个方法(例如过度使用GET方法或POST方法),也不添加过多的操作以至于HTTP的四个方法不够用。
(3)这个API中所使用的表述格式应该是常见的通用格式。
GET/POST响应中的资源表述格式,常见的有HTML、XML、JSON;POST/PUT请求中的资源表述格式,常见的有标准的HTML表单参数、XML、JSON。
(4)使用HTTP响应状态代码来表达各种出错情况。
(5)如何对RESTful API进行版本控制,请分享您认为实用的做法? 答:直接将版本号写在URI中。
4 设计一个线程安全的单例模式的类
答:Java中单例模式定义:“一个类有且仅有一个实例,并且由该类自行实例化、并向整个系统提供。” 例如,windows中就只能打开一个任务管理器。
显然,单例模式的要点有三个:1)一个类只能有一个实例;2)它必须自行创建这个实例;3)它必须自行向整个系统提供这个实例。
public class Singleton{ private Singleton(){ //私有构造函数 } private static Singleton instance = null; //静态私有实例对象 public static Singleton getInstance(){//提供公共函数访问实例对象 if(instance == null){ synchronized(Singleton.class){ //(使用双重同步锁),保证是线程安全的
if(instance == null) //这里要判断一下 instance = new Singleton(); } } return instance; } }
5 java程序与其它程序比较,有哪些优缺点?
答:java的优点是能够提高开发者的效率,而它最主要的缺点在于执行速度较慢。
1)面向对象,比面向过程的C语言更具吸引力,但在这方面并不比C++更有价值。不过,和C++相比较,还是有一些开发效率的提升。
这种效率的提升主要来自于java对直接内存操作的约束。java在运行时强制执行严格的类型规则,不会以导致内存冲突的方式直接管理内存,因此,java不会出现使C++程序员降低效率的bug。
2)java避免无意间破坏内存的另一个办法是自动垃圾收集。java比C++更有效率的原因在于java不用再在内存冲突的问题上纠缠不清。而且,当不再需要为显示释放内存担心的时候,效率会更高,程序设计也会更加容易。
3)java运行时保护内存完整性第三个方法是数组边界检查。4)最后一个确保java程序健壮性的例子是对对象引用的检查,每次使用的时候,java都会确保这些引用不为空。而在C++中,若引用了空指针,会导致程序崩溃。
java中仅仅会抛出一个异常。
4)java程序具有平台无关性,更容易提供多平台支持。因此,成本会更低。
6 java虚拟机以及内存区域划分
答:栈内存 与 堆内存。
堆内存 存放对象和数组。
数组和对象在没有引用变量指向它们时,才变成垃圾,不能再被引用。但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。
堆内存空间必须使用new关键字才能开辟。
1)当启动一个java程序时,一个虚拟机实例也就诞生了。java虚拟机实例的天职是:负责运行一个java程序。当程序关闭退出,这个java虚拟机实例就自动消亡。
如果在同一台计算机上同时运行着三个java程序,就会得到三个虚拟机实例。java虚拟机实例通过调用一个类中main()方法来运行一个java程序。
2)组成:类装载器子系统;内存区;执行引擎子系统。类装载器负责将类或接口装入内存。执行引擎负责执行被装载类的方法中的指令。
3)当java虚拟机运行一个java程序时,它需要内存来存储很多东西:例如字节码,从装载的.class文件中得到的其他信息,程序创建的对象,传递给方法的参数,返回值,局部变量,以及运算的中间结果等。
java虚拟机把这些东西组织到“运行时数据区”中。
4)每个java虚拟机实例都会有一个方法区以及一个堆。它们是由该虚拟机实例中的所有线程所共享的。当虚拟机装载一个.class文件时,它会从这个文件中解析出类型信息,然后把类型信息放在方法区中。
方法区中存放的信息有:a 这个类型的全限定名 b 这个类型的父类的全限定名 c 记录是类还是接口 d 访问修饰符 e 父接口全限定名的有序列表 f 这个类型的常量池 g 字段信息 h 方法信息 i 除常量之外的静态变量
j 指向classLoader类的引用 h 指向class类的引用(对每一个被装载的类型来说,java虚拟机相应地为它创建一个实例,java虚拟机以某种方式将这个实例与存储在方法区的类型信息相关联)
程序运行时创建的对象都存放在堆中。堆中存放对象实例和数组。
堆中存放的信息有:a 一种可能的堆空间设计:对象池与句柄池,对象池中存放对象实例或数组信息。一个对象引用变量是一个指向句柄池的引用,
而一个句柄含有两个指针,指向对象实例的指针和指向方法区中类型的指针。
b 另一种可能的堆空间设计:对象指针直接指向一组数据,而这个数据包括对象实例信息和一个指向方法区中类数据的指针。
5)当一个新线程被创建时,它都将得到自己的PC寄存器(程序计数器)以及一个java栈。如果线程正在执行的是一个java方法(非本地方法),那么PC的值总是指向下一条被执行的指令,
而它的java栈则存储该线程中java方法调用的状态--包括它的局部变量、被调用时传递过来的参数、它的返回值以及运算的中间结果等等。而本地方法调用的状态存储在本地方法栈中。
6)java栈是由许多栈桢组成的。一个栈桢包含一个java方法调用的状态。一个方法的调用到结束对应着一栈桢的入栈到出栈过程。
当线程调用一个java方法时,虚拟机压入一个新的栈桢到该线程的java栈中。当该方法返回时,这个栈桢就从java栈中弹出并被抛弃。
7)由于所有线程都共享方法区,因此它们对方法区的访问必须被设计为线程安全的。
8)对于每一个装载的类型,虚拟机都会在方法区中存储以下类型信息:
a 这个类型的全限定名;b 这个类型的直接父类的全限定名称;c 这个类型是类类型还是接口类型;d 这个类型的访问修饰符;e 任何直接父接口的全限定名的有序列表;
f 类型的常量池;g 字段信息;h 方法信息;i 所有常量之外的所有静态变量;j 一个到类classLoader的引用; k 一个到类class的引用。
9)由于所有线程共享java虚拟机实例的堆信息,因此,多线程对堆的访问必须是线程安全的。
10)堆空间的设计结构:分为句柄池和对象池;句柄池中存储的是一个指向对象的指针和一个指向方法区的指针。对象的引用变量中存储的是指向句柄的指针。
这样设计的好处是当移动对象池中的对象时,句柄部分只需要修改一下指针指向对象实例的新地址即可。
7 GC垃圾收集器
答:1)除了释放不再被引用的对象,垃圾收集器还要处理堆碎块。
2)垃圾收集一方面提高了用户生产效率,不需要用户去释放占用的内存;另一方面使得程序保持完整性。
3)但是,使用垃圾收集器有一个潜在缺陷,加大了程序负担,影响了程序性能。因为java虚拟机要追踪哪些对象被正在执行的程序所引用,
并动态地终结并释放不再被使用的对象,这需要更多的CPU时间。
4)垃圾收集算法:垃圾检测通常通过建立一个根对象的集合并且检查从这些根对象开始的可触及性来实现。java虚拟机的根对象集合根据实现不同而不同,
但是总会包含局部变量中的对象引用以及类变量中的对象引用。
根对象的另一个来源是被加载的类的常量池中的对象引用,比如字符串。
5)任何被根对象引用的对象都是可触及的,从而是活动的。另外,任何被活动的对象引用的对象也都是可触及的。程序可以访问任何可触及的对象。
所以,这些对象必须保存在堆里。任何不可触及的对象都可以被收集,因为程序没有办法来访问它们。
6)区分活动对象和垃圾的两个基本方法是引用计数和跟踪。引用计数垃圾收集器:通过为堆中的每一个对象保存一个计数来区分活动对象和垃圾对象。
计数器记录下了对这个对象的引用次数。
跟踪垃圾回收器实际上追踪从根结点开始的引用图,在追踪中遇上的对象以某种方式打上标记,当追踪结束时,没有被划伤标记的对象,就当作垃圾回收。
7)按代收集器通过把对象按照寿命来分组解决效率低下的问题,更多地收集那些短暂出现的年幼对象,而非寿命较长的对象。在这个方法中,堆被划分为两个或者更多个子堆,
每一个子堆为一“代”对象服务。最年幼的那一带进行最频繁的垃圾收集。如果一个年幼的对象经历了好几次垃圾收集后仍然存活,那么这个对象就成长为寿命更高的一代。
8 java内存分配机制
答:分代分配内存、分代回收内存。对象将根据存活的时间分为:年轻代、年老代、永久代(也就是方法区)。
1)年轻代:对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代)。大部分的对象在创建后很快就不再使用,于是被年轻代的GC机制清理掉,
IBM研究表明,98%的对象都是很快消亡的。年轻代上的内存分为三个:Eden区,即内存首次分配的区域;和两个存活区:survivor 0 和 survivor 1。
Eden区是连续的内存空间,因此,在它上分配内存很快。当Eden区满的时候,执行一次垃圾收集,将剩下的对象复制到survivor 0,如此,。。。。直到 survivor 0
满了的时候,在survivor 0上执行垃圾收集,留下的对象复制到survivor 1,以后Eden区满执行垃圾收集后,剩余的对象就复制到survivor 1,
(survivor 0 和 survivor 1一定有一个是空的)。当survivor 0 和 survivor 1切换了多次后,仍然存活的对象被复制到老年区。
2)年老代:对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,
能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行垃圾收集。
9 java的对象访问机制
答:1)通过句柄池访问;堆中有专门一块区域作为句柄池。引用变量中存储的是句柄的地址,句柄中存储对象实例的地址。这种方式,在对象变动时,只需更改句柄即可,稳定。
2)直接指针访问。引用变量中存储的是对象实例在堆中的地址,对象实例中不仅包含实例本身信息,还包含指向对象类型数据的指针。
并且在当前的HotSpot虚拟机中用的就是这种方式。这种方式的特点就是访问速度快。
10 java的GC机制
答:分代收集。
1)年轻代的垃圾收集使用“停止-复制”算法进行清理。
2)年老代不能采用“停止-复制”算法,否则会相当低效。因此,它采用“标记-整理”算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
3)方法区(永久代):
永久代的回收有两种:常量池中的常量,无用的类信息。常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
- 类的所有实例都已经被回收
- 加载类的ClassLoader已经被回收
- 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
11 多线程的同步机制(synchronized、wait、sleep)
答:(1)synchronized块或synchronized方法;wait,notify;
12 java集合框架
答:
13 堆与栈的区别
答:java中将内存分为两种,一种是堆内存,一种栈内存。1)在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。2)当在一段代码块中定义一个变量时,java就在栈中为这个变量分配空间。
14 java中异常类型
答:Exception:a)IOException:aa)EoFException;bb)FileNotFoundException;b)RuntimeException(有多种runtime异常);
对于异常重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少、更限制或者不抛异常。