Java基础
1. 面向对象的特征
1.1 继承
可以让某个类型的对象获得另一个类型的对象的属性的方法。子类将继承父类的所有功能(子类确实会继承父类的一切资源,无论是否私有,继承即代码复用,子类将包含父类的一切成员。而由于访问权限设置,子类将无法直接访问被private所修饰的成员),并在无需重新编写原来的类的情况下对这些功能进行扩展。
继承的实现可以通过继承(Inheritance)和组合(composition)来实现。继承概念的实现方式分为实现继承和接口继承
1.2 封装
封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
封装是对象和类概念的主要特征。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。
1.3 多态
多态就是指一个类实例的相同方法在不同情形有不同的表现形式。
多态机制使具有不同内部结构的对象可以共享相同的外部接口,是实现解耦的重要概念
2. final, finally, finalize的区别
2.1 final
final是一个修饰符也是一个关键字。
- 被final修饰的类无法被继承
- 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是它指向的对象的内容是可变的。 - 被final修饰的方法将无法被重写,但允许重载
注意:类的private方法会隐式地被指定为final方法。
2.2 finally
finally是一个关键字。
- finally在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出或者捕获,finally块都会执行,通常用于释放资源。
- finally块正常情况下一定会被执行。但是有至少两个极端情况:
如果对应的try块没有执行,则这个try块的finally块并不会被执行
如果在try块中jvm关机,例如system.exit(n),则finally块也不会执行(都拔电源了,怎么执行) - finally块中如果有return语句,则会覆盖try或者catch中的return语句,导致二者无法return,所以强烈建议finally块中不要存在return关键字
2.3 finalize
finalize()是Object类的protected方法,子类可以覆盖该方法以实现资源清理工作。
GC在回收对象之前都会调用该方法
finalize()方法是存在很多问题的:
- java语言规范并不保证finalize方法会被及时地执行,更根本不会保证它们一定会被执行
- finalize()方法可能带来性能问题,因为JVM通常在单独的低优先级线程中完成finalize的执行
- finalize()方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
- finalize方法最多由GC执行一次(但可以手动调用对象的finalize方法)
3. Exception,Error,运行时异常与一般异常有何异同
Exception和Error:
- 它俩都继承了Throwable类
- Error是无法预期的严重错误导致JVM无法继续执行,任何情况下都不应该尝试去捕获一个error
大多数应用程序都不应该试图捕获它。
在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
--《JDK API 1.6.0 中文版>
- Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件,是达成健壮性的手段。
运行时异常(RuntimeException)和一般异常(checked Exception):
- checked Exception是编译期必须处理的异常,否则编译无法通过。这种异常一般不会影响程序的主体,容易手动诊断修复。所以Java要求在编译就对此类可能的异常做出处理以保证程序一旦遇到此类异常依然可以正常运行。
- 运行时异常直接继承了Exception类,它属于unchecked Exception。
- 相较于编译时被检查的异常,运行时异常通常情况下是程序绝对不想遇到的,会非常显著的影响程序运行,从设计者角度不提倡从程序中catch出来处理。
4. 请写出5种常见到的runtime exception
4.1 NPE
Null Pointer Exception 空指针异常
4.2 IOOBE
Index Out Of Bound Exception 下标越界异常
4.3 NFE
Number Format Exception 数字转换异常
4.4 CCE
Class Cast Exception 类转换异常
4.5 IAE
Illegal Argument Exception 非法参数异常
5. int 和 Integer 有什么区别,Integer的值缓存范围
- int是基本数据类型(原始数据类型),直接存储数值,作为成员变量时默认初始值为0
- Integer则是对象,是java为int这个原始类型提供的包装类。作为成员变量时默认初始化为null。
- 从Java5开始引入了自动装箱/拆箱机制,使二者可以快速的转换。
JVM会自动维护八种基本类型的常量池,而int常量池中初始化-128到127的范围。
所以当Integer i=127时,在自动装箱过程中是取自常量池中的数据。
而当Integer i=128时,128不在常量池范围内,所以在自动装箱过程中需要一个new 128的过程。
Java5中引入了Integer缓存的相关特性,用于提升性能并节省内存。整型对象在内部实现中通过使用相同的对象(加大对简单数字的重利用,Java 定义在自动装箱时对于值在 -128~127 之间的值,他们被装箱为Integer 对象后,会存在内存中被重用,始终只有一个对象。)引用实现了缓存和重组。
此规则适用于整数区间 -128 到 +127。
Java6中引入了IntegerCache这个Integer的内部类,可以使用JVM的启动参数来设置最大值。这个机制使得根据应用程序的实际情况来灵活地调整提高性能成为可能。
这种缓存行为不仅适用于Integer对象,所有整数类型的类都有类似的缓存机制。但除了Integer可以通过参数改变范围之外,其他的都不可以。
6.包装类,装箱和拆箱
6.1 包装类
Java的八种数据类型不是对象,而Java提供了包装类将基本数据类型包装成一个对象,这样就可以以对象的形式来操作基本数据类型。
八种包装类的父类并不相同:
- Integer,Byte,Float,Double,Short,Long都继承了Number这个抽象类,Number类主要将数字包装类中的内容变成基本数据类型。
- Character跟Boolean属于Object的子类
6.2 装箱
将基本数据类型转换成包装类的过程称之为装箱。
在JDK1.5之后,对程序的包装类功能进行了改变,增加了自动装箱和自动拆箱功能,允许了包装类直接进行运算(+,-,*,/,%,++,--)
6.3 拆箱
将包装类转换成基本数据类型的过程称之为拆箱。
使用包装类,可以将字符串实现基本数据类型的转换操作(parseXXX()方法)
注意!除过char的包装类Character,所有的包装类都有parseXXX()方法,包括boolean的包装类Boolean也有parseBoolean()方法
7. String、StringBuilder、StringBuffer有什么区别
这三个类都是用于处理字符串的Java类,底层也都是通过字符数组来实现的。
7.1 String
String类直接继承了Object类,被final修饰,是一个不可变类(String类没有append(), delete(), insert()这三个成员方法)。
- String类是不可变的,即字符串常量
- 每次对String类型进行改变的时候都等同于生成了一个新的String对象,然后将指针指向新的String对象。
- 对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。
- 在String str = "hello world!"中
JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;
否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。 - 通过new关键字生成对象是在堆进行的,而在堆进行对象生成的过程是不会去检测该对象是否已经存在。
因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。 - 当内存中的无引用对象多了以后,GC会进行垃圾回收,这个过程会消耗很长的时间。
- 所以String的执行速度可以说是三者中最慢的。
但在某些特殊情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这些时候String对象的速度并不比StringBuffer对象慢。
String string = "this is only a" + "simple" + "test.";
StringBuffer buffer = new StringBuffer("this is only a").append(" simple").append(" test.");
这里的话,String的执行速度是远远大于StringBuffer的。但是如果拼接的字符串属于不同对象,则StringBuffer和StringBuilder都远远快于String。
7.2 StringBuffer
StringBuffer和StringBuilder都直接继了AbstractStringBuilder类,而AbstractStringBuilder类直接继承了Object。所以StringBuffer和StringBuilder其实并不是String的子类。(被final所修饰的String其实也不可能有任何子类)
StringBuffer和StringBuilder类拥有的成员属性以及成员方法基本相同。区别在于StringBuffer类的成员方法前面多了一个关键字:synchronized。因此,
StringBuffer是线程安全的。
- StringBuffer是一个线程安全的可变字符序列,是一个类似String的字符串缓冲区,但不能修改。
- StringBuffer每次的操作都是对对象本身进行操作,而不是生成新的对象,再改变对象引用。
- StringBuffer上的主要操作是append()和insert()方法。
这些方法允许被重载,以用于接受任意类型的数据。
每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串的缓冲区中。
7.3 StringBuilder
StringBuilder是一个在JDK1.5中新增的可变字符序列。该类设计意图是在字符串缓冲区被单个线程使用的时候作为StringBuffer的一个替换。
- StringBuilder是线程不安全的。
- StringBuilder提供一个与StringBuffer兼容的API,但不保证同步。
- 如果可能,优先采用StringBuilder而不是StringBuffer,因为它的执行速度比StringBuffer要快,但当涉及到线程安全性时,请谨记StringBuilder是线程不安全的。
8. 重载和重写的区别
8.1 方法的重载Overloading
方法重载是让类以统一的方式处理不同类型数据的一种手段。
多个同名函数同时存在,具有不同的参数个数/类型。
重载是一个类中多态性的一种表现
重载方法的方法名相同,但参数类型和个数不一样,返回值类型可以相同也可以不相同。所以无法以返回值作为重载函数的区分标准。
- 重载必须具有不同的参数列表
- 重载可以有不同的返回值类型
- 可以有不同的访问修饰符
- 可以抛出不同的异常
8.2 方法的重写Overriding
重写时父类与子类之间多态性的一种表现。
子类对父类的函数进行重新定义。如果子类中定义某方法与其父类有相同的名称和参数,则认为此方法被重写(但请注意被private修饰的方法,private方法默认是被final修饰的,子类可以继承但由于访问权限修饰符是private而无法访问。因此父类的private方法不存在重写的说法)。
Java允许子类继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做一定的修改。这就需要方法的重写/方法的覆盖。
子类中的方法如果与父类中的某一方法具有相同的方法名,返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可以使用super关键字调用,该关键字表示当前类的父类对象的引用。
- 覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能被称为是方法的重写/覆盖。
- 覆盖的方法的返回值必须和被覆盖的方法的返回值一致。
- 覆盖的方法所抛出的异常必须和被覆盖方法的所抛出异常一致,或者是其子类。
- 父类被private或final修饰的方法不能被重写,如果子类使用相同方法头,也只是定义了一个新方法,与父类中的方法没有任何关系,也不能被称为方法的重写。
- 子类不能缩小父类方法的访问权限。(子类重写方法的访问权限必须大于或等于被重写方法的访问权限)
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)
9. 抽象类和接口有什么区别
9.1 抽象类
抽象类是用来捕捉子类的通用特性的。
它不能被实例化,只能被用作子类的超类。
抽象类是被用来创建继承层级里子类的模板。
- 抽象类可以有非抽象的方法(可以有默认的方法实现)
- 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的抽象方法的实现。
- 抽象类可以有构造器,它的构造器是有存在意义的。
- 除了不能实例化之外,抽象类和普通Java类没有任何区别。
- 抽象类可以有public,protected和default这些修饰符
- 抽象类可以有main方法而且可以运行它
- 抽象类可以继承一个类和实现一个或多个接口
- 抽象类比接口速度要快
- 如果往抽象类中添加新的方法,可以给他提供默认的实现(类似于模板方法设计模式中的钩子)。这样就不需要改变已有的代码。
9.2 接口
接口是抽象方法的集合。
如果一个类实现了某个接口,就继承了这个接口的抽象方法。
如同契约模式,如果实现了一个接口,就必须确保使用这些方法。(接口的实现类必须实现接口里每一个抽象方法)
接口只是一种形式,自身不能做任何事情。
- 接口是完全抽象的,它不存在任何方法的实现。
- 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
- 接口不能有构造器,因为在接口中构造器是毫无意义的。
- 接口不同于普通Java类,是一个完全不同的类型。
- 接口方法默认修饰符是public,不可以使用其他的修饰符。
- 接口没有main方法,因此不能运行。
- 接口只可以继承一个或者多个其他接口。不可以继承类。
- 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
- 如果往接口中添加方法,那么必须改变实现该接口的类。(因为接口中的方法都是抽象方法,子类又必须实现全部的抽象方法)
请注意,在JDK1.8中,接口中被允许定义static方法和default方法。
面向对象的原则要求我们:多使用组合,少使用继承。因此优先使用接口而不是抽象类。
10. 说说反射的用途及实现
反射的概念在1982年首次提出,主要指程序可以访问,检测和修改其本身状态或行为的一种能力。
当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。
Java并不是动态语言,但有一个非常突出的动态相关机制:反射。
反射Reflection是Java程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类和对象的内部属性。
JVM在运行时才动态加载类或调用方法/属性,并不需要事先在编译时期知道运行的对象是谁。
Java反射框架提供如下功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以获取并调用private所修饰的属性/方法)
- 在运行时调用任一对象的方法
反射最重要的用途就是开发各种通用框架。
大部分框架都是配置化(通过诸如XML配置文件配置JavaBean,Action等)的,为了保证框架的通用性,需要根据配置文件加载不同额对象或类,调用不同的方法。
这时候就必须使用反射来在运行时动态加载需要加载的对象。
但请注意,反射很强大,但在性能方面会有一定的损耗,用于字段和方法接入时反射要远远慢于直接写代码。因此注意不要滥用反射,造成过度编程。
11. 说说自定义注解的场景及实现
注解Annotation是JDK1.5引入的。它可以创建文档,跟踪代码中的依赖性,甚至执行基本编译时的检查。
注解以 @注解名 的方式存在于代码中。
根据注解参数的个数,注解可以分为标记注解,单值注解,完整注解三类。这三类都不会直接影响到程序的语义,只是作为注解(标识)存在。
通过反射机制编程可以实现对这些元数据的访问。
注解默认只存在与源代码级,但可以通过设置增加生命周期。
自定义注解类似于创建一个新的接口文件,本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。
自定义注解类编写规则:
- Annotation型定义为@interface,所有的Annotation会自动继承lang包下的Annotation接口,并且不能再去继承别的类或接口。
- 参数成员只能使用public或者default这两个访问权限修饰
- 参数成员只能使用基本数据类型和String, Enum, Class(这里的Class并不是类对象,而是字节码文件.class), annotations等数据类型或其中一些类型的数组。
- 要获取类方法和字段的注解信息,必须通过反射来获取Annotation对象,除此之外没有任何其他方法来获取注解对象。
- 注解可以没有定义的成员,虽说这样做注解也几乎没有意义了。
- 自定义注解基本都要使用到元注解来增加生命周期。(因为默认的自定义注解只存在于源码阶段)
12. HTTP请求的GET与POST方式的区别
超文本传输协议Http是一个设计来使客户端和服务器顺利进行通讯的协议。
Http在客户端和服务器之间以request-response protocal(请求-回复协议)工作。
12.1 GET方式请求
使用get方法时,查询字符串(键值对的形式)被附加在URL地址后面一起发送到服务器。
- get请求能够被缓存
- get请求会保存在浏览器的浏览记录中
- 以get请求的url能瓯北保存为浏览器书签
- get请求有长度限制
- get请求主要用于获取数据
- get只允许ASCII字符
- get请求只产生一个tcp数据包
12.2 POST方式请求
使用post方法时,查询字符串在post信息中单独存在,和Http请求一起发送到服务器。
- post请求不能被缓存下来。
- post请求不会保存在浏览器浏览记录中
- 以post请求的url无法保存为浏览器书签(这句话不严谨)
- post请求理论上没有长度限制
- post对字符没有限制,也允许二进制数据
- post请求会产生两个tcp数据包。(此处在知乎有异议存在)
Http协议中:
- get和post本质上就是tcp连接,并无差别。但由于Http的规定和浏览器/服务器的限制,导致他们在应用过程中体现出不同。
- get和post与数据如何传递没有关系。
- Http协议对get和post都没有长度的限制。造成get有长度限制的原因在于浏览器和服务器对URL长度有限制。
- get和post对于安全性没有关系。
总体而言:
GET的语义是请求获取指定的资源。
GET方法是安全,幂等(Idempotent,指同一个请求方法执行多次和仅执行一次的效果完全相同),可缓存的(除非在Header中设置了相关的约束),GET方法的报文主题没有任何语义。
POST的语义是根据请求负荷(报文主题)对指定的资源做出处理,具体的处理方法视资源类型而不同。
POST不安全(没错,POST并不安全:这里的安全和通常理解的安全意义不同——如果一个方法的语义在本质上是【只读】的,那么这个方法就是安全的),不幂等,(大部分)不可缓存。为了针对其不可缓存性,有一些方法可以用来进行优化。
13. Session与Cookie区别
13.1 Session
session是一个抽象概念,开发者为了实现中断和继续等操作,将user agent和server之间一对一的交互,抽象为会话,进而衍生出会话状态,即session。
我们所常说的session,是为了绕开cookie的各种限制,通常借助cookie本身和后端存储实现的,一种更高级的会话状态实现。
- session是存储在服务器端的。
- session默认被存在服务器的一个文件里(并不是内存)。
- session的运行依赖session id,而session id是存在cookie中的。也就是说,如果浏览器禁用cookie,session也会失效(但这种情况下可以通过其他方式实现session,例如在url中传递session_id)
- session可以方法在文件,数据库,甚至内存。
- 维持一个会话的核心就是客户端的唯一标识,即session id。
13.2 Cookie
Cookie是实际存在的,http协议中定义在header中的字段。
cookie可以被认为是session的一种后端无状态实现。
- cookie存储于浏览器(客户端)
- cookie根据请求的路径自动发送,服务器端可以对其进行处理。
- cookie是可以被其他任何网站使用的,非常容易泄露用户隐私。
- 一般浏览器对个人网站站点有cookie数量和大小的限制。
14. 列出自己常用的JDK包
14.1 java.lang
语言包,系统会自动导入,不需要import语句。
这是Java语言的核心包,包括object,数据类型包装类,数学类Math,String,System,Class,Throwable等关键类。
14.2 java.util
工具实用包。
实用包提供了各种实用功能的类,主要包括日期累,数据结构类和随机数类等。
14.3 java.sql
访问数据库的包。
提供使用Java编程语言访问并处理存储在数据源(通常是一个数据关系库)中的数据的API。借助这个包可以动态地安装不同驱动程序来访问不同数据源。
14.4 java.io
提供与流相关的输入输出包,其中包含了大量的使用了装饰者模式的装饰类。
15. MVC设计思想
MVC是一种软件架构的思想。将一个软件按照模型,视图,控制器进行划分。
其中,模型用来封装业务逻辑,视图用来实现表示逻辑,控制器用来协调模型与视图(视图要通过控制器来调用模型,模型返回的处理结果也要先交给控制器,由控制器来选择合适的视图来显示处理结果。)
- 模型Model
业务逻辑包含了业务数据的加工与处理以及相应的基础服务(为了保证业务逻辑能够正常进行的事务,安全,权限,日志等等的功能模块) - 视图View
展现模型处理的结果,提供相应的操作界面,方便用户使用。 - 控制器Controller
视图发请求给控制器,由控制器来选择相应的模型来处理;模型返回的结果给控制器,由控制器选择合适的视图。
使用mvc的思想来设计一个软件,最根本原因是为了实现模型的复用。模型不用关心处理结果如何展示,也可以使用不同的视图来访问同一个模型。
mvc使代码的维护性更好:修改模型不会影响到视图,反之修改视图也不会影响到模型。
但使用mvc会增加代码量,相应地也会增加软件开发的成本,设计难度也会增加。
16. equals与==的区别
16.1 ==
"=="在Java编程语言中是一个二元操作符,用于比较原生类型和对象。
对原生类型而言,使用"=="比较的是值是否相等。
对于对象而言,"=="比较的事两个对象基于内存的引用。如果两个对象的引用完全相同(指向同一个对象,地址值相等)时,返回true,否则为false。
16.2 equals()方法
equals()是Object类中的一个public方法。Object类的equals()方法底层依赖的是==运算符。这个方法设计意图是让开发者去重写的,让开发者自己去定义满足什么条件的两个Object是equal的。所以我们不能单纯的说equals到底比较的是什么。
equals()方法根据具体的业务逻辑来定义该方法,用于检查两个对象的相等性。在开发中,equals和hashcode是有契约的(只要重写equals方法,原则上必须重写hashcode方法)。
而当没有重写equals()方法时,equals由于Object底层使用==实现,所以与==效果是一样的。
注意,equals是一个方法,而基本数据类型不是对象,无法调用方法,所以比较原生类型只能使用==。
在JDK中说明了实现equals()方法应该遵守的约定:
- 自反性: x.equals(x)必须返回true。
- 对称性: x.equals(y)与y.equals(x)的返回值必须相等。
- 传递性: x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
- 一致性: 如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。
- 非null:x不是null,y为null,则x.equals(y)必须为false。
17. hashCode和equals方法的区别与联系
equals()与hashCode()都是Object类中可以被子类重写的方法。(设计者设计这两个方法的意图也是让开发者重写)
equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码,在Java中都可以用来对比两个对象是否相等。
重写的equals()里一般比较的相当全面且复杂,进而导致效率较低;而利用作为native方法的hashCode()进行对比,则只需要生成一个hash值进行比较就可以了,效率很高。
但hashCode()的比较并不完全可靠,有时候不同的对象生成的hashcode也会一样(重写的hash值的计算公式可能出现问题),所以只能说hashCode()在大部分时候可靠。
因此结论:
- equals()为true的两个对象他们的hashCode肯定相等,也就是equals()的结果是绝对可靠的。
- hashCode()相等的两个对象他们的equals()不一定相等。
每当需要对比的时候,首先用hashCode()去对比,如果hashCode不一样,则表示这俩对象肯定不相等,因此不需要再调用效率较低的equals()方法;如果hashCode()相同,再对比equals(),如果为true,则表示对象相同。
这种比较方式大大提高了效率,也同时保证了对比的绝对可靠。
18. 什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用
无论何种数据类型,都是以二进制的形式在网络上传送。为了由一个进程把Java对象发送给另一个进程,需要把其转换为字节序列才能在网络上传送,把Java对象转换成字节序列的过程就称为对象的序列化;将字节序列恢复成Java对象的过程称为对象的反序列化。
IO包下的ObjetOutPutStrean的writeObject()方法可以将参数指定的对象进行序列化并且把得到的字节流写到一个目标输出流上去以待今后读取。利用对象的序列化实现保存应用程序的当前工作状态,下次再使用时将自动地恢复到上次执行的状态。
只有实现了serializable或externalizable接口的类的对象才可以被序列化。externalizable是serializable的子类,实现这个接口的类完全由自身来控制序列化的行为,而仅仅实现serializable的类可以采用默认的序列化方法。只要实现这两个接口其中之一,就标志着这个类的对象可以被序列化。
序列化就是一种用来处理对象流(讲对象的内容进行流化)的机制。可以对流化后的对象进行读写操作,也可以将流化的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
19. Object类中常见的方法,为什么wait notify会放在Object里边?
关于wait(),暂停的是持有锁的对象,所以想调用wait()必须为:对象.wait();
notify()唤醒的是等待锁的对象,调用必须为对象.notify()。
wait(),notify(),notifyAll()都必须使用在同步中,因为要对持有监视器(锁)的线程操作,而只有同步才具有锁。
在synchronized中的锁可以是任意对象,任意对象都可以调用wait()和notify(),所有wait()和notify()都必须属于所有对象的父类Object。
这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有一锁上的被等待线程,可以被同一个锁上的notify()唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
在JDK1.5之后,将同步synchronized替换成了Lock,将同步锁对象换成了Condition对象,并且Condition对象可以有多个,用以减少消耗资源和尽量避免所有线程都在等待的情况出现。
所以JDK1.5提供了Lock接口和Condition对象。Condition中的await(),signal(),signalAll()代替Object中的wait(),notify(),notifyAll(),这样我们可以指定唤醒某一方,从而减少了消耗。
20. Java的平台无关性如何体现出来的
Write once, run anywhere。
这句话可以翻译为一次编写,到处运行。这是SUN用来展示Java程序设计语言的跨平台特性的口号。
Java是一个平台无关的语言,由于JVM的存在,Java代码几乎可以运行在任何安装了JVM的设备上。
对平台无关性的支持,如果对安全性和网络移动性的支持一样,是分布在整个Java体系结构中的,所有的组成成分:语言,class文件,API以及虚拟机都扮演着重要的角色。
20.1 JVM
JVM在运行Java程序时,与其下的硬件和操作系统之间起到一个缓冲的作用,无论Java程序运行在哪儿,它只需要与Java平台交互,而不需要担心具体底层硬件和操作系统做了什么,因此,它能够运行在任何安装了JVM的设备上。
20.2 Java语言
Java的基本数据类型的值域和行为都是由语言本身定义的。它的占宽是由目标平台决定的。所以Java程序运行时,不管其平台是什么,Java中的数据类型都是一样的,这一点在Java虚拟机内部跟class文件中都是一致的,以确保基本数据类型在所有平台上的一致性。
20.3 Java class文件
class文件定义了一个特定于Java虚拟机的二进制格式,clss文件可以在任何平台上创建,也可以在任何平台上运行。它的格式有严格的定义和约束,而这些与JVM所处的平台是无关的。
20.4 可伸缩性
Java的可伸缩性指的是JVM可以在各种不同类型的计算机上实现。尽管现在Java在web领域大行其道,但最初的设计却是期望用于嵌入式设备和消费电器平台的,而不是桌面计算机。J2EE,J2SE,J2ME三个基础API集合使Java可以在各种平台运行:
企业版的存在表明了Java平台在高端服务器的可用性;标准版提供了在浏览器中启动传统applet的功能和桌面环境下的Java平台;在低端通过不同的行业子集,显示了Java平台可以向下伸缩,改变自己以适应不同消费性电器市场和嵌入式系统所需求的能力。
21. JDK和JRE的区别
21.1 JRE
JRE(Java Runtime Envioment)是Java的运行环境。面向Java的使用者,而不是开发者。
如果下载并安装了JRE,那么系统就可以运行Java程序。JRE是运行Java程序所必须环境的集合,包含JVM标准实现以及Java核心类库。
JRE包含JVM,Java平台核心类和支持文件,不包含开发工具(编译器,调试器等)
21.2 JDK
JDK(Java Development Kit)或者J2SDK(Java2 Software Development Kit),是Java开发工具包。它提供了Java的开发环境(提供了编译器javac等工具,可以将.java文件编译为.class文件)和运行环境(提供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。
如果下载并安装了JDK,系统不仅可以运行Java程序,更允许你开发Java程序。
JDK是整个Java的核心,包括了JRE,一堆Java工具tools.jar和Java标准类库(rt.jar)。
21.3 JDK和JRE的区别
JRE主要包含了Java类库的class文件(都在lib目录下打包成了jar)和虚拟机(jvm,dll)。安装JRE时安装程序会自动把JRE的Java.exe添加到系统变量中,允许用户不用自己配置环境变量而运行Java程序。
JDK主要包含了java类库的class文件并自带一个JRE,而且在jdk/jre/bin下的client和server两个文件夹下都包含jvm.dll,说明JDK自带的JRE有两个虚拟机。JDK所提供的运行环境和工具都需要开发者手动进行环境变量的配置以后才能使用。JDK的bin目录下有各种Java程序需要用到的命令,与JRE的bin目录最明显的区别就是JDK文件下才有javac,也就是Java的编译器。
22. Java 8有哪些新特性
JDK1.8是自从JDK1.5以来Java语言最大的一次版本升级,带来了诸如编译器,类库,开发工具和JVM等新特性。
22.1 Lambda表达式和函数式接口
Lambda表达式(闭包)是JDK1.8中最大的变化。它允许我们将一个函数当作方法的参数,或者说把代码当作数据。很多基于JVM平台的语言一开始就支持Lambda表达式,但Java选择使用匿名内部类来替代Lambda表达式。
Lambda表达式的设计被讨论了很久,而且花费了很多功夫来交流。不过最后取得了一个折中的办法,得到了一个新的简明并且紧凑的Lambda表达式结构。
语言的设计者思考了很多如何让现有的功能和Lambda表达式友好兼容,于是就有了函数接口。函数接口是一种只有一个方法的接口,函数接口可以隐式地转换成Lambda表达式。JDK1.8提供了一个特殊的注解 @FunctionalInterface 来克服函数接口的脆弱性(函数接口有且只能有一个抽象方法,只要有人在接口里多添加一个方法,这个接口就不再是函数接口)并且显式地表明函数接口的目的。
Lambda是JDK1.8最大的亮点,它巨大的潜力吸引越来越多的开发人员转到这个开发平台来,并且在纯Java提供最新的函数式编程的概念。
22.2 接口的默认方法和静态方法
JDK1.8在接口声明时增加了两个新的概念:默认(default)和静态(static)方法。默认方法允许在接口中添加新的方法,而不会破坏实现了这个接口的已有的类的兼容性,也就是说不会强迫实现接口的类去实现新的默认方法。
所有接口的实现类都会通过继承得到默认方法(如果需要的话重写默认方法也是允许的)。JVM平台的接口的默认方法实现是很高效的,并且方法调用的字节码指令支持默认方法。默认方法使已经存在的接口可以修改而不会影响编译的过程。util包下的Collection中就新添了一些额外的默认方法: stream(), parallelStream(), forEach(), removeIf()。默认方法时非常强大的,但使用之前一定要仔细考虑是不是真的需要使用默认方法,因为在层级很复杂的情况下很容易引起模糊不清甚至编译错误。
22.3 方法引用
方法引用提供了一个很有用的语义来直接访问类或者实例的已经存在的方法或者构造方法。结合Lambbda表达式,方法引用使语法机构紧凑简明而不需要复杂的引用。
//构造方法应用
final Car car = Car.create(Car::new);
//静态方法引用(collide是Car类的一个静态方法)
cars.forEach(Car::collide);
//类实例的方法引用(reapair是Car类的一个实例方法/非静态方法)
cars.forEach(Car::repair);
22.4 重复注解
JDK1.5加入注解,但有一个限制:同一个地方不能使用同一个注解超过一次。JDK1.8则打破了这个规则,引入了重复注解,允许相同的注解在声明使用的时候重复使用超过一次。
重复注解本身需要被 @Repeatable 注解。实际上并不是语言上的改变,而是编译器层面的改动,技术层面仍然是一样的。
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
反射的API也提供了一个新的方法getAnnotaionsByType()来返回重复注解的类型。
22.5 更好的类型判断
JDK1.8在类型推断方面改进了很多。在很多情况下,编译器可以推断参数的类型,从而保持代码的整洁。
22.6 注解的扩展
JDK1.8扩展了注解可以使用得到范围,现在几乎可以在所有的地方:局部变量,泛型,超类和接口实现,甚至是方法的Exception声明上使用注解。
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
//方法实现
}
}
22.7 Java库的新特性
JDK1.8新添了很多类,并且扩展了很多的现有的类来更好地支持现代并发,函数式编程,日期/时间等。
- Optional是一个容器,可以保存一些类型的值或者null,提供很多有用的方法来显式检查null避免空指针异常。
- Stream引入了在Java中可以使用的函数式编程。这是目前为止对Java库最大的一次功能添加,希望程序猿通过编写有效,整洁和简明的代码来提高效率。
- JSR 310是JDK1.8新引入的日期时间API,用于改进日期时间的管理。
- Base64成为了JDK1.8的标准库的一部分。
- JDK1.8提供了一个新的Nashorn Javascript引擎,允许我们在JVM运行特定的Javascript应用, Nashorn Javascript引擎是javax.script.ScriptEngine的另一个实现,允许Java和JavaScript互相操作。
23. 浅拷贝和深拷贝的区别
Java克隆是为了得到一个 完全一致的对象。
相同点:对象完全一样。这包括里头所有的变量,对象。
不同点:对象的内存地址不一样。
浅拷贝---能复制变量,如果对象内还有对象,则只能复制对象的地址
深拷贝---能复制变量,也能复制当前对象的 内部对象,请注意clone()方法执行的是浅拷贝, 在编写程序时要注意这个细节。