一、什么是面向对象,面向对象和面向过程的区别
面向对象是一种基于面向过程的编程思想,面向过程是依次执行一个个具有一定功能的函数来解决问题,而面向对象是把数据和操作数据的方法封装成一个对象,由对象自己去解决问题。面向对象有继承和多态而面向过程没有。
二、面向对象的三大特性
封装:把数据和操作数据的方法封装起来,对数据的访问只能已定义的接口。
继承:从已有类得到类信息创建新类。
多态:对象不同的实现有不同的表现,多态可以分为运行时多态(方法重写)和编译时多态(方法重载),其中运行时多态需要做到两件 事,子类继承父类并重写父类方法,父类型引用子类型。
三、Java 程序是如何执行的
- 把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。
- 把 class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM;
- Java 虚拟机使用类加载器(Class Loader)装载 class 文件;
- 类加载完成之后,会进行字节码校验;
- 字节码校验通过之后 JVM 解释器会把字节码翻译成机器码交由操作系统执行;
- 但不是所有代码都是解释执行的,JVM 对此做了优化,比如,以 Hotspot 虚拟机来说,它本身提供了 JIT(Just In Time)也就是我们通常所说的动态编译器,它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行。
基于采样的热点判定
主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种判定方式的优点是实现简单;缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
Java 源代码(.java)-> 编译 -> Java 字节码(.class) ->通过 JVM(Java 虚拟机)把字节码解释成机器码交由操作系统执行。每种类型的服务器都会运行一个 JVM,Java 程序只需要生成 JVM 可以执行的代码即可,JVM 底层屏蔽了不同服务器类型之间的差异,从而可以在不同类型的服务器上运行一套 Java 程序。
四、JDK、JRE、JVM 有哪些区别?
JDK(Java Development Kit)Java 开发工具包,它包括:java开发工具(编译器)、Java 运行环境(JRE,Java Runtime Environment)、基础类库。
JRE:Java Runtime Environment(Java 运行环境)的简称,包括jvm和核心类库;
JVM:Java Virtual Machine(Java虚拟机)的简称,解释并执行字节码文件。
五、重载和重写的区别?
重载是编译时多态,方法名相同,参数列表必须不同,返回值可同可不同。
重写是运行时多态,方法名,参数列表,返回值必须和父类一致。
六、是否可以重写private和static 方法?
不能!,子类不能访问父类私有的方法,即父类的私有方法对子类来说是不可见的,所以在子类定义一个和父类一模一样的私有方法,jvm只会认为这是子类特有的方法,故不能private方法不能重写。
static方法也不能被重写,当子类写了一个和父类一模一样的静态方法,堆区中的实例对象是没有静态方法的引用的,因为静态方法是通过,l类名.方法名()来调用的,即使是通过对象.方法名来调用静态方法,底层也会改成类名.方法名。所以调用的是父类还是子类的静态方法有栈中的引用变量来决定,比如下面例题的subToSuper是父类就调用父类的静态方法,所以重写静态方法没有意义。
补充
创建子类时,会先创建父类再在父类的外围创建子类对象把父类没有的属性补充上去,方法和静态方法都存在方法区,对象会有指向方法的引用
面试题:
七、创建对象的4种方式详情?
new
clone
newInstance
反序列化
拓展:拷贝分为深度拷贝和浅拷贝,区别在于浅拷贝拷贝一个对象时如果对象有引用类型的成员变量,该成员变量依然指向原来的堆内存,深拷贝需要重写clone方法,把成员变量对应的对象拷贝一份。
八、接口和抽象类的区别?
1、定义关键字不同
接口使用关键字interface来定义。抽象类使用关键字abstract来定义。
2、继承和实现的关键字不同
接口的实现类使用implements来实现接口,抽象类的子类使用extends继承抽象类。
3、子类的扩展数量不同
一个类可以实现多个接口,但只能继承一个父类。
4、构造方法不同
接口不能有构造方法,抽象类可以有构造方法。
5、属性访问控制符不同
接口的属性访问控制符只能是public static final,抽象类没有限制.
6、方法控制符不同
接口中的普通方法默认是public abstract,且只能是public (jdk1.8后可以有default方法和static方法),抽象类中抽象方法不能是private,其他没限制。
7、方法实现不同
接口的抽象方法不能有实现,default和static方法必须有实现。抽象类普通方法必须有实现,抽象方法不能有实现。
8、静态代码块使用不同
接口中不能有静态代码块,抽象类中可以有。
JDK8中的改变:
1、在 JDK1.8中,允许在接口中包含带有具体实现的方法,使用 default 修饰,这类方法就是默认方法。
2、抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能包含静态方法,JDK1.8 以后可以包含。之前不能包含是因为,接口不可以实现方法,只可以定义方法,所以不能使用静态方法(因为静态方法必须实现)。现在可以包含了,只能直接用接口调用静态方法。JDK1.8 仍然不可以包含静态代码块
拓展:
1、抽象类和接口都不能直接实例化。
2、抽象类的变量是普通的变量,接口中的变量是公共的静态的常量。
3、抽象类可以有方法的声明也可以有方法的实现,接口只能有方法的声明。
4、抽象类可以没有抽象方法,但有抽象方法的只能是抽象类。
5、普通子类要实现抽象类的所有抽象方法,抽象的子类不必如此,接口的实现类要实现接口的全部方法,抽象的实现类除外。
6、java8后接口引入默认方法和静态方法。
九、Java修饰符
Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:
访问控制修饰符
关于public修饰符的一道题
有一段java源程序,他的类名是A1,那么可以保存他的源文件可以是?
首先源文件肯定要.java结尾,如果如果该类由public修饰则源文件名必须与类名一致即A1.java,如果没有public则xxx.java,xxx没有限制。一个源文件中可以有多个类,但是只能有一个public类。
protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)
接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。protected不能修饰类(内部类除外)
接口及接口的成员变量和成员方法不能声明为 protected
访问控制和继承
子类的访问控制符要大于或等于父类的访问控制符
十、static 修饰符
这里简单说一下对象的创建过程。
1、父静-》子静-》父成-》父构-》子成-》子构
类加载
类加载2推荐、
类加载
被static修饰的成员变量,成员方法,被类的所有实例对象所共享,在类加载的时候就已经分配空间并初始化。
静态环境中不能直接访问非静态内容,但是可以通过对象访问。
十一、break,continue,return的区别及作用
1、break 结束循环
2、continue 结束本次循环
3、return 程序返回
十二、自动装箱与拆箱详情
1、什么是自动装箱与拆箱?
自动装箱——》Integer a=100;//基本类型赋值给包装类
自动拆箱——》int b=a; //包装类赋值给基本类型
Integer a=100;
反编译后:
Integer a=Integer.valueof(100);
int b=a;
反编译后:
int b=a.intValue();
2、什么时候会触发自动装箱和自动拆箱呢?
触发自动装箱:
把基本数据类型赋值给包装类
调用equal方法时如果参数是基本数据类型也会触发自动装箱
触发自动拆箱:
包装类和基本数据类型出现在==,+,-,*,/等运算符两边时会触发拆箱
面试题:
解释:当包装类和基本数据类型位于==号两边时会触发自动拆箱,只需要保证值相等就会返回true。
当调用equal时会将参数装箱成包装类,然后判断num3和参数的类型是否一致,否返回false,是,再比较值是否相等。
2.判断下面程序片段输出的结果:
Integer num1 = 200;
Integer num2 = 200;
System.out.println(num1==num2);
Integer num3 = 100;
Integer num4 = 100;
System.out.println(num3==num4);
输出结果:false,true
解释:只有==两边分别为包装类和基本类型才会触发自动拆箱,这里两边均为包装类比较的是地址,200超过127,新建对象,100<127,不创建对象。
3、什么时候会创建包装类对象?
包装类的缓存值
boolean:true和false
byte:-128~127
char:0~127
short:-128~127
int:-128~127
long:-128~127
小于-128,大于127时才会创建对象
十三、包装类与字符串类的相互转换
// 数字字符串转 int 类型
String ss="123";
1. int ii = Integer.parseInt(ss);
// int 类型转数字字符串
1. String str = Integer.toString(ii);
十四、Java中Int与Integer的区别?
1、int 是基本类型,直接存数值;而integer引用数据类型。
2、Int的声明不需要实例化,且变量声明后的初始值为0;Integer的是一个类,初始值为null,需要进行实例化,才能对变量数据进行处理。
十五、this 和 super的异同
1、this 是一个指向当前对象的引用,即调用方法的那个对象,保存了当前对象的地址。而super只是一个关键字,代表着子类继承父类的那一部分(没有继承父类构造方法但是可以调用)。
2、相同点:都不能在静态环境下使用,因为this和super是非静态的,都依赖具体的对象,静态环境中没有具体对象,所以静态环境中不能有非静态内容,除非在静态环境中创建对象。而且类加载的时候会先执行静态环境的内容,而这时候非静态的内容还不存在所以会出错。
3、this()和super()均需要放在第一行,所以this()和·super不能同时存在。
十六、面向对象五大基本原则是什么
1、单一职责原则:类的功能要单一,不能包罗万象。
2、开放封闭原则:对拓展开放,对修改关闭。
3、里氏替换原则:子类可以代替父类出现在父类能够出现的任何地方。比如你能代替你爸爸去姥姥家干活。
4、依赖倒置原则:高层次的模块不应该依赖低层次模块。
5、接口分离原则:采用多个与客户类有关的接口比使用一个通用接口好。
十九、对象的创建方式
详细
对象的创建方式分为显式创建与隐式创建。
显式创建:
1、new关键字创建对象
2、反射创建对象
3、克隆clone
4、java.io.ObjectlnputStream 对象的 readObject() 方法
二十三、静态变量与实例变量的区别
1、静态变量由所有类实例对象共享,实例变量属于某个具体的实例对象
2、静态变量在内存中只有一个副本,实例变量在每个对象中都有一个副本。
3、静态变量当且仅当在类加载时被初始化,实例变量在创建对象时初始化。
二十四、switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?
在 switch(expr 1) 中,expr1 只能是一个整数表达式或者枚举常量。而整数表达式可以是 int 基本数据类型或者 Integer 包装类型。由于,byte、short、char 都可以隐式转换为 int,所以,这些类型以及这些类型的包装类型也都是可以的。而 long 和 String 类型都不符合 switch 的语法规定,并且不能被隐式的转换为 int 类型,所以,它们不能作用于 switch 语句中。不过,需要注意的是在 JDK1.7 版本之后 switch 就可以作用在 String 上了。
二十五、字节和字符的区别?
字节是存储容量的基本单位;
字符是数字、字母、汉字以及其他语言的各种符号;
1 字节 = 8 个二进制单位,一个字符由一个字节或多个字节的二进制单位组成。
二十四、String,StringBuffer和StringBuilder详细
1、String 字符串常量,每次对他进行修改都会先去字符串常量池中找,有则引用,没有就创建一个新对象。
2、StringBuffer 字符串可变序列,线程安全。
3、StringBuilder 字符串可变序列,线程不安全。
1、String 为什么要设计为不可变类?
String对象不可变保证了hashcode的唯一性,可以对hashcode进行缓存,不必每次都去计算新的哈希码。
也可以避免各种安全隐患。
2、String、StringBuilder、StringBuffer 的区别?
String:用于字符串操作,属于不可变类;【补充:String 不是基本数据类型,是引用类型,底层用 char 数组实现的】
StringBuilder:与 StringBuffer 类似,都是字符串缓冲区,但线程不安全;
StringBuffer:也用于字符串操作,不同之处是 StringBuffer 属于可变类,对方法加了同步锁,线程安全。
StringBuffer的补充:
说明:StringBuffer 中并不是所有方法都使用了 Synchronized 修饰来实现同步:
@Override
public StringBuffer insert(int dstOffset, CharSequence s) {
// Note, synchronization achieved via invocations of other StringBuffer methods
// after narrowing of s to specific type
// Ditto for toStringCache clearing
super.insert(dstOffset, s);
return this;
}
3、执行效率
执行效率:StringBuilder > StringBuffer > String
4、String 字符串修改实现的原理?
当用 String 类型来对字符串进行修改时,其实现方法是首先创建一个 StringBuffer,其次调用 StringBuffer 的 append() 方法,最后调用 StringBuffer 的 toString() 方法生成String对象,并把String对象的地址返回。
5、String str = “i” 与 String str = new String(“i”) 一样吗?
不一样,因为内存的分配方式不一样。String str = “i” 的方式,Java 虚拟机会将会把字符串i存到常量池中,并在堆内存中生成一个对应的拘留对象,并使常量池中的i字符串指向拘留对象,然后把拘留对象地址返回给str;而 String str = new String(“i”) 则会创建一个String对象,并使String对象指向i的拘留对象。
public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str3 == str4); // false
System.out.println(str3.equals(str4)); // true
}
}
在执行 String str1 = “abc” 的时候,JVM 会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串常量池中的内存地址;如果该字符串还不存在字符串常量池中,那么就会在字符串常量池中创建该字符串对象,然后再返回。所以在执行 String str2 = “abc” 的时候,因为字符串常量池中已经存在“abc”字符串对象了,就不会在字符串常量池中再次创建了,所以栈内存中 str1 和 str2 的内存地址都是指向 “abc” 在字符串常量池中的位置,所以 str1 = str2 的运行结果为 true。
而在执行 String str3 = new String(“abc”) 的时候,JVM 会首先检查字符串常量池中是否已经存在“abc”字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建 “abc” 字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的 “abc” 字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址,即栈内存中存储的地址是堆内存中对象的内存地址。String str4 = new String(“abc”) 是在堆内存中又创建了一个对象,所以 str 3 == str4 运行的结果是 false。str1、str2、str3、str4 在内存中的存储状况如下图所示:
6、String 类的常用方法都有那些?
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换,并返回新字符串。
trim():去除字符串两端空白。
split():分割字符串(分割的字符已经被删掉),返回一个分割后的String数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
String sa="abcd";
String a="ab";
String b="cd";
String sa1=a+b;
System.out.println(sa.equals(sa1));
7、final 修饰 StringBuffer 后还可以 append 吗?
可以。final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。
二十六、Object 的常用方法有哪些?
clone 方法:用于创建并返回当前对象的一份拷贝;
getClass 方法:用于返回当前运行时对象的 Class;
toString 方法:返回对象的字符串表示形式;
finalize 方法:实例被垃圾回收器回收时触发的方法;
equals 方法:用于比较两个对象的内存地址是否相等,一般需要重写;
hashCode 方法:用于返回对象的哈希值;
notify 方法:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒一个。
notifyAll 方法:作用跟 notify() 一样,只不过会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
wait 方法:让当前对象等待;
二十七、final、finally、finalize 的区别?
final:被final修饰的类不能被继承,方法不能被重写,变量不能被修改。
finally:与cry/catch语句一起使用,当程序发生异常或错误时执行finally里的语句。
finalize:垃圾回收时jvm会自动调用被回收对象的finalize方法。
27、finally 是不是一定会被执行到?
不一定。下面列举两种执行不到的情况:
(1)当程序进入 try 块之前就出现异常时,会直接结束,不会执行 finally 块中的代码;
(2)当程序在 try 块中强制退出时也不会去执行 finally 块中的代码,比如在 try 块中执行 exit 方法。
28、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会。程序在执行到 return 时会首先将返回值存储在一个指定的位置,其次去执行 finally 块,最后再返回。因此,对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉;而对引用类型是有影响的,返回的是在 finally 对 前面 return 语句返回对象的修改值。
29、try-catch-finally 中那个部分可以省略?
catch 可以省略。try 只适合处理运行时异常,try+catch 适合处理运行时异常+普通异常。也就是说,如果要处理普通异常就需要try+catch,如果只需要处理运行时异常可以不加catch。
30、static关键字的作用?
可以用来修饰静态变量,静态方法,静态代码块,静态内部类。
静态变量只属于类不属于具体实例,静态方法在类加载时已经初始化,只能访问类的静态属性和静态方法,非静态内部类依赖外部类的具体实例,静态内部类不依赖具体实例。
31、super 关键字的作用?
(1)访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
(2)访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
(3)this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
32、transient 关键字的作用?
对于不希望被序列化的变量可以用transient来修饰。
transient只能修饰变量不能修饰类和方法。
33、== 和 equals 的区别?
==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
equals方法不能用于比较基本数据类型的变量,只能用于比较引用类型的变量。如果equals没有重写则比较的是引用的地址,如果重写了则比较的是值(很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等)。
35、为什么重写 equals() 就一定要重写 hashCode() 方法?
如果用不到 HashMap、HashSet 等 Java 集合,用不到哈希表的话,可以仅仅重写 equals() 方法。而工作中的场景是常常用到 Java 集合,所以 Java 官方建议重写 equals() 就一定要重写 hashCode() 方法。
对于对象集合的判重,如果一个集合含有 10000 个对象实例,仅仅使用 equals() 方法的话,那么对于一个对象判重就需要比较 10000 次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的 hashCode 不相同,也不再需要调用 equals() 方法,从而大大减少了 equals() 比较次数。
所以从程序实现原理上来讲的话,既需要 equals() 方法,也需要 hashCode() 方法。那么既然重写了 equals(),那么也要重写 hashCode() 方法,以保证两者之间的配合关系。
36、ashCode()与equals()的相关规定:
1、如果两个对象相等,则 hashCode 一定也是相同的;
2、两个对象相等,对两个对象分别调用 equals 方法都返回 true;
3、两个对象有相同的 hashCode 值,它们也不一定是相等的;
4、因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖;
5、hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
37、& 和 && 的区别?
&&:短路与,当第一个表达式的值为 false 的时候,则不再计算第二个表达式;
&:1、逻辑运算发 2、位运算符
不管第一个表达式结果是否为 true,第二个都会执行。除此之外,& 还可以用作位运算符:当 & 两边的表达式不是 Boolean 类型的时候,& 表示按位操作。
38、Java 中的 Math.round(-1.5) 等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。
39、两个二进制数的异或结果是什么?
两个二进制数异或结果是这两个二进制数差的绝对值。表达式如下:a^b = |a-b|。无进位相加。
两个二进制 a 与 b 异或,即 a 和 b 两个数按位进行运算。如果对应的位相同,则为 0(相当于对应的算术相减),如果不同即为 1(相当于对应的算术相加)。由于二进制每个位只有两种状态,要么是 0,要么是 1,则按位异或操作可表达为按位相减取值相对值,再按位累加。
40、Error 和 Exception 的区别?
Error 类和 Exception 类的父类都是 Throwable 类。主要区别如下:
Error 类: 一般是指与虚拟机相关的问题,如:系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出等。这类错误将会导致应用程序中断,仅靠程序本身无法恢复和预防;
Exception 类:分为运行时异常和受检查的异常。
41、运行时异常与受检异常有何异同?
运行时异常:如:空指针异常、指定的类找不到、数组越界、方法传递参数错误、数据类型转换错误。可以编译通过,但是一运行就停止了,程序不会自己处理;
受检查异常:要么用 try … catch… 捕获,要么用 throws 声明抛出,交给父类处理。
42、throw 和 throws 的区别?
(1)throw:在方法体内部,表示抛出异常,由方法体内部的语句处理;throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例;
(2)throws:在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理;表示出现异常的可能性,并不一定会发生这种异常。
43、常见的异常类有哪些?
NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
SQLException:提供关于数据库访问错误或其他错误信息的异常。
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
IOException:当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
45、Java 的泛型是如何工作的 ? 什么是类型擦除 ?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如:List 在运行时仅用一个 List 来表示。这样做的目的,是确保能和 Java 5 之前的版本开发二进制类库进行兼容。
类型擦除:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 则会被转译成普通的 Object 类型,如果指定了上限如 则类型参数就被替换成类型上限。
补充:
List list = new ArrayList();
1、两个 String 其实只有第一个起作用,后面一个没什么卵用,只不过 JDK7 才开始支持 Listlist = new ArrayList<> 这种写法。
2、第一个 String 就是告诉编译器,List 中存储的是 String 对象,也就是起类型检查的作用,之后编译器会擦除泛型占位符,以保证兼容以前的代码。
46、什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T> 它通过确保类型必须是 T 的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是 T 的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面 <?> 表示了非限定通配符,因为 <?> 可以用任意类型来替代。
47、List<? extends T> 和 List <? super T> 之间有什么区别 ?
这两个 List 的声明都是限定通配符的例子,List<? extends T> 可以接受任何继承自 T 的类型的 List,而List <? super T> 可以接受任何 T 的父类构成的 List。例如 List<? extends Number> 可以接受 List 或 List。
补充:
Array 不支持泛型,要用 List 代替 Array,因为 List 可以提供编译器的类型安全保证,而 Array却不能。
48、如何边遍历边移除 Collection 中的元素?集合
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
一种最常见的错误代码如下:
for(Integer i : list){
list.remove(i)
}
49、Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
50、如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。
// list to array
List<String> list = new ArrayList<String>();
list.add("123");
list.add("456");
list.toArray();
// array to list
String[] array = new String[]{"123","456"};
Arrays.asList(array);
51、ArrayList 和 LinkedList 的区别是什么?
52、ArrayList 和 Vector 的区别是什么?
ArrayList是线程不安全的,Vector是线程安全的。
ArrayList扩容是增加0.5倍,Vector是增加1倍。
53、多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
54、为什么 ArrayList 的 elementData 加上 transient 修饰?
transient表示不序列化属性。用transient修饰ArrayList的elementData可以遍历elementData只序列化已经存进集合里的元素,加快序列化速度和减小文件大小。
private transient Object[] elementData;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
*// Write out element count, and any hidden stuff*
int expectedModCount = modCount;
s.defaultWriteObject();
*// Write out array length*
s.writeInt(elementData.length);
*// Write out all elements in the proper order.*
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。
55、List 和 Set 的区别?
List , Set 都是继承自Collection 接口
List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
Set和List对比
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
56、说一下 HashSet 的实现原理?
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
57、Hashset如何检查重复?HashSet是如何保证数据不可重复的?
向Hashset中add()元素时,会检查重复。add方法底层是调用Hashmap的put方法,会比较hashcode,再调用equals方法。
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
return map.put(e, PRESENT)==null;
}
58、HashSet与HashMap的区别?
59、在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
Queue<String> queue = new LinkedList<String>();
queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());
60、HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现
JDK1.7采用的是数组+链表,JDK1.8采用的是数组+链表/红黑树。若遇到哈希冲突,节点数小于8将冲突值挂到链表中,大于8且小于64转成红黑树。
JDK1.7
JDK1.8
JDK1.7与1.8的区别?
resize 扩容优化
引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
二十五、ArrayList和LinkedList的区别详细
ArrayList的底层是数组,查询和向数组尾部添加元素效率比较高,向中间添加元素和删除元素效率较低。
LinkedList的底层是链表,删除和增加元素效率较高,查询效率较低。
二十六、类的实例化顺序
1、父类静态代变量或父类静态代码块
2、子类静态变量或子类静态代码块
3、父类非静态变量(父类实例成员变量)
4、父类构造函数
5、子类非静态变量(子类实例成员变量)、
6、子类构造函数。
二十七、类加载详细
父亲委托机制
二十八、集合
Java String 类
String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上:
String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob"); // String 对象创建
String s5 = new String("Runoob"); // String 对象创建
Java StringBuffer 和 StringBuilder 类
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
Java 数组
1. dataType[ ] arrayRefVar = new dataType[arraySize];
2. dataType arrayRefVar[ ] = new dataType[arraySize];
3. dataType[ ] arrayRefVar = {value0, value1, ..., valuek};
4. dataType[ ] arrayRefVar = new int[ ]{3, 1, 2, 6, 4, 2}
SimpleDateFormat 格式化日期
SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类
import java.util.*;
import java.text.*;
public class DateDemo {
public static void main(String args[]) {
Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(dNow));
}
}
注意:有的格式大写,有的格式小写,例如 MM 是月份,mm 是分;HH 是 24 小时制,而 hh 是 12 小时制。
以上实例编译运行结果如下:
当前时间为: 2018-09-06 10:16:34
字符串转时间
import java.util.*;
import java.text.*;
public class DateDemo {
public static void main(String args[]) {
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd");
String input = args.length == 0 ? "1818-11-11" : args[0];
System.out.print(input + " Parses as ");
Date t;
try {
t = ft.parse(input);
System.out.println(t);
} catch (ParseException e) {
System.out.println("Unparseable using " + ft);
}
}
}
Date
java.util 包提供了 Date 类来封装当前的日期和时间。
public class DateDemo
{
public static void main(String args[]) {
//格式化日期的类
SimpleDateFormat ft = new SimpleDateFormat ("yyyy/MM/dd hh:mm:ss");
//日期对象,包含当前时间,
Date tn = new Date();
System.out.println(ft.format(tn));
}
}
日期比较
Java使用以下三种方法来比较两个日期:
使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
使用方法 before(),after() 和 equals()。例如,一个月的12号比18号早,则 new Date(99, 2, 12).before(new Date (99, 2, 18)) 返回true。
使用 compareTo() 方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。
正则表达式
正则表达式是由一些具有特殊含义的字符组成的字符串,多用于查找、替换符合规则的字符串。在表单验证、Url映射等处都会经常用到。
详情【正则表达式详解】
finalize方法
垃圾回收器回收对象时先调用finalize方法
垃圾回收器(Garbage Collection)也叫GC,垃圾回收器主要有一下特点:
当对象不再被程序所使用的时候,垃圾回收器将会将其回收
垃圾回收是在后台运行的,我们无法命令垃圾回收器马上回收资源,但是我们可以告诉他可以尽快回收资源(System.gc()和Runtime.getRuntime().gc())
垃圾回收器在回收某个对象的时候,首先会调用该对象的finalize()方法
GC主要针对堆内存
finalize()是Object里面的一个方法,当一个堆空间中的对象没有被栈空间变量指向的时候,这个对象会等待被java回收:jdk里面是这样实现的:
protected void finalize() throws Throwable { }
}
流(Stream):是一个抽象概念,是一连串的数据(字符或字节,一个字符等于两个字节),是以先进先出的方式发送信息的通道。
流的特点:
1. 先进先出:最先写入输出流的数据最先被输入流读取到。
2. 顺序存取:可以一个接一个的往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间数据。
3. 只读或只写。
public class DateDemo
{
public static void main(String args[]) {
//格式化日期的类
SimpleDateFormat ft = new SimpleDateFormat ("yyyy/MM/dd hh:mm:ss");
//日期对象,包含当前时间,
Date tn = new Date();
System.out.println(ft.format(tn));
//字节流
FileInputStream fileInputStream;
FileOutputStream fileOutputStream;
//字符流
InputStreamReader InputStreamReader;
OutputStreamWriter outputStreamWriter;
//缓冲流
//字节缓冲流
BufferedInputStream bufferedInputStream;
BufferedOutputStream bufferedOutputStream;
//字符缓冲流
BufferedReader bufferedReader;
BufferedWriter bufferedWriter;
//字符流便捷类
try {
int length;
char[] chars=new char[1024];
StringBuffer sb=new StringBuffer();
FileReader fileReader=new FileReader("D://Jaava笔记//spring//笔记及讲义//spring课堂笔记.txt");
while ((length=fileReader.read(chars))!=-1){
sb.append(chars,0,length);
System.out.println(sb.toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
Scanner
java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。
next() 与 nextLine() 区别
next():
1、一定要读取到有效字符后才可以结束输入。
2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
next() 不能得到带有空格的字符串。
nextLine():
1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
2、可以获得空白。
package com.cailibin;
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String str ="abcdefgijk";
String str2=null;
while (scanner.hasNextLine()){
str2=scanner.next();
System.out.println("输出的数据时"+str2);
}
}
}
next()以有效字符后的空白结尾,nextLine()以回车结尾
Java 异常处理
自定义异常
public class MyException extends Exception {
public MyException(){
System.out.println("自定义异常");
}
}
public class ScannerDemo {
public static void main(String[] args) throws MyException{
Scanner scanner = new Scanner(System.in);
String str ="abcdefgijk";
String str2=null;
while (scanner.hasNextLine()){
str2=scanner.next();
System.out.println("输出的数据时"+str2);
throw new MyException();
}
}
}
继承
为什么需要继承?继承解决了什么问题?
继承可以解决了两段或多段功能相似的代码的冗余问题。即将多段代码中相同的部分提取出来组成一个父类。
方法的重写规则
1. 参数列表与被重写方法的参数列表必须完全相同。
2. 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
3. 访问权限不能比父类中被重写的方法的访问权限更低。
4. 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
![方法重载与重写](https://img-blog.csdnimg.cn/5c9cb78bd2d24073a5420d2ffe11f387.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg5NDgxNg==,size_16,color_FFFFFF,t_70#pic_center)
更正上图,java7后重写的返回类型可以不同但必须是父类返回值的派生类
多态
多态就是同一个事件发生不同的对象上会产生不同的结果。
多态的优点:
1. 消除类型之间的耦合关系
2. 接口性
多态存在的三个必要条件
1. 继承
2. 重写
3. 父类引用指向子类对象
多态的好处
- 应用程序不必为每个派生类编写功能调用,只需要对抽象基类进行处理就行。大大提高了程序的复用性。
- 派生类的功能可以被基类方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和维护性。
多态在什么地方用
可以用在方法的参数中和方法的返回类型中
接口与抽象类的区别
- 接口没有构造方法,抽象类有构造方法。
- Java 8 后接口可以有成员变量但必须用public static final关键字修饰,抽象类没有这个要求。
- 接口的方法不能被实现,抽象类可以。java 8后接口中的方法可以被实现但要用default修饰。
- 接口不可以有静态方法,抽象类可以。Java 8后接口可以有静态方法。
Java 枚举(enum)
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
//枚举的使用
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) {
System.out.println(myVar);
}
}
}
values(), ordinal() 和 valueOf() 方法
- values() 返回枚举类中所有的值。
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
- valueOf()方法返回指定字符串值的枚举常量。
public enum Color {
RESD,GREEN,BLUE;
}
@Test
public void test12(){
for (Color myVar : Color.values()) {
System.out.println(myVar);
System.out.println("index"+myVar.ordinal());
System.out.println(Color.valueOf("RESD"));
}
}
ArrayList的使用方法
ArrayList空间效率低但查询速度快。自动扩容会降低ArrayList的存储效率。
序列化
序列化和反序列化详解
Java 网络编程
多线程
自动装箱与自动拆箱详细