Java
Java部分
对String的理解
== equals和HashCode
- ==比较的是内存的存放地址 其实就是比较的栈中的内容
- equals则比较的是值是否相同(前提是必须重写equals方法,否则等同于==) 比较的是堆中的内容
String 中的源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;
if (n == anotherString.count) {
int i = 0;
while (n-- != 0) {
// 比较值 如果值不相同则返回false
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}
- 同一个对象多次调用hashCode方法 总是返回相同的整型数
equals为true 那么hashCode一定相同;hashCode相同equals未必为true 、
hashCode只有在集合中会用到
为什么覆盖equals时总要覆盖hashCode?
如果不覆盖 会导致该类无法结合所有基于散列的集合一起正常运作;这样的集合包括HashMap、HashSet和Hashtable
闭包和局部内部类的区别
内部类
仅仅是一个编译时的概念
outer.java里面定义了一个内部类inner 编译成功 会生成两个完全不同的.class文件了 分别是outer.class和outer$inner.class 所以内部类可以和外部类重名
为什么使用内部类?
每个内部类都能独立地继承一个(接口的)实现 所以无论外围类是否已经继承了某个(接口的)实现 对于内部类都没有影响
内部类使得多重继承的解决方案可以弥补接口的一些缺陷
成员内部类
普通的内部类 可以无限制的访问外围类的所有变量和方法
- 不能存在static的方法和变量
- 只有先创建外部类 才能创建内部类
局部内部类和匿名内部类
- 局部内部类是嵌套在方法和作用域内的
目的是创建一个辅助类来解决复杂问题 但又不想此类公有
匿名内部类属于局部内部类的一种
引用外部变量时 外部变量需要是final类型的(之所以这样 是因为当主方法结束时,局部变量会被cleaned up 而内部类可能还在运行。当局部变量声明为final时,当使用已被cleaned up的局部变量时会把局部变量替换成常量 也就是说 变量是final时 编译器会将final局部变量复制一份 复制品直接作为局部内部类中的数据成员 当局部变量访问这个局部变量时 真正访问的是这个局部变量的复制品 所以 当运行栈中真正的局部变量死亡时 局部内部类仍然看作可以访问局部变量 另外 final也保证来数据的一致性 这就是Java闭包的概念)
什么是闭包
把函数以及变量包起来 使得变量的生命周期延长
闭包带来的问题
匿名函数的变量引用 也叫变量引用泄漏 会导致线程安全问题 这也是局部变量需要声明为final的原因
abstract class InnerClass {
public abstract void print();
}
public class Outer {
public void test1(final String s1) {// 参数必须是final
//局部内部类
InnerClass c = new InnerClass() {
public void print() {
System.out.println(s1);
}
};
c.print();
}
public void test2(final String s2) {// 参数必须是final
//匿名内部类
new Outer() { //名字可以跟外部类一样
public void print() {
System.out.println(s2);
}
}.print();
}
public static void main(String[] args) {
Outer o=new Outer();
o.test1("inner1");
o.test2("inner2");
}
}
静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
- 子类可以继承父类的静态属性 和非静态的属性一样 会被隐藏
- 子类可以继承父类的静态方法 但是不可以重写 同名的静态方法会隐藏父类的静态方法(相当于新建了一个和父类相同的方法)
原因:
静态方法和属性属于类 不需要继承机制就可以调用
静态内部类的设计意图
内部类的设计意图
- 更好的封装,把内部类封装在外部类里,不允许同包其他类访问
- 可以变相的实现多继承 弥补接口设计上的缺陷
- 内部类实现接口可以解决与外部类的方法冲突
静态内部类的设计意图
- 创建静态内部类不需要外围类的对象
- 静态内部类不持有外部类的引用 可以防止内存泄露
成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用
- 成员内部类
可以访问外部类的所有方法和变量 - 静态内部类
不能访问外部类的非静态变量和方法 - 局部内部类
定义在方法中的内部类
可以访问外部类的成员 访问外部方法的局部变量时该局部变量需要final修饰 - 匿名内部类
内部类的一种简写
什么是内部类?内部类的作用
- 更好的封装,把内部类封装在外部类里,不允许同包其他类访问
- 可以变相的实现多继承 弥补接口设计上的缺陷
- 内部类实现接口可以解决与外部类的方法冲突
String StringBuffer和StringBuilder的区别
- String
不可变对象 每次对String变量进行改变时都会生成一个新对String对象 然后将指针指向新的String对象
String msg="hello";
System.out.println(msg.hashCode());
msg="world";
System.out.println(msg.hashCode());
结果:
99162322
113318802
hashCode
不同 显然已经不是同一个对象
-
StringBuffer
线程安全 效率比String高
append
insert
两个主要方法 使用字符串缓冲区 -
StringBuilder
非线程安全
区别
- String对比StringBuffer
每次改变String对象都会生成一个新的String对象 然后指针指向它 这就会对性能产生影响
使用 StringBuffer 每次都是对StringBuffer对象本身进行操作 不需要生成新对对象
String s2 = “This is only a”;
String s3 = “ simple”;
String s4 = “ test”;
String s1 = s2 + s3 + s4;
这种使用方法 会额外创建一个StringBuffer 之后将StringBuffer转化成Stirng
使用策略
- 少量数据 直接用
String
;单线程操作大量数据 用StringBuilder
;多线程操作大量数据 用StringBuffer
- 不要使用String类的"+"来进行频繁的拼接 应该使用StringBuffer或StringBuilder类
String result = "";
for (String s : hugeArray) {
result = result + s; //此种方式会不断创建result对象 性能差
}
// 使用StringBuilder 推荐用这种方式
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();
- StringBuilder一般用成局部变量 因为是非线程安全的 用完就直接丢弃;
StringBuffer一般用作全局变量
对多态的理解
多态由继承实现 可以增加的结构性便于代码的拓展和维护
对反射的理解
大多数框架设计的基础
Class
类本身就是一个对象 java.lang.Class
的实例对象
实例化Class对象的三种方式:
- .class
- getClass
- forName
通过类的类类型创建该类对象实例:
newInstance
方法的反射
步骤:
- 获取该类的类类型
- 通过类类型获取类的方法(
getMethods
) - 循环遍历获取到的方法
- 通过这些方法的getReturnType()得到返回值类型的类类型,又通过该类类型得到返回值类型的名字
-
getName()
得到方法的名称,getParameterTypes()
获取这个方法里面的参数类型的类类型
成员变量的反射
成员变量也是对象 java.lang.reflect.Field
类的对象
Class c = obj.getClass();
Field[] fs = c.getDeclaredFields(); //获取自己申明的所有成员变量
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
构造函数的反射
获取到类类型是反射的第一步
// 第一步先获取Class类型
Class c = obj.getClass();
/*
* 首先构造函数也是对象,是java.lang.Constructor类的对象
* 也就是java.lang. Constructor中封装了构造函数的信息
* 和前面说到的一样,它也有两个方法:
* getConstructors()方法获取所有的public的构造函数
* getDeclaredConstructors()方法得到所有的自己声明的构造函数
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
//我们知道构造方法是没有返回值类型的,但是我们可以:
System.out.print(constructor.getName()+"(");
//获取构造函数的参数列表 得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
动态加载
编译时刻加载的类是静态加载类;运行时刻加载的类是动态加载类
动态加载类一般基于反射
对注解的理解
概念
插入到代码中的元数据 可以在编译期使用预编译工具进行处理 也可以在运行期进行反射处理
本质上 注解是一种特殊的接口 程序可以通过反射获取
作用
- 编写文档
- 代码分析
- 编译检查
分类
- 系统内置注解
- 元注解 标记注解的注解
- 自定义注解
自定义主注解
- 格式
public @interface 注解名 {定义体} - 注解参数支持的数据类型
所有基本数据类型;String;Class;枚举;注解;数组 - 参数定义要点
只能用public或者default修饰;
注解类型必须是确定的值 要么指定默认值 要么使用的时候指定
依赖注入和控制反转
依赖注入实现了控制反转的思想
关于控制反转 简单的比喻: "不要打电话给我们询问面试结果 如果合适 我们会主动打电话给你"
控制反转是指从主动到被动 是所有 框架 最基本的特征 框架制定接口规范(面试要求) 对实现了接口的类进行调用(通过了面试 来上班)
静态代理和动态代理的区别
抽象类和接口的区别
- 抽象类和普通类的区别
(1) 抽象类不能实例化 (原因是当一个类实例化之后,就意味着这个对象可以调用类中的属性或者方法了,但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用)
(2) 抽象类只能用 public 或 protected 来修饰
(3) 抽象类被子类继承后,子类必须实现其所有的抽象方法,如果没有实现父类的抽象方法,则子类得定义为抽象类。 - 抽象类和接口的区别
语法上抽象类和接口的区别:
(1) 抽象类中可以有方法的实现,也可以有方法的定义,但接口中只能有方法的定义
(2) 抽象类,单继承;接口,多实现
(3) 接口中不能出现静态方法和静态代码块,抽象类中可以(不能有静态的抽象方法:因为静态属于字节码,不需要对象就可以运行;而抽象方法没有方法体,运行没有意义,所以不能共存。)
设计上接口与抽象类的区别:
抽象类是对事物本身的抽象,接口是对事物行为的抽象
抽象类和接口的应用场景
- 抽象类应用场景
作为接口和实现类的桥接 不必实现所有的接口;
有些方法可以共享 无需子类实现 而另外一些方法有需要子类根据自己的情况重写(多态) - 接口应用场景
实现特定的某些不存在联系的功能
深拷贝和浅拷贝
创建对象的两种方式:
- new 操作符创建一个对象
看new操作符后面的类型 知道类型才知道需要分配多大内存 --> 调用构造方法初始化 --> 把引用发布到外部 - clone 方法复制一个对象
步骤大致后new相同 但是分配的内存和源对象相同
浅拷贝
按位拷贝 会创建一个新对象有着原始对象属性值的精确拷贝
如果属性是基本数据类型 拷贝的就是基本数据类型的值;如果属性是引用类型 拷贝的就是内存地址(如果其中一个对象改变了这个地址 就会影响到其他对象)
深拷贝
拷贝所有的属性 并且拷贝属性动态分配的内存;当对象和它所引用的对象一起拷贝时就发生深拷贝
相对于浅拷贝 速度慢 开销大
关于泛型
- 泛型参数的限定,使用extends关键字,限定多个类型时用&隔开。如:<T extends Runnable& Serializable>
- 泛型参数限定中,如果限定的类型是class而不是interface,则class必须放在限定类表中的第一个,且最多只能存在一个class。如:<T extends ArrayList & Runnable& Serializable>
- 通配符只能用在泛型类的泛型参数中,不能单独使用。如Couple<?>、Couple<? extends Employee> 、Couple<? super Manager>
- 通配符的超类型限定适用于写入,通配符的子类型限定适用于读取,无限定通配符适用于一些非null判断等简单操作
- 通配符的捕获可以借助泛型类型限定来辅助