1. 简述 JDK,JRE,JVM?
一, JDK
JDK(Java Development Kit) 是整个 JAVA 的核心,
包括了 Java 运行环境(Java Runtime Envirnment),
一堆 Java 工具(javac/java/jdb 等)
和 Java 基础的类库(即 Java API 包括 rt.jar).
JDK 是 java 开发工具包,
基本上每个学 java 的人都会先在机器 上装一个 JDK,
那他都包含哪几部分呢?
在目录下面有 六个文件夹,
一个 src 类库源码压缩包,
和其他几个声明文件.
其中, 真正在运行 java 时起作用的
是以下四个文件夹: bin,include,lib, jre.
有这样一个关系, JDK 包含 JRE, 而 JRE 包 含 JVM.
bin: 最主要的是编译器(javac.exe)
include:java 和 JVM 交互用的头文件
lib: 类库
jre:java 运行环境
二, JRE
JRE(Java Runtime Environment,Java 运行环境)
包含 JVM 标准实现及 Java 核心类库.
JRE 是 Java 运行环境, 并不是一个开发环境,
所以没有包含任何开发工具(如编译器和调试器)
JRE 是指 java 运行环境.
光有 JVM 还不能成 class 的 执行,
因为在解释 class 的时候
JVM 需要调用解释所需要的类库 lib. (
jre 里有运行. class 的 java.exe)
JRE ( Java Runtime Environment ),
是运行 Java 程序必不可少的
(除非用其他一些编译环境编译成. exe 可执行文件......)
JRE 的 地位就象一台 PC 机一样,
我们写好的 Win64 应用程序需要操作系统帮 我们运行,
同样的, 我们编写的 Java 程序也必须要 JRE 才能运行.
三, JVM
JVM(Java Virtual Machine),
即 java 虚拟机, java 运行时的环境,
JVM 是一种用于计算设备的规范,
它是一个虚构出来的计算机,
是通过在实际的计算机上仿真模拟
各种计算机功能来实现的.
针对 java 用户,
也就是拥有可运行的. class 文件包 (jar 或者 war) 的用户.
里面主要包含了 jvm 和 java 运行时基本类库(rt.jar).
rt.jar 可以简单粗暴地理解为:
它就是 java 源码编译成的 jar 包.
Java 虚拟机在执行字节码时,
把字节码解释成具体平台上的机器指令执行.
这就是 Java 的能够 " 一次编译,
到处运行 " 的原因.
2.JDK,JRE,JVM 三者的有什么联系和区别?
1. 三者联系:
JVM 不能单独搞定 class 的执行,
解释 class 的时候 JVM 需要调用解释所需要的类库 lib.
在 JDK 下面的的 jre 目录里面
有两个文件夹 bin 和 lib,
在这里可以认为 bin 里的就是 jvm,
lib 中则是 jvm 工作所需要的类库,
而 jvm 和 lib 和起来就称为 jre.
JVM+Lib=JRE.
总体来说就是,
我们利用 JDK(调用 JAVA API)开发了属于
我们自己的 JAVA 程序后,
通过 JDK 中的编译程序(javac)
将我们的文本 java 文件编译成 JAVA 字节码,
在 JRE 上运行这些 JAVA 字节码,
JVM 解析这些字节码,
映射到 CPU 指令集或 OS 的系统调用.
2. 三者区别:
JDK 和 JRE 区别:
在 bin 文件夹下会发现,
JDK 有 javac.exe 而 JRE 里面没有,
javac 指令是用来将 java 文件编译成 class 文件的,
这是开发者需要的,
而用户 (只需要运行的人) 是不需要的.
JDK 还有 jar.exe, javadoc.exe 等等
用于开发的可执行指令文件.
这也证实了一个是开发环境,
一个是运行环境.
b.JRE 和 JVM 区别:
JVM 并不代表就可以执行 class 了,
JVM 执行. class 还需要 JRE 下的 lib 类库的支持,
尤其是 rt.jar.
3, 简述 Java 程序编译和运行的过程?
Java 程序从源文件创建到程序运行要经过两大步骤:
1, 源文件由编译器编译成字节码(ByteCode);
2, 字节码由 java 虚拟机解释运行.
因为 java 程序既要编译同时
也要经过 JVM 的解释运行,
所以说 Java 被称为半解释语言
第一步(编译):
创建完源文件之后,
程序先要被 JVM 中的 java 编译器
进行编译为. class 文件.
java 编译一个类时,
如果这个类所依赖的类还没有被编译,
编译器会自动的先编译这个所依赖的类,
然后引用.
如果 java 编译器在指定的目录下
找不到该类所依赖的类的 .class 文件
或者 .java 源文件,
就会报 "Cant found sysbol" 的异常错误.
编译后的字节码文件格式
主要分为两部分:
常量池和方法字节码.
常量池记录的是代码出现过的
(常量, 类名, 成员变量等)
以及符号引用(类引用, 方法引用, 成员变量引用等);
方法字节码中放的是各个方法的字节码.
第二步(运行):
java 类运行的过程大概分为两个步骤:
(1)类的加载
(2)类的执行.
需要说明的一点的是:
JVM 主要在程序第一次运行时主动使用类的时候,
才会立即去加载.
换言之,
JVM 并不是在运行时就会把所有使用到的类
都加载到内存中,
而是用到, 不得不加载的时候,
才加载进来, 而且只加载一次!
4, 请说出八种基础数据类型及字节大小?
byte 8 位
short 16 位
int 32 位
long 64 位
float 32 位
double 64 位
boolean 1 位
char 16 位
5. 说说 & 和 && 的区别?
& 和 && 都是逻辑运算符,
都是判断两边同时真则为真, 否则为假;
但是 && 当第一个条件不成之后,
后面的条件都不执行了,
而 & 则还是继续执行,
直到整个条件语句执行完为止.
6.float 型 float f=3.4 是否正确?
答:
不正确;
精度不准确,
应该用强制类型转换,
如下所示: float f=(float)3.4
7.short s1 = 1; s1 = s1 + 1; 有什么错? short s1 = 1; s1 += 1; 有什么错?
short s1 = 1; s1 = s1 + 1; s1+1
运算结果是 int 型,
需要强制转换类型;
short s1 = 1; s1 += 1;
可以正确编译, 自动类型提升.
8.int 和 Integer 有什么区别?
Java 提供两种不同的类型:
引用类型和原始类型(或内置类型);
int 是 java 的原始数据类型,
Integer 是 java 为 int 提供的封装类.
引用类型和原始类型的行为完全不同,
并且它们具有不同的语义.
引用类型和原始类型具有不同的特征和用法,
它们包括: 大小和速度问题,
这种类型以哪种类型的数据结构存储,
当引用类型和原始类型
用作某个类的实例数据时所指定的缺省值.
对象引用实例变量的缺省值为 null,
而原始类型实例变量的缺省值
与它们的类型有关.
9. 在 JAVA 中, 如何跳出当前的多重嵌套循环?
在最外层循环前加 label 标识,
然后用 break:label 方法即可跳出多重循环.
- ok:while(true){
- while(true){
- break ok;
- }
- }
10. 使用嵌套的 for 循环打印九九乘法表.
- public class test {
- public static void main(String[] args) {
- for (int i = 1; i <= 9; i++) {
- for (int j = 1; j <= i; j++) {
- System.out.print(j + "*" + i + "=" + i * j + " ");
- }
- System.out.println();
- }
- }
- }
撩课 Java + 系统架构点击开始学习
11, 面向对象的特征有哪些方面
1. 抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,
以便更充分地注意与当前目标有关的方面.
抽象并不打算了解全部问题, 而只是选择其中的一部分,
暂时不用部分细节.
抽象包括两个方面,
一是过程抽象,
二是数据抽象.
2. 继承:
继承是一种联结类的层次模型,
并且允许和鼓励类的重用,
它提供了一种明确表述共性的方法.
对象的一个新类可以从现有的类中派生,
这个过程称为类继承.
新类继承了原始类的特性,
新类称为原始类的派生类(子类),
而原始类称为新类的基类(父类).
派生类可以从它的基类那里继承方法和实例变量,
并且类可以修改或增加新的方法使之更适合特殊的需要.
3. 封装:
封装是把过程和数据包围起来,
对数据的访问只能通过已定义的界面.
面向对象计算始于这个基本概念,
即现实世界可以被描绘成一系列完全自治,
封装的对象,
这些对象通过一个受保护的接口访问其他对象.
4. 多态性:
多态性是指允许不同类的对象对同一消息作出响应.
多态性包括参数化多态性和包含多态性.
多态性语言具有灵活, 抽象, 行为共享, 代码共享的优势,
很好的解决了应用程序函数同名问题.
12.java 创建对象的几种方式
采用 new
通过反射
采用 clone
通过序列化机制
前 2 者都需要显式地调用构造方法.
造成耦合性最高的恰好是第一种,
因此你发现无论什么框架,
只要涉及到解耦必先减少 new 的使用.
13. 修饰符 public,private,protected, 以及不写时的区别
修饰符 | 当前类 | 同一 package | 子孙类 | 其他 package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
不写时默认为 friendly/default
friendly 权限是 java 的默认权限, 也称作包 (package) 访问权限
只要不加 private,public,protect 的
就是 friendly 访问权限,
所有的成员仅限同一个包内的成员访问
14.String s = new String("xyz"); 创建了几个 String Object
两个, 一个字符对象, 一个字符对象引用对象
15.Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
- Math.round(11.5)==12;
- Math.round(-11.5)==-11;
round 方法返回与参数最接近的长整数,
参数加 1/2 后求其 floor
16.Java 有没有 goto?
java 中的保留字, 现在没有在 java 中使用
17.Overload 和 Override 的区别. Overloaded 的方法是否可以改变返回值的类型
方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现.
重写 Overriding 是父类与子类之间多态性的一种表现,
重载 Overloading 是一个类中多态性的一种表现.
如果在子类中定义某方法与其父类有相同的名称和参数,
我们说该方法被重写 (Overriding).
子类的对象使用这个方法时,
将调用子类中的定义,
对它而言,
父类中的定义如同被 "屏蔽" 了.
如果在一个类中定义了多个同名的方法,
它们或有不同的参数个数或有不同的参数类型,
则称为方法的重载(Overloading).
Overloaded 的方法是可以改变返回值的类型
18.abstract class 和 interface 有什么区别
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),
它用于要创建一个体现某些基本行为的类,
并为该类声明方法,
但不能在该类中实现该类的情况.
不能创建 abstract 类的实例.
然而可以创建一个变量,
其类型是一个抽象类,
并让它指向具体子类的一个实例.
不能有抽象构造函数或抽象静态方法.
Abstract 类的子类为它们父类中的
所有抽象方法提供实现,
否则它们也是抽象类为.
取而代之, 在子类中实现该方法.
知道其行为的其它类可以在类中实现这些方法
接口 (interface) 是抽象类的变体.
在接口中, 所有方法都是抽象的.
多继承性可通过实现这样的接口而获得.
接口中的所有方法都是抽象的,
没有一个有程序体.
接口只可以定义 static final 成员变量.
接口的实现与子类相似,
除了该实现类不能从接口定义中继承行为.
当类实现特殊接口时,
它定义 (即将程序体给予) 所有这种接口的方法.
然后, 它可以在实现了该接口的类的
任何对象上调用接口的方法.
由于有抽象类,
它允许使用接口名作为引用变量的类型.
通常的动态联编将生效.
引用可以转换到接口类型或从接口类型转换,
instanceof 运算符可以用来
决定某对象的类是否实现了接口
19. 接口是否可继承接口?
接口可以继承接口.
抽象类可以实现 (implements) 接口,
抽象类是否可继承实体类, 但
前提是实体类必须有明确的构造函数
20.swtich 是否能作用在 byte 上, 是否能作用在 long 上, 是否能作用在 String 上
swtich()里面必须是 int 和 enum-- 即枚举类型.
short, char 或者 byte 他会自动转换为 int 的..
long 不能自动转换为 int..
因为 long 比 int 范围大.. 可能会丢失精度..
在 java 的 1.7 之后的 jdk 版本,
java 中的 switch 里面表达式的类型可以是 string 类型,
之前是不可以使用的
21.final, finally, finalize 的区别
1,final 修饰符(关键字).
被 final 修饰的类,
就意味着不能再派生出新的子类,
不能作为父类而被子类继承.
因此一个类不能既被 abstract 声明,
又被 final 声明. 将变量或方法声明为 final,
可以保证他们在使用的过程中不被修改.
被声明为 final 的变量必须在声明时给出变量的初始值,
而在以后的引用中只能读取.
被 final 声明的方法也同样只能使用,
即不能方法重写.
22,finally 是在异常处理时
提供 finally 块来执行任何清除操作.
不管有没有异常被抛出, 捕获, finally 块都会被执行.
try 块中的内容是在无异常时执行到结束.
catch 块中的内容,
是在 try 块内容发生 catch 所声明的异常时,
跳转到 catch 块中执行.
finally 块则是无论异常是否发生,
都会执行 finally 块的内容,
所以在代码逻辑中有需要
无论发生什么都必须执行的代码,
就可以放在 finally 块中.
3,finalize 是方法名.
java 技术允许使用 finalize()方法
在垃圾收集器将对象从内存中
清除出去之前做必要的清理工作.
这个方法是由垃圾收集器
在确定这个对象没有被引用时对这个对象调用的.
它是在 object 类中定义的,
因此所有的类都继承了它.
子类覆盖 finalize()方法以整理系统资源或者被执行其他清理工作.
finalize()方法是在垃圾收集器
删除对象之前对这个对象调用的.
###2.Overload 和 Override 的区别.
首先重载和重写是应用于两个不同场景下面的两种不同的手段:
两者各自的特征:
重载(Overload): 首先是位于一个类之中或者其子类中,
具有相同的方法名,
但是方法的参数不同,
返回值类型可以相同也可以不同.
(1): 方法名必须相同
(2): 方法的参数列表一定不一样.
(3): 访问修饰符和返回值类型可以相同也可以不同.
其实简单而言: 重载就是对于不同的情况写不同的方法.
比如, 同一个类中,
写不同的构造函数用于初始化不同的参数.
重写(override): 一般都是表示子类和父类之间的关系,
其主要的特征是:
方法名相同,
参数相同,
但是具体的实现不同.
重写的特征:
(1): 方法名必须相同, 返回值类型必须相同
(2): 参数列表必须相同
(3): 访问权限不能比父类中被重写的方法的访问权限更低.
例如: 如果父类的一个方法被声明为 public,
那么在子类中重写该方法就不能声明为 protected.
(4): 子类和父类在同一个包中,
那么子类可以重写父类所有方法,
除了声明为 private 和 final 的方法.
(5): 构造方法不能被重写,
简单而言: 就是具体的实现类对于父类的该方法实现不满意,
需要自己在写一个满足于自己要求的方法.
23. Java 中的 String,StringBuilder,StringBuffer 三者的区别?
首先说运行速度, 或者说是执行速度,
在这方面运行速度快慢为: StringBuilder> StringBuffer> String
String 最慢的原因:
String 为字符串常量,
而 StringBuilder 和 StringBuffer 均为字符串变量,
即 String 对象一旦创建之后该对象是不可更改的,
但后两者的对象是变量, 是可以更改的.
而 StringBuilder 和 StringBuffer 的对象是变量,
对变量进行操作就是直接对该对象进行更改,
而不进行创建和回收的操作,
所以速度要比 String 快很多.
String ----> 字符串常量
StringBuffer ----> 字符串变量(线程安全的)
StringBuilder ----> 字符串变量(非线程安全的)
String: 适用于少量的字符串操作的情况
StringBuilder: 适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer: 适用多线程下在字符缓冲区进行大量操作的情况
24.GC 是什么? 为什么要有 GC?
GC 是垃圾收集的意思,
内存处理是编程人员容易出现问题的地方,
忘记或者错误的内存回收
会导致程序或系统的不稳定甚至崩溃,
Java 提供的 GC 功能
可以自动监测对象是否超过作用域
从而达到自动回收内存的目的,
Java 语言没有提供释放已分配内存的显示操作方法. J
ava 程序员不用担心内存管理,
因为垃圾收集器会自动进行管理.
要请求垃圾收集,
可以调用下面的方法之一:
System.gc()或 Runtime.getRuntime().gc(),
但 JVM 可以屏蔽掉显示的垃圾回收调用.
垃圾回收可以有效的防止内存泄露,
有效的使用可以使用的内存.
垃圾回收器通常是作为一个单独的低优先级的线程运行,
不可预知的情况下对内存堆中
已经死亡的或者长时间没有使用的对象进行清除和回收,
程序员不能实时的调用垃圾回收器
对某个对象或所有对象进行垃圾回收.
Java 有了 GC,
就不需要程序员去人工释放内存空间.
当 Java 虚拟机发觉内存资源紧张的时候,
就会自动地去清理无用变量所占用的内存空间.
当然, 如果需要,
程序员可以在 Java 程序中显式地使用 System.gc()
来强制进行一次立即的内存清理.
25. 构造器如何工作?
Java 在构造实例时的顺序是这样的:
1, 分配对象空间, 并将对象中成员初始化为 0 或者空
,java 不允许用户操纵一个不定值的对象.
2, 执行属性值的显式初始化
3, 执行构造器
4 , 将变量关联到堆中的对象上
而执行构造器的步骤有可以分为以下几步:
1,Bind 构造器的参数
2, 如果显式的调用了 this, 那就递归调用 this 构造器然后跳到步骤 5
3, 递归调用显式或者隐式的父类构造器, 除了 Object 以外, 因为它没有父类
4, 执行显式的实例变量初始化(也就是上边的流程中的第二步, 调用返回以后执行,
这个步骤相当于在父构造器执行后隐含执行的, 看样子像一个特殊处理)
26. 构造器 Constructor 是否可被 override?
构造器 Constructor 不能被继承,
因此不能重写 Overriding,
但可以被重载 Overloading
1). 构造器不能是 native,final,static,synchronized 的,
可以是 public,private, 或什么都没有.
2). 构造器函数里可以写 return 呢, 但后面什么都不许有(包括 null)
3). 构造器不能返回值.
但如果有个 "构造器" 返值了,
它就不是构造器喽, 只是个普通方法
4). super();this(); 这两个方法只能在构造方法里调用.
5). 成员变量声明时候赋值, 比构造函数还早.
27. 写一个 Singleton 出来.
Singleton 模式主要作用是保证在 Java 应用程序中,
一个类 Class 只有一个实例存在.
一般 Singleton 模式通常有几种种形式:
第一种形式: 定义一个类,
它的构造函数为 private 的,
它有一个 static 的 private 的该类变量,
在类初始化时实例话,
通过一个 public 的 getInstance 方法获取对它的引用,
继而调用其中的方法.
- public class Singleton {
- private Singleton(){}
- // 在自己内部定义自己一个实例, 是不是很奇怪?
- // 注意这是 private 只供内部调用
- private static Singleton instance = new Singleton();
- // 这里提供了一个供外部访问本 class 的静态方法, 可以直接访问
- public static Singleton getInstance() {
- return instance;
- }
- }
第二种形式:
- public class Singleton {
- private static Singleton instance = null;
- public static synchronized Singleton getInstance() {
- // 这个方法比上面有所改进, 不用每次都进行生成对象, 只是第一次
- // 使用时生成实例, 提高了效率!
- if (instance==null)
- instance=new Singleton();
- return instance; }
- }
其他形式:
定义一个类,
它的构造函数为 private 的,
所有方法为 static 的.
一般认为第一种形式要更加安全些
28.error 和 exception 有什么区别?
Error 类和 Exception 类都继承自 Throwable 类.
二者的不同之处:
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked).
2.表示一个由程序员导致的错误.
3.应该在应用程序级被处理.
Error:
1.总是不可控制的(unchecked).
2.经常用来用于表示系统错误或低层资源的错误.
3.如何可能的话, 应该在系统级被捕捉.
error 表示恢复不是不可能但很困难的情况下的一种严重问题.
比如说内存溢出.
不可能指望程序能处理这样的情况.
exception 表示一种设计或实现问题.
也就是说,
它表示如果程序运行正常,
从不会发生的情况.
29.HashMap 和 Hashtable 的区别?
hashmap:
1. 线程不安全
2. 允许有 null 的键和值
3. 效率高一点,
4. 方法不是 Synchronize 的要提供外同步
5. 有 containsvalue 和 containsKey 方法
6.HashMap 是 Java1.2 引进的 Map interface 的一个实现
7.HashMap 是 Hashtable 的轻量级实现
hashtable:
1. 线程安全
2. 不允许有 null 的键和值
3. 效率稍低,
4. 方法是是 Synchronize 的
5. 有 contains 方法方法
6.Hashtable 继承于 Dictionary 类
7.Hashtable 比 HashMap 要旧
30.== 和 equals()区别?
对于 ==,
如果作用于基本数据类型的变量,
则直接比较其存储的
"值" 是否相等;
如果作用于引用类型的变量,
则比较的是所指向的对象的地址
对于 equals 方法,
注意: equals 方法不能作用于基本数据类型的变量
如果没有对 equals 方法进行重写,
则比较的是引用类型的变量所指向的对象的地址;
诸如 String,Date 等类对 equals 方法进行了重写的话,
比较的是所指向的对象的内容.
31. 静态变量和实例变量的区别?
静态变量也叫类变量,
这种变量前加了 static 修饰符.
可以直接用类名调用,
也可以用对象调用,
而且所有对象的同一个类变量
都是共享同一块内存空间.
实例变量也叫对象变量,
这种变量没有加 static 修饰符.
只能通过对象调用,
而且所有对象的同一个实例变量
是共享不同的内存空间的.
区别在于:
静态变量是所有对象共有的,
某一个对象将它的值改变了,
其他对象再去获取它的值,
得到的是改变后的值;
实例变量则是每一个对象私有的,
某一个对象将它的值改变了,
不影响其他对象取值的结果,
其他对象仍会得到实例变量
一开始就被赋予的值.
实例变量必须创建对象后
才可以通过这个对象来使用,
静态变量
则可以直接使用类名来引用.
32. 垃圾回收器的基本原理是什么?
垃圾回收器是 Java 平台中用的
最频繁的一种对象销毁方法.
垃圾回收器会全程侦测 Java 应用程序的运行情况.
当发现有些对象成为垃圾时,
垃圾回收器就会销毁这些对象,
并释放这些对象所占用的内存空间.
在这里, 程序开发人员需要知道,
在哪些情况下垃圾回收器
会认为这些对象是垃圾对象.
通常情况下, 如果发生以下两种情况时,
系统会认为这些对象是垃圾对象,
需要销毁.
一是将一个 NULL 值赋值给对象.
二是对象其超出了作用范围,
33. 垃圾回收器可以马上回收内存吗?
不会马上回收,
只有在必须回收时才会回收,
或者你可以调用垃圾回收方法,
虚拟机会在空闲时回收,
至于什么时候回收,
虚拟机说了算
34. 有什么办法主动通知虚拟机进行垃圾回收?
对于 GC 来说,
当程序员创建对象时,
GC 就开始监控这个对象的地址,
大小以及使用情况.
通常, GC 采用有向图的方式记录和管理堆 (heap) 中的所有对象.
通过这种方式确定哪些对象是 "可达的",
哪些对象是 "不可达的".
当 GC 确定一些对象为 "不可达" 时,
GC 就有责任回收这些内存空间.
可以. 程序员可以手动执行 System.gc(),
通知 GC 运行,
但是 Java 语言规范
并不保证 GC 一定会执行.
System.gc()的工作原理
Java 中的内存分配
是随着 new 一个新的对象来实现的,
这个很简单,
而且也还是有一些
可以 "改进" 内存回收的机制的,
其中最显眼的
就是这个 System.gc()函数.
乍一看这个函数似乎是可以进行垃圾回收的,
可事实并不是那么简单.
其实这个 gc()函数的作用只是提醒虚拟机:
程序员希望进行一次垃圾回收.
但是它不能保证垃圾回收一定会进行,
而且具体什么时候进行
是取决于具体的虚拟机的,
不同的虚拟机有不同的对策.
35. 内部类可以引用他包含类的成员吗?
完全可以.
如果不是静态内部类,
那没有什么限制!
一个内部类对象可以访问
创建它的外部类对象的成员包括私有成员.
如果你把静态嵌套类当作内部类的一种特例,
那在这种情况下不可以访问外部类的
普通成员变量,
而只能访问外部类中的静态成员.
内部类的访问规则:
1, 内部类可以直接访问外部类中的成员,
包括私有.
之所以可以直接访问外部类中的成员,
是因为内部类中持有了
一个外部类的引用,
格式 外部类名. this
2, 外部类要访问内部类, 必须建立内部类对象.
内部类定义在局部时,
1, 不可以被成员修饰符修饰
2, 可以直接访问外部类中的成员,
因为还持有外部类中的引用.
但是不可以访问它所在的局部中的变量.
只能访问被 final 修饰的局部变量.
36.Java 中的异常处理机制的简单原理和应用?
一, Execption 可以分为
java 标准定义的异常
程序员自定义异常 2 种
1. 一种是当程序违反了 java 语规则的时候,
JAVA 虚拟机就会将发生的错误
表示为一个异常.
这里语法规则指的是
JAVA 类库内置的语义检查.
例如 int i = 2 / 0
或者 String str = null;str.length();
2. 另一种情况就是 JAVA 允许程序员
扩展这种语义检查,
程序员可以创建自己的异常,
并自由选择在何时用 throw 关键字
引发异常.
例如 Exception ex = new Exception(" 这是我自定义的异常;
throw ex;
所有的异常都是 Thowable 的子类.
异常处理是与程序执行是并行的.
二, 异常的处理方式
1. 捕获异常
- try {
- int i = 2 / 0;
- } catch (Exception ex) {
- ex.printStackTrace();
- System.out.println("异常信息:" + ex.getMessage());
- }
2. 上抛异常 throws
- public void test() throws Exception {
- String str = null;
- str.length();
- }
37. 运行时异常与一般异常有何异同?
(1)运行时异常
都是 RuntimeException 类
及其子类异常,
如 NullPointerException,
IndexOutOfBoundsException 等,
这些异常是不检查异常,
程序中可以选择捕获处理,
也可以不处理.
这些异常一般是由程序逻辑错误引起的,
程序应该从逻辑角度
尽可能避免这类异常的发生.
当出现 RuntimeException 的时候,
我们可以不处理.
当出现这样的异常时,
总是由虚拟机接管.
比如: 我们从来没有人
去处理过 NullPointerException 异常,
它就是运行时异常,
并且这种异常还是最常见的异常之一.
出现运行时异常后,
系统会把异常一直往上层抛,
一直遇到处理代码.
如果没有处理块,
到最上层,
如果是多线程就由 Thread.run()抛出,
如果是单线程就被 main()抛出.
抛出之后,
如果是线程,
这个线程也就退出了.
如果是主程序抛出的异常,
那么这整个程序也就退出了.
运行时异常是 Exception 的子类,
也有一般异常的特点,
是可以被 Catch 块处理的.
只不过往往我们不对他处理罢了.
也就是说,
你如果不对运行时异常进行处理,
那么出现运行时异常之后,
要么是线程中止,
要么是主程序终止.
如果不想终止,
则必须扑捉所有的运行时异常,
决不让这个处理线程退出.
队列里面出现异常数据了,
正常的处理应该是把异常数据舍弃,
然后记录日志.
不应该由于异常数据
而影响下面对正常数据的处理.
(2)非运行时异常
是 RuntimeException 以外的异常,
类型上都属于 Exception 类及其子类.
如 IOException,SQLException 等
以及用户自定义的 Exception 异常.
对于这种异常,
JAVA 编译器强制要求我们
必需对出现的这些异常进行 catch 并处理,
否则程序就不能编译通过.
所以, 面对这种异常不管我们是否愿意,
只能自己去写一大堆 catch 块
去处理可能的异常.
38. 为什么 Map 接口不继承 Collection 接口?
Collection 是最基本的集合接口,
声明了适用于 JAVA 集合(只包括 Set 和 List)
的通用方法.
Set 和 List 都继承了 Conllection;
Set 具有与 Collection 完全一样的接口,
因此没有任何额外的功能,
不像前面有两个不同的 List.
实际上 Set 就是 Collection, 只 是行为不同.
(这是继承与多态思想的典型应用: 表现不同的行为.)
Set 不保存重复的元素(至于如何判断元素相同则较为负责)
Map 没有继承于 Collection 接口
从 Map 集合中检索元素时,
只要给出键对象,
就会返回对应的值对象.
Collection 和 Map 的区别
容器内每个为之所存储的元素个数不同.
Collection 类型者,
每个位置只有一个元素.
Map 类型者,
持有 key-value pair,
像个小型数据库
尽管 Map 接口和它的实现也是集合框架的一部分,
但 Map 不是集合,
集合也不是 Map.
因此, Map 继承 Collection 毫无意义,
反之亦然.
如果 Map 继承 Collection 接口,
那么元素去哪儿?
Map 包含 key-value 对,
它提供抽取 key 或 value 列表集合的方法,
但是它不适合 "一组对象" 规范.
40.comparable 和 comparator 的不同之处?
Comparable 可以认为是一个内比较器,
实现了 Comparable 接口的类有一个特点,
就是这些类是可以和自己比较的,
至于具体和另一个实现了 Comparable 接口的类如何比较,
则依赖 compareTo 方法的实现,
compareTo 方法也被称为自然比较方法.
如果开发者 add 进入
一个 Collection 的对象想要 Collections 的 sort 方法
帮你自动进行排序的话,
那么这个对象必须实现 Comparable 接口.
compareTo 方法的返回值是 int,
有三种情况:
1, 比较者大于被比较者
(也就是 compareTo 方法里面的对象),
那么返回正整数
2, 比较者等于被比较者, 那么返回 0
3, 比较者小于被比较者, 那么返回负整数
Comparator 可以认为是是一个外比较器,
个人认为有两种情况
可以使用实现 Comparator 接口的方式:
1, 一个对象不支持自己和自己比较
(没有实现 Comparable 接口),
但是又想对两个对象进行比较
2, 一个对象实现了 Comparable 接口,
但是开发者认为 compareTo 方法中的
比较方式并不是自己想要的那种比较方式
Comparator 接口里面有一个 compare 方法,
方法有两个参数 T o1 和 T o2,
是泛型的表示方式,
分别表示待比较的两个对象,
方法返回值和 Comparable 接口一样是 int,
有三种情况:
1,o1 大于 o2, 返回正整数
2,o1 等于 o2, 返回 0
3,o1 小于 o2, 返回负整数
总结
两种比较器 Comparable 和 Comparator,
后者相比前者有如下优点:
1, 如果实现类没有实现 Comparable 接口,
又想对两个类进行比较
或者实现类实现了 Comparable 接口,
但是对 compareTo 方法内的比较算法不满意,
那么可以实现 Comparator 接口,
自定义一个比较器,
写比较算法
2, 实现 Comparable 接口的方式比
实现 Comparator 接口的耦合性要强一些,
如果要修改比较算法,
要修改 Comparable 接口的实现类,
而实现 Comparator 的类是在外部进行比较的,
不需要对实现类有任何修 改.
从这个角度说,
其实有些不太好,
尤其在我们将实现类的. class 文件
打成一个. jar 文件
提供给开发者使用的时候.
实际上实现 Comparator 接口的方式
后面会写到就是一种典型的策略模式.
当然, 这不是鼓励用 Comparator,
意思是开发者还是要在具体场景下
选择最合适的那种比较器而已.
学习地址:
撩课 - JavaWeb 系列 1 之基础语法 - 前端基础
撩课 - JavaWeb 系列 2 之 xml
撩课 - JavaWeb 系列 3 之 MySQL
撩课 - JavaWeb 系列 4 之 JDBC
撩课 - JavaWeb 系列 5 之 Web 服务器 - idea
31. 静态变量和实例变量的区别?
静态变量也叫类变量,
这种变量前加了 static 修饰符.
可以直接用类名调用,
也可以用对象调用,
而且所有对象的同一个类变量
都是共享同一块内存空间.
实例变量也叫对象变量,
这种变量没有加 static 修饰符.
只能通过对象调用,
而且所有对象的同一个实例变量
是共享不同的内存空间的.
区别在于:
静态变量是所有对象共有的,
某一个对象将它的值改变了,
其他对象再去获取它的值,
得到的是改变后的值;
实例变量则是每一个对象私有的,
某一个对象将它的值改变了,
不影响其他对象取值的结果,
其他对象仍会得到实例变量
一开始就被赋予的值.
实例变量必须创建对象后
才可以通过这个对象来使用,
静态变量
则可以直接使用类名来引用.
32. 垃圾回收器的基本原理是什么?
垃圾回收器是 Java 平台中用的
最频繁的一种对象销毁方法.
垃圾回收器会全程侦测 Java 应用程序的运行情况.
当发现有些对象成为垃圾时,
垃圾回收器就会销毁这些对象,
并释放这些对象所占用的内存空间.
在这里, 程序开发人员需要知道,
在哪些情况下垃圾回收器
会认为这些对象是垃圾对象.
通常情况下, 如果发生以下两种情况时,
系统会认为这些对象是垃圾对象,
需要销毁.
一是将一个 NULL 值赋值给对象.
二是对象其超出了作用范围,
33. 垃圾回收器可以马上回收内存吗?
不会马上回收,
只有在必须回收时才会回收,
或者你可以调用垃圾回收方法,
虚拟机会在空闲时回收,
至于什么时候回收,
虚拟机说了算
34. 有什么办法主动通知虚拟机进行垃圾回收?
对于 GC 来说,
当程序员创建对象时,
GC 就开始监控这个对象的地址,
大小以及使用情况.
通常, GC 采用有向图的方式记录和管理堆 (heap) 中的所有对象.
通过这种方式确定哪些对象是 "可达的",
哪些对象是 "不可达的".
当 GC 确定一些对象为 "不可达" 时,
GC 就有责任回收这些内存空间.
可以. 程序员可以手动执行 System.gc(),
通知 GC 运行,
但是 Java 语言规范
并不保证 GC 一定会执行.
System.gc()的工作原理
Java 中的内存分配
是随着 new 一个新的对象来实现的,
这个很简单,
而且也还是有一些
可以 "改进" 内存回收的机制的,
其中最显眼的
就是这个 System.gc()函数.
乍一看这个函数似乎是可以进行垃圾回收的,
可事实并不是那么简单.
其实这个 gc()函数的作用只是提醒虚拟机:
程序员希望进行一次垃圾回收.
但是它不能保证垃圾回收一定会进行,
而且具体什么时候进行
是取决于具体的虚拟机的,
不同的虚拟机有不同的对策.
35. 内部类可以引用他包含类的成员吗?
完全可以.
如果不是静态内部类,
那没有什么限制!
一个内部类对象可以访问
创建它的外部类对象的成员包括私有成员.
如果你把静态嵌套类当作内部类的一种特例,
那在这种情况下不可以访问外部类的
普通成员变量,
而只能访问外部类中的静态成员.
内部类的访问规则:
1, 内部类可以直接访问外部类中的成员,
包括私有.
之所以可以直接访问外部类中的成员,
是因为内部类中持有了
一个外部类的引用,
格式 外部类名. this
2, 外部类要访问内部类, 必须建立内部类对象.
内部类定义在局部时,
1, 不可以被成员修饰符修饰
2, 可以直接访问外部类中的成员,
因为还持有外部类中的引用.
但是不可以访问它所在的局部中的变量.
只能访问被 final 修饰的局部变量.
36.Java 中的异常处理机制的简单原理和应用?
一, Execption 可以分为
java 标准定义的异常
程序员自定义异常 2 种
1. 一种是当程序违反了 java 语规则的时候,
JAVA 虚拟机就会将发生的错误
表示为一个异常.
这里语法规则指的是
JAVA 类库内置的语义检查.
例如 int i = 2 / 0
或者 String str = null;str.length();
2. 另一种情况就是 JAVA 允许程序员
扩展这种语义检查,
程序员可以创建自己的异常,
并自由选择在何时用 throw 关键字
引发异常.
例如 Exception ex = new Exception(" 这是我自定义的异常;
throw ex;
所有的异常都是 Thowable 的子类.
异常处理是与程序执行是并行的.
二, 异常的处理方式
1. 捕获异常
- try {
- int i = 2 / 0;
- } catch (Exception ex) {
- ex.printStackTrace();
- System.out.println("异常信息:" + ex.getMessage());
- }
2. 上抛异常 throws
- public void test() throws Exception {
- String str = null;
- str.length();
- }
37. 运行时异常与一般异常有何异同?
(1)运行时异常
都是 RuntimeException 类
及其子类异常,
如 NullPointerException,
IndexOutOfBoundsException 等,
这些异常是不检查异常,
程序中可以选择捕获处理,
也可以不处理.
这些异常一般是由程序逻辑错误引起的,
程序应该从逻辑角度
尽可能避免这类异常的发生.
当出现 RuntimeException 的时候,
我们可以不处理.
当出现这样的异常时,
总是由虚拟机接管.
比如: 我们从来没有人
去处理过 NullPointerException 异常,
它就是运行时异常,
并且这种异常还是最常见的异常之一.
出现运行时异常后,
系统会把异常一直往上层抛,
一直遇到处理代码.
如果没有处理块,
到最上层,
如果是多线程就由 Thread.run()抛出,
如果是单线程就被 main()抛出.
抛出之后,
如果是线程,
这个线程也就退出了.
如果是主程序抛出的异常,
那么这整个程序也就退出了.
运行时异常是 Exception 的子类,
也有一般异常的特点,
是可以被 Catch 块处理的.
只不过往往我们不对他处理罢了.
也就是说,
你如果不对运行时异常进行处理,
那么出现运行时异常之后,
要么是线程中止,
要么是主程序终止.
如果不想终止,
则必须扑捉所有的运行时异常,
决不让这个处理线程退出.
队列里面出现异常数据了,
正常的处理应该是把异常数据舍弃,
然后记录日志.
不应该由于异常数据
而影响下面对正常数据的处理.
(2)非运行时异常
是 RuntimeException 以外的异常,
类型上都属于 Exception 类及其子类.
如 IOException,SQLException 等
以及用户自定义的 Exception 异常.
对于这种异常,
JAVA 编译器强制要求我们
必需对出现的这些异常进行 catch 并处理,
否则程序就不能编译通过.
所以, 面对这种异常不管我们是否愿意,
只能自己去写一大堆 catch 块
去处理可能的异常.
38. 为什么 Map 接口不继承 Collection 接口?
Collection 是最基本的集合接口,
声明了适用于 JAVA 集合(只包括 Set 和 List)
的通用方法.
Set 和 List 都继承了 Conllection;
Set 具有与 Collection 完全一样的接口,
因此没有任何额外的功能,
不像前面有两个不同的 List.
实际上 Set 就是 Collection, 只 是行为不同.
(这是继承与多态思想的典型应用: 表现不同的行为.)
Set 不保存重复的元素(至于如何判断元素相同则较为负责)
Map 没有继承于 Collection 接口
从 Map 集合中检索元素时,
只要给出键对象,
就会返回对应的值对象.
Collection 和 Map 的区别
容器内每个为之所存储的元素个数不同.
Collection 类型者,
每个位置只有一个元素.
Map 类型者,
持有 key-value pair,
像个小型数据库
尽管 Map 接口和它的实现也是集合框架的一部分,
但 Map 不是集合,
集合也不是 Map.
因此, Map 继承 Collection 毫无意义,
反之亦然.
如果 Map 继承 Collection 接口,
那么元素去哪儿?
Map 包含 key-value 对,
它提供抽取 key 或 value 列表集合的方法,
但是它不适合 "一组对象" 规范.
40.comparable 和 comparator 的不同之处?
Comparable 可以认为是一个内比较器,
实现了 Comparable 接口的类有一个特点,
就是这些类是可以和自己比较的,
至于具体和另一个实现了 Comparable 接口的类如何比较,
则依赖 compareTo 方法的实现,
compareTo 方法也被称为自然比较方法.
如果开发者 add 进入
一个 Collection 的对象想要 Collections 的 sort 方法
帮你自动进行排序的话,
那么这个对象必须实现 Comparable 接口.
compareTo 方法的返回值是 int,
有三种情况:
1, 比较者大于被比较者
(也就是 compareTo 方法里面的对象),
那么返回正整数
2, 比较者等于被比较者, 那么返回 0
3, 比较者小于被比较者, 那么返回负整数
Comparator 可以认为是是一个外比较器,
个人认为有两种情况
可以使用实现 Comparator 接口的方式:
1, 一个对象不支持自己和自己比较
(没有实现 Comparable 接口),
但是又想对两个对象进行比较
2, 一个对象实现了 Comparable 接口,
但是开发者认为 compareTo 方法中的
比较方式并不是自己想要的那种比较方式
Comparator 接口里面有一个 compare 方法,
方法有两个参数 T o1 和 T o2,
是泛型的表示方式,
分别表示待比较的两个对象,
方法返回值和 Comparable 接口一样是 int,
有三种情况:
1,o1 大于 o2, 返回正整数
2,o1 等于 o2, 返回 0
3,o1 小于 o2, 返回负整数
总结
两种比较器 Comparable 和 Comparator,
后者相比前者有如下优点:
1, 如果实现类没有实现 Comparable 接口,
又想对两个类进行比较
或者实现类实现了 Comparable 接口,
但是对 compareTo 方法内的比较算法不满意,
那么可以实现 Comparator 接口,
自定义一个比较器,
写比较算法
2, 实现 Comparable 接口的方式比
实现 Comparator 接口的耦合性要强一些,
如果要修改比较算法,
要修改 Comparable 接口的实现类,
而实现 Comparator 的类是在外部进行比较的,
不需要对实现类有任何修 改.
从这个角度说,
其实有些不太好,
尤其在我们将实现类的. class 文件
打成一个. jar 文件
提供给开发者使用的时候.
实际上实现 Comparator 接口的方式
后面会写到就是一种典型的策略模式.
当然, 这不是鼓励用 Comparator,
意思是开发者还是要在具体场景下
选择最合适的那种比较器而已.
41.Iterator,ListIterator 和 Enumeration 的区别?
迭代器是一种设计模式,
它是一个对象,
它可以遍历并选择序列中的对象,
而开发人员不需要了解
该序列的底层结构.
迭代器通常被称为 "轻量级" 对象,
因为创建它的代价小.
Java 中的 Iterator 功能比较简单,
并且只能单向移动:
(1) 使用方法 iterator()要求容器返回一个 Iterator.
第一次调用 Iterator 的 next()方法时,
它返回序列的第一个元素.
注意: iterator()方法是 java.lang.Iterable 接口
被 Collection 继承.
(2) 使用 next()获得序列中的下一个元素.
(3) 使用 hasNext()检查序列中是否还有元素.
(4) 使用 remove()将迭代器新返回的元素删除.
Iterator 是 Java 迭代器最简单的实现,
为 List 设计的 ListIterator 具有更多的功能,
它可以从两个方向遍历 List,
也可以从 List 中插入和删除元素.
-------------
ListIterator 的特点:
它的父类接口是 Iterator,
名称是系列表迭代器,
允许程序员按任一方向遍历列表,
迭代期间修改列表,
并获得迭代器在列表中的当前位置.
ListIterator 没有当前元素,
它的光标位置始终位于调用 previous()
所返回的元素和调用 next()
所返回的元素之间.
长度为 n 的列表的迭代器有 n+1 个
可能的指针位置.
------------------
Enumeration 的特点:
API 中是这样描述的,
它主要是和 Vector 结合配套使用.
另外此接口的功能与 Iterator 接口的功能是重复的,
此外, Iterator 接口添加了
一个可选的移除操作,
并且使用较短的方法名.
新的实现应该优先
考虑使用 Iterator 接口
而不是 Enumeration 接口.
-----------------------
java 中的集合类都提供了
返回 Iterator 的方法,
就是迭代器,
它和 Enumeration 的主要区别
其实就是 Iterator 可以删除元素,
但是 Enumration 却不能.
42.Java 中 Set 与 List 有什么不同?
1,Set 不允许重复, List 允许重复
2,Set 没有顺序, List 有顺序
List 接口对 Collection 进行了简单的扩充,
它的具体实现类常用的有 ArrayList 和 LinkedList.
你可以将任何东西放到一个 List 容器中,
并在需要时从中取出.
ArrayList 从其命名中可以看出
它是一种类似数组的形式进行存储,
因此它的随机访问速度极快,
而 LinkedList 的内部实现是链表,
它适合于在链表中间
需要频繁进行插入和删除操作.
在具体应用时可以根据需要自由选择.
前面说的 Iterator 只能对容器进行向前遍历,
而 ListIterator 则继承了 Iterator 的思想,
并提供了对 List 进行双向遍历的方法.
Set 接口也是 Collection 的一种扩展,
而与 List 不同的时,
在 Set 中的对象元素不能重复,
也就是说你不能把同样的东西
两次放入同一个 Set 容器中.
它的常用具体实现有 HashSet 和 TreeSet 类.
HashSet 能快速定位一个元素,
但是你放到 HashSet 中的对象
需要实现 hashCode()方法,
它使用了前面说过的哈希码的算法.
而 TreeSet 则将放入
其中的元素按序存放,
这就要求你放入
其中的对象是可排序的,
这就用到了集合框架提供的
另外两个实用类 Comparable 和 Comparator.
一个类是可排序的,
它就应该实现 Comparable 接口.
有时多个类具有相同的排序算法,
那就不需要在每分别重复定义
相同的排序算法,
只要实现 Comparator 接口即可.
集合框架中还有两个很实用的公用类:
Collections 和 Arrays.
Collections 提供了对一个 Collection 容器
进行诸如排序, 复制, 查找和填充等
一些非常有用的方法,
Arrays 则是对一个数组进行类似的操作.
43.arraylist 与 vector 的区别?
ArrayList 与 Vector 的区别,
这主要包括两个方面:.
1. 同步性:
Vector 是线程安全的,
也就是说是它的方法之间是线程同步的,
而 ArrayList 是线程序不安全的,
它的方法之间是线程不同步的.
如果只有一个线程会访问到集合,
那最好是使用 ArrayList,
因为它不考虑线程安全,
效率会高些;
如果有多个线程会访问到集合,
那最好是使用 Vector,
因为不需要我们自己
再去考虑和编写线程安全的代码.
备注:
对于 Vector&ArrayList,Hashtable&HashMap,
要记住线程安全的问题,
记住 Vector 与 Hashtable 是旧的,
是 java 一诞生就提供了的,
它们是线程安全的,
ArrayList 与 HashMap 是 java2 时才提供的,
它们是线程不安全的.
2. 数据增长:
ArrayList 与 Vector
都有一个初始的容量大小,
当存储进它们里面的元素的个数超过了容量时,
就需要增加 ArrayList 与 Vector 的存储空间,
每次要增加存储空间时,
不是只增加一个存储单元,
而是增加多个存储单元,
每次增加的存储单元的个数
在内存空间利用与程序效率之间
要取得一定的平衡.
Vector 默认增长为原来两倍,
而 ArrayList 的增长策略
在文档中没有明确规定
从源代码看到的是增长为原来的 1.5 倍.
ArrayList 与 Vector 都可以设置初始的空间大小,
Vector 还可以设置增长的空间大小,
而 ArrayList 没有提供
设置增长空间的方法.
总结:
即 Vector 增长原来的一倍,
ArrayList 增加原来的 0.5 倍.
44. 什么类实现了 List 接口?
List 接口的实现类中
最经常使用最重要的就是这三个:
- ArrayList,
- Vector,
- LinkedList.
1. 三个都直接实现了 AbstractList 这个抽象类
2,ArrayList 和 Vector 都实现了
RandomAccess 接口.
而 LinkedList 没有.
这是什么意思呢?
在 JDK 中,
RandomAccess 接口是一个空接口,
所以它没有实际意义. 就是一个标记,
标记这个类支持高速随机訪问,
所以, arrayList 和 vector 是支持随机訪问的,
可是 LinkedList 不支持持
3.serializbale 接口表明他们都支持序列化.
45. 什么类实现了 Set 接口?
- HashSet
- LinkedHashSet
- TreeSet
HashSet 是使用哈希表 (hash table) 实现的,
其中的元素是无序的.
HashSet 的
add,
remove,
contains 方法
的时间复杂度为常量 O(1).
--------------------
TreeSet 使用树形结构
算法书中的红黑树 red-black tree
实现的.
TreeSet 中的元素是可排序的,
但 add,remove 和 contains 方法的时间
复杂度为 O(log(n)).
TreeSet 还提供了
- first(),
- last(),
- headSet(),
tailSet()等
方法来操作排序后的集合.
-----------------------
LinkedHashSet 介于 HashSet 和 TreeSet 之间.
它基于一个由链表实现的哈希表,
保留了元素插入顺序.
LinkedHashSet 中基本方法的
时间复杂度为 O(1).
46. 如何保证一个集合线程安全?
Java 提供了不同层面的线程安全支持.
在传统集合框架内部,
除了 Hashtable 等同步容器,
还提供了所谓的同步包装器(Synchronized Wrapper),
可以调用 Collections 工具类提供的包装方法,
来获取一个同步的包装容器,
例如 Collections.synchronizedMap().
但是它们都是利用非常粗粒度的同步方式,
在高并发情况下的性能比较低下.
另外, 更加普遍的选择是利用并发包(java.util.concurrent)
提供的线程安全容器类:
各种并发容器,
比如 ConcurrentHashMap,
CopyOnWriteArrayList.
各种线程安全队列(Queue/Deque),
比如 ArrayBlockingQueue,
SynchronousQueue.
各种有序容器的线程安全版本等.
具体保证线程安全的方式,
包括有从简单的 synchronized 方式,
到基于更加精细化的,
比如基于分离锁实现的 ConcurrentHashMap 等并发实现等.
具体选择要看开发的场景需求,
总体来说,
并发包内提供的容器通用场景,
远远优于早期的简单同步实现.
为什么需要 ConcurrentHashMap
首先, Hashtable 本身比较低效,
因为它的实现基本就是
将 put,get,size 等方法
简单粗暴地加上 "synchronized".
这就导致了所有并发操作都要竞争同一把锁,
一个线程在进行同步操作时,
其它线程只能等待,
大大降低了并发操作的性能.
47. 是否可以往 TreeSet 或者 HashSet 中添加 null 元素?
1.TreeSet 是二差树实现的,
Treeset 中的数据是自动排好序的,
不允许放入 null 值
2.HashSet 是哈希表实现的,
HashSet 中的数据是无序的,
可以放入 null,
但只能放入一个 null,
两者中的值都不能重复,
就如数据库中唯一约束
3.HashSet 要求放入的对象
必须实现 HashCode()方法,
放入的对象, 是以 hashcode 码作为标识的,
而具有相同内容的 String 对象,
hashcode 是一样,
所以放入的内容不能重复.
但是同一个类的对象可以放入不同的实例
48.hashCode() 和 equals() 方法的重要性? 如何在 Java 中使用它们?
Java 中的 HashMap 使用
hashCode()和 equals()方法
来确定键值对的索引,
当根据键获取值的时候
也会用到这两个方法.
如果没有正确的实现这两个方法,
两个不同的键可能会有相同的 hash 值,
因此可能会被集合认为是相等的.
而且, 这两个方法也用来发现重复元素,
所以这两个方法的实现对 HashMap 的
精确性和正确性是至关重要的.
同一个对象(没有发生过修改)
无论何时调用 hashCode(),
得到的返回值必须一样.
hashCode()返回值相等,
对象不一定相等,
通过 hashCode()和 equals()
必须能唯一确定一个对象.
一旦重写了 equals(),
就必须重写 hashCode().
而且 hashCode()生成哈希值的依据应该是
equals()中用来比较是否相等的字段.
如果两个由 equals()规定相等的对象
生成的 hashCode 不等,
对于 HashMap 来说,
他们可能分别映射到不同位置,
没有调用 equals()比较是否相等的机会,
两个实际上相等的对象可能被插入到不同位置,
出现错误.
其他一些基于哈希方法的集合类
可能也会有这个问题.
----------------
怎么判断两个对象是相同的?
使用等号 == 判断两个对象是否相同,
这种是严格的相同,
即内存中的同一个对象
Object 的 equal 方法就是使用 == 判断两个对象是否相同
------------
集合 set 要求元素是唯一的, 怎么实现?
要实现元素的唯一,
需要在往集合 set 中添加元素时,
判断集合 set 是否存在相同的元素,
如果存在, 则不添加, 反之.
那么怎么确定两个元素是否相同,
1. 如果是使用等号 == 判断两个元素是否相同,
即默认使用 Object 的 equals 的方法.
2. 如果没有使用等号 == 判断两个元素是否相同,
而是按照某种业务规则判断两个元素是否相同,
即重写了 Object 的 equals 的方法.
----------------------
当重写 equals 方法, 必须重写 hashCode 方法吗?
不是必须的,
得看具体的情况
当 equals 方法返回的结果和使用等号
比较的结果是一致的时候,
是没有必要重写 hashCode 方法.
当用等号比较对象,
只有是内存中同一个对象实例,
才会返回 true,
当然调用其 hashCode()方法
肯定返回相同的值,
这满足了满足了 hashCode 的约束条件,
所以不用重写 hashCode()方法.
当 equals 方法返回的结果
和使用等号比较的结果是不一致的时候,
就需要重写 hashCode 方法.
当重写后的 equals 方法
不认为只有是在内存中同一个对象实例,
才返回 true,
如果不重新 hashCode 方法()
Object 的 hashCode()方法 是对内存地址的映射,
hashCode 方法返回的值肯定是不同的,
这违背了 hashCode 的约束条件,
所以必须要重新 hashCode 方法,
并满足对 hashCode 的约束条件.
49.array 和 arraylist 的区别?
两者间的区别:
Array 的容量是固定的,
ArrayList 的容量是根据需求自动扩展
ArrayList 提供了 添加, 插入或移除
某一范围元素的方法
而 Array 中,
只能一次获取或设置一个元素值
用 Synchronized 方法可以
很容易地创建 ArrayList 的同步版本
而 Array 将一直保持
它知道用户实现同步为止
array 数组的用法
type [] name = new type [size];
注意: size 不能省略, type 前后要一致
缺点: 在数据间插入数据是
ArrayList 动态数组的用法
是 Array 的复杂版本
动态的增加和减少元素,
实现 ICollection 和 IList 接口
灵活的设置数组大小
50. 如何将一个字符串转换为 arraylist?
string 转 ArrayList
先将字符串按照某个字符切割, 转为 string 数组
然后用 Arrays 的 asList 方法, 将数组转为 List
- public class test1 {
- public static void main(String[] args) {
- //string 转 ArrayList
- String str1 = "a,b,c";
- ArrayList<String> list =
- new ArrayList<String>(Arrays.asList(str1.split(",")));
- System.out.println(list);
- }
- }
ArrayList 转 string
- public class test1 {
- public static void main(String[] args) {
- //ArrayList 转 string
- ArrayList<String> list = new ArrayList<String>();
- list.add("a");
- list.add("b");
- list.add("c");
- System.out.println(list);//[a, b, c]
- String list_str = StringUtils.join(list,",");
- System.out.println(list_str);//a,b,c
- }
- }
来源: http://www.jianshu.com/p/c34a210df373