1.访问修饰符public,private,protected以及不写(默认)的区别?
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
2.JAVA中的几种基本类型,各占用多少字节?
下图单位是bit,非字节 1B=8bit
数据类型 | 大小 | 范围 | 默认值 |
byte(字节) | 8 | -128~127 | 0 |
short(短整型) | 16 | -32768~32768 | 0 |
int(整型) | 32 | -2147483648~2147483648 | 0 |
long(长整型) | 64 | -9233372036854477808~9233372036854477808 | 0 |
float(浮点型) | 32 | -3.40292347E+38~3.40292347E+38 | 0.0f |
double(双精度) | 64 | -1.79769313486231570E+308~1.79769313486231570E+308 | 0.0d |
char(字符型) | 16 | '\u000~u\ffff' | '\u0000' |
boolean(布尔型) | 1 | true/false | false |
3.float f = 3.4;这个表达式是否正确?
不正确。在Java里面,没有小数点的默认是int类型,有小数点的默认是double类型。因此3.4是双精度数,将双精度浮(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化),会造成精度损失,因此需要强制类型转换 float f = (float)3.4 或者写成 float f = 3.4f 。
4.short s1 = 1; s1 = s1 + 1; 这个表达式有错吗?short s1 = 1; s1 += 1; 这个表达式有错吗?
前面这个表达式,由于1是int类型,因此 s1 + 1 的运算结果也是int类型,再把int类型的结果赋值给short类型的s1是会报错的,需要对结果进行强制类型转换才能通过编译。后面这个表达式, s1 += 1; 相当于 s1 = (short)(s1 + 1); 表达式,其中会有隐含的强制类型转换,可以正常通过编译。
5.int和Integer有什么区别?
Java是一个近乎纯洁的面向对象编程的语言,但是为了编程的方便还是引入了基本数据类型。为了能够将这些基本数据类型当作对象操作,Java为每一个基本数据类型都引入了相应的包装类型(Wrapper Class),int类型的包装类型就是Integer。从Java5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java为每个原始类型(基本数据类型)提供了相应的包装类型:
原始类型:byte,short,int,long,float,double,char,boolean
包装类型:Byte,Short,Integer,Long,Float,Double,Character,Boolean
Integer a = new Integer(3);
Integer b = 3;//将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b);//false 两个引用没有引用同一对象
System.out.println(a == c);//true a自动拆箱成int类型再和c比较
下面这段代码也和自动装箱/拆箱有关系:
Integer f1 = 100, f2 = 100, f3 = 128, f4 = 128;
System.out.println(f1 == f2); // true
System.out.println(f3 == f4); // false
为什么第一个运算的结果是true,第二个却是false呢?
首先注意,f1、f2、f3和f4都是Integer对象引用,所以下面的==运算比较的不是值而是引用。
装箱的本质是什么呢?是当我们给一个Integer对象赋一个int类型值的时候,会调用Integer类的静态方法valueOf(),看看valueOf()方法的源代码就知道为什么会产生这样的结果。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Interger类的内部类,其代码如下:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
简单得说,如果整型字面量的值的范围在-128到127之间,那么就不会new一个新的Integer对象,而是直接引用常量池中的Integer对象。
6.&和&&的区别?
&运算符有两种用法,一是按位与,二是逻辑与。&&运算符是短路与。
逻辑与跟短语与的差别是非常大的,虽然二者都要求运算符左右两边的布尔值都是true整个表达式的值才是true,但是如果&&运算符左边的表达式是false的话,右边的表达式会直接被短路掉,不会进行运算,这就是&&运算符被称为短路运算的原因。
很多时候我们需要用到的可能都是&&而不是&,例如在验证用户登录的时候,判定用户名不是null而且不是空字符串,应当写为:userName != null && !userName.equals(""),二者的顺序不能交换,更不能使用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。
7.Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的结果是12,Math.round(-11.5)的结果是-11。
四舍五入的原理是在参数上加0.5,然后进行向下取整。
那么Math.round(11.6)和Math.round(-11.6)的结果又是什么呢?
答案是12和-12。
11.6 + 0.5 = 12.1,然后向下取整为12;
-11.6 + 0.5 = -11.1,然后向下取整为-12。
要特别理解向下取整是什么意思。
8.两个对象值相同(x.equals(y) == true)但是却可有不同的hashCode,这句话对不对?
不对。如果两个对象x和y满足equals()结果为true,那么它们的哈希码(hashCode)应当相同。
Java对于equals)(方法和hashCode()方法是这样规定的:
1.如果两个对象相同(equals()方法返回true),那么它们的hashCode值一定要相同;
2.如果两个对象的hashCode相同,它们并不一定相同。
当然,你未必要按照要求去做,但是如果你违背了上述原则,就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁地冲突将会造成存取性能急剧下降)。
9.switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
switch可作用于char byte short int
switch可作用于char byte short int对应的包装类
switch不可作用于long double float boolean,包括他们的包装类
switch中可以是字符串类型,String(jdk1.7之后才可以作用在string上)
10.String s = "Hello";s = s + "world!";这两行代码执行后,原始的String对象中的内容到底变了没有?
没有。因为String是不可变类(immutable class),不可变类,顾名思义就是说类的实例是不可被修改的。实例的信息是在创建的时候提供,并且在整个生命周期中都不可改变。在这段代码中,s原来指向一个String对象,内容是“hello”,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为”helloworld!",原来那个对象还存在内存中,只是s这个引用变量不再指向他了。
通过上面的说明,我们很容易得出一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为,String对象建立后不能改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,他允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即 StringBuffer。
11.final在java中有什么作用?
1. final修饰类中的属性或者变量:
无论属性是基本类型还是引用类型,final所起的作用都是变量里面存放的“值”不能变。
这个值,对于基本类型来说,变量里面放的就是实实在在的值,如1,“abc”等。
而引用类型变量里面放的是个地址,所以用final修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。
例如:类中有一个属性是final Person p=new Person("name"); 那么你不能对p进行重新赋值,但是可以改变p里面属性的值,p.setName('newName');
final修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对final属性可以在三个地方赋值:声明时、初始化块中、构造方法中。总之一定要赋值。
2. final修饰类中的方法:
作用:可以被继承,但继承后不能被重写。
3. final修饰类:
作用:类不可以被继承。
思考一个有趣的现象:
byte b1=1;
byte b2=3;
byte b3=b1+b2;//当程序执行到这一行的时候会出错,因为b1、b2可以自动转换成int类型的变量,运算时java虚拟机对它进行了转换,结果导致把一个int赋值给byte-----出错
如果对b1 b2加上final就不会出错
final byte b1=1;
final byte b2=3;
byte b3=b1+b2;//不会出错,相信你看了上面的解释就知道原因了。
12.如何将字符串反转?
1.通过String类的charAt()的方法来获取字符串中的每一个字符,然后将其拼接为一个新的字符串。
public static String charAtReverse(String str){
String reverse = "";
for (int i = 0; i < str.length(); i++) {
reverse = str.charAt(i) + reverse;
}
return reverse;
}
2.通过String的toCharArray()方法可以获得字符串中的每一个字符串并转换为字符数组,然后用一个空的字符串从后向前一个个的拼接成新的字符串。
public static String reverseCharArray(String str){
char[] array = str.toCharArray();
String reverse = "";
for (int i = array.length - 1; i >= 0; i--) {
reverse += array[i];
}
return reverse;
}
3.通过StringBuiler的reverse()的方法,最快的方式。
public static String reverseStringBuilder(String str){
StringBuilder stringBuilder = new StringBuilder(str);
return stringBuilder.reverse().toString();
}
4.通过递归的方式。首先通过String的toCharArray()方法获取字符数组,再使用递归方式交换字符数组中第1个和第n个,第2个和第n-1个,以此类推,最后得到逆序的字符数组,再通过String的valueOf()方法将字符数组变成字符串。
public static String reverseString(String str){
char[] s = str.toCharArray();
helper(0,s.length - 1,s);
return String.valueOf(s);
}
private static void helper(int start, int end, char[] s) {
if (start >= end){
return;
}
char temp = s[start];
s[start] = s[end];
s[end] = temp;
helper(start+1,end-1,s);
}
13.抽象类必须要有抽象方法吗?
抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。
如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。
14.java中的IO流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
15.BIO、NIO、AIO有什么区别?
一、同步阻塞I/O(BIO):
同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解
二、同步非阻塞I/O(NIO):
同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持
三、异步非阻塞I/O(AIO):
异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。
四、IO与NIO区别:
- IO面向流,NIO面向缓冲区
- IO的各种流是阻塞的,NIO是非阻塞模式
- Java NIO的选择允许一个单独的线程来监视多个输入通道,可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入或选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道
五、同步与异步的区别:
同步:发送一个请求,等待返回,再发送下一个请求,同步可以避免出现死锁,脏读的发生
异步:发送一个请求,不等待返回,随时可以再发送下一个请求,可以提高效率,保证并发
同步异步关注点在于消息通信机制,
阻塞与非阻塞关注的是程序在等待调用结果时(消息、返回值)的状态:
- 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
- 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
不同层次:
1. CPU层次:操作系统进行IO或任务调度层次,现代操作系统通常使用异步非阻塞方式进行IO(有少部分IO可能会使用同步非阻塞),即发出IO请求后,并不等待IO操作完成,而是继续执行接下来的指令(非阻塞),IO操作和CPU指令互不干扰(异步),最后通过中断的方式通知IO操作的完成结果。
2. 线程层次:操作系统调度单元的层次,操作系统为了减轻程序员的思考负担,将底层的异步非阻塞的IO方式进行封装,把相关系统调用(如read和write)以同步的方式展现出来,然而同步阻塞IO会使线程挂起,同步非阻塞IO会消耗CPU资源在轮询上,3个解决方法:
- 多线程(同步阻塞)
- IO多路复用(select、poll、epoll)
- 直接暴露出异步的IO接口,kernel-aio和IOCP(异步非阻塞)