1、面向对象的三个基本特征是:封装、继承、多态
2、final 、finally、finalize 的区别
简单区别
- final:用于申明变量、方法以及类,分别表示属性不可交变,方法不可覆盖,类不可继承。
- finally:是异常处理语句结构的一部分,表示总是执行。
- finalize:是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
中等区别:
- final:java中的关键字,修饰符。
- 如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类的和final的类。
- 如果将变量或者方法声明为final,可以保证它们在使用中不被改变.
- 被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
- 被声明final的方法只能使用,不能重载。
- finally:java的一种异常处理机制。
- finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
- finalize:Java中的一个方法名。
- Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。它是在Object类中定义的,因此所的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
详细区别:
- final:
- 定义变量,包括静态的和非静态的。
- 定义方法的参数。
- 定义方法。
- 定义类。
- finally:
- finalize:
3、Exception、Error、运行时异常与一般异常有何异同
- 在java中,异常对象都是派生于Throwable类的一个实例。如果java内置的异常类不能够满足需求,用户还可以创建自己的异常类。可以看出,所有的异常都是由Throwable类,下一层分解为两个分支:Error和Exceprion。
Error层次结构描述了java运行时系统的内部错误和资源耗尽错误。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。应用程序不应该抛出这种类型的对象。
Exceprion这个层次结构又分解为连个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有没有问题,但由于像I/O错误这类异常导致的异常属于其他异常。
4、请写出5种常见到的runtime exception
- IndexOutOfBoundsException(下标越界异常)
- NullPointerException(空指针异常)
- NumberFormatException(String转换为指定的数字类型异常)
- ArithmeticException(算术运算异常 如除数为0)
- ArrayStoreException(向数组中存放与声明类型不兼容对象异常)
- SecurityException(安全异常)
- IOException(其他异常)
- FileNotFoundException(文件未找到异常)
- IOException(操作输入流和输出流时可能出现的异常)
- EOFException(文件已结束异常)
5、int 和 Integer 有什么区别,Integer的值缓存范围
基本比较:
- Integer是int的包装类;int是基本数据类型。
- Integer变量必须实例化后才能使用;int变量不需要。
- Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值。
- Integer的默认值是null;int的默认值是0。
深入比较:
- 由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false
- Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true
- 非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false
- 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
- java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100)。而java API中对Integer类型的valueOf的定义如下,对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
6、包装类,装箱和拆箱
- 在进行类型转换的范畴内,有一种特殊的转换,需要将int这样的基本数据类型转换为对象;所有基本类型都有一个与之对应的类,即包装类(wrapper)。也是基本包装类
基本数据类型 | 对应的包装类 | 包装类的父类 |
byte | java.lang.Byte | java.lang.Number |
short | java.lang.Short | java.lang.Number |
int | java.lang.Integer | java.lang.Number |
long | java.lang.Long | java.lang.Number |
char | java.lang.Charatcer | java.lang.Object |
float | java.lang.Float | java.lang.Number |
double | java.lang.Double | java.lang.Number |
boolean | java.lang.Boolean | java.lang.Object |
- 将基本数据类型变成包装类称为装箱。
- 将包装类变为基本数据类型称为拆箱。
//Integer与int转换
public class WrapperDemo{
public static void main(String args[]){
int a = 40 ; // 基本数据类型
Integer b = new Integer(a) ; // 装箱:将基本数据类型变为包装类
int temp = b.intValue() ; // 拆箱:将包装类变为基本数据类型
}
};
//Float与float转换
public class WrapperDemo{
public static void main(String args[]){
float a = 40.1f ; // 基本数据类型
Float b = new Float(a) ; // 装箱:将基本数据类型变为包装类
float c = b.floatValue() ; // 拆箱:将包装类变为基本数据类型
}
};
- 在JDK1.5之前,对于程序本身来说,包装类不能直接进行“+、-、*、/、++、--”等操作,因为是一个类,所以不能这么操作。
- 在JDK1.5之后,对包装类功能进行了改变,增加了自动装箱和自动拆箱功能,而且,可以使用包装类直接进行数字运行。
- 自动装箱和自动拆箱也就是,可以自动由int-->Integer类型转变,而自动拆箱就是自动由Integer-->int转变。
public class WrapperDemo{
public static void main(String args[]){
Integer a = 40 ; // 自动装箱成Integer
Float b = 40.4f ; // 自动装箱成Float
int c = a ; // 自动拆箱为int
float d = b ; // 自动拆箱为float
}
};
//字符串变为指定的数据类型
public class WrapperDemo{
public static void main(String args[]){
String str1 = "40" ; // 由数字组成的字符串
String str2 = "30.3" ; // 由数字组成的字符串
int x = Integer.parseInt(str1) ; // 将字符串变为int型
float f = Float.parseFloat(str2) ; // 将字符串变为int型
System.out.println("整数乘方:" + x + " * " + x + " = " + (x * x)) ;
System.out.println("小数乘方:" + f + " * " + f + " = " + (f * f)) ;
}
};
运行结果:
整数乘方:40 * 40 = 1600
小数乘方:30.4 * 30.4 = 918.08999
- Java中有8种基本数据类型的包装类,可以将基本数据类型以类的形式操作。
- 基本数据类型变为包装类的过程,称为装箱,
- 将包装类变为基本数据类型的过程称为拆箱。
- 在JDK1.5后,提供自动装箱和自动拆箱功能。
- 使用包装类,可以将字符串实现基本数据类型的转换操作。
7、String、StringBuilder、StringBuffer
- String (JDK1.0) 不可变字符序列,被final修饰
- StringBuffer (JDK1.0) 线程安全的可变字符序列,成员方法前面多了一个关键字synchronized,其实没什么用
- StringBuilder(JDK1.5) 非线程安全可变字符序列,速度快
- 字符串广泛应用 在Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
- 对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
- 当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
- 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
//String str="hello world"和String str=new String("hello world")的区别
public class TestString{
public static void main(String[] args) {
String str1 = "hello world";
String str2 = new String("hello world");
String str3 = "hello world";
String str4 = new String("hello world");
System.out.println("判断一:"+str1==str2);
System.out.println("判断二:"+str1==str3);
System.out.println("判断三:"+str2==str4);
}
}
运行结果:
判断一:false
判断二:true
判断三:false
- String str1 = "hello world";和String str3 = "hello world"; 都在编译期间生成了 字面常量和符号引用,运行期间字面常量"hello world"被存储在运行时常量池(当然只保存了一份)。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。
- 通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。
//常见笔试题
public class TestString{
public static void main(String[] args) {
System.out.println("第一种情况:"+ test001());
System.out.println("第二种情况:"+ test002());
System.out.println("第三种情况:"+ test003());
System.out.println("第四种情况:"+ test004());
test005();
}
/**
* "test"+1在编译期间就已经被优化成"test2"
* 因此在运行期间,变量a和变量b指向的是同一个对象。
*/
public static Boolean test001(){
String a = "test1";
String b = "test" + 1;
boolean flag = (a == b);
return flag;
}
/**
* 由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量
* 来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。
*/
public static Boolean test002(){
String a = "test1";
String b = "test";
String c = b + 1;
boolean flag = (a == c);
return flag;
}
/**
* 对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,
* 对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会
* 被优化成:String c = "hello" + 2
*/
public static Boolean test003(){
String a = "test1";
final String b = "test";
String c = b + 1;
boolean flag = (a == c);
return flag;
}
/**
* 这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确
* 定,因此a和c指向的不是同一个对象。
*/
public static Boolean test004(){
String a = "test1";
final String b = getHello();
String c = b + 1;
boolean flag = (a == c);
return flag;
}
/**
* 这里面涉及到的是String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA
* SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字
* 符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的
* 是同一个对象。
*/
public static void test005(){
String a = "hello";
String b = new String("hello");
String c = new String("hello");
String d = b.intern();
System.out.println(a==b);
System.out.println(b==c);
System.out.println(b==d);
System.out.println(a==d);
}
public static String getHello() {
return "test";
}
}
运行结果:
第一种情况:true
第二种情况:false
第三种情况:true
第四种情况:false
false
false
false
true
- String str = new String("abc")创建了多少个对象? ----1个
- String str = new String("abc")涉及到几个String对象? -----2个
- 这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念 该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。
/**
* 1的效率比2的效率要高
* 1中的"love"+"you"在编译期间会被优化成"loveyou",而2中的不会被优化。
* 1中只进行了一次append操作,而在2中进行了两次append操作。
*/
public class TestString{
public static void main(String[] args) {
String str1 = "I";
//str1 += "love"+"you"; //1
//str1 = str1+"love"+"you"; //2
}
}
- 小结
- 如果要操作少量的数据用 String。
- 多线程操作字符串缓冲区下操作大量数据 StringBuffer。
- 单线程操作字符串缓冲区下操作大量数据 StringBuilder。
8、重载(Overload)和重写(Override)的区别
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
重写规则
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
- 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
- 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
- 最常用的地方就是构造器的重载。
- 重载规则
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
9、抽象类和接口有什么区别
10、说说反射的用途及实现
- 在运行时构造一个类的对象;判断一个类所具有的成员变量和方法;调用一个对象的方法;生成动态代理。反射最大的应用就是框架
- Java.lang.Class和Java.lang.reflect包下的API,用于表示或者处理当前JVM中的类,接口和对象。
- Java 反射机制是一个非常强大的功能,在很多的项目比如 Spring,MyBatis 都都可以看到反射的身影。通过反射机制,我们可以在运行期间获取对象的类型信息。利用这一点我们可以实现工厂模式和代理模式等设计模式,同时也可以解决 Java 泛型擦除等令人苦恼的问题。
- 获取一个对象对应的反射类,在 Java 中有下列方法可以获取一个对象的反射类
- 通过 getClass() 方法
- 通过 Class.forName() 方法
- 使用 类.class
- 通过类加载器实现,getClassLoader()
String str = "AAAA";
Class cLass1 = str.getClass();
Class cLass2= String.class;
Class cLass3= Class.forName("java.lang.String");
if (cLass1 == cLass2) {
System.out.println("cLass1 == cLass2");
}
if (cLass2 == cLass3) {
System.out.println("cLass2 == cLass3");
}
- spring 的 ioc/di 也是反射....
- javaBean和jsp之间调用也是反射....
- struts的 FormBean 和页面之间...也是通过反射调用....
- JDBC 的 classForName()也是反射.....
- hibernate的 find(Class clazz) 也是反射....
11、说说自定义注解的场景及实现
- 注解(Annotation)是java1.5之后提供的一种语法。其主要作用是编译检查(比如@override)和代码分析(通过代码中添加注解,利用注解解析器对添加了注解的代码进行分析,获取想要的结果,一般自定义的注解都是这一种功能)。
- JDK提供的注解最常用的是3个,@Override,@Deprecated和@SuppressWarnings.都是Java自带注解
- @Override表示子类重写了父类的方法,或者实现了接口的方法。帮助开发者确认子类是否正确的覆盖了父类的方法,若父类中没有此方法,编译器即报错。但是,子类与父类有同样的方法,但子类的方法上没有@Override注解,是不会报错。
- @Deprecated用于提示开发者,标注此注解的方法已经被弃用了。请使用另外推荐的方法
- 使用toString1()方法,编译器会提示,将toString1()画一条横线,表示此方法已经过期。
public class TestClass{
@Deprecated()
public String toString1(){
return "TestClass";
}
public static void main(String[] args){
TestClass test = new TestClass();
test .toString1();
}
}
- @SuppressWarnings是抑制警告的意思。比如我们新建一个变量,但是没有用,编译器会提示此变量未使用的警告。如果在方法中,添加了@SuppressWarnings的相关注解,这个警告就不会再提示了。
public class TestClass{
@SuppressWarnings({"unused"})
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
}
}
- 自定义注解,用于帮助为相关代码打上标签,然后我们在解析注解的逻辑中就可以通过这些标签来完成相关的工作,比如,权限控制,日记记录等等。
- 来源
12、HTTP请求的GET与POST方式的区别
13、Session与Cookie区别
14、列出自己常用的JDK包
15、MVC设计思想
16、equals与==的区别
17、hashCode和equals方法的区别与联系
18、什么是Java序列化和反序列化,如何实现Java序列化?请解释Serializable 接口的作用
19、Object类中常见的方法,为什么wait notify会放在Object里边?
20、Java的平台无关性如何体现出来的
21、JDK和JRE的区别
22、Java 8有哪些新特性