Java基础知识查漏补缺
-
null和void
-
null
null是所有引用类型的默认值,但null既不是对象也不是一种类型,它仅是一种特殊的值,它可以被赋予任何引用类型,也可以将null转为任何类型。
注意:java中的自动拆装箱的过程中,任何含有null值的包装类在Java拆箱生成基本数据类型时候都会抛出一个空指针异常。值为null的引用类型变量,使用instanceof操作会返回false
-
void
void不是函数,是方法的修饰符,void的意思是该方法没有返回值,意思就是方法只会运行方法中的语句,但是不返回任何东西。
-
Void
java.lang.Void是一种类型。Void类型不可以继承与实例化。
-
-
单元测试中,Mock对象的存在破坏了面向对象中的封装
mock对象:也成为伪对象,在测试中的利用mock对象来代替真实对象,方便测试的进行。
java的封装性:指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,通过该类提供的方法实现对内部信息的操作访问。
反射机制:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
-
transient变量
java 的transient关键字的作用是需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
-
Servlet的生命周期
创建Servlet的实例是由Servlet容器来完成的,且创建Servlet实例是在初始化方法init()之前
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源
-
Java使用监视器机制实现了进程之间的同步执行。
同步的两种方式:同步块和同步方法
对于同步来说都是使用synchronized方法
每一个对象都有一个监视器,或者叫做锁。 -
序列化问题
Java在序列化时不会实例化static变量和transient修饰的变量,因为static代表类的成员,transient代表对象的临时数据,被声明这两种类型的数据成员不能被序列化
-
finally语句块执行问题
try-catch-finally块中,finally块在以下几种情况将不会执行。
(1)finally块中发生了异常。
(2)程序所在线程死亡。
(3)在前面的代码中用了System.exit();
(4)关闭了CPU
注:在catch中的return语句不影响finally的执行
-
return与finally的执行顺序对返回值的影响
(1)当仅try中含有
return
时,finally
语句块会执行,但finally对return
变量的重新赋值修改无效;(2)当
try
和finally
中均含有return
语句时,return
值以finally
语句块中的返回值为准。 -
排序算法复杂度问题(最优+最坏+平均,稳定性)
-
final关键字全面总结
1.final修饰变量,则等同于常量
2.final修饰方法中的参数,称为最终参数。
3.final修饰类,则类不能被继承
4.final修饰方法,则方法不能被重写。
5.final 不能修饰抽象类
6.final修饰的方法可以被重载 但不能被重写
7.final不能修饰接口
8.final类型的变量一定要初始化,但允许先声明,然后在构造方法或者构造代码块中初始化。
-
static关键字全面总结
1.static修饰表示静态或全局,被修饰的属性和方法可以通过类名直接访问
2.static修饰的代码块表示静态代码块,当JVM加载类时,就会执行且只执行一次
3.static修饰的属性(变量),在类加载时被创建并进行初始化,只会被创建一次
4.static修饰的变量可以重新赋值
5.static方法中不能用this和super关键字
6.static方法必须被实现,而不能是抽象的abstract
7.static方法不能被重写
8.static可以修饰类(JDK1.8)
9.访问静态方法不需要实例对象,即所引用对象即使为null,调用其静态方法也不会NPE
- final和static的异同
都可以修饰类、方法、成员变量
都不能用于修饰构造方法static可以修饰类的代码块,final不可以
final可以修饰方法内的局部变量,而static不可以在接口中,属性都是默认public static final(jdk1.8) 修饰的
不能用private修饰,final修饰的属性必须赋值,三个关键字可以省略
-
静态方法调用问题
public final class Main { public static void main(String[] args) { Main m = (Main) null; //这样子是不报错的 其实编译器底层会对null这样做处理 System.out.println(m); //输出null m.doSomething(); //正常输出 m.doSomething1(); //NPE异常 } private static void doSomething() { System.out.println("doSomething"); } private void doSomething1() { System.out.println("doSomething1"); } }
静态方法调用规则:
- 所引用对象是否为null无关紧要,因为访问静态方法不需要实例对象。
- 如果引用不为null,运行时对象类型也无关紧要,因为静态调用不会导致动态调用分派,而是与类相关。
因此,静态方法的访问,不建议用实例调用,反而让语意变得晦涩了。
-
X.equals(9)自动装箱问题
在执行equals方法前会先对9进行自动装箱,即先调用Integer.valueOf()方法,因为JVM会缓存Integer的[-128,127]范围的数,所以直接返回引用,不会创建新对象
-
byte和char在c++和Java中的区别
java中只有byte, boolean是一个字节, char是两个字节,所以127的char类型加1不会溢出
对于c/c++语言来说, char是一个字节,127的char类型加1会发生溢出
-
switch判定条件变化
在Java7之前,switch只能支持 byte、short、char、int或者其对应的封装类以及Enum类型。(因为这些类型都可经过自动拆卸、自动向上转型得到int类型)
在Java7之后,String类型得到支持,原理:(本质上还是对int类型值的匹配)
通过对case后面得String对象调用hashCode方法,得到一个int类型得hash值,然后用这个hash值来唯一标识这个case。那么当匹配时,首先调用exp的hashCode,得到exp的hash值,用这个hash值来匹配所有case,如果没有匹配成功,就说明不存在;如果匹配成功了,接着会调用字符串的equals方法进行匹配。(hash值一致,equals可不一定返回的就是true)。
所以,exp不能为null,cas子句使用的字符串也不能为null,不然会出现空指针异常。 -
socket编程中客户端和服务端操作图解
-
Java初始化过程
1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
3.其次,初始化父类的普通成员变量和代码块,在执行父类的构造方法;
4.最后,初始化子类的普通成员变量和代码块,在执行子类的构造方法。
4.最后,初始化子类的普通成员变量和代码块,在执行子类的构造方法;
-
Java运行时多态题目
执行对象实例化过程中遵循多态特性 ==> 调用的方法都是将要实例化的子类中的重写方法,只有明确调用了super.xxx关键词或者是子类中没有该方法时,才会去调用父类相同的同名方法。
-
Java静态分派
static方法不能被子类覆写,在子类中定义了和父类完全相同的static方法,则父类的static方法被隐藏,
Son.staticmethod()或new Son().staticmethod()都是调用的子类的static方法,Father.staticmethod()或者Father f = new Son(); f.staticmethod()调用的都是父类的static方法。
-
面向字符的流
-
常用集合类总结
[注]:Collection有两个子接口:List和Set,二者主要区别在于:list数据有序存放、可重复;set中数据无序存放,不可重复。
-
线程安全的集合对象
(1)Vector 同ArrayList一样使用数组实现,但同一时刻只能有一个线程写,实现同步开销较大,访问速度较ArrayList慢。
(2)HashTable
a. 无论是key还是value都不允许有null值的存在;在HashTable中调用Put方法时,如果key为null,直接抛出NullPointerException异常;
b. 遍历使用的是Enumeration列举; c.对整张hash表加锁
(3)StringBuffer
(4)ConcurrentHashMap
将Hash表分为16桶(segment),每次只对需要的桶加锁
(5)Collections的SynchronizedXXX方法
可以将指定的集合包装成线程同步的集合,如:
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
-
线程不安全的集合对象
(1)ArrayList
a. 当操作是在一列数据的后面添加数据而不是在前面或者中间,并需要随机地访问其中的元素时,使用ArrayList性能比较好。
b. ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。(2)LinkedList
a. 当对一列数据的前面或者中间执行添加或者删除操作时,并且按照顺序访问其中的元素时,要使用LinkedList。
b. LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。(3)HashMap
a. 采用数组方式存储key-value构成的Entry对象,无容量限制;
b.基于key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式去解决;
c. 在插入元素时,可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;
d. 遍历使用的是Iterator迭代器;(4)HashSet
a. 基于HashMap实现,无容量限制;
b. 是非线程安全的;
c. 不保证数据的有序;(5)TreeMap
a. 典型的基于红黑树的Map实现,因此它要求一定要有key比较的方法,要么传入Comparator比较器实现,要么key对象实现Comparator接口;
b. 是非线程安全的;(6)TreeSet
a. 基于TreeMap实现的,支持排序;
b. 是非线程安全的(7)StringBuilder
-
ArrayList和LinkedList,Vector的比较
(1)性能上
ArrayList底层数据结构是数组,适合随机查找和遍历,不适合插入和删除,线程不安全,效率高。LinkedList底层数据结构是链表, 适合数据的动态插入和删除,随机访问和遍历速度比较慢,线程不安全,效率高。Vector实现了线程安全,但开销较大,访问速度较ArrayList慢。
(2)同步性上
Vectors是可同步的,是线程安全的。ArrayList是不可同步的,不是线程安全的。所以,一般单线程推荐用ArrayList,多线程中则用Vector 。
(3)数据增长
往一个ArrayList或者Vector里插入一个元素时,如果内部数组空间不够,ArrayList或Vector会扩展它的大小。Vector在默认情况下增长一倍的大小,而ArrayList会增加50%的大小。
-
-
Java类加载器
类的加载是由类加载器完成的,类加载器包括:根加载器( BootStrap )、扩展加载器( Extension )、系统加载器( System )和用户自定义类加载器( java.lang.ClassLoader 的子类)。从 Java 2 ( JDK 1.2 )开始,类加载过程采取了父亲委托机制( PDM )。 PDM 更好的保证了 Java 平台的安全性,在该机制中, JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。 JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类加载器的说明:
- Bootstrap :一般用本地代码实现,负责加载 JVM 基础核心类库( rt.jar );
- Extension :从 java.ext.dirs 系统属性所指定的目录中加载扩展类库,它的父加载器是 Bootstrap ;
- system class loader :又叫应用类加载器,其父类是 Extension 。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中加载类,是用户自定义加载器的默认父加载器。
- 用户自定义类加载器: java.lang.ClassLoader 的子类
-
Java中的类型转换
- 自动类型转换(隐式转换)
数值型数据的转换:byte→short→int→long→float→double。
字符型转换为整型:char→int。注意:表达式中类型的自动提升,自动向上转型
但有些时候自动类型提升会发生问题,如:
byte b = 50;
b = b * 2;
//会发出类型不匹配的错误,提示无法从int转为byte这是由于,在
b*2
的时候自动进行了类型提升,转为int类型,如果要保证结果为byte则需要强转b=(byte)(b*2);
注意:char 类型比较特殊,char 自动转换成 int、long、float 和 double,但 byte 和 short 不能自动转换为 char,而且 char 也不能自动转换为 byte 或 short。
-
强制类型转换(显式转换)
TarType t = (TarType)SrcType;
-
WEB开发中的会话跟踪技术
会话跟踪是一种灵活、轻便的机制,它使Web上的状态编程变为可能。
HTTP是一种无状态协议,每当用户发出请求时,服务器就会做出响应,客户端与服务器之间的联系是离散的、非连续的。当用户在同一网站的多个页面之间转换时,根本无法确定是否是同一个客户,会话跟踪技术就可以解决这个问题。当一个客户在多个页面间切换时,服务器会保存该用户的信息。
有四种方法可以实现会话跟踪技术:URL重写、隐藏表单域、Cookie、Session。
1)隐藏表单域:,非常适合步需要大量数据存储的会话应用。
2)URL 重写:URL 可以在后面附加参数,和服务器的请求一起发送,这些参数为名字/值对。
3)Cookie:一个 Cookie 是一个小的,已命名数据元素。服务器使用 SET-Cookie 头标将它作为 HTTP
响应的一部分传送到客户端,客户端被请求保存 Cookie 值,在对同一服务器的后续请求使用一个
Cookie 头标将之返回到服务器。与其它技术比较,Cookie 的一个优点是在浏览器会话结束后,甚至
在客户端计算机重启后它仍可以保留其值
4)Session:使用 setAttribute(String str,Object obj)方法将对象捆绑到一个会话 -
JavaWeb中的监听器
三类八种(ServletContext,HttpSession,ServletRequest)
-
监听三个域对象的创建和销毁的监听器:
* ServletContextListener
* HttpSessionListener
* ServletRequestListener -
监听三个域对象的属性变更的监听器(属性添加,移除,替换):
* ServletContextAttributeListener
* HttpSessionAttributeListener
* ServletRequestAttributeListener -
监听HttpSession中的JavaBean的状态改变(绑定,解除绑定,钝化,活化)
* HttpSessionBindingListener
* HttpSessionActivationListene
-
-
Java类的种类
(a)内嵌于Web页中,由浏览器来观看的Applet
(b)可独立运行的 Application
(c)服务器端的 Servlet
-
Java内部类
-
成员内部类
* 可以被任何修饰符修饰;
* 可访问外部类的属性(包括私有)
* 当外部类和内部类属性同名时,内部类访问外部类需要借助this,即
外部类名.this.属性名
* 创建成员内部类对象时需要先创建外部类,通过外部类对象创建内部类对象
-
局部内部类(方法内部类)
* 只在外部类的方法中可用
* 方法内部类不能使用访问控制符和static修饰符(因为方法内部类不能在方法以外的地方使用)
-
静态内部类
* 静态内部类不能直接访问外部类的非静态成员,但可以通过
new 外部类().成员
的方式访问;* 当外部类和内部类的静态成员相同时,可通过
类名.静态成员
访问外部类的静态成员;如果不同,可以只解调用外部类的静态成员名;* 创建静态内部类的对象时,不需要外部类的对象,可以直接创建;
-
匿名内部类——局部内部类的特例
* 匿名内部类中不能定义任何静态成员、方法,但可以定义常量属性(final)、普通属性(public、private等);
* 匿名内部类中可以有额外的方法(外部类或接口没有的方法);
* 匿名内部类中可以对其他类实例化;
26.桥接模式
(1)定义
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
(2)目标
将抽象与实现解耦。
(3)涉及角色
Abstraction:定义抽象接口,拥有一个Implementor类型的对象引用
RefinedAbstraction:扩展Abstraction中的接口定义
Implementor:是具体实现的接口,Implementor和RefinedAbstraction接口并不一定完全一致,实际上这两个接口可以完全不一样,Implementor提供具体操作方法,而Abstraction提供更高层次的调用。
ConcreteImplementor:实现Implementor接口,给出具体实现
(1)定义
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
(2)目标
将抽象与实现解耦。
(3)涉及角色
Abstraction:定义抽象接口,拥有一个Implementor类型的对象引用
RefinedAbstraction:扩展Abstraction中的接口定义
Implementor:是具体实现的接口,Implementor和RefinedAbstraction接口并不一定完全一致,实际上这两个接口可以完全不一样,Implementor提供具体操作方法,而Abstraction提供更高层次的调用。
-
Java类成员的访问控制权限
从大到小:public > protected > default > private
-
MVC模式
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
MVC只是将分管不同功能的逻辑代码进行了隔离,增强了可维护和可扩展性,增强代码复用性,因此可以减少代码重复。但是不保证减少代码量,多层次的调用模式还有可能增加代码量。
-
String、StringBuffer、StringBuilder的区别
-
String
记住:对String对象的任何改变都不影响到原对象,相关的任何修改操作都会生成新的对象。
使用字符数组保存字符串,且被final修饰,所以String对象不可变(不可变意思是,任何修改都是生成新的对象)
//面试常见考查 public class Main { 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); //false System.out.println(str1==str3); //true System.out.println(str2==str4); //false } }
-
StringBuffer
线程安全。效率比
String
类效率高。继承自
AbstractStringBuilder
类,使用字符数组保存字符串,该对象可变,任何修改不会创建新对象。 -
StringBuilder
操作字符串比
String
类和StringBuffer
类效率高,有封装好的操作方法。继承自
AbstractStringBuilder
类,使用字符数组保存字符串,该对象可变,任何修改不会创建新对象。 -
常见面试考察点
//JVM对于字符串相加的优化 //(1)对于直接相加的字符串——JVM会直接优化(拼凑字符串) String a = "hello2"; String b = "hello" + 2; //在编译期间直接优化成"hello2" System.out.println((a == b)); //true; //(2)对于间接相加(包含字符串引用)——JVM不会进行优化 String a = "hello2"; String b = "hello"; String c = b + 2; //符号引用的存在,所以该语句在编译期间不会被优化,生成 的对象c实际上是保存在堆内存中的,因此a和c指向的并不是同一个对象。 System.out.println((a == c)); //输出false //(3)对于间接相加但使用了final类型——JVM会进行优化 String a = "hello2"; final String b = "hello"; String c = b + 2; //对于final修饰的变量,会在常量池中保存一个副本,且在编译期间 会被替代为真实的值,即该语句在编译期间会被优化成String c="hello"+2; System.out.println((a == c)); //输出true
-