short s1=1;s1=s1+1有什么错?
short s1=1;s1+=1;有什么错?
第一个是有错的,short在内存中占2个字节,而整数1默认为int型占4个字节,s1+1其实这个时候就向上转型为int类型了,因此第一行代码必须强转才行。第二个之所以可以,是因为这句话翻译过来就是s1++,也就是short类型的数据自身加增1,因此不会有问题。
静态成员类、非静态成员类有什么区别?什么是匿名内部类?
静态成员类相当于外部类的静态成员,是外部类在加载的时候进行初始化,非静态成员类相当于外部类的普通成员,当外部类创建对象的时候才会初始化。匿名内部一般都是在方法里面直接通过new ClassName(){};形式的类。比如我们new Thread(new Runnable(){}).start();就用到了匿名内部类。
abstract class 和 interface有什么区别?
前者是抽象类,可以有抽象方法,也可以没有。后者是接口,只能有抽象方法。他们都不能创建对象,需要被继承。
ArrayList是不是线程安全的?如果不是,如何是ArrayList成为线程安全的?
不安全的。可以使用Collections.synchronizedList(list)将list变为线程安全的。
当一个对象被当做参数传递到一个方法后,此方法可以改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
java中只有值传递,没有引用传递。这里的引用本身就是值,传递的是引用这个值。
请描述下JVM加载class文件的原理机制。
JVM加载class是动态性的,也就是当“需要”的时候才会加载,这是也是为节约JVM内存来考虑的。同时JVM的类加载是父类委托机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”。
我们知道了ThreadLocal是如何实现线程私有变量的。
但是问题来了,如果线程数很多,一直往ThreadLocalMap中存值,那内存岂不是要撑死了?当然不是,设计者使用了弱引用来解决这个问题:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
不过这里的弱引用只是针对key。每个key都弱引用指向ThreadLocal。当把ThreadLocal实例置为null以后,没有任何强引用指向ThreadLocal实例,所以ThreadLocal将会被GC回收。然而,value不能被回收,因为当前线程存在对value的强引用。只有当前线程结束销毁后,强引用断开,所有值才将全部被GC回收,由此可推断出,只有这个线程被回收了,ThreadLocal以及value才会真正被回收。听起来很正常?
那如果我们使用线程池呢?常驻线程不会被销毁。这就完蛋了,ThreadLocal和value永远无法被GC回收,造成内存泄漏那是必然的。
而我们的请求进入到系统时,并不是一个请求生成一个线程,而是请求先进入到线程池,再由线程池调配出一个线程进行执行,执行完毕后放回线程池,这样就会存在一个线程多次被复用的情况,这就产生了这个线程此次操作中获取到了上次操作的值。
怎么办呢?
解决办法就是每次使用完ThreadLocal对象后,都要调用其remove方法,清除ThreadLocal中的内容。
public class Test {
static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(() -> new AtomicInteger(0));
static class Task implements Runnable {
@Override
public void run() {
int initial = sequencer.get().getAndIncrement();
System.out.println(initial);
sequencer.remove();//这里不删有问题
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new Task());
executor.execute(new Task());
executor.execute(new Task());
executor.execute(new Task());
executor.execute(new Task());
executor.execute(new Task());
executor.shutdown();
}
}
Java 7中switch开始支持String。
对于编译器来说,switch中其实只能使用整型,任何类型的比较都要转换成整型。比如byte。short,char(ackii码是整型)以及int。
那么接下来看下switch对String得支持,有以下代码:
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
反编译后内容如下:
public class switchDemoString
{
public switchDemoString() {
}
public static void main(String args[]) {
String str = "world";
String s;
switch((s = str).hashCode()) {
default:
break;
case 99162322:
if(s.equals("hello"))
System.out.println("hello");
break;
case 113318802:
if(s.equals("world"))
System.out.println("world");
break;
}
}
}
看到这个代码,你知道原来字符串的switch是通过equals()和hashCode()方法来实现的。还好hashCode()方法返回的是int,而不是long。
仔细看下可以发现,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。
泛型——当泛型遇到重载
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>另一个是List<Integer>,但是,这段代码是编译通不过的。因为我们前面讲过,参数List<String>和List<Integer>编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
网友评论