1、面对对象三大特征
(1)封装
封装,就是将数据和基于数据的操作封装在一起,使数据被隐藏在类的内部,只提供一些公共方法使之与外部发生联系。
好处:1、减少耦合;2、隐藏实现细节;3、对成员变量进行更精确的控制(在set方法过滤掉不合理的数据)
(2)继承
继承是指在已有类的基础上来建立新类。当子类继承父类后,子类能够获得父类的成员属性和成员方法。
好处:继承(和组合都)是实现复用的重要手段,也是实现多态的基础。
坏处:1、强耦合(父变子变)2、破坏了封装(父类实现细节对子类是透明的)
(1)子类拥有父类的所有属性和方法,但是父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但不能使用。
(2)子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
(3)子类可以用自己的方式重写父类的方法。
(4)构造器不能被继承,但可以使用super()调用父类的构造器,每一个构造方法都必须显式或隐式调用父类的构造器,因为类初始化时会先初始化父类。如果构造器第一句没有显示调用父类构造器,那么编译器自动调用super()调用父类无参构造,如果父类没有无参构造则必须显式调用父类的有参构造,否则编译器会报错。
(3)多态
同一个类型的对象在执行同一个方法时,可能会表现出不同的行为特征。(因为调用对象的实际类型在运行期才能确定)
多态的两个条件:继承重写、向上转型
多态的实现形式:继承和接口
2、访问修饰符
private:在当前类使用
default(不写):在当前类或者同包使用
protected:与当前类同包的类和当前类不同包的子类使用
public:在任何地方可用
3、String是基本数据类型吗?
不是。基本数据类型包括数值类型和布尔型
数值类型又分为整型(byte short int long)、字符型(char)以及浮点型(float double)
而String类型是属于引用类型。
4、float f=3.4;是否正确?
不正确,对于整型来说,不加后缀就是int,对于浮点型来说,不加后缀就是doule。将double赋值给float属 于下转型,会造成精度损失,因此需要强制类型或者将3.4改为3.4f
5、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
short s1 = 1; s1 = s1 + 1;
首先对于第一句,虽然数值1默认是int类型,但是对于byte、short、char这三种类型在赋值的时候,会自动给他们加上强制类型转换,所以没有错误。
对于第二句,算术表达式会将结果提升到至表达式中最高等级操作数的类型。对于byte short char 会提升至int。题目中s1是short类型的,1是int类型是,两者相加会变成int类型,而int类型赋值给short类型属于向下转向,需要进行强制转换,所以是错误的。
short s1 = 1; s1 += 1;
首先对于第一句,同上面一样,所以没有错误。
对于第二句,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含 的强制类型转换,所以没有错误。
6、Java 有没有 goto?
goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。但是Java 中支持带标签的 break 和 continue 语句,即在想要跳出的那层循环前加上一个标记,比如A,然后用break A;可以跳出多层循环。
7、int 和 Integer 有什么区别?
Integer是int的一个包装类型,可以通过new Integer(3)将3转换为包装类型,此外,还利用自动装箱/拆箱机制,使得二者可以相互转换。
int的包装类型是Integer,char的包装类型是Character,其余都是把首字母变大写
8、== equals instanceOf
==
如果两个变量都是基本数据类型,且都是数值类型(不要求类型严格相同),那么只要两个变量的值相等,都会返回true
比如
65==‘A’ //true
65==65.0f //true
如果两个变量都是引用类型,那么必须要求是同一个对象。
另外这两个变量是同一类型或者有父子关系才能比较,否则编译错误。
String s1=new String(“hello”);
String s2=new String(“hello”);
s1==s2; //不是同个对象false
equals()
Object类提供的equals()方法:与==一样要求两个引用变量指向同一个对象。
String类重写了Object的equals()方法:字符串对象里包含的字符序列相同即可(即值相等)
String s1=new String(“hello”);
String s2=new String(“hello”);
s1.equals(s2); //字符序列相同,true
instanceof
用于判断前面的对象是否是后面对象的类或者其子类、实现类的实例,如果是则为true。
A instanceof B为true必须满足两个条件
一、在编译时,A与B具有同类或子类关系,否则编译错误
String a="hello";
a instanceof Math;//String不是Math子类,同类,实现类,编译出错
二、运行时,A实际类型与B有同类或子类或实现类的关系,此时返回true
Object b="hello"
b instanceof Math;//编译时,Object与Math具有基础关系,编译不出错,但是运行时,String与Math没有同类、继承、实现关系,返回false
b instanceof Comparable;//编译时,Object与Comparable具有继承关系,编译不出错,且运行时,String实现了Comparable接口,返回true
b instanceof Object;//String是Object的子类,返回true
9、自动装箱/拆箱
手动装箱:
方法一:
Integer inobj=Integer.valueOf(5);
方法二:
Integer inobj=new Integer(5);
自动装箱:
把一个基本数据类型赋给其对应的包装类变量,其会自动将其变为包装类变量
方式:
Integer inobj=5;//本质其实是调用了Integer.valueOf(5)
自动装箱的本质是:当我们把int 值赋值给Integer的时候,会调用 Integer 类的静态方法 valueOf(int i),而这个方法用到了一个数组 ,缓存直接量-128到127的对应的Integer实例,当一个在-128到127之间的直接量出现,它会去这个数组对应的位置拿到这个实例,而不在这个范围内的直接量的值都会分别new一个Integer对象
public class zhuangxiang {
public static void main(String[] args) {
Integer a=3;
Integer b=3;
Integer c=128;
Integer d=128;
System.out.println(a==b);//true
System.out.println(c==d);//false
}
}
手动拆箱
Integer iObj=new Integer(3);
int i=iObj.intValue();
自动拆箱
把包装类对象直接赋值给一个对应的基本数据类型变量,其会自动将其变为基本数据类型变量
int it=inobj;
10、&和&&的区别
& 可以作为逻辑与,也可以作为位与。当作为逻辑与时它是非短路的,即当使用多个表达式时,即使第一个表达式能得到结果时,即为F时,后面的表达式仍要计算
&& 只能作为逻辑与,且是短路的,当使用多个表达式时,第一个表达式能得到结果时,即为F时,后面的表达式就不计算了
很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不 是 null 而且不是空字符串,应当写为:username != null &&!username.eq uals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不 成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
11、Math.round(11.5) 等于多少? Math.round(-11.5)等于多少?
Math.round(int i)是向大的取整,比11.5大的是12,比-11.5大的是-11。所以Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。
12、swtich 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?
早期的 JDK 中,switch(expr)中,expr 可以是 byte、short、char、int四个基本数据类型。
后面又引入了枚举类型和String类型
不能是boolean或者long…
13、用最有效率的方法计算 2 乘以 8?
2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)
num << 5表示:num*2^5
14、数组有没有 length()方法?String 有没有 length()方法?
数组没有length(),只有length属性:a.length
String有length()方法:a.length()
15、在 Java 中,如何跳出当前的多重嵌套循环?
Java 中支持带标签的 break 和 continue 语句,即在想要跳出的那层循环前加上一个标记,比如A,然后用break A;可以跳出多层循环。
16、构造器(constructor)是否可被重写(override)?
不可以,子类只会继承父类的成员属性和成员方法,不会继承构造器,所以不能重写,不过可以通过super()来调用。
17、两个对象值相同(x.equals(y) == true),但却可有不同的 hash code, 这句话对不对?
一般两个对象调用equals()发现相同,其hashCode应该也相等,但是重写这些方法就可能会导致他们俩不一样,但是这种做法是不推荐的,会产生一些无法预料的问题。
18、String类可以被继承吗
String类被final修饰,不可以被继承
19、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性, 并可返回变化后的结果,那么这里到底是值传递还是引用传递(*)
值传递。Java里方法的参数传递只有一种:值传递。所谓值传递,就是将实际参数值的副本传入方法内,而参数本身不受任何影响。
对于引用类型,传递的也是值类型,系统复制引用变量(地址)赋值给方法,但没有复制对象,使得他们指向了同个对象。
(虽然对象改变了,但引用仍指向同一个地址)
20、String 和 StringBuilder、StringBuffer 的区别?(*)
String是不可变类,一旦被创建,这个对象的字符序列就不可改变,是线程安全的。
StringBuilder、StringBuffer的功能与String是类似的,但是它们两个是可变的。其中,StringBuffer是线程安全的,所以性能想多较低,而StringBuilder没有实现线程安全,所以性能略高,一般在单线程使用
三者执行速度:StringBuilder>StringBuffer>String
选择:
String:在字符串不经常变化的场景中可以使用String类,如:常量的声明、少量的变量运算等。
StringBuffer:如果字符串经常变化(拼接、替换、删除等),且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等。
StringBuilder:如果字符串经常变化(拼接、替换、删除等),且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。
补充 1:有一个面试题问:做字符串拼接时用StringBuilder(或StringBuffer)好还是直接用String搭配+号性能好?
一般来说是前者,不过也有用+号略好的时候。
如果连接后得到的字符串在静态存储区中是早已存在的,那么用+做字符串连接是优于 StringBuffer / StringBuilder 的 append 方法的。
String name = ”I ” + ”am ” + ”chenssy ” ;
StringBuffer name = new StringBuffer(”I ”).append(” am ”).append(” chenssy ”);
补充 2:下面也是一个面试题,问程序的输出,看看自己能不能说出正确答案。
public static void main(String[] args) {
//凡是编译时内确定的就会从常量池取
String a="Programming";
final String e="Programming";
String b=new String("Programming");
String c="Program"+"ming";
String d="Program";
String f="ming";
String g=d+f;//编译时不能确定
//==对于引用类型必须是同个对象
//String类的equals()只要字符序列相等即可
System.out.println(a==b);//false
System.out.println(a==c);//true;a字面量会存常量池,c编译时可确定,所以也从常量池取,宏变量在编译时也可确定
System.out.println(g==c);//false;g编译时不可确定,不能从常量池取
System.out.println(a.equals(b));//true
System.out.println(a.equals(c));//true
System.out.println(g.equals(c));//true
System.out.println(a.intern()==b.intern());//true
}
String:intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的 字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用
1. String类能否被继承?
不能,String类是被final修饰的类,该类是最终类,不能被继承。
2. String底层存储数据的类型是什么?
JDK1.9之前是char[]来存储数据,1.9之后用byte[]来存储数据,原因是我们存储的大多数数据都是一个字节,这样的情况下用char就会浪费一个字节,造成内存的浪费。
3. String类型描述的字符串内容是常量,一旦定义是不可被改变的,因此会被虚拟机放在常量池中,如果后续我们的代码中有相同的字符串内容则直接可以使用常量池中已有的对象,无需创建新的对象,提高了性能。
4. 请问下面的代码会创建几个对象?分别存放在什么地方?
String str1="he"; //0或1个对象,存放在常量池
String str2=new String("he"); //1或2个对象,一个在常量池中,一个在堆内存中
5. String str="abcdef";
String str1="abc"+"def";
System.out.println(str==str1); //true,因为常量有优化机制,而变量没有
6. 既然StringBuilder类的对象本身可以修改,那么为什么还会有返回值呢?
返回值返回的是自身,目的为了连续调用,例如: s.reverse().append("a").insert(4,"s");
7. 如何实现StringBuilder类型与String类型之间的转换呢?
String转StringBuilder: StringBuilder sb=new StringBuilder(s);
StringBuilder转String: String s=sb.toString();
21、String创建多少个对象
String s1=new String(“Java”);创建了多少对象?
一个或者两个
先去常量池中判断有没有此字符串,如果有则只在堆上创建一个对象s1并且指向常量池中的字符串,如果常量池中没有此字符串,则会创建 2 个对象,先在常量池中新建此字符串“Java”,然后把此引用返回给堆上的对象s1
String s = “a”+“b”+“c”+"d"被编译器优化成了String s = “abcd”;
创建了0个或者1个吧
如果字符串常量池没有该字符串,则在常量池创建此字符串;如果有则不创建。
22、String类设计成不可变的好处
数据安全:防止被修改
线程安全
性能:String大量运用在哈希的处理中,由于String的不可变性,可以只计算一次哈希值,然后缓存在内部,后续直接取就好了。
23、写一个方法来判断一个String是否是回文(顺读和倒读都一样的词)?
回文就是正反都一样的词,如果需要判断是否是回文,只需要比较正反是否相等即可。String类并没有提供反转方法供我们使用,但StringBuffer和StringBuilder有reverse方法。
private static boolean isPalindrome(String str) {
if (str == null)
return false;
StringBuilder strBuilder = new StringBuilder(str);
strBuilder.reverse();
return strBuilder.toString().equals(str);
}
假设面试官让你不使用任何其他类来实现的话,我们只需要首尾一一对比就知道是不是回文了。
private static boolean isPalindromeString(String str) {
if (str == null)
return false;
int length = str.length();
System.out.println(length / 2);
for (int i = 0; i < length / 2; i++) {
if (str.charAt(i) != str.charAt(length - i - 1))
return false;
}
return true;
}
24、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?(*)
重载是发生同一个类中的,要求“两同一不同”——两个方法同个类,相同方法名,形参列表不同(与方法返回值无关)
重写发生在子类与父类之间,要求“两同两小一大”——方法名相同、形参列表相同、子类方法返回值类型小于或等于父类、子类方法声明抛出异常类小于等于父类、子类方法访问权限要大于等于父类
补充:华为的面试题中曾经问过这样一个问题:为什么不能根据返回类型来区分 重载,说出你的答案吧!
因为int f() void f()
如果调用时是int result=f();那么系统可以识别调用的是返回值为int的方法,但是java运行调用时忽略返回值的类型比如f();此时系统无法判断。
25、描述一下 JVM 加载 class 文件的原理机制?(*)
JVM 类加载机制分为五个部分:加载,连接(验证,准备,解析),初始化五个过程。
加载阶段:.class文件被类加载器加载进内存,产生Class对象
验证阶段:确保class文件的字节流包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。
准备阶段:为类变量分配内存并设置零值。(这个阶段进行内存分配的仅包括类变量,不包括实例变量)
解析阶段:将常量池的符号引用替换为直接引用。
初始化阶段: 真正执行类中定义的代码。执行静态变量声明时赋值语句和静态初始化块来显式初始化类变量。
一旦一个类被载入JVM,同一个类就不会被再次载入
任意一个类在 JVM 中的唯一性,是由加载它的类加载器和类的全限定名共同确定的。因此,比较两个类是否“相等”的前提是这两个类是由同一个类加载器加载的,否则,即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。JVM 的类加载机制,规定一个类有且只有一个类加载器对它进行加载。而如何保证这个只有一个类加载器对它进行加载呢?则是由双亲委派模型来实现的。
双亲委托机制(*)
类加载过程采取了父类委托机制(双亲委派)。双亲委托机制就是在类的加载时,会先让父类加载器试图加载,每一层的类加载器都是如此,因此所有请求最终都会被传到最顶层的类加载器中,当父类加载器无法加载该类时((在它的加载路径下没有找到所需加载的Class)),子类加载器才会自己去加载。
类加载器包括:
- 根类加载器(BootStrap):负责加载 JAVA_HOME\lib 目录
- 扩展类加载器(Extension):负责加载 JAVA_HOME\jre\lib\ext
- 系统类加载器(System):负责加载用户路径(classpath)上的类库
- 用户自定义类加载器(jav a.lang.ClassLoader 的子类)
双亲委托机制优点
- 保证同个类只加载一次。
比如加载位于 rt.jar 包中的类 java.lang.Object,第一次加载时委托给顶层的根类加载器进行加载,之后的加载也是会委托来到根类加载器,而同一个类加载器对同一个类只会加载一次(同限定类名+同类加载器)。
26、char 型变量中能不能存贮一个中文汉字?为什么?
可以,java使用的编码是 Unicode,一个char占两个字节。
27、抽象类和接口的异同(*)
相同:
- 抽象类和接口都不能够实例化,只用于被其他类继承或实现
- 接口和类都可以包含抽象方法,而实现接口或继承抽象类的普通子类必须实现全部抽象方法,否则该类仍然需要被声明为抽象类。
不同:
❤设计目的:接口是一种规范,规范了实现者必须向外提供哪些服务;抽象类体现的是一种模板式设计,作为多个子类的共同抽象父类。
❤构造器:接口不含构造器;抽象类里可含义构造器,但不是为了创建实例,而是为了让子类通过super()调用来完成抽象类的初始化操作(子类初始化会先初始化父类)
❤初始化块:接口不可以包含初始化块;抽象类可以包含初始化块。
❤变量:接口里只能定义静态常量(所有变量会默认加上public static final);抽象类里既可以定义静态常量,也可以定义普通成员变量
❤方法:接口里可以包含抽象方法、类方法、默认方法(default修饰,其实看做就是个实例方法,所以不能用static修饰)和私有方法,但是不包含普通方法(因为接口里所有的普通方法都会默认加上public abstract,即抽象方法);抽象类里可以包含普通方法
❤父类:接口可以直接实现多个接口;抽象类只能有一个直接父类
28、静态内部类(Static Nested Class)和内部类(Inner Class)的不同?
静态内部类可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,(简单来说就是内部类必须等外部类实例化完后,才能根据其进行实例化,静态内部类则不需要),如下是内部类的实例化,Card实例化依托于poker对象。
29、内存溢出和内存泄漏
内存溢出:Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。
内存泄漏:应该被垃圾回收的对象没有被垃圾回收。
在上图中:对象 X 引用对象 Y,X 的生命周期比 Y 的生命周期长,Y生命周期结束的时候,就变成无用但可达的对象,垃圾回收器不会回收对象Y。
29、Java 中会存在内存泄漏吗,请简单描述
由于垃圾回收机制无法回收无用但可达的对象,所以可能会存在内存泄漏
30、抽象的方法是否可同时是静态的(static),是否可同时是本 地方法(native),是否可同时被 synchronized 修饰?
都不能。抽象方法需要子类继承重写的,而静态的方法是属于类的,无法被继承,故而无法被重写
本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的
synchronized 和方法的实现细节有关,抽象方法不涉 及实现细节
31、静态变量和实例变量的区别?
静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,可以让多个对象共享,不需要创建对象就可以访问到它;
实例变量是每个对象各自具有的,对象之间不共享,需要先创建对象然后通过对象才能访问到它。
32、是否可以从一个静态方法内部调用非静态方法?
不可以,静态方法只能访问静态成员(变量或方法),因为非静态成员的调用要先创建对象,而静态方法无需创建对象就可以调用, 因此在调用静态方法时可能非静态成员的对象并没有被初始化(没初始化完成是不可以使用该对象的)。
33、浅拷贝和深拷贝(*)
浅拷贝:只克隆该对象所有成员变量值(包括引用类型的成员变量值),不会克隆引用类型的成员变量所引用的对象。
其中Address是User中的一个引用类型变量,只拷贝引用类型的成员变量值,但该值所引用的对象并没有进行克隆(Address对象仍是同一个)
深拷贝:保证了引用类型的成员变量所引用的对象都被复制了
如何实现对象深克隆?
有两种方式:
1.实现 Cloneable 接口并重写 Object 类中的 clone()方法;
实现深克隆的方式:
public class User implements Cloneable {
int age;
Address address;
public User(int age,Address address){
this.age=age;
this.address=address;
}
public User clone() throws CloneNotSupportedException{
User user = (User) super.clone();
user.address=(Address) address.clone();
return user;
}
}
public class Address implements Cloneable{
String detail;
public Address(String detail){
this.detail=detail;
}
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
public class DeepClone {
public static void main(String[] args) throws CloneNotSupportedException {
User user=new User(3,new Address("天河"));
User user1 = user.clone();
System.out.println(user==user1);//false
System.out.println(user.address==user1.address);//false
}
}
2.实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。
补充:
(1)对象序列化就是把对象转换为与平台无关的二进制流,从而将对象保持到磁盘上,或者允许其在网络中传输
实现对象序列化的步骤:
1、创建一个ObjectOutPutStream,它是一个处理流,必须建立在节点流的基础上
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("object.txt"));
2、将对象per序列化成二进制,保存在object.txt
oos.writeObject(per)
(2)反序列化:从磁盘或网络中获取的二进制流恢复成原来的Java对象
实现对象反序列化的步骤:
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("object.txt"));
//从磁盘的"object.txt"文件将二进制反序列化为对象
Person p =(Person)ois.readObject();
34、java的值传递和引用传递有什么区别?(*)
值传递:是指在调用函数时将实际参数复制一份传递到函数中。这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
在java中,引用类型作为参数传递,实际也是值传参,它是将引用类型对象的地址复制了一份,再传递到函数中(这里就不满足了引用传递的条件之一)
所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响(函数内改了这个地址的值,另一个地址的值有没有改变),而不是看地址指向的对象的变化。
35、反射
Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。
通过反射调用成员方法
1:获取Class对象(三种方法)
2:通过Class对象获取Constructor对象
3:Constructor.newInstance()创建对象
4:通过Class对象获取Method对象
5: Method对象调用invoke方法来调用方法
这样就利用了Class类与反射包完成了在运行时获取类的信息并调用对象成员方法——即反射机制
1)什么是反射?(*)
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
2)哪里用到反射?(*)
1.JDBC中,利用反射动态加载了数据库驱动程序。
2.Web服务器中利用反射调用了Sevlet的服务方法。
3.Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
4.很多框架都用到反射机制,注入属性,调用方法,如Spring。
36、强软弱虚引用(*)
(1)强引用(StrongReference)——决不被回收
如果一个对象具有强引用,那垃圾回收器绝不会回收它。
当内存空间不足时,Java
虚拟机宁愿内存溢出,抛出OutOfMemoryError
错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
如果强引用对象不使用时,需要弱化从而使GC
能够回收,如下:
strongReference = null;
(二) 软引用(SoftReference)——对内存敏感
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
软引用可用来实现内存敏感的高速缓存。 应用场景: 浏览器的后退按钮。按后退时,显示的网页内容如果是直接从缓存中取出 的,那么将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会 造成内存溢出。这时候就可以使用软引用,很好的解决了实际的问题; (有些浏览器可能回退时是重新构建的)
(三) 弱引用(WeakReference)——内存不敏感
如果一个对象只具有弱引用,则内存空间无论是否充足,垃圾回收器都会回收它;
(四) 虚引用(PhantomReference)
虚引用顾名思义,就是形同虚设。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。它必须和引用队列联合使用,用于跟踪对象被垃圾回收的状态。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
37、阻塞队列BlockingQueue
38、final关键字
final变量(常量)
final修饰成员变量(类变量和实例变量)一定要显式赋初值(一旦赋值不可改),因为当类初始化时,系统会为类的类变量分配内存并分配默认值,一般为0 null等;而当创建对象时,系统会为该对象的实例变量分配内存并分配默认值。如果自己不显式为成员变量赋初值就会永远为默认值,那么就没意义,java规定final修饰的成员变量必须由程序显示指定初始值。
如果修饰的是类变量:必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定;
如果修饰的是实例变量:声明时、非静态初始化块中、构造方法其中之一
系统不会为局部变量分配默认值
如果修饰的是局部变量:声明时或者使用时,当且仅有一次赋值,一旦赋值之后再次赋值就会出错
final如果修饰引用类型变量,它保存的仅是一个引用,final只保证这个引用类型变量所引用的地址不变,即一直引用同个对象,但这个对象完全可以发送改变
final方法
final修饰的方法可以被子类继承,但不能被子类重写。
如果子类中存在一个和父类一模一样的方法,此时是它不是重写,而是子类自己定义的一个方法。
final类
final类不能被继承,final类中所有方法都是final的,它的成员变量可以为final,也可以为非final。
39、static
static方法
- 被static修饰的方法就是静态方法,即类方法;没有被static修饰的方法就是非静态方法,即实例方法。
- 因为静态方法属于类,不属于某个实例对象,所以静态方法只能被重载,而不能被重写,在静态方法中也不能使用 this 或 super。(因为静态方法不属于某个实例)
- 静态方法会在类加载的时候被分配和加载入内存中;非静态方法属于对象的具体实例,只有在类对象创建时,在对象的内存中才有这个方法的代码块。
- 静态方法中不能调用非静态方法和非静态变量;非静态方法可以调用静态方法,也可以调用非静态方法。
- 静态方法可以由实例对象调用,也可以由类名直接调用,即“类名.方法名 ”或者“ 对象名.方法名”;非静态方法必须由实例对象来调用,即“对象名.方法名”,
40、父类及子类的静态代码块、构造代码块、构造函数、静态成员变量、普通成员变量的执行顺序?
(1)执行父类的静态代码块或父类静态成员变量声明语句;
(2)执行子类的静态代码块或子类静态成员变量声明时语句;
(3)执行父类的初始化块或父类实例成员变量声明时语句;
(4)执行父类构造器
(3)执行子类的初始化块或子类实例成员变量声明时语句;
(4)执行子类构造器
41、强制类型转换
基本数据类型
基本数据类型之间的转换只能在数值之间进行,数值类型与布尔类型之间不可互转。
自动类型转换:
1、当小范围赋值给大范围,java会自动类型转换为大范围
2、表达式类型自动提升:当一个算术表达式中包含多个基本类型的值,整个表达式的数据类型会发生自动提升到与表达式中最高等级操作数同样的类型
强制类型转换:当大范围赋给小范围,如果不强制类型转换会报异常
引用类型
引用类型强制转换的两个条件:
1、两个类型具有基础关系
2、引用的实际类型实际上是子类类型
Father father = new Son();
Son son = (Son) father;
异常
1、Throwable:
Throwable有两个子类:Error、Exception。
2、Error:(不可处理)
一般是指 Java 虚拟机相关的问题,如系统崩溃、虚拟机出错误、线程死锁等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常应用程序无法处理这些错误。因此应用程序不应该捕获Error对象,也无须在其throws子句中声明该方法抛出任何Error或其子类。
3、Exception:(可处理的)
Exception是程序可以处理的异常,可以分为运行时异常与编译时异常(受检异常):
(1)运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等。
这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
出现运行时异常后,如果没有捕获处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。
(2)非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如ClassNotFindException、IOException、EOFException、FileNotFoundException、SQLException等以及用户自定义的Exception异常。
对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行处理(要么捕获要么声明抛出),否则程序就不能编译通过。
Error:一般是虚拟机相关问题,无法处理
Exception:1、RuntimeException及其子类(可处理可不处理) 2、除了RuntimeException及其子类的异常(强制处理)
4、异常的捕捉–try、catch、finally:
try快包含着可能出现异常的代码块
catch块捕获异常后对异常进行处理,没有出行异常不执行
finally代码块不论程序是否发生异常,总是会执行,所以finally一般用来关闭资源。
只有try是必须的,catch和finally出现其中一个即可
关于异常的题
1、error和exception有什么区别?运行时异常与编译时异常的区别?(参考第2题回答)
Error一般是虚拟机相关的问题,大多数与代码编写与执行操作无关,如系统崩溃、内存异常等。这种错误无法恢复、不可能捕获,直接会导致程序中断,所以我们无需处理。
Exception是我们可以处理的,它分为编译时异常和运行时异常。运行时异常是RuntimeException类及其子类,一般是由程序逻辑错误引起的问题,如IndexOutOfBoundsException,运行时异常可以不显示处理,一旦出现异常,系统会一直往上抛,直到最上层,最上层可能是Thread.run(),也可能是main()。编译时异常是RuntimeException以外的异常,但类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。编译时异常要求我们必须显示处理(要么catch要么声明抛出)。
2、Java中的异常处理机制的简单原理和应用:
(1)异常是指java程序运行时所发生的非正常情况或错误。Java中所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error 和 Exception;
(2)Error一般是虚拟机相关的问题,大多数与代码编写与执行操作无关,如系统崩溃、内存异常等。这种错误无法恢复、不可能捕获,直接会导致程序中断,所以我们无需处理。
(3)Exception是我们可以处理的,它分为编译时异常和运行时异常。运行时异常是RuntimeException类及其子类,一般是由程序逻辑错误引起的问题,如IndexOutOfBoundsException,运行时异常可以不显示处理,一旦出现异常,系统会一直往上抛,直到最上层,最上层可能是Thread.run(),也可能是main()。编译时异常是RuntimeException以外的异常,但类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。编译时异常要求我们必须显示处理(要么catch要么声明抛出)。
3,请写出你最常见到的5个runtime exception:
NullPointerException——程序试图访问一个空的数组中的元素或访问空的对象中的 方法或变量时产生异常;
IndexOutOfBoundsExcention——由于数组下标越界或字符串访问越界引起异常;
ArithmeticException——算术异常,比如除了0;
NumberFormatException——数字格式化异常,比如试图将一个String转换为指定的数字类型
ClassCastException——强制类型转换时可能发生
OutofMemoryException——内存溢出;
NoSuchMethodException——未找到调用的方法
4、throw和throws区别
throw在程序内抛出,这种异常一般不是系统需要抛出的,而是某些数据不符合我们业务需求,我们自己主动抛出的,它抛出的是一个实例。
throws用在方法上声明,是系统自动抛出的异常。
5、异常中的return或者throw
当在try或者catch中执行到了return或者throw语句,它会先跳出去执行finally,执行完finally后再回来执行try块或者catch块的rerurn或者throw语句
那么如果finally中也出现了return/throw,则执行完finally后不再回去,直接结束(尽量避免finally出现return或throw)
public int test2() {
int i = 1;
try {
System.out.println("try语句块中");//1
return 1;//会先去执行finally,执行完再回来
} finally {
System.out.println("finally语句块中");//2
return 2;//由于finally里面也有return,故不再回去try块,最终返回2
}
}
try语句块中
finally语句块中