Java语法基础
JDK,JRE 和 JVM 的联系和区别:
- JDK 是 java 开发工具包,是 java 开发环境的核心组件,并提供编译、调试和运行一个 java 程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件。
- JRE 是 java 运行时环境,是 JVM 的实施实现,提供了运行 java 程序的平台。JRE 包含了 JVM,但是不包含 java 编译器 / 调试器之类的开发工具。
- JVM 是 java 虚拟机,当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理 / 垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。
区别:
- JDK 用于开发,JRE 用于运行 java 程序;
- JDK 和 JRE 中都包含 JVM;
- JVM 是 java 编程语言的核心并且具有平台独立性。
Java 中的值传递和引用传递
- 值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。
- 引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。
一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
Java有没有goto?
java中的保留字,现在没有在java中使用。
★&和&&的区别
- &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
-
&&具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于
if(str!= null&& !str.equals(s))
表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException;如果将&&改为&,则会抛出NullPointerException异常。If(x==33 &++y>0)
y会增长,If(x==33 && ++y>0)
不会增长 - &可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
★在Java中如何跳出当前的多重嵌套循环?
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出外层循环。
另外,我个人通常并不使用标号这种方式,而是让外层的循环条件表达式的结果可以受到里层循环体代码的控制,
switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
在switch(e)中,e只能是一个整数表达式或者枚举常量,整数表达式可以是int基本类型或Integer包装类型,由于byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long类型不符合switch的语法规定,并且不能被隐式转换成int类型,所以它不能作用于swtich语句中。从idk 1.7之后switch开始支持String。
char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,Unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在Unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
用最有效率的方法算出2乘以8等于几?
2<< 3,(左移三位)因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以,2乘以8等于几的最效率的方法是2<< 3。
Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应。例如,ceil的英文意义是天花板,ceil方法表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;floor的英文意义是地板,floor方法表示向下取整,Math.ceil(11.6)的结果为11,Math.ceil(-11.6)的结果是-12;最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。
Java当中的四种引用
强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:
-
强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
-
软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
-
弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
-
虚引用:如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
更多了解参见深入对象引用:http://blog.csdn.net/dd864140130/article/details/49885811
为什么要有不同的引用类型
不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。
☀a==b
与a.equals(b)
有什么区别
如果a 和b 都是对象,则a==b
是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b)
是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
==用来判断两个对象之间的内存地址是否一样,如果是基本数据类型变量直接比较值,而引用类型变量要比较对应的引用的内存的首地址。
equals用来比较两个对象长得是否一样,判断两个对象的某些特征是否一样。
3*0.1==0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来。
☀Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
数据类型
☀Integer与int的区别
- Integer是int的包装类,int是基本数据类型;
- Integer变量必须实例化后才能使用;int变量不需要;
- Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
- Integer的默认值是null;int的默认值是0。即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况。
有了基础数据类型,为什么还需要包装类型?
虽然有时将原生数据类型作为对象处理也很容易,但大部分时候集合类会存储对象,而无法存储原生对象。这样通过包装类提供的大量的实用方法,就能够让集合类支持原生对象。所以我们需要使用包装类。
而且由于我们创建了这些类的实例,所以可以将它们存储在任何集合类中,并将它们作为集合进行传递。同时,当方法只支持对象参数时,我们也可以将它们作为参数传递给方法进行运算。
int 和Integer谁占用的内存更多?
Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。
java中int,char,long各占多少字节?
类型 | 位数 | 字节数 |
---|---|---|
short | 2 | 16 |
char | 2 | 16 |
int | 4 | 32 |
float | 4 | 32 |
long | 8 | 64 |
double | 8 | 64 |
64位的JVM当中,int的长度是多少?
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。
能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?
可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化,int 类型的高 24 位将会被丢弃,因为byte 类型的范围是从 -128 到 127。
怎么将 byte 转换为 String?
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?
不行,不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。
a=a+b与a+=b有什么区别吗?
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。
short s1= 1; s1 =s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型,编译器将报告需要强制转换类型的错误。
对于short s1= 1; s1 += 1;因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换,因此可以正确编译。
float f = 1.1; 正确吗?
不正确。 1.1是双精度, 将双精度赋值给浮点型,属于向下转型,会造成精度的丢失。如果要强制类型转换,可以写成这样 float f = (float)1.1; 或者 float f=1.1F;
java当中使用什么类型表示价格比较好?
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
main 方法
main方法是做什么用的?
main方法是Java程序的入口方法,JVM在运行的时候会首先查找main方法。
☀不用main方法如何运行一个类?
不行,没有main方法我们不能运行Java类。
在Java 7之前,你可以通过使用静态初始化运行Java类。但是,从Java 7开始就行不通了。
main方法如何传递参数?传递参数的类型是什么?能不能改变该参数类型?
String数组,不能改变。
☀main方法为什么是静态的?能不能改为非静态?
main()方法一定是静态的,如果main()是非静态的那么在调用main方法时JVM就得实例化它的类。
如果从main()方法去掉“static”这个声明,虽然编译依然可以成功,但在运行时会导致程序失败。
在实例化时,还得调用类的构造函数。如果这个类的构造函数有参数,那么届时就会出现歧义。
main方法能被重载吗?
可以,我们可以重载main()方法。一个Java类可以有任意数量的main()方法。
main方法能被覆盖吗?
在Java中静态方法在编译时会编译在一起,main方法是静态方法,所以你在Java中不能覆盖静态方法。
main方法的返回类型是什么?能不能改变?
void,不能改变。
main方法的作用域用什么修饰?能不能改变?
public,不能改变。
main方法可以同步吗?
main方法可以在Java中同步,synchronized修饰符允许用于main方法的声明中,这样就可以在Java中同步main方法了。
main方法可以终结吗?
可以在Java中终结main方法。
如果将main方法声明为private会怎么样?
程序能够完全编译,但是在运行时会给出“Main method is not public”的提示信息。
如果将main方法的static修饰符删除会怎么样?
答:程序能够编译。但在运行时将抛出一个错误“NoSuchMethodError”。
如果我们使用static public void代替public static void会怎么样?
程序能正常编译和运行。
如果我们不给main方法提供字符串数组作为参数会怎么样?
程序能编译但会抛出一个运行时错误“NoSuchMethodError”。
main方法中字符串数组的第一个参数是什么?
字符串数组是空的。它没有任何元素。这是不同于C/C++会默认以程序名称作为第一个参数。
如果我们不提供命令行参数,然后main方法的宇符串数组会是空的还是为NULL?
它是空的。但不是NULL。
一个应用程序中是否可以包含多个具有main方法的类?
是的,这是可能的。在启动应用程序时,我们要指定运行类的名称。JVM只会在 指定名称的类中寻找main方法。因此,具有多个main方法的类之间并没有冲突。
我们可否在同一个类中定义多个main方法?
程序编译会失败。编译器会提示main方法已经在类中定义过了。
Java面向对象编程
面向对象程序设计思想
☀面向对象的三个特征
- 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
- 继承:继承是从已有类得到继承信息创建新类的过程。继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
- 多态:多态是指允许不同子类型的对象对同一消息作出不同的响应。用同样的对象引用调用同样的方法但是做了不同的事情。
image抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且只会忽略与当前目标无关的那些方面。
多态的好处
允许不同子类型的对象对同一消息作出不同的响应。主要有以下优点:
- 可替换性:多态对已存在代码具有可替换性
- 可扩充性:增加新的子类不影响已经存在的类结构
- 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。
- 灵活性
- 简化性
☀如何实现多态?
主要有以下三种方式:
- 接口实现
- 继承父类重写方法
- 同一类中进行方法重载
☀Java中实现多态的机制是什么?
答案一:
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
答案二:
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。
☀面向对象编程的五个基本原则 (S.O.L.I.D)
-
SRP 单一职责原则
要求每一个类都只有一个职责,避免职责分散。除了类之外,接口和方法的设计都应该遵循这项原则。
-
OCP 开闭原则
对扩展开放,对修改闭合。
-
LSP 里氏替换原则
子类必须能替换掉父类。
-
ISP 接口隔离原则
客户端不应该被迫依赖它们不需要使用的接口。
-
DIP 依赖反转原则
父类不依赖子类,抽象不依赖具体。
☀重写和重载的区别
-
在一个类中定义了一个方法,然后再在子类中使用相同的返回类型、名称和参数重新定义这个方法,那么在子类中的这个方法就被称为重写了父类的方法。
重载表示同一个类中可以有多个名称相同的方法,但这些方法的参数个数或类型不同。
- 重载发生在一个类中,同名的方法有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重载对返回类型没有特殊的要求。
- 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常。
-
方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
如果在编译时能够确定执行多态方法中的哪一个,称为编译时多态,否则称为运行时多态。
☀为什么不能根据返回类型来区分重载?
因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。
例如:
float max(int a, int b); int max(int a, int b);
当调用max(1, 2);时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。
类
内部类的各种类型有哪些?
-
成员内部类
定义在另一个类(外部类)的内部,而且与成员方法和属性平级叫成员内部类
- 成员内部类中不能存在static关键字
-
静态内部类
使用static修饰的成员内部类叫静态内部类。
-
局部内部类
定义在代码块、方法体内、作用域内的类叫局部内部类。
-
匿名内部类
匿名内部类就是没有名字的局部内部类,不使用关键字class, extends, implements, 没有构造方法。
内部类的作用
- 内部类可以很好的实现隐藏
一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 - 内部类拥有外围类的所有元素的访问权限
- 可实现多重继承
- 可以避免修改接口而实现同一个类中两种同名方法的调用。
静态内部类与非静态内部类的区别
静态内部类不会持有外围类的引用,而非静态内部类会隐式持有外围类的一个引用。
内部类可以引用它的包含类的成员吗?有没有什么限制?
完全可以。如果不是静态内部类,那没有什么限制。
如果把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,
例如,下面的代码:
class Outer { static int x; static class Inner{ voidtest(){ syso(x); } } }
静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起来挺诡异的,如下所示。
/**
* 扑克类(一副扑克)
*
*/
public class Poker {
private static String[] suites = {"黑桃", "红桃", "草花", "方块"};
private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
private Card[] cards;
/**
* 构造器
*
*/
public Poker() {
cards = new Card[52];
for(int i = 0; i < suites.length; i++) {
for(int j = 0; j < faces.length; j++) {
cards[i * 13 + j] = new Card(suites[i], faces[j]);
}
}
}
/**
* 洗牌 (随机乱序)
*
*/
public void shuffle() {
for(int i = 0, len = cards.length; i < len; i++) {
int index = (int) (Math.random() * len);
Card temp = cards[index];
cards[index] = cards[i];
cards[i] = temp;
}
}
/**
* 发牌
* @param index 发牌的位置
*
*/
public Card deal(int index) {
return cards[index];
}
/**
* 卡片类(一张扑克)
* [内部类]
*
*/
public class Card {
private String suite; // 花色
private int face; // 点数
public Card(String suite, int face) {
this.suite = suite;
this.face = face;
}
@Override
public String toString() {
String faceStr = "";
switch(face) {
case 1: faceStr = "A"; break;
case 11: faceStr = "J"; break;
case 12: faceStr = "Q"; break;
case 13: faceStr = "K"; break;
default: faceStr = String.valueOf(face);
}
return suite + faceStr;
}
}
}
测试代码:
class PokerTest {
public static void main(String[] args) {
Poker poker = new Poker();
poker.shuffle(); // 洗牌
Poker.Card c1 = poker.deal(0); // 发第一张牌
// 对于非静态内部类Card
// 只有通过其外部类Poker对象才能创建Card对象
Poker.Card c2 = poker.new Card("红心", 1); // 自己创建一张牌
System.out.println(c1); // 洗牌后的第一张
System.out.println(c2); // 打印: 红心A
}
}
接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concreteclass)?抽象类中是否可以有静态的main方法?
接口可以继承接口。抽象类可以实现(implements)接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。
☀抽象类和接口有什么区别?
- 抽象类可以有构造方法,接口中不能有构造方法。
- 抽象类中可以有普通成员变量,接口中不能有普通成员变量。
- 抽象类中可以有非抽象的普通方法,接口中不能有非抽象的普通方法。
- 抽象类中的抽象方法的访问类型可以是public,protected和default,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
- 抽象类中可以有静态方法,接口中不能有静态方法。
- 抽象类和接口中都可以有静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
- 抽象类的子类使用extends关键字来继承抽象类,接口的子类使用implements来实现接口
- 一个类可以实现多个接口,但只能继承一个抽象类。
abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
- abstract的method不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系。
- native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。
- synchronized也不可以与abstract合用,synchronized应该是作用在一个具体的方法上才有意义。而且,方法上的synchronized同步所使用的同步锁对象是this,而抽象方法上无法确定this是什么。
☀接口的意义
-
有利于代码的规范
对于一个大型项目而言,架构师往往会对一些主要的接口来进行定义,或者清理一些没有必要的接口。这样做的目的一方面是为了给开发人员一个清晰的指示,告诉他们哪些业务需要实现;同时也能防止由于开发人员随意命名而导致的命名不清晰和代码混乱,影响开发效率。
-
有利于对代码进行维护
比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现现有的类已经不能够满足需要,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
-
保证代码的安全和严密
一个好的程序一定符合高内聚低耦合的特征,那么实现低耦合,定义接口是一个很好的方法,能够让系统的功能较好地实现,而不涉及任何具体的实现细节。这样就比较安全、严密一些,这一思想一般在软件开发中较为常见。
☀抽象类的意义
为其他子类提供一个公共的类型封装子类中重复定义的内容定义抽象方法,子类虽然有不同的实现,但是定义时一致的。
一个java文件内部可以有类?(非内部类)
只能有一个public公共类,但是可以有多个default修饰的类。
访问修饰符public,private,protected以及不写时的区别?
访问权限:
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
private | √ | × | × | × |
无修饰符 | √ | √ | × | × |
子类重写父类的protected方法有什么限制?
- final修饰的类方法不可被子类重写
- java5.3以后方法参数个数必须一致
- 重写时访问级别只可以等于或者宽松于当前重写方法的访问级别
对象
什么是不可变对象
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
我们能创建一个包含可变对象的不可变对象吗?
是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
☀静态变量和实例变量的区别
- 实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。实例变量存储在堆当中,其引用存在当前线程栈。
- 静态变量是被static修饰符修饰的变量,不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。静态变量存储在方法区,属于类所有。
Java 创建对象的几种方式
-
采用new
-
通过反射
-
采用clone
-
通过序列化机制
前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,因此发现无论什么框架,只要涉及到解耦必先减少new的使用。
☀Object中有哪些公共方法?
-
equals()
-
clone()
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
-
getClass()
final方法,获得运行时类型。
-
notify()
该方法唤醒在该对象上等待的某个线程。
-
notifyAll()
该方法唤醒在该对象上等待的所有线程。
-
wait()
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
- 其他线程调用了该对象的notify方法。
- 其他线程调用了该对象的notifyAll方法。
- 其他线程调用了interrupt中断该线程。
- 时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
-
toString()
-
hashCode()
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。
-
finalize()
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
★深拷贝和浅拷贝的区别是什么?
- 浅拷贝:又叫浅复制,将对象中的所有字段复制到新的对象(副本)中。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
- 深拷贝:又叫深克隆,将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
继承
访问控制
☀final有哪些用法
- 被final修饰的类不可以被继承。
- 被final修饰的方法不可以被重写。
- 被final修饰的变量不可以被改变。
- 被final修饰的常量,在编译阶段会存入常量池中。
使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。
☀是否可以从一个static方法内部发出对非static方法的调用?
不可以。静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化。
☀static都有哪些用法?
- 两个基本的用法:静态变量和静态方法。也就是被static所修饰的变量/方法都属于类的静态资源,为类实例所共享。
- static也用于静态块,多用于初始化操作:
public calss PreCache{
static{
//执行相关操作
}
}
- static也多用于修饰内部类,此时称之为静态内部类。
- 最后一种用法就是静态导包,即import static。import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名。资源名,可以直接使用资源名,比如:
import static java.lang.Math.*;
public class Test{
public static void main(String[] args){
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
★父类的静态方法能否被子类重写
不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏。
☀final, finally, finalize的区别
-
final 用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、类不可继承。内部类要访问局部变量,局部变量必须定义成final类型。
-
finally是异常处理语句结构的一部分,表示总是执行。
-
finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。
String类
String是基本数据类型吗?
String不是基本数据类型。 java中的基本数据类型就八种: byte, short, int, long, float, double, char,boolean。剩下的都是引用类型。
String是可变的话?
String是final类型的,不可变。
怎么比较两个字符串的值一样,怎么比较两个字符串是否同一对象?
比较字符串的值是否相同用equals,比较字符串对象是否同一个用==。
switch中可以使用String吗
jdk7+中的switch可以使用String类型。
String.trim()方法去掉的是哪些字符?
trim去掉字符串首尾的空白字符。
☀String可以被子类继承吗?
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
可以自定义java.lang.String类并使用吗?
可以自定义java.lang.String类并编译成功,但不能被加载使用,具体请学习类加载机制。
String与byte[]两者相互之间如何转换?
String →byte[] :通过String类的getBytes方法;
byte[] → String:通过new String(byte[])构造器。
String s = "Hello";s = s + "world!";这两行代码执行后,原始的String对象中的内容到底变了没有?
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了+操作,s所指向的那个对象没有发生了改变。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
☀String s = new String("xyz");创建了几个StringObject?是否可以继承String类?
两个或一个都有可能,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。New String每写一遍,就创建一个新的对象,它使用常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’,那么这里就不会创建”xyz”了,直接从缓冲区拿,这时创建了一个StringObject;但如果以前没有用过"xyz",那么此时就会创建一个对象并放入缓冲区,这种情况它创建两个对象。至于String类是否继承,答案是否定的,因为String默认final修饰,是不可继承的。
☀String和StringBuffer的区别
- String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。
- StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer。
☀StringBuffer 和 StringBuilder 的区别
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
- StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d";
对于如下代码:
String s1 = "a"; String s2 = s1 + "b"; String s3 = "a" + "b"; System.out.println(s2 == "ab"); System.out.println(s3 == "ab");
第一条语句打印的结果为false,第二条语句打印的结果为true,这说明javac编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期再去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。
题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象。
你对String对象的intern()熟悉么?
intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回。
比如 :
String s1=”aa”;
String s2=s1.intern();
System.out.print(s1==s2);//返回true
String s1=”ab”, String s2=”a”+”b”, String s3=”a”, String s4=”b”, s5=s3+s4请问s5==s2返回什么?
返回false。在编译过程中,编译器会将s2直接优化为”ab”,会将其放置在常量池当中,s5则是被创建在堆区,相当于s5=new String(“ab”);
String创建方式
下面代码返回结果是什么?
String c =new String("abcd");
String d =new String("abcd");
System.out.println(c == d);
System.out.println(c.equals(d));
用new创建的c,d两个字符串,equals为true很简单因为equals永远比较的是值,而==为false说明两个字符串的引用不一样。用new创建的字符串每次都会在JVM堆中创建,所以c,d都对应堆中的两个不同的字符串。
下面代码返回结果是什么?
String a ="abcd";
String b ="abcd";
System.out.println(a == b);
System.out.println(a.equals(b));
用""创建的a,b两个字符串,==和equals比较返回都为true,这是因为a,b都指向了方法区的同一个字符串。所以,当同样的一个字符串用""重复创建时只在方法区创建一次。
数组
去掉一个Vector集合中重复的元素
Vector newVector = new Vector();
for (int i=0;i<vector.size();i++)
{
Object obj = vector.get(i);
if(!newVector.contains(obj)
newVector.add(obj);
}
还有一种简单的方式,利用了Set不允许重复元素:
HashSetset = new HashSet(vector);
泛型
简述泛型、反射、注解应用场景及各自解决了哪些问题 TODO
谈谈反射机制,动态代理是基于什么原理?
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如面向切面的编程(AOP)。
实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 cglib(基于 ASM)等。
Java中的泛型是什么 ? 使用泛型的好处是什么?
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
Java的泛型是如何工作的 ? 什么是类型擦除 ?
类型擦除:所有类型参数都用他们的限定类型替换,包括类、变量和方法
工作原理:
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
你可以把List<String>传递给一个接受List<Object>参数的方法吗?
不可以,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储String s。
如何阻止Java中的类型未检查的警告?
如果你把泛型和原始类型混合起来使用,例如下列代码,java 5的javac编译器会产生类型未检查的警告,例如
List<String> rawList = newArrayList()
注意: Hello.java使用了未检查或称为不安全的操作;
这种警告可以使用@SuppressWarnings(“unchecked”)注解来屏蔽。
Java中List<Object>和原始类型List之间的区别?
- 在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。
- 可以把任何带参数的类型传递给原始类型List,但却不能把List<String>传递给接受 List<Object>的方法,因为会产生编译错误。
编写一段泛型程序来实现LRU缓存?
mport java.util.LinkedHashMap;
import java.util.Map;
public LRUCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public LRUCache(int cacheSize) {
super(16, 0.75, true);
this.cacheSize = cacheSize;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() >= cacheSize;
}
}
Array中可以用泛型吗?
不可以
如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
最简单的情况下,一个泛型方法可能会像这样:
public V put(K key, V value) {
return cahe.put(key,value);
}
C++模板和java泛型之间有何不同?
Java泛型和C++模板有很多不同点:
- C++模板可以使用int等基本数据类型。Java则不行,必须转而使用Integer。
- 在Java中,可以将模板的参数类型限定为某种特定类型。
- 在C++中,类型参数可以实例化,但java不支持。
- 在Java中,类型参数不能用于静态方法和变量,因为它们会被不同类型参数指定的实例共享。在C++,这些类时不同的,因此类型参数可以用于静态方法和静态变量。
- 在Java中,不管类型参数是什么,所有的实例变量都是同一类型。类型参数会在运行时被抹去。在C++中,类型参数不同,实例变量也不同。
集合
☀Java集合框架的基础接口有哪些?
imageJava容器可分为两大类:
- Collection
- List
- ArrayList
- LinkedList
- Vector(了解,已过时)
- Set
-
HashSet
- LinkedHashSet
- TreeSet
-
HashSet
- List
- Map
-
HashMap
- LinkedHashMap
- TreeMap
- ConcurrentHashMap
- HashTable(了解,已过时)
-
HashMap
集合框架中的泛型有什么优点?
- 泛型允许我们为集合提供一个可以容纳的对象类型。因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。
- 泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Enumeration和Iterator接口的区别?
- Enumeration的速度是Iterator的两倍,也使用更少的内存。
- 与Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合。
- Iterator能够删除元素,Enumeration并不能删除元素
Iterater和ListIterator之间有什么区别?
- 可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。
- Iterator只可以向前遍历,而ListIterator可以双向遍历。
- ListIterator继承了Iterator接口,添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
如何对一组对象进行排序?
如果需要对一个对象数组进行排序,可以使用Arrays.sort()方法。
如果需要排序一个对象列表,可以使用Collection.sort()方法。
两个类都有用于自然排序(使用Comparable)或基于标准的排序(使用Comparator)的重载方法sort()。
Collections内部使用数组排序方法,所有它们两者都有相同的性能,只是Collections需要花时间将列表转换为数组。
如何实现集合排序?
可以使用有序集合,如 TreeSet 或 TreeMap,也可以使用有顺序的的集合,如 List,然后通过 Collections.sort() 来排序。
如何打印数组内容
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。
与Java集合框架相关的有哪些最好的实践?
- 根据需要确定集合的类型。如果是单列的集合,我们考虑用Collection下的子接口ArrayList和Set。如果是映射,我们就考虑使用Map
- 确定完我们的集合类型,我们接下来确定使用该集合类型下的哪个子类。我认为可以简单分成几个步骤:
- 是否需要同步
- 去找线程安全的集合类使用
- 迭代时是否需要有序(插入顺序有序)
- 去找Linked双向列表结构的
- 是否需要排序(自然顺序或者手动排序)
- 去找Tree红黑树类型的(JDK1.8)
- 是否需要同步
- 估算存放集合的数据量有多大,无论是List还是Map,它们实现动态增长,都是有性能消耗的。在初始集合的时候给出一个合理的容量会减少动态增长时的消耗
- 使用泛型,避免在运行时出现ClassCastException
- 尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性
poll()方法和remove()方法区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
Collection
Collection和Collections的区别
- Collection是集合类的上级接口,继承它的接口主要有Set和List。
- Collections是针对集合类的一个帮助类,提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
Collection集合接口和Map接口有什么关系?
没关系,Collection是List、Set父接口不是Map父接口。
Map
☀HashMap和HashTable的区别
共同点:
- 从存储结构和实现来讲基本上都是相同的,都是实现Map接口
区别:
- 同步性:
- HashMap是非同步的,非线性安全
- HashTable是同步的,线性安全
- 是否允许为null:
- HashMap允许为null
- HashTable不允许为null
- contains方法:
- HashTable有contains方法
- HashMap把Hashtable的contains方法去掉了,改成了containsValue和containsKey
- 继承不同:
- HashMap是Java1.2引进的Map interface的一个实现
- HashTable继承自Dictionary类
HashMap是线程安全的吗?线程安全的Map都有哪些?性能最好的是哪个?
HashMap不是线程安全的。线程安全的有HashTable、ConcurrentHashMap、SynchronizedMap,性能最好的是ConcurrentHashMap。
使用HashMap有什么性能问题吗?
使用HashMap要注意避免集合的扩容,它会很耗性能,根据元素的数量给它一个初始大小的值。
HashMap的数据结构是怎样的?默认大小是多少?内部是怎么扩容的?
HashMap是数组和链表组成的,默认大小为16,当HashMap中的元素个数超过数组大小*loadFactor(默认值为0.75)时就会把数组的大小扩展为原来的两倍大小,然后重新计算每个元素在数组中的位置。
HashMap的链表结构设计是用来解决什么问题的?
HashMap的链表结构设计是用来解决key的hash冲突问题的。
HashMap使用对象作为key,如果hashCode相同会怎么处理?
key的hash冲突,如果key equals一致将会覆盖值,不一致就会将值存储在key对应的链表中。
HashMap中的get操作是什么原理?
先根据key的hashcode值找到对应的链表,再循环链表,根据key的hash是否相同且key的==或者equals比较操作找到对应的值。
怎么按添加顺序存储元素?怎么按A-Z自然顺序存储元素?怎么自定义排序?
按添加顺序使用LinkedHashMap,按自然顺序使用TreeMap,自定义排序TreeMap(Comparetor c)。
☀HashMap和TreeMap有什么区别?底层数据结构是什么?
- TreeMap 实现了 SortMap 接口,其能够根据键排序,默认是按键的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时得到的记录是排过序的,所以在插入和删除操作上会有些性能损耗,TreeMap 的键和值都不能为空,其为非并发安全 Map,TreeMap 基于红黑树实现。
- HashMap 基于哈希散列表实现,主要根据键的 hashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,当用 Iterator 遍历 HashMap 时得到的记录顺序是随机的,HashMap 允许键值均为空,其为非并发安全 Map。
所以一般情况下我们选用 HashMap,因为 HashMap 的键值对在取出时是随机的,其依据键的 hashCode 值和键的 equals 方法存取数据,具有很快的访问速度,所以在 Map 中插入、删除及索引元素时其是效率最高的实现。而 TreeMap 取出来的是排序后的键值对,所以效率会低点。
LinkedHashMap和PriorityQueue的区别
PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 保证遍历顺序是元素插入的顺序。
WeakHashMap与HashMap的区别是什么?
WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。
☀HashMap的实现原理
-
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
-
在 Java8 之前,其底层实现是数组 + 链表实现,Java8 使用了数组 + 链表 + 红黑树实现。
当我们往HashMap中put元素时,首先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾。如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
需要注意JDK1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
TreeMap实现原理 TODO
ConcurrentHashMap的工作原理
ConcurrentHashMap在jdk 1.6和jdk 1.8实现原理是不同的。
jdk 1.6:
ConcurrentHashMap是线程安全的,但是与HashTable相比,实现线程安全的方式不同。HashTable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。
具体实现:
- 该类包含两个静态内部类 HashEntry 和 Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;
Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。
在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表→红黑树”的实现。
TreeMap, LinkedHashMap, HashMap的区别是什么?
- HashMap的底层实现是散列表,因此它内部存储的元素是无序的;
- TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。
- LinkedHashMap可以看作能够记住插入元素的顺序的HashMap。
☀HashTable和ConcurrentHashTable的区别
- ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。
- ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
Comparator和Comparable的区别?
- Comparable 用于定义对象的自然顺序,而 Comparator 用于定义用户定制的顺序。
- Comparable 总是只有一个,但是可以有多个 Comparator 来定义对象的顺序。
TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
Collections工具类的sort方法有两种重载的形式:
- 要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;
- 不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用。
Set
☀HashSet和TreeSet的区别
-
相同点
单列集合,元素不可重复
-
不同点
-
底层存储的数据结构不同
HashSet底层用的是HashMap哈希表结构存储,而TreeSet底层用的是TreeMap树结构存储
-
存储时保证数据唯一性依据不同HashSet是通过复写hashCode()方法和equals()方法来保证的,而TreeSet通过Compareable接口的compareTo()方法来保证的
-
有序性不一样
HashSet无序,TreeSet有序
-
☀Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==还是equals()?
Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。
☀a.hashCode()有什么用?与a.equals(b)有什么关系?
- hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,使用 equal() 方法来判断两个相等的对象,必须具有相同的 hashCode。
- 将对象放入到集合中时,首先判断要放入对象的hashCode是否已经在集合中存在,不存在则直接放入集合。如果hashCode相等,然后通过equals()方法判断要放入对象与集合中的任意对象是否相等:如果equals()判断不相等,直接将该元素放入集合中,否则不放入。
- 如果两个对象根据eqauls()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的)。
- 若两个对象equals返回true,则hashCode有必要也返回相同的int数。
- 若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高哈希表的性能。
- 若两个对象hashCode返回相同int数,则equals不一定返回true。
- 若两个对象hashCode返回不同int数,则equals一定返回false。
- 同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
☀hashCode相等两个类一定相等吗?TODO
☀有没有可能两个不相等的对象有相同的hashCode?
有可能,两个不相等的对象可能会有相同的 hashCode 值,这就是为什么在 hashMap 中会有冲突。如果两个对象相等,必须有相同的hashCode 值,反之不成立。
可以在hashCode中使用随机数字吗?
不行,因为同一对象的 hashCode 值必须是相同的
List
☀List和 Map的区别
- List是存储单列数据的集合,Map是存储键和值这样的双列数据的集合。
- List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
List,Set, Map是否继承自Collection接口?
List,Set是,Map不是
List、Map、Set三个接口,存取元素时,各有什么特点?
-
List与Set具有相似性,它们都是单列元素的集合,所以,它们有一个共同的父接口,叫Collection。
-
Set里面不允许有重复的元素。
存元素:Set的add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true,当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
取元素:Set取元素时,不能细说要取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。
-
List表示有先后顺序的集合。
存元素:当多次调用add(Obj e)方法时,每次加入的对象按先来后到的顺序排序。有时候也可以插队,即调用add(int index,Obj e)方法,就可以指定当前对象在集合中的存放位置。
取元素:List除了可以用Iterator接口取得所有的元素,再逐一遍历各个元素之外,还可以调用get(index i)来明确说明取第几个。
-
-
Map与List和Set不同,它是双列的集合,存元素用put方法,定义如下:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。
Map取元素可以根据key获得相应的value,即get(Object key)返回值为key所对应的value。也可以获得所有的key的结合,还可以获得所有的value的结合,还可以获得key和value组合成的Map.Entry对象的集合。
-
List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值,value可多值。
List、Set 和 Map 的初始容量和加载因子
-
List
ArrayList 的初始容量是 10;加载因子为 0.5; 扩容增量:原容量的 0.5 倍 +1;一次扩容后长度为 16。
Vector 初始容量为 10,加载因子是 1。扩容增量:原容量的 1 倍,如 Vector 的容量为 10,一次扩容后是容量为 20。
-
Set
HashSet,初始容量为 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashSet 的容量为 16,一次扩容后容量为 32
-
Map
HashMap,初始容量 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashMap 的容量为 16,一次扩容后容量为 32
ArrayList,Vector,LinkedList的存储性能和特性
- ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差。而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,索引就变慢了,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
- LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用。
☀ArrayList和LinkedList的区别 ?分别应用在什么场景?
ArrrayList底层的数据结构是数组,支持随机访问。数组查询具有所有查询特定元素比较快,而插入、删除和修改比较慢(数组在内存中是一块连续的内存,如果删除或插入需要移动内存)。
LinkedList 的底层数据结构是双向循环链表,不支持随机访问。链表不要求内存是连续,在当前元素中存放下一个或上一个元素的地址,查询时需要从头部开始一个一个的找,所以查询效率低,插入不需要移动内存,只需要改变引用指向即可,所以插入或删除的效率高。
使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
ArrayList使用在查询比较多,但是插入和删除比较少的情况,而LinkedList使用在查询比较少而插入或删除的效率高。
☀ArrayList和Vector的区别
-
这两个类都实现了List接口,它们都是有序的集合,底层是数组。可以按位置索引号取出某个元素,允许元素重复和为null。
-
ArrayList与Vector的不同点主要包括两个方面:
-
同步性
Vector是线程安全的,也就是说它的方法之间是线程同步的,而ArrayList是线程不安全的,它的方法之间是线程不同步的。
如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
-
扩容大小
Vector增长原来的一倍,ArrayList增加原来的0.5倍。
-
List和Vector有什么区别?
Vector是List接口下线程安全的集合。
遍历ArrayList时如何正确移除一个元素
ArrayList和Array有什么区别?
- Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
- Array是指定大小的,而ArrayList大小是固定的
ArrayList和HashMap默认大小?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段。
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
List是线程安全的吗?如果要线程安全要怎么做?
List中的Vector才是线程安全的,其他要实现线程安全使用工具类Collections.synchronizedList(new ArrayList())方法。
怎么给List排序?
使用List自身的sort方法,或者使用Collections.sort(list)方法;
Arrays.asList方法后的List可以扩容吗?
Arrays.asList使用的是final数组,并且不支持add方法,不支持扩容。
List和Array之间如何互相转换?
List→Array使用toArray方法,Array→List使用Arrays.asList(array)方法,由于它是固定的,不固定的可以使用new ArrayList(Arrays.asList(array))。
了解Fail-Fast机制吗?
它是 java 集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如 :假设存在两个线程(线程 1、线程 2),线程 1 通过 Iterator 在遍历集合 A 中的元素,在某个时候线程 2 修改了集合 A 的结构,那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生 fail-fast 机制。
原因: 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
在遍历过程中,所有涉及到改变 modCount 值得地方全部加上 synchronized;
使用 CopyOnWriteArrayList 来替换 ArrayList。
Fail-fast和Fail-safe有什么区别
Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。当检测到正在遍历的集合的结构被改变时,Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。
异常
Java 中,throw 和 throws 有什么区别?
- throw 用于抛出 java.lang.Throwable 类的一个实例化对象,可以通过关键字 throw 抛出一个Exception。
- throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。
☀Error和Exception有什么区别?
- Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
- Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
- Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
- Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。
你了解哪些 Error、Exception 或者 RuntimeException?
image
运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。
☀简单说说Java中的异常处理机制的简单原理和应用
-
异常是指java程序运行时(非编译)所发生的非正常情况或错误,Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。
-
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。
-
Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。例如,说内存溢出和线程死锁等系统问题。
-
Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。其中又分为系统异常和普通异常:
- 系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件挂掉;
- 普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
- java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。
-
说说常见的几个运行时异常
- ClassCastException(类转换异常)
- IndexOutOfBoundsException(数组越界)
- NullPointerException(空指针)
- ArrayStoreException(数据存储异常,操作数组时类型不一致)
- IO操作的BufferOverflowException异常
I/O
字节流与字符流的区别
- 字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
- 字节流默认不使用缓冲区;字符流使用缓冲区。
- 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。
实现一个拷贝文件的工具类使用字节流还是字符流?
我们拷贝的文件不确定只含字符流,又可以能有字节流(图片、声音、图像等),为考虑到通用性,要使用字节流。
Java 提供了哪些 IO 方式? NIO 如何实现多路复用?
Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。
首先,传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
java.io 包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。
很多时候,人们也把 java.net 下面提供的部分网络 API,比如 Socket、ServerSocket、HttpURLConnection 也归类到同步阻塞 IO 类库,因为网络通信同样是 IO 行为。
第二,在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。
第三,在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
简述Java IO与NIO的区别
- Java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。
- Java IO是阻塞IO,而NIO是非阻塞IO。
- Java NIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情。
JDBC
☀JDBC如何进行事务处理?
- Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务
- 当事务完成后用commit()显式提交事务 。
- 如果在事务处理过程中发生异常则通过rollback()进行事务回滚 。
数据库连接池的原理。为什么要使用连接池?
- 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接,更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发、测试及性能调整提供依据。
- 使用连接池是为了提高对数据库连接资源的管理。由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。
JDBC的DriverManager是用来做什么的?
JDBC的DriverManager是一个工厂类,我们通过它来创建数据库连接。当JDBC的Driver类被加载进来时,它会自己注册到DriverManager类里面,然后我们会把数据库配置信息传成DriverManager.getConnection()方法,DriverManager会使用注册到它里面的驱动来获取数据库连接,并返回给调用的程序。
execute,executeQuery,executeUpdate的区别是什么?
- Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。
- Statement的executeQuery(String query)接口用来执行select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。
- Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者什么也不返回,对于DDL语句,返回值是int类型,如果是DML语句的话,它就是更新的条数,如果是DDL的话,就返回0。
只有当不确定是什么语句的时候才应该使用execute()方法,否则应该使用executeQuery或者executeUpdate方法。
SQL查询出来的结果分页展示一般怎么做?
Oracle:
select * from
(select *,rownum as tempid from student ) t
where t.tempid between ” + pageSize*(pageNumber-1) + ” and ” + pageSize*pageNumber
MySQL:
select * from students limit ” + pageSize*(pageNumber-1) + “,” + pageSize;
sql server:
select top ” + pageSize + ” * from students where id not in +
(select top ” + pageSize * (pageNumber-1) + id from students order by id) +
“order by id;
JDBC的ResultSet是什么?
在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。
ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了ResultSet的next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回false。可以在for循环中用它来遍历数据集。
默认的ResultSet是不能更新的,游标也只能往下移。也就是说只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet。
当生成ResultSet的Statement对象要关闭或者重新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。 可以通过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。
☀JDBC访问数据库的基本步骤是什么?
- 加载驱动
- 通过DriverManager对象获取连接对象Connection
- 通过连接对象获取会话
- 通过会话进行数据的增删改查,封装对象
- 关闭资源
☀说说PreparedStatement和Statement的区别
与Statement相比,
-
PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);
-
PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;
-
当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?
要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);
要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
网友评论