反射
- 用于在运行时动态获取类的字节码和方法(是获取不是创建,类的字节码在类加载阶段加载到内存中)
- 反射机制可以在运行时认识编译期不了解的对象信息,并能调用相应方法修改属性值
- 动态代理就是基于反射机制实现的
1 解释"static"和"final"关键字?
(1)static修饰变量称静态变量,被所有对象所共享,在内存中只有一份(区别于类的实例变量,实例变量每个实例都有一份),它仅在类初次加载时才被初始化,可作全局变量来用。
(2)static修饰方法称静态方法,不依赖于任何对象,因此没有this。 因此static方法中不能访问非static的变量和方法,因为它们必须依赖具体对象才能被调用,而static方法使用时这些对象可能还未创建。
(3)static还有关键作用就是构造静态代码块以优化程序性能。static块可以置于类中任何地方,类中可以有多个static块。在类初次加载时,会按static块顺序来执行每个static块,并且只会执行一次。
(4)static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定,而static方法是编译时静态绑定的。
(5)java中的final语义:1. 修饰类表示不可继承;2. 修饰属性,表示不可变,不可变是指,对基本类型来说值不可变,对引用来说引用不可变,但引用指向的对象可变;3. 修饰方法,表示该方法不允许重写。
(6)在JVM的ClassFIle文件结构中,有一个access_flag标志位,有一个标记位为ACC_FINAL,表明该类不允许有子类,也就是final对于类的语义了。每一个类都有一个父类索引super_class,除了Object以外,取值都不为0,也就是含有一个指向constant_pool的有效索引值。JVM会检查super_class指向的父类,若是父类同时设置了ACC_FINAL,则会出错。这部分在编译时会进行检查。因为final类不允许继承,也就隐含该类的所有方法都不许覆盖。从这个角度来说,抽象类和接口,显然是不能声明为final的。
方法里也有ACC_FINAL标记,该标记和ACC_ABSTRACT冲突。ACC_FINAL设置之后,不允许子类覆盖该方法,这个检查是在编译器进行的。但是父类中的private由于对子类是透明的,因此子类也无法覆盖该方法。
属性中也有ACC_FINAL标记位。设置后就不能设置为ACC_VOLATILE。当设置了ACC_VOLATILE后,虚拟机将保证所有线程看到一致的值。
2 抽象类(使用abstract修饰的类)和接口(interface)的区别
(1)抽象类可拥有任意成员变量,也可拥有非抽象方法;但接口仅能有static final的成员变量(static:所有的实现共用,可以使用A.name方式直接访问,防止多出现中的变量重名问题),同时所有方法必须抽象。
(2)在某种程度说,接口是抽象类的特殊化。
(3)子类只能继承一个抽象类,但可以实现多个接口。
(4)抽象类是对类抽象,而接口是对行为抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
(5)对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。
(6)默认情况下,建议使用接口而不是抽象类
4 "=="和equals方法有什么区别?
- (1)操作符比较两个变量的值是否相等,即比较变量对应内存中存储数值是否相同。要比较两个基本类型(int、long…)的数据是否相等,只能用。
如果一个变量是对象的引用,这就涉及两块内存,对象本身占用一块(堆内存),变量也占用一块内存(栈内存)。例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时变量obj对应内存中存储的数值就是对象占用内存的首地址。对于这种变量,如果要比较两个变量是否指向同一个对象,就需要用==操作符比较。 - (2)equals方法用于比较两个对象的内容是否相同。例如:
String a=new String("foo");
String b=new String("foo");
这里创建两个对象,用a,b这两个变量分别指向一个对象,这是两个不同的对象(两个new,分配的内存肯定不同),内存首地址不同,即a和b中存储的数值不同,所以a==b将返回false,而这两个对象中的内容是相同的,所以a.equals(b)将返回true。
字符串的比较基本上都是使用equals方法。
- 如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,实现代码如下:
这说明如果一个类没有定义equals方法,默认equals方法就是用操作符,这时用equals和会得到同样结果,如果比较两个不同对象则总返回false。如果你的类希望能比较该类创建的两个实例对象的内容是否相同,那么必须覆盖(自定义)equals方法。boolean equals(Object o){ return this==o; }
hashcode相等两个类一定相等吗?equals呢?相反呢?- ----使用equals判断两个类是否相等,因此equals则类一定相等,hashcode也相等
- ----hashcode相等则不一定,因为可能存在hash的冲突
5.Overload和Override的区别
- Overload是重载,Override是覆盖,也就是重写。
- 重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。
- 重写Override表示子类方法可与父类的方法名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这是面向对象(封装、继承、多态)多态的一种表现。
- 子类覆盖父类方法时,只能比父类抛出更少异常,或抛出父类异常的子异常,因为子类可解决父类的一些问题,不能比父类有更多的问题。
- 子类方法的访问权限只能比父类的更大。如果父类方法是private类型,则等于子类增加一个新方法(子类不可见父类的private类型 public protected private)。(异常更少,访问权限更大)
- 从JVM角度看,Overload是在编译期确定的(静态绑定,编译期确定调用的具体方法,运行期类加载阶段做一次常量池解析即可调用),而Override是运行期确定(动态绑定)。因此Override不能用于私有或静态方法(因为这些方法都是静态绑定)
6.HashMap的数据结构是什么?如何实现的?和HashTable、ConcurrentHashMap的区别?
- HashMap以键值对(KV)形式存储元素。HashMap需要一个hash函数,它使用hashCode()和equals()方法执行添加和检索。调用put(key,v)时HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在,value会被更新成新值。
- 在Java 8中,HashMap的数据结构是由Node<k,v>作为元素组成的数组:(1)如果多个值hash到同一桶中则组成一个链表,当这个链表的节点个数超过一定值时,将这个链表重构为一个二叉树;(2)如果发现map中的元素个数超过了阈值,则进行空间扩容——空间倍增。
- HashMap和HashTable数据结构和操作基本相同,区别是前者是非线程安全,并且HashMap接受value为null。
- ConcurrentHashMap和HashTable都是线程安全的,区别是:HashTable每次操作都会锁住整个表结构,导致一次只能有一个线程访问HashTable对象,而ConcurrentHashMap不会,只会锁住某个节点,只有在涉及到size操作时才会锁整个表结构。
- HashMap使用hashCode()和equals()方法确定键值对索引,当根据k获取v时也会用到这两个方法。如果没有正确实现这两个方法,两个不同k可能有相同hash值,可能会被集合认为相等。而且这两个方法也用来发现重复元素。所以这两个方法对HashMap的精确性和正确性是至关重要的。
8. 为什么要装箱(int – Integer)
- (1)把基本类型包装成类,可以使这个类型具有很多方法和状态,比如方法返回Boolean,对处理失败可以直接返回null,而不应该是默认的初始值false(对基本类型来说)
- (2)Java向面向对象语言的靠近。其实Java还不算纯正的面向对象语言。真正的面向对象,没有基本数据类型,只有对象。
- (3)用int型的包装类类型来解决基本类型不可以做泛型参数的问题,基本类型是不可做泛型参数的。
List <int> list = new ArrayList<int> (); //合法
List<Integer> list = new ArrayList<Integer> (); //OK
9. String和StringBuilder、StringBuffer的区别?
- String和StringBuffer/StringBuilder,都可以储存和操作字符串。
- String是只读字符串,创建后不能更改(每次String变化都是新创建一个String,并把新String指向原来变量)。
- StringBuffer:字符串变量(线程安全),可直接修改
- StringBuilder:字符串变量(线程不安全)可直接修改。StringBuilder在单线程下使用,效率比StringBuffer高。
- 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象
10. String s = new String(“xyz”);创建了几个字符串对象?
答:两个对象,一个是常量区的"xyz",一个是用new创建在堆上的对象。
12. 什么时候用断言(assert)?
断言是常用的调试方式,一般用于保证程序最基本、关键的正确性。断言通常在开发和测试时开启,软件发布后通常关闭。断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为false,那么系统会报告一个AssertionError。
要在运行时启用断言,可以在启动JVM时使用-enableassertions或者-ea标记。要在运行时选择禁用断言,可以在启动JVM时使用-da或者-disableassertions标记。要在系统类中启用或禁用断言,可使用-esa或-dsa标记。还可以在包的基础上启用或者禁用断言。
13. Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally如何使用?
- Java把各种异常进行分类,并提供良好的接口。
- 每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。
- Java异常处理通过5个关键词实现:try、catch、throw、throws和finally。
- 一般用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;
- try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;
- throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常;
- finally确保范围内的代码无论如何(及时抛了异常)都要被执行;
- try语句可以嵌套。
14.(容器)List、Map、Set三个接口存取元素时,各有什么特点?
- List以特定索引来存取元素,可以有重复元素 List。
- Set不能存放重复元素(可用对象的equals()方法区分元素是否重复) Set。
- Map保存键值对(KV)映射,映射关系可以是一对一或多对一。
- Set和Map都有基于哈希和树的两种实现版本,基于哈希的理论插入/读取时间复杂度都为O(1),而基于排序树版本在插入/删除元素时会按照元素的键(key)构成排序树从而达到排序和去重的效果(HashMap–TreeMap)(HashSet–TreeSet)。
18. 用Java写一个单例类
单例:该对象的实例只创建一次,可避免对象重复创建,优化程序性能。
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
19. Exception和Error的区别
- Error一般指系统问题,如系统崩溃,虚拟机错误,内存不足,方法调用栈溢出等。对这类错误导致的程序中断,仅靠程序本身无法恢复和预防,建议让程序终止。
- Exception表示程序可处理的异常,可捕获且可能恢复。这类异常应该尽可能处理,使程序恢复运行,不应该随意终止异常。
- Exception又分运行时异常(RuntimeException)和受检异常(CheckedException )。运行时异常(NullPointerException等),编译能通过,但一运行就终止,出现这类异常时程序会终止。而受检异常(强制必须处理,否则编译不通过)要么用try–catch捕获,要么用throws抛出给父类处理,否则编译不通过。
20. 范型是什么?有什么作用?
- 泛型本质是参数化类型,就是说所操作的数据类型被指定为一个参数,泛型在编译时候检查类型安全,并且所有的强制转换都是自动和隐式的,可以提高代码重用率(安全、简单、便于复用)。
为什么说JAVA的泛型是伪泛型?
- 真实泛型(如C#)在源码、编译器、运行期都是真实存在的,List与List是切实不同的类型
- 伪泛型(JAVA,又称类型擦除)只在源码存在,编译后字节码中已经替换为原生类型,并在相应地方插入强制转换代码。ArrayList与ArrayList是同一个类。