基础
jvm,jdk,jre
1.字节码
.class文件,相对二进制文件是一种中间文件,只能在虚拟机上运行,也是java可移植性的由来。
2.jvm
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机,会把字节码变成二进制机器码。JVM 有针对不同系统的特定实现(Windows,Linux,macOS)。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
3.jdk和jre:
顾名思义,jdk为工具,javac命令等。jre为运行环境
jdk包含jre,运行程序只要jre,开发需要jdk。
那些东西会被继承
1.成员变量和成员方法
父类private的不会被继承。
private以上都可被继承
2.类变量和类方法
同成员变量
3.构造方法
构造函数不能继承
ps:无法继承的东西是拥有的,只是无法访问
重载和重写,多态
1.重载
方法名必须相同(显然)
三者必有一:参数列表必须有不同之处(顺序,个数,类型)(重载的意义所在)
方法返回值和访问修饰符可以不同。(扩展)
2.重写
方法名必须相同(显然)
参数列表必须相同(和重载相反处)
访问修饰符范围大于等于父类;(不然会影响向上转型实现多态)
插播多态:
Father father = new Son();
father.method();
1.这里多态体现在哪?
改变赋值为Father father=new Son1()
同样执行father.method();会根据father指向子类不同而多态,method方法的内容呈现多态
继承和接口都能实现多态,手段都是向上转型
2.若method权限被降低,father就不能调用method方法了,向上转型就废了
如果父类方法访问修饰符为 private 则子类就不能重写该方法。(因为根本不继承)
返回值范围小于等于父类,抛出的异常范围小于等于父类(里氏代换原则)
拆箱装箱
Interger it=new Interger(i); 装箱
int i=it.intvaule(); 拆箱
区别是基本类型和引用类型的区别,引用类型有避免空指针的好处
==的自动拆箱问题
例如
1 public class Main {
2 public static void main(String[] args) {
3
4 Integer i1 = 100;
5 Integer i2 = 100;
6 Integer i3 = 200;
7 Integer i4 = 200;
Integer a = 1;
Integer b = 2;
Integer c = 3;
System.out.println(c==(a+b));//true
9 System.out.println(i1==i2); //true
10 System.out.println(i3==i4); //false
11 }
12 }
总结
1.Integer类创建对象的时候,数值在[-128,127]之间,会返回cache数组中的已经存在的对象的引用。
2.包装类的"= ="运算只在遇到算术运算的情况下会自动拆箱,如a==b+c
3.当a和b在[-128,127],a= =b也可能成立(a在数值上=b),看起来和2矛盾,但是结合1即知是因为是返回缓存的地址。
== 与 equals
对象的==和equals
1.==
基本数据类型比较的是值,引用数据类型比较的是内存地址
2.equals
**没被重写等效==,**重写了可以实现别的效果。
我们很熟悉的equals判断字符串内容就是String类重写了方法。
hashCode 与 equals
上面说equals判断字符串内容就是String类重写了equals方法,有那么简单吗?实际上重写 equals 时还必须重写 hashCode 方法!
1.我们先介绍hashCode
1.hashCode() 是 Object.类的方法,作用是获取对象的哈希码。
2.哈希码是hashCode() 对堆上的对象产生独特值,利用的是内存地址。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等。
2.为什么要hashcode?
假设,HashSet中已经有1000个元素。当插入第1001个元素时,需要怎么处理? “将第1001个元素逐个的和前面1000个元素进行比较”?显然,这个效率是相等低下的。
HashSet 如何检查重复:
它使用哈希算法根据元素的hashcode计算出元素在散列表中的位置,如果位置上有对象,进行equals比较(判断对象是否真的相同),因为不同的key求hash是可能产生hash碰撞的),两次判断都相同才视为相同对象(equals不同会放到一个桶内)成功插入,否则不插入。这样的方法避免了一个大循环,速度upup。
简而言之hashcode只是在散列表中应用来缩短查找时间(求hash的时间复杂度是O(1),比遍历快多了),判重还得看equals,对象hashcode相同并不一定相同,可能哈希冲突。
3.为什么重写 equals 时必须重写 hashCode 方法
简单地说就是保证两者判重的逻辑一致,即分别调用equals和hashcode时要返回结果相同(只需要对象会用Hashset存储时才有这个要求)。
看hashmap源码可知散列表是会通过hashcode确定数组下标,再用equal判重的。假设你认为a和b是同一个对象,但是只重写equal使两者相同,而hashcode不同。根据hashmap源码连数组下标都不会一样,根本轮不到equal方法调用就会被Hashset当成不同的对象,和你认为的矛盾。
举例:当我们重写equals为属性name相同就相同时,我们hashcode方法应该重写为name相同hashcode就相同。
java值引用
java只有值引用,基本数据类型传递值,引用类型传递引用,都是传递值。
深拷贝 vs 浅拷贝
1.深拷贝,涉及new开辟内存,拷贝内存数据
new b
b.1=a.1;
b.2=a.2;
2.浅拷贝,拷贝引用
a=b
3.ps:集合框架可通过new(tmp)实现深拷贝,这是源码帮我们实现的
4.clone方法
对当前对象浅拷贝。
那么如何深拷贝呢?(1比较方便)
1.序列化这个对象再序列化回来,引出5
2.重写clone方法
5.什么是序列化
即将对象写到IO流中,变成字节的序列
好处是:这些字节序列相比对象
1.能保存在磁盘上
2.可以通过网络传输(所有网络上传输的对象都需要能序列化)
6.为什么实现Serializbale接口就能够进行序列化?
Serializbale接口和RandomAccess接口原理基本相同,起标识类的作用。别的方法使用这些类的时候,会用instanceof检查是不是标识类的子类,然后采取不同的措施。
1.对于Serializbale,不是则报错
2.对于RandomAccess,不是则不会使用随机存储访问的方法,换用链式储存的方法
字符型常量和字符串常量的区别?
- 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。换句话,char是基本数据类型,而String是引用数据类型。
- 注意: char 在 Java 中占两个字节
String对象创建过程
当创建 String 类型的对象时,虚拟机会在==常量池(因为String字符串内容是不变的)==中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
Object对象有那些方法(看过源码)
1.clone方法
实现Cloneable接口才能调用该方法
2.getClass
运行时获得类型
3.toString
String地址
4.finalize
用于释放资源,很少用,因为垃圾回收时必执行一次,释放两次资源会报错
5.equls和hashcode
6.wait,notify,notifyAll
当前线程等待该对象的锁,还可以wait(Long timeout)
唤醒对象上等待的某个进程
唤醒对象上等待的所有进程
notify等关于线程的方法为什么放在了object类里?
任何对象都可以被多个线程竞争
StringBuffer,StringBuilder,String对比
1.String
表面:
String的特点是不变性,即String对象的字符串内容是不变的,对String进行操作的函数实质都是产生了一个新的String对象,包括字符串的拼接。
经典考题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnmnenTk-1597201197982)(file:///C:\Users\zzz\Documents\Tencent Files\657133058\Image\Group2\JM\M[\JMM[4U6_{EXZRA4$0{T8}`H.jpg)]
答:abc-----xyz
源码:String类中使用final关键字修饰字符数组来保存字符串的内容,所以其是不可变的不可继承的,因为内容被声明成了常量。
2.StringBuffer和StringBuilder比较,和spring比较
相同:
这两种对象都是可变的,原因自然是源码和String相反,没有使用final修饰字符数组。都继承AbstractStringBuilder类,所以API也大致相同。
不同:
线程安全性:buffer是线程安全的,其方法加了同步锁,而builder是线程不安全的。不过自然builder效率略快一些。
和String比:
显而易见有带缓冲区的优势,这个优势也决定了StringBuffer和StringBuilder更加适合大字符串,因为String对象老是会new一个新的String,对大字符串很不友好。反之小量字符串适合String
总之
大数据单线程StringBuilder
大数据多线程StringBuffer
小数据spring
3.StringBuffer的API
1.构造方法
· public StringBuffer() :无参构造方法
· public StringBuffer(int capacity) :指定容量的字符串缓冲区对象
· public StringBuffer(String str) :指定字符串内容的字符串缓冲区对象
2.添加功能
· public StringBuffer append(String str):可以把任意类型数据添加到字符串缓冲区里面,并返回字符串缓冲区本身
· public StringBuffer insert(int offset,String str):在指定位置把任意类型的数据插入到字符串缓冲区里面,并返回字符串缓冲区本身
3.删除功能
· public StringBuffer deleteCharAt(int index):删除指定位置的字符,并返回本身
· public StringBuffer delete(int start,int end):删除从指定位置开始指定位置结束的内容,并返回本身
4.替换功能
· public StringBuffer replace(int start,int end,String str):使用给定String中的字符替换词序列的子字符串中的字符
5.反转和截取:略
super和this
this
1.调用当前实例方法
this()调用对象无参构造函数
2.调用当前实例变量
3.除了和方法参数冲突的情况都是可省的
super
1.调用父类构造方法
super()无参
super(10):自动搜索父类匹配的构造
2.用于访问父类成员变量 number ,调用其父类 的方法。
super.number = 10;
super.showNumber();
Final Finally Finalize
final:
1.类final不可被继承,原理是方法和成员都隐性加上final
2.变量final意味是常量,且必须赋初值
3.方法final不可重写
4.private不可继承的原因可以说是被隐式指定为了final
Finally
try catch后面总是会执行的代码块,可以把释放资源写在这里。注意就算别的地方return都还是会先执行finally块再return
finalize
Object类的方法,垃圾收集器在将对象从内存里面回收时调用的方法,做一些清理工作。重写之可以整合系统其他资源或做一些别的清理工作。
Comparable和Comparator的区别
1.Comparable:内部比较器
继承这个接口后,重写实现compareTo方法,放入treemap等有序集合中即会自动排序。
2.Comparator:外部比较器
类无法修改时,可以使用此外部比较器(内部比较器则要修改类的源代码)。此时在treemap等有序集合中就会通过compare方法的规则重排序。
用法:先sort方法动态绑定这个外部比较器(这里用的Collections工具类,对象.sort也可以)
Collections.sort(arrayList, new Comparator<Integer>() { @Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
3.集合里面排序的规则
规则:compareTo/compare方法return负数代表降序,正数代表升序。
内部类
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
1.匿名内部类的Lambda(常用)
基本的很熟悉了,就是new 接口/抽象类,然后实现方法。讲一下和Lambda结合的方法。Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例,完全可以用于简化匿名内部类,因为其方法很多情况only one。
interface C{
public void getData(int e);
}
public class Anonymous {
public void test1(A a){
System.out.println("interface A 的getNum是: ");
a.getNum();
}
public void test2(C c){
System.out.println("interface C 的getData是: " );
c.getData(10);
}
public static void main(String[] args){
Anonymous an = new Anonymous();
an.test1(new A() {//匿名内部类实现
public void getNum() {
System.out.println("A 实现");
}
});
an.test2(new C() {//匿名内部类实现
public void getData(int e) {
System.out.println("C 实现");
}
});
//Lambda表达式,形参表为空,代码体中直接写方法体
an.test1(()->{
System.out.println("A 实现");
});
//Lambda表达式,有形参写->前面
an.test2((c)->{
System.out.println("C 实现");
});
}
}
Lambda表达式更简洁的本质是规定了被继承的接口抽象方法只有一个,那么创建的时候很多东西都确定了,以test2为例子,test2方法参数为接口C,所以类名省略,接口C只有一个方法,所以除了方法体外都省略,剩下的只有具体参数和方法体。
实际应用举例:
外部比较器:
strList.sort((s1, s2) -> (s1 + s2).compareTo(s2 + s1));
等效
strList.sort(new Comparator<String>() {
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1);
}
});
ps:Lambda代码块只有一条return语句,甚至可以省略return关键字。如果代码块只有一条语句,Lambda表达式会自动返回这条表达式的值。所以这里return也省略了。
2.成员内部类
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw {
//内部类
public void drawSahpe() { System.out.println("drawshape");
}
}
}
1.类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法
2.在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
3.成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。
4.由于Draw类像一个成员,所以不同于类只有两种修饰权限,它有四种。和成员差不多:private 修饰,则只能在外部类的内部访问,如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。
总是显式定义无参构造而不利用隐式的(super的扩展)
1.我们知道每一个子类的构造方法必调用super方法(帮助子类做初始化工作),先是看有无显式super去调用父类的特定构造。不显式的调用,编译也会自动插入super()调用父类无参构造的。
2.当类无构造方法时,隐式存在一个无参构造。
3.如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super()
来调用父类中特定的构造方法,则编译时将发生错误。因为我们总是习惯不调用super,所以父类总是显式定义一个无参构造。
import java 和 javax 有什么区别?
javax 之前只是扩展 API 包,后面成为了默认的懒得改到java包了。两者都是java自带api。
接口和抽象类的区别是什么?
常规的:
1.方法
接口的方法默认是 public abstract,也只能如此