Java面试精髓
Java编译、执行
.java文件-》javac编译-》.class文件-》JVM-》解释器-》二进制机器语言
所以Java程序跨平台
Java类的加载
父类静态成员变量-》父类静态语句块-》子类静态成员变量-》子类静态语句块-》父类非静态成员变量-》父类非静态语句块-》父类构造方法-》子类非静态成员变量-》子类非静态语句块-》子类构造方法
Java对象排序
Collections.sort()函数
public static <T extends Comparable<? super T>> void sort(List<T> list)//实现了comparable的一个类作为集合中对象
public static <T> void sort(List<T> list, Comparator<? super T> c)//集合中的对象不做要求,要求sort()方法中传入Comparator类重写compare()方法
//实现comparable接口
public void demoForComparable() {
CutePig pig1 = new CutePig("猪聪明", 18); // 第一个对象
CutePig pig2 = new CutePig("猪可爱", 12); // 第二个对象
CutePig pig3 = new CutePig("猪能干", 16); // 第三个对象
ArrayList<CutePig> pigList = new ArrayList<>();
pigList.add(pig1);
pigList.add(pig2);
pigList.add(pig3);
Collections.sort(pigList);
System.out.println("按年龄排序(升序)后结果如下");
for (CutePig pig : pigList) {
System.out.println(pig.toString());
}
}
public static void main(String[] args) {
SortObj obj = new SortObj();
obj.demoForComparable();
}
class CutePig implements Comparable<CutePig> {
String name;
int age;
public CutePig(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "name is " + this.name + "; age is " + this.age;
}
@Override
public int compareTo(CutePig other) {
return this.age - other.age;//升序排列
}
}
//sort()中重写compare()
public void demoForComparator() {
SweetHeart love1 = new SweetHeart("猪可爱", "pig");
SweetHeart love2 = new SweetHeart("哈士奇", "dog");
SweetHeart love3 = new SweetHeart("大火鸡", "chicken");
ArrayList<SweetHeart> loveList = new ArrayList<>();
loveList.add(love1);
loveList.add(love2);
loveList.add(love3);
Collections.sort(loveList, new Comparator<SweetHeart>() {
@Override
public int compare(SweetHeart sweet1, SweetHeart sweet2) {
return sweet2.type.compareTo(sweet1.type);//降序排列
}
}
);
for (SweetHeart love : loveList) {
System.out.println(love.toString());
}
}
public static void main(String[] args) {
SortObj obj = new SortObj();
obj.demoForComparator();
}
Java存储对象
Java对象:对象头、实例数据、对齐补白(对齐填充字符)
对象头:mark word(hash值、GC分代年龄、锁状态等)+class对象指针(指向对象所属类的class对象指针)+(如果是数组)长度
实例数据:成员变量、父类继承下的成员变量
对齐补白:JVM要求Java对象的起始地址为8的倍数,对象头+实例数据不足8的倍数时由对齐补白补齐
对象头大小:32位计算机(8),64位计算机(16,开启jvm指针压缩为12)
成员变量大小:boolean类型单独存在为4,数组中存在为1;reference引用32位机子(4),64位机子(8,开启jvm指针压缩为4)
数组对象:64为机子未开启指针压缩24(8mark word+8class指针+8数组长度),开启指针压缩16(8mark word+4class指针+4数组长度)
继承自父类的子类对象内存计算:子类对象头+父类成员变量+父类填充+子类成员变量+子类填充
指针压缩:-XX:+UseCompressedOops
Java基础数据
数据类型 | 名字 | 字节大小 |
---|---|---|
整型 | byte | 1 |
short | 2 | |
int | 4 | |
long | 8 | |
浮点型 | float | 4 |
double | 8 | |
字符型 | char | 2 |
布尔型 | boolean | 1 |
基础语法
1.switch(数据填入的类型)
版本 | 引入 |
---|---|
Java5之前 | byte、short、char、int、 |
Java5开始 | enum |
Java7开始 | String |
2.二的次幂高效计算用>>(向左去位)或<<(向右加位)
3.Math.round()->加0.5向下取整
4.short s1;s1=s1+1(下整型)出错
5.short s1;s1+=1 <====> s1=(short)(s1+1)
Java编码
unicode
注释
单行
多行
文档注释
Java访问权限
当前类 | 同包 | 子类 | 其它包 | |
---|---|---|---|---|
private | ✔ | |||
default | ✔ | ✔ | ||
protect | ✔ | ✔ | ✔ | |
public | ✔ | ✔ | ✔ | ✔ |
运算符
&:按位与、逻辑与
&&:左边位false直接结束
关键字
final:不可继承、(引用)不可改变
finally:一定被执行
finalize:Object类的方法,System.gc()垃圾收集器调用finalize()回收
this:(类似指向本身的指针)、调用本身的构造方法、形参和成员变量相同时,this区分
super:(类似指向超类的指针)(超类指离自己最近的父类)用法与this相同
static:修饰方法和变量的时候,不属与任何一个实例,优先于对象存在(第一次加载时被初始化分配空间);静态类只能访问静态方法
break、continue:break跳出多重循环
target:
for( ){
for( ){
break target;
}
}
面向对象
需要实例化对象开销大
overload:编译时多态
override:运行时多态
实现多态:继承、重写、向上转型
五大设计原则
1.单一职责
2.开闭原则
3.里式替换
4.依赖倒置
5.接口分离
抽象类、接口比较
相同点:不能实例化、包函抽象方法、子类覆写
不同:抽象类子类不是abstract类需提供所有的方法实现、抽象类可以有构造方法;接口不可private、protect修饰,接口更关注行为
构造方法的作用:用于完成对象的初始化
hashCode、equal
hashCode:散列码、对象在hash表中的索引
在hashSet中加入Object,先计算加入位置与hashCode比较,不相同对象没有重复
hashCode相同,调用equal比较hashCode相同的对象,相同加入失败,不同散列到其它位置
Java传参
值传递
Java常用包
lang、io、nio、net、util、sql
编译
静态编译
动态编译
字符和字符串
字符:’ 'ASCII值
字符串:地址值(每次操作都是创建一个新对象)
StringBuilder
StringBuffer(线程安全)
==比较
基本数据类型比较的是值
对象引用类型比较的是对象的内存地址
整型字面量在-128到127之间,自动装箱不会new新的Integer对象,直接引用常量池中
反射机制
动态加载类,性能比Java代码慢
Stu stu = new Stu();
Class cla1 = stu.getClass();//对象获取
Class cla2 = Class.forName("xxxx.xxxx")//路径获取
Class cla3 = Stu.class//类获取
代理
静态代理
public interface Subject {
void test();
}
public class RealSubject implements Subject {
@Override
public void test() {
System.out.println("this is RealSubject test");
}
}
public class ProxySubject implements Subject {
Subject mSubject;
public ProxySubject(Subject pSubject){
mSubject = pSubject;
}
@Override
public void test() {
if(mSubject != null){
mSubject.test();
}
}
}
public class Client {
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);
proxySubject.test();
}
}
优点:
代理类可以接受一个已经实现了Subject接口的对象,任何实现了Subject接口的对象都可以通过代理类进行代理,实现了通用型
缺点:
当接口增删改方法,那么代理类已得要跟着修改;代理类的每个接口对象对应一个委托对象,如果委托对象很多,代理类就会变得异常臃肿
动态代理
动态代理有别用静态代理,它是通过要代理的类,动态的生成代理类。
//委托对象接口
public interface Subject {
void test();
}
//委托对象实现接口
public class RealSubject implements Subject {
@Override
public void test() {
System.out.print("this is dynamic RealSubject test");
}
}
//动态代理类方法调用处理程序
public class DynamicProxy implements InvocationHandler {
Object mObj;
public DynamicProxy(Object pObj){
mObj = pObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("this is Dynamic proxy invode");
if(mObj != null){
result = method.invoke(mObj, args);
}
return result;
}
}
//使用
public class Client {
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
DynamicProxy proxy = new DynamicProxy(realSubject);
Subject subject = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
proxy
);
subject.test();
}
}
object类的方法
- registerNatives()
private static native void registerNatives();
static {
registerNatives();
}
本地方法:
本地方法是由其他语言(比如C,C++,或者汇编)编写的,编译成和处理器相关的机器代码。本地方法保存在动态连接库中,格式是各个平台专有的。Java方法是平台无关的,单本地方法却不是。运行中的Java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法
当包含registerNatives()方法的类被加载的时候,注册的方法就是该类所包含的除了registerNatives()方法以外的所有本地方法
一个Java程序要想调用一个本地方法,需要执行两个步骤:第一,通过System.loadLibrary()将包含本地方法实现的动态文件加载进内存;第二,当Java程序需要调用本地方法时,虚拟机在加载的动态文件中定位并链接该本地方法,从而得以执行本地方法。registerNatives()方法的作用就是取代第二步,让程序主动将本地方法链接到调用方,当Java程序需要调用本地方法时就可以直接调用,而不需要虚拟机再去定位并链接
registerNatives()好处:
-
通过registerNatives方法在类被加载的时候就主动将本地方法链接到调用方,比当方法被使用时再由虚拟机来定位和链接更方便有效;
-
如果本地方法在程序运行中更新了,可以通过调用registerNative方法进行更新;
-
Java程序需要调用一个本地应用提供的方法时,因为虚拟机只会检索本地动态库,因而虚拟机是无法定位到本地方法实现的,这个时候就只能使用registerNatives()方法进行主动链接。
-
通过registerNatives()方法,在定义本地方法实现的时候,可以不遵守JNI命名规范(在Object中定义的本地方法registerNatives,那这个方法对应的本地方法名就叫Java_java_lang_Object_registerNatives,而在System类中定义的registerNatives方法对应的本地方法名叫Java_java_lang_System_registerNatives)
-
wait(),wait(long),wait(long,int),notify(),notifyAll()
Object类的final native方法,调用方法必须获得对象锁 在同步块或者方法中执行
wait线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒
notify随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态
notifyAll解除所有那些在该对象上调用wait方法的线程的阻塞状态,也只有一个线程拿到锁
-
toString()
返回对象的字符串表示形式
默认的字符串内容是“类名+哈希编码“(未重写)
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
一个字符串和另外一种类型连接的时候,另外一种类型会自动转换成String类型,然后再和字符串连接。
-
getClass()
返回Object的运行时类(基于反射)
object.getClass (运行时动态加载)
Object.class(java编译时加载)
getName():String:获得该类型的全称名称。
getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。
getInterfaces():Class[]:获得该类型实现的所有接口。
isArray():boolean:判断该类型是否是数组。
isEnum():boolean:判断该类型是否是枚举类型。
isInterface():boolean:判断该类型是否是接口。
isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。
isAssignableFrom(Class cls):boolean:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。
getComponentType():Class:如果该类型是一个数组,那么返回该数组的组件类型。
-
clone()
Object clone() 方法用于创建并返回一个对象的拷贝。
被克隆对象应该实现Cloneable接口
clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。
//处理浅拷贝 @Data public class Address implements Cloneable { private String type; private String value; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { Object obj=super.clone(); Address a=((Person)obj).getAddress(); ((Person)obj).setAddress((Address) a.clone()); return obj; } }
-
finalize()用于在垃圾回收
当对象的内存不再被使用时,GC在收集垃圾时就会调用这个方法(对象不一定总会被回收,GC会在内存用完时才会进行垃圾回收)
finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
-
equals()
equals与“= =”比较,两者区别是:
“= =”表示的是变量值完全相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址,所以基本数据类型:“= =”比较的是他们的值。引用类型(类、接口、数组) :用 “= =” 进行比较的时候,比较的是他们在内存中的存放地址,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。)Object原生的equals()方法内部调用的是= =,与= =具有相同的含义
这个equals标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义(如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类)
如String中equals方法判断相等的步骤是:
1).若A==B 即是同一个String对象 返回true
2).若对比对象是String类型则继续,否则返回false
3).判断A、B长度是否一样,不一样的话返回false
4).逐个字符比较,若有不相等字符,返回false -
hashCode()
hashCode()方法返回一个整形数值,表示该对象的哈希码值(hashCode返回的是由对象存储地址转化得到的值)存放于散列表中
正向代理和反向代理
虽然正向代理服务器和反向代理服务器所处的位置都是客户端和真实服务器之间,所做的事情也都是把客户端的请求转发给服务器,再把服务器的响应转发给客户端,但是二者之间还是有一定的差异的。
1、正向代理其实是客户端的代理,帮助客户端访问其无法访问的服务器资源。反向代理则是服务器的代理,帮助服务器做负载均衡,安全防护等。
2、正向代理一般是客户端架设的,比如在自己的机器上安装一个代理软件。而反向代理一般是服务器架设的,比如在自己的机器集群中部署一个反向代理服务器。
3、正向代理中,服务器不知道真正的客户端到底是谁,以为访问自己的就是真实的客户端。而在反向代理中,客户端不知道真正的服务器是谁,以为自己访问的就是真实的服务器。
4、正向代理和反向代理的作用和目的不同。正向代理主要是用来解决访问限制问题。而反向代理则是提供负载均衡、安全防护等作用。二者均能提高访问速度。