目录
精选牛客网鄙视题&详解(一)
1. 经典问题:继承关系中程序执行的顺序问题
- 初始化父类中的静态成员变量和静态代码块 ;
- 初始化子类中的静态成员变量和静态代码块 ;
- 初始化父类的普通成员变量和代码块,再执行父类的构造方法
- 初始化子类的普通成员变量和代码块,再执行子类的构造方法;
// 答案:YXYZ
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
2. Java Object类的基本方法
-
getClass():
返回正在运行的class 类,一般当我们在多线程的时候想看那个线程正在执行的时候可以查看public final native Class<?> getClass();
-
hashCode():
可以想象数组的索引,表示Hash码值,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。public native int hashCode();
-
equals():
一般equals和==
是不一样的,但是在Object中两者是一样的
从equal可以看出我们比较的是对象的引用,他其实与==
没有差别, 但是为什么我们认为他是比较的内容?因为我们重写了他public boolean equals(Object obj) { return (this == obj); } //重写,比较的是内容 public boolean equals(Object obj) { if (obj instanceof Boolean) { return value == ((Boolean)obj).booleanValue(); } return false; }
-
clone():
赋值一份引用,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。
protected native Object clone() throws CloneNotSupportedException;
-
toString():
返回该对象的字符串表示,一般情况下我们都会重写它public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
notify():
他的作用就是唤醒等待的线程public final native void notify();
-
notifyAll()
他的作用就是唤醒所有等待的线程 -
wait()
他的作用就是让线程上锁,处于等待状态 -
finalize():
他的作用主要就是当jvm进行垃圾回收之前会执行,用于释放资源,在释放资源之前她会先检查一下该对象的状态是否可达,一般情况下不要调用,因为会抛异常,如果已经释放资源,你依旧调用这个时候就会抛出异常
3. 类加载时静态代码块执行的顺序
-
类加载是并不是静态块最先初始化,而是静态域!
-
而静态域中包含:静态变量、静态块和静态方法,而他们的初始化顺序取决于然们的位置,从上到下依次进行初始化
-
构造代码块({}内的部分)在每一次创建对象时执行,始终在构造方法前执行
-
构造方法在新建对象时调用( 就是new的时候 )
public class B { public static B t1 = new B(); public static B t2 = new B(); { System.out.println("构造块"); } static { System.out.println("静态块"); } public static void main(String[] args) { B t = new B(); } }
- 分析:由题目可知该类中既有静态变量,又有静态块,还有静态方法,
所以首先初始化静态变量t1(这时忽略其他带有static的部分)打印"构造块",再初始化静态变量t2(这时还是忽略其他带有static的部分)打印"构造块",再然后静态块打印"静态块",最后执行main打印"构造块"
- 分析:由题目可知该类中既有静态变量,又有静态块,还有静态方法,
4. Java反射机制
Java反射机制主要提供了以下功能:
-
在运行时判断任意一个对象所属的类;
-
在运行时构造任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法;
-
在运行时调用任意一个对象的方法;生成动态代理。
解析:JAVA反射机制概念:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射是通过一个名为Class的特殊类,用Class.forName(“className”);得到类的字节码对象,然后用newInstance()方法在虚拟机内部构造这个对象(针对无参构造函数)。
也就是说反射机制让我们可以先拿到java类对应的字节码对象,然后动态的进行任何可能的操作,
使用反射的主要作用是方便程序的扩展。
5. try{} catch{} finally()的使用
-
鄙视题1
try { system.out.println("try"); throw new Exception(); } catch { system.out.println("catch"); } finally { system.out.println("finally"); } // 执行结果 try,catch,finally
-
鄙视题2
public int add(int a,int b) { try { return a+b; } catch (Exception e) { System.out.println("catch语句块");//不会执行 } finally{ System.out.println("finally语句块"); } //因为try中有return,所以finally之后的都不会执行 System.out.println("我不会出现的!");//不会执行 return 0;//不会执行 } // 执行结果 finally语句块 /* 1、finally块一定会执行,无论是否try…catch。 2、finally前有return,会先执行return语句,并保存下来,再执行finally块,最后return。 3、finally前有return、finally块中也有return,先执行前面的return,保存下来,再执行finally的return,覆盖之前的结果,并返回。 */
-
鄙视题3
public static void main(String[] args) { try { int i = 100 / 0; System.out.println(i);//后面的语句不会执行 } catch (Exception e) { System.out.println(1); throw new RuntimeException();//只会执行finally中的,其他的不会执行 } finally { System.out.println(2222); } //不会执行 System.out.println(3); } // 执行结果 1 2222 Exception in thread "main" java.lang.RuntimeException /* 需要理解Try…catch…finally与直接throw的区别: - try catch是直接处理,处理完成之后程序继续往下执行. - throw则是将异常抛给它的上一级处理,程序便不往下执行了。 - 本题的catch语句块里面,打印完1之后,又抛出了一个RuntimeException,程序并没有处理它,而是直接抛出,因此执行完finally语句块之后,程序终止了 */
-
鄙视题4
public class TryCatchDemo { public int calculate() { int x = 1; try { System.out.println("A"); return ++x; } catch (Exception e) { //System.out.println("D"); } finally { System.out.println("B"); ++x; } // 因为try中有return,所以finally之后的都不会执行 System.out.println("C");// 不会执行 return x;// 不会执行 } public static void main(String[] args) { TryCatchDemo tryCatchDemo=new TryCatchDemo(); int y=tryCatchDemo.calculate(); System.out.println(y); } } // 执行结果 A B 2 /* 解析: 如果 try 语句里有 return,那么代码的行为如下: A,有返回值:就把返回值保存到局部变量中 (执行”return ++x”,x=2,保存在局部变量) B,执行 jsr 指令跳到 finally 语句里执行 (准备执行finally{…}代码块) C,执行完 finally 语句后,返回之前保存在局部变量表里的值 (虽然执行”++x”后x=3,但是返回保存在局部变量中的x=2) */
throws:写在方法声明之后,表示方法可能抛出异常,调用者需要处理这个异常。
throw:写在方法体中,表示方法一定会抛出一个异常,要么try…catch处理,要么throws抛出。
6. ArrayList list = new ArrayList(20);中的list扩充几次?
当第一次插入元素时才分配10(默认)个对象空间。之后扩容会按照1.5倍增长。
初始化的三种方式:
- public ArrayList(),默认的新构造器,将会以默认的大小来初始化内部的数组(默认大小为不同jdk版本不同:jdk1.7、jdk1.8中默认初始数组容量为0,而jdk1.6中默认为10)
- public ArrayList(Collection<?extends E> c),用一个ICollection对象来构造,并将该集合的元素添加到ArrayList
- public ArrayList(int initialCapacity),用指定的大小来初始化内部的数组
7. Java中的新生代、老年代、永久代和各种GC
JVM中的堆,一般分为三大部分:新生代、老年代、永久代
一、 新生代
- 主要是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
- 新生代又分为 Eden区、ServivorFrom、ServivorTo三个区(默认的:Edem : from : to = 8 : 1 : 1 )
- Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
- ServivorTo:保留了一次MinorGC过程中的幸存者。
- ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。
- 当JVM无法为新建对象分配内存空间的时候(Eden满了),Minor GC被触发。因此新生代空间占用率越高,Minor GC越频繁。
- MinorGC的过程:采用复制算法
首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,一般是15,则赋值到老年代区)
同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区)
然后,清空Eden和ServicorFrom中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。
二、 老年代
- 老年代的对象比较稳定,所以MajorGC不会频繁执行。
- 在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
- MajorGC采用标记—清除算法:
首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。
三、 永久代
- 指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。
- Class在被加载的时候被放入永久区域。它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。
- 在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制。
- Major GC和Full GC区别
- Full GC:收集young gen、old gen、perm gen
- Major GC:有时又叫old gc,只收集old gen
8. 可以把任何一种数据类型的变量赋给Object类型的变量。
引用类型都直接或间接继承自Object
基本类型 在赋值时都会 先自动装箱成其相对应包装类型,然后赋值。所以是正确的。
补充:java的装箱与拆箱
-
定义:装箱就是将基本数据类型转化为包装类型,那么拆箱就是将包装类型转化为基本数据类型。
Java中基础数据类型与它们的包装类进行运算时,编译器会自动帮我们进行转换,转换过程对程序员是透明的,这就是装箱和拆箱,装箱和拆箱可以让我们的代码更简洁易懂
原始类型 所占位数 包装类型 byte 1字节,8位 Byte short 2字节,16位 Short int 4字节,32位 Integer long 8字节,64位 Long float 4字节,32位 Float double 8字节,64位 Double char 2字节,16位 Character boolean 长度不明确 Boolean 当表格中左边列出的基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱.
- 进行 = 赋值操作(装箱或拆箱)
- 进行+,-,*,/混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList,HashMap等集合类 添加基础类型数据时(装箱)
-
示例
/* 装箱: 因为10是属于基本数据类型的,原则上它是不能直接赋值给一个对象Integer的,但当javac发现在一个需要Integer的上下文里出现了int类型的值,就会通过Integer.valueOf()自动把这个值装箱为Integer,自动将基本数据类型转化为对应的封装类型。 */ Integer a = 10; /* 拆箱:故名思议就是将对象重新转化为基本数据类型 自动拆箱有个很典型的用法就是在进行运算的时候:因为对象时不能直接进行运算的,而是要转化为基本数据类型后才能进行加减乘除 */ int b = a; int i = new Integer(2);//这是拆箱
-
鄙视题
int num1 = 297; Integer num2 = 297; System.out.println("num1==num2: " + (num1 == num2)); Integer num3 = 97; Integer num4 = 97; System.out.println("num3==num4: " + (num3 == num4)); num3 = 297; num4 = 297; System.out.println("num3==num4: " + (num3 == num4)); // 结果 num1 == num2: true num3 == num4: true num3 == num4: false /* 分析:考察自动装箱拆箱缓存问题 这是因为JVM会自动维护八种基本类型的常量池,int常量池中初始化-128~127的范围,所以当为Integer i=127时,在自动装箱过程中是取自常量池中的数值,而当Integer i>128时,i不在常量池范围内,所以在自动装箱过程中需new i,所以地址不一样。 */
以下时实现自动装箱过程所调用的方法:
//boolean原生类型自动装箱成Boolean public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } //byte原生类型自动装箱成Byte public static Byte valueOf(byte b) { final int offset = 128; return ByteCache.cache[(int)b + offset]; } //byte原生类型自动装箱成Byte public static Short valueOf(short s) { final int offset = 128; int sAsInt = s; if (sAsInt >= -128 && sAsInt <= 127) { // must cache return ShortCache.cache[sAsInt + offset]; } return new Short(s); } //char原生类型自动装箱成Character public static Character valueOf(char c) { if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; } return new Character(c); } //int原生类型自动装箱成Integer public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //int原生类型自动装箱成Long public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); } //double原生类型自动装箱成Double public static Double valueOf(double d) { return new Double(d); } //float原生类型自动装箱成Float public static Float valueOf(float f) { return new Float(f); } /* 通过分析源码发现,只有double和float的自动装箱代码没有使用缓存,每次都是new 新的对象,其它的6种基本类型都使用了缓存策略。使用缓存策略是因为,缓存的这些对象都是经常使用到的(如字符、-128至127之间的数字),防止每次自动装箱都创建一此对象的实例。而double、float是浮点型的,没有经常使用到的数据,缓存效果没有其它几种类型使用效率高。且装箱调用的都是valueOf方法。 而拆箱跟自动装箱的方向相反,将Integer及Double这样的引用类型的对象重新简化为基本类型的数据。 */
-
总结:
自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作,装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的xxxvalue方法实现的(xxx代表对应的基本数据类型)。
9. jre 判断程序是否执行结束的标准是()
// 程序可以理解为进程 所有的前台线程执行完毕
-
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
-
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
-
主线程也即是指 main()函数,是一个前台线程,使用new Thread方式创建的线程默认都是前台线程。
-
前台线程和后台线程的区别和联系:
1、后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止。所有剩余的后台线程都会停止。
2、可以在任何时候将前台线程修改为后台线程。
3、不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
10. Java的集合
- 鄙视题1
下面哪些接口直接继承自Collection接口()
答案:List、Map
- 鄙视题2
对 Map 的用法,正确的有:
A.new java.util.Map().put("key" , "value") ;
B.new java.util.SortedMap().put("key" , "value") ;
C.new java.util.HashMap().put( null , null ) ;
D.new java.util.TreeMap().put( 0 , null ) ;
// 正确答案: C D
/*
解析:
Map和SortedMap是接口,不能直接new对象,A,B错误
HashMap 允许null-null键值对 C正确
TreeMap 允许value值为null,不允许key值为null,D是value为null,key不为null,正确
*/
- ArrayList是线程不安全的,在多线程的情况下不要使用。
- 如果一定在多线程使用List的,您可以使用Vector,因为Vector和ArrayList基本一致,区别在于Vector中的绝大部分方法都使用了同步关键字修饰,这样在多线程的情况下不会出现并发错误哦,还有就是它们的扩容方案不同,ArrayList是通过原始容量*3/2+1,而Vector是允许设置默认的增长长度,Vector的默认扩容方式为原来的2倍。 切记Vector是ArrayList的多线程的一个替代品。
- LinkedList
- LinkedList的链式线性表的特点为: 适合于在链表中间需要频繁进行插入和删除操作。
- LinkedList的链式线性表的缺点为: 随机访问速度较慢。查找一个元素需要从头开始一个一个的找。速度你懂的。