目录
四、String、Stringbuffer、Stringbuilder
理解StackOverflowError与OutOfMemoryError
一、JVM背景
二、static 静态
static特点:
1,随着类的加载而加载,也就是说静态会随着类的消失而消失,说明他的生命周期最长
2,优先于对象的存在。(静态先存在,对象后存在。)
3,被所有对象所共享
4,可以直接被类名调用
实例变量和类变量的区别:
1,存放位置。
类变量随着类的加载而存在于方法区中。
实例变量随着对象的建立而存在于堆内存中。
2,生命周期:
类变量生命周期最长,随着类的消失而消失。
实例变量生命周期随着对象的消失而消失。
三、JAVA的多态
-
final关键字
final 即可以理解为可以看成最终的
final修饰类类不能被继承,但是可以继承其他类
final修饰方法不能被重写,但可以被子类继承使用
final修饰变量
局部变量,变量的值只能赋值一次,一旦赋值,不可改变
成员变量,变量的值只能赋值一次,一旦赋值不能改变
必须在创建对象之前完成赋值(可以直接赋值,也可以构造方法中赋值)
-
abstract抽象
抽象方法 : 没有方法体的方法。
抽象类:包含抽象方法的类。抽象方法所在的类,必须是抽象类
模板方法模式:
将固定的流程定义在父类中,不同的地方使用抽象类,让子类去重写
-
interface接口
a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
c、一个类可以实现多个接口;
d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)
四、String、Stringbuffer、Stringbuilder
String基础
String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。
String 与内存
String存放在堆中还是栈中_工程师小D的博客-CSDN博客_string存在堆里还是栈
String a = "abc";// 常量池 开辟一个空间值为“abc”
String b = "abc";// 查到常量池已经存在,指向从空间
System.out.println(a==b); //true 同样指向常量池
System.out.println(a=="abc"); //true "abc"会先去常量池找,如果存在就指向常量池。所以一样
String c = new String("abc");// 在堆中开辟新空间值为“abc”,c存储的是堆地址
System.out.println(c=="abc");// false
System.out.println("==============");
String d = "ab";
String f = d + "c";// d + "c" 会生成新的值到堆中
System.out.println(f=="abc");//false
System.out.println(f==(d + "c")); //false
System.out.println("==============");
final String g = "ab";
String h = g + "c"; //final会变成常量相当于 "ab"+"c"
System.out.println(h=="abc");//true
System.out.println(h==(g + "c")); //true
为什么String不可变?
String源码,String类是final修饰的,意味着不可继承的,参数value是final修饰的,final关键字修饰的常量初始化后不可修改值。
为什么要设计成不可变?
1、字符串的值都是被存在常量池中,如果多个字符串指向同一个值,但值又可别修改,就会问题。
2、字符串不可变,就能实现多线程安全了。
3、字符串不可变,意味着hashCode值不变,在创建时就可以被缓存,提高了性能,所以String很适合做Map的键
装B用的,改变String值
public static void main(String[] args) {
String str = "ABC";
System.out.println("args = " + str);
try {
Field field = String.class.getDeclaredField("value"); //反射
field.setAccessible(true);
char[] value = (char[]) field.get(str);
value[0] = 'M';
System.out.println("args = " + str);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
String | StringBuffer | StringBuilder |
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 | StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 | 可变类,速度更快 |
不可变 | 可变 | 可变 |
线程安全 | 线程不安全 | |
多线程操作字符串 | 单线程操作字符串 |
synchronized原理
synchronized关键字实现原理_小志的博客的博客-CSDN博客_synchronized关键字原理
- synchronized关键字是用来控制线程同步的
- synchronized可以修饰类、方法、变量。
synchronized通过监视器锁(monitor)的对象来完成,每个对象有一个monitor
(A)、 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为 1,该线程即为 monitor的所有者。
(B)、如果线程己经占有该monitor,只是重新进入,则进入monitor 的进入数加1。
(C)、如果其他线程己经占用了monitor,则该线程进入阻塞状态,直到 monitor的进入数为 0,再重新尝试获取 monitor 的所有权。
hashCode 与 equals
- equals:比较的是对象的内存地址是否相同(相当于==操作符);
- hashCode:hashCode方法的返回值符合上述规范。
通常情况两个对象==是判断内存地址,如果内存地址指向一样,那一定相同。
比较hashcode,如果hashcode不同,则元素一定不相等;如果相同,再用equals判断。
HashMap采用这两个方法实现散列存储,提高键的索引性能。
为什么重写equals一定要重写hashcode
Object规定,equals相等的对象必须具有相等的哈希码
package com.example.demo.basic;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author 10450
* @description 重写Equals
* 重写Equals必须重写hashCode 因为Object规定,Equals相等,必须hashCode一样
* @date 2022/9/29 17:31
*/
public class EqualseTest {
public static void main(String[] args) {
/**
* 不重写hashCode
* put方法把实例p1放到了一个哈希桶(hash bucket)
*/
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.out.println(p1.equals(p2));// true
Map<Point, String> map = new HashMap<>();
map.put(p1, "p1");
System.out.println(map.get(p2)); // null 因为hashCode
EqualseVO2 v1 = new EqualseVO2("张三", 2);
EqualseVO2 v2 = new EqualseVO2("张三", 2);
System.out.println(v1.equals(v2));// true
Map<EqualseVO2, String> mapv = new HashMap<>();
mapv.put(v1, "v1");
System.out.println(mapv.get(v2)); // v1
}
}
@Data
class EqualseVO2 {
String name;
int age;
public EqualseVO2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
//对比内存地址
if (this == obj)
return true;
if (obj == null)
return false;
//对比类对象
if (getClass() != obj.getClass())
return false;
EqualseVO2 vo = (EqualseVO2) obj;
//业务上判断是否相同
return name == vo.name && age == vo.age && Objects.equals(name, vo.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point)) return false;
Point point = (Point) obj;
return x == point.x && y == point.y;
}
}
五、JDK8 & JDK11
JDK8新特性
1、lamda表达式
(parameters) -> expression或(parameters) ->{statements; }
2、方法引用:
双冒号::
- 如果是静态方法,则是ClassName::methodName。如 Object ::equals
- 如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals;
- 构造函数.则是ClassName::new
public class FunctionYY {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("aa");
list.add("bb");
list.forEach(System.out::println);
List<Student> users = new ArrayList<Student>();
users.add(new Student(20, "张三"));
users.add(new Student(22, "李四"));
users.add(new Student(10, "王五"));
users.forEach((Student user) -> System.out.println(user.getAge()));
}
}
3、函数式接口
就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口
@FunctionalInterface注解
如Runnable接口类、java.util.Comparator等等
JDK 1.8 新增加的函数接口: java.util.function
4、HashMap引入红黑树的数据结构和扩容的优化
六、死锁
6.1 死锁条件
互斥资源、不可抢占、请求和保持、循环等待
public class DeadLockTest {
public static String lock1 = "lock1";
public static String lock2 = "lock2";
public static void main(String[] args) {
DLThread1 threadA = new DLThread1();
DLThread2 threadB = new DLThread2();
threadA.start();
threadB.start();
}
static class DLThread1 extends Thread {
@Override
public void run() {
System.out.println(this.getName() + "DLThread1 start!");
try {
synchronized (lock1) {
System.out.println(this.getName() + "DLThread1 拿到lock1");
Thread.sleep(3000);
synchronized (lock2) {
System.out.println(this.getName() + "DLThread1 拿到lock2");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " DLThread1 end!");
}
}
static class DLThread2 extends Thread {
@Override
public void run() {
System.out.println(this.getName() + " DLThread2 start!");
try {
synchronized (lock2) {
System.out.println(this.getName() + "DLThread2 拿到lock2");
Thread.sleep(3000);
synchronized (lock1) {
System.out.println(this.getName() + "DLThread2 拿到lock1");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " DLThread2 end!");
}
}
}
6.2 如何解决死锁
通过信号量来控制Semaphore资源使用情况
/**
* @author 10450
* @description 新增信号量解决死锁
* @date 2021/12/18 21:41
*/
public class DeadLockTest {
public static final Semaphore a1 = new Semaphore(1);
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
DLThread1 threadA = new DLThread1();
DLThread2 threadB = new DLThread2();
threadA.start();
threadB.start();
}
static class DLThread1 extends Thread {
@Override
public void run() {
System.out.println(LocalDateTime.now()+ " DLThread1 start!");
try {
if(a1.tryAcquire(3, TimeUnit.SECONDS)) {
System.out.println(LocalDateTime.now() + " DLThread1 拿到 a1");
Thread.sleep(3000);
if(a2.tryAcquire(3, TimeUnit.SECONDS)){
System.out.println(LocalDateTime.now()+ " DLThread1 拿到 a2");
}else{
System.out.println(LocalDateTime.now()+ " DLThread1 放弃 a2");
}
}else{
System.out.println(LocalDateTime.now()+ " DLThread1 放弃 a1");
}
a1.release();
a2.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now()+ " DLThread1 end!");
}
}
static class DLThread2 extends Thread {
@Override
public void run() {
System.out.println(LocalDateTime.now()+ " DLThread2 start!");
try {
if(a2.tryAcquire(3, TimeUnit.SECONDS)) {
System.out.println(LocalDateTime.now()+ " DLThread2 拿到 a2");
Thread.sleep(3000);
if(a1.tryAcquire(3, TimeUnit.SECONDS)) {
System.out.println(LocalDateTime.now()+ " DLThread2 拿到 a1");
}else{
System.out.println(LocalDateTime.now()+ " DLThread2 放弃 a1");
}
}else{
System.out.println(LocalDateTime.now()+ " DLThread1 放弃 a2");
}
a1.release();
a2.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now()+ " DLThread2 end!");
}
}
}
6.3 死锁排查
1、jps -l
2、jstack查询堆信息
七、反射
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class c1 = Class.forName("com.example.demo.reflect.ReflectVO");
Method sayHi = c1.getMethod("sayHi", new Class[] {String.class});
sayHi.invoke(c1.newInstance(),new Object[] { new String("周杰伦")});
// //第二种
// ReflectVO p = new ReflectVO();
// Class c2 = p.getClass();
// //第三种
// Class c3 = ReflectVO.class;
}
}
八、类的加载
8.1
8.2 双亲委派机制
两个原因: 1. 沙箱安全机制, 自己写的java.lang.String.class类不会被加载, 这样便可以防止核心API库被随意修改 2. 避免类重复加载. 比如之前说的, 在AppClassLoader里面有java/jre/lib包下的类, 他会加载么? 不会, 他会让上面的类加载器加载, 当上面的类加载器加载以后, 就直接返回了, 避免了重复加载.
问题
接口与抽象类的区别
相同点
(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
理解StackOverflowError与OutOfMemoryError
- StackOverflowError:递归过深,递归没有出口。
- OutOfMemoryError:JVM空间溢出,创建对象速度高于GC回收速度。
finally final finalize区别
finalize是方法名,java技术允许使用finalize()方法在垃圾收集器将对象从内存中清楚出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
1、面向对象的特征有哪些方面?
2、访问修饰符 public,private,protected,以及不写(默认)时的区别?
3、String 是最基本的数据类型吗?
4、float f=3.4;是否正确?
5、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
6、Java 有没有 goto?
7、int 和 Integer 有什么区别?
8、&和&&的区别?
9、解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。
10、Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
11、switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?
12、用最有效率的方法计算 2 乘以 8?
13、数组有没有 length()方法?String 有没有 length()方法?
14、在 Java 中,如何跳出当前的多重嵌套循环?
15、构造器(constructor)是否可被重写(override)?
16、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
17、是否可以继承 String 类?
18、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
19、String 和 StringBuilder、StringBuffer 的区别?
20、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?