JAVA基础
Object类
new 一个Object多少字节?
16字节。
8字节的mark word。4字节的指针(指向类元信息地址,应该是class?)。
默认开启指针压缩,所以是4字节,关闭的话为8字节。
内存对齐填充了最后四个字节,希望字节数是8的倍数。
有哪些方法
hashcode,equals,toString,wait,notify
为什么wait/notify在Object内而不是Thread
因为等待/通知机制设计是为了解决线程间通信的。
线程通信是否可执行,是由资源决定的,而非线程。
一个线程使用完毕某个资源,别的线程才能使用。
线程知道自己是因为要获取哪个资源而被阻塞,关注的是资源,而不在乎是因为谁(具体哪个线程)。
封装,继承,多态
封装。将数据或函数等集合在一个类中。隐藏实现的细节。隔离性。安全。
继承。子类拥有父类方法,减少代码重复量。
多态。一个接口,多个实现类,可以做出不同的反应。编译类型和运行类型不同。
拆箱,装箱
Integer i = 10; //装箱
int n = i; //拆箱
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
为什么要有包装类
包装类extends Object。体现Java面向对象的思想。
集合类泛型必须是一个Object的子类,因此泛型不能是int等基本数据类型。
接口和抽象类有什么共同点和区别?
共同点 :
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(Java 8 可以用
default
关键字在接口中定义默认方法)。
区别 :
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
final:
final int a = 10;
修饰成员/局部变量,表示常量,不可修改final static double PAI_Value=3.14;
修饰静态变量,相当于全局常量final Student student = new Student();
修饰对象,表示对象的指针不可修改,但student.id=1
仍可以修改。不能修改的是指针,而不是指向的具体值,不能student = student2
final class A{}
修饰类,表示该类不可被继承,抽象类,接口都不能被final修饰final void function(){}
修饰方法,表示不可以被重写,可以被重载
总结:
final 修饰类,不可被继承
修饰方法,不可被重写
修饰变量,不可被修改,常量
abstract:
abstract
修饰的类,不可生成实例- 抽象类可以没有抽象方法,但抽象方法所属的类一定是抽象类
- 不能和
private static
共同修饰方法
interface:
-
接口是抽象方法和常量值的定义的集合
-
只能有抽象方法,不能有非抽象方法(这在抽象类是允许的),没有方法体
-
默认public修饰,可以改用为protected,但不能是其他
-
接口中的所有成员变量都默认是由public static final修饰的(静态常量)。
接口中的所有方法都默认是由public abstract修饰的。
== 和 equals() 的区别
==
对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
JAVA只有值传递,引用数据类型存储的是内存地址的值,所以本质完全相同,就是在比较值
Object
类 equals()
方法:
public boolean equals(Object obj) {
return (this == obj);
}
若没有重写 equals()
方法,相当于==。比如String就重写了,所以equals比较的是值,而不是内存地址是否相同。重写equals方法一定要重写hashcode方法。避免两个equals的对象,放入到map中,经过hashcode成为了两个。
String, StringBuffer and StringBuilder
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
String字节数
在String中,一个英文字符占1个字节;
而中文字符根据编码的不同所占字节数也不同。
- 在UTF-8编码下,一个中文字符占3个字节。(包括中文标点符号)
- 而使用GBK编码时一个中文字符占2个字节。
/n 两个字节
统计字符数用codePointCount,像𝄞这个字符,在char数组里长这样\uD834\uDD1E,用length计算是=2,而中文字符=1,所以没有过于特殊的字符情况下length不会错,但也有例外。
string B;
B.codePointCount(0,B.length())
String底层
private final char value[]; //1.8是一个字符数组
// jdk9 改成了byte数组
final修饰,因此不可变,是指针不可变。当然数组内的字符是可以改变的。
super关键字
-
super(a,b)
调用父类的构造方法 -
super.func()
调用父类的成员 -
调用
super()
必须写在子类构造方法的第一行, 否则编译不通过 -
浅拷贝:拷贝的只是指针
-
深拷贝:再生成一个相同的对象B,指向B
大坑
自定义注解?注解原理?泛型原理?异常深入?
泛型
List<String> list = new ArrayList<String>();
List list = new ArrayList();
// list中只能放String, 不能放其它类型的元素
泛型类(接口一样)
class Notepad<K,V>{ // 此处指定了两个泛型类型
private K key ; // 此变量的类型由外部决定
private V value ; // 此变量的类型由外部决定
}
public class GenericsDemo09{
public static void main(String args[]){
Notepad<String,Integer> t = null ; // 定义两个泛型类型的对象
t = new Notepad<String,Integer>() ; // 里面的key为String,value为Integer
}
}
// 类后加尖括号,内部放泛型
class Info<T extends Number>{
// 此处表示泛型只能是数字类型 可以通过这种手段进行泛型上下边界的限制
// extends表示T必须是E本身或者是E的子类
}
<? super E>
// 此处表示泛型必须是E本身或者是E的父类
泛型方法
必须在返回值前边加一个<T>
,来声明这是一个泛型方法
public <T> T function(Class<T> c){}
// Class<T>表示泛型的具体类型 c可以用来创建泛型类的对象(反射 c.newInstance)
// 就是你调用这个泛型方法,首先得指明泛型是什么具体类型(此时才知道了泛型具体是什么)
// 可以用Class.forName("完全类名")来表示泛型
注解
-
@Deprecated
:表示代码被弃用 -
@SuppressWarnings
:表示关闭编译器警告信息 有参数,直接用(all)吧
元注解: 是针对 public @interface Annotation {}
自己实现注解时用到的基础注解
@targert
表示可以修饰什么内容@Retention & @RetentionTarget
表示注解在它所修饰的类中可以被保留到何时@Inherited
表示被该注解修饰的类 的子类 会一起继承该注解
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。特点:动态获取信息。
Class.newInstance()
// 通过反射,调用无参构造方法,生成对象
//使用反射机制给属性赋值
Class studentClass = Class.forName("javase.reflectBean.Student");
Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)
// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
noField.set(obj, 22222);
//使用反射机制调用方法
Class userServiceClass = Class.forName("javase.reflectBean.UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null
Class类
是一个真实存在的类,表示运行时的类。java类运行,生成.class文件。
获取class对象
可通过类名.class
、对象.getClass()
、Class.forName("类名")
等方法获取class对象
Constructor类
代表一个构造方法。
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必须设置可访问
cs2.setAccessible(true);
//创建user对象
User user2= (User) cs2.newInstance(25,"hiway2");
System.out.println("user2:"+user2.toString());
//getConstructor() 是只返回public修饰的
//getDeclaredConstructor 返回所有,包括private
//加s返回构造器数组
Field类
代表一个成员变量。API与Constructor类似
Class<?> clazz = Class.forName("reflect.Student");
//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
// 否则抛NoSuchFieldException
Field field = clazz.getField("age");
//获取当前类所字段(包含private字段),注意不包含父类的字段
Field fields2[] = clazz.getDeclaredFields();
// 有Declared,就不包括继承的字段,且可以拿到所有修饰包括private
// 没有Declared,只能拿到public修饰的
ageField.set(对象实例,成员变量的具体值);
Field scoreField = clazz.getDeclaredField("score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
Method类
代表一个方法。
getDeclaredMethod/getDeclaredMethods
方法只能获取当前类的方法;
getMethods方法获取Method对象时,会把父类的方法也获取到。
// invoke方法,执行该method,参数是 (对象,method参数)
Class clazz = Class.forName("reflect.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();
//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);
//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");
反射执行流程
异常
Throwable 是 Java 语言中所有错误与异常的超类。Error是无法处理的,Exception才是程序可以处理的。
自定义异常
自定义异常类(xxxException)继承RuntimeException,在捕获异常的地方
全局异常处理类
类上加@RestControllerAdvice注解 方法中指定捕获的类@ExceptionHandler(xxxException.class)
/**
* 全局异常处理类
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 自定义异常
**/
@ExceptionHandler(CustomException.class)
public Result customException(CustomException e) {
return Result.failed(e.getCode(),e.getMessage(),e.getDate());
}
@ExceptionHandler(HttpServerErrorException.class)
public Result httpServerErrorException(HttpServerErrorException e) {
return Result.failed(503,e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result bindException(Exceptione) {
return Result.failed(500,"参数校验异常" + e.getMessage());
}
}
受检查异常和不受检查异常
受检查异常:是你必须要捕获的异常,否则无法通过编译。
不受检查异常:你可以捕获或者不捕获。只能打印日志
并发
synchronized关键字和Lock的实现类都是悲观锁
- 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
- 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
// synchronized用在普通方法上,默认的锁就是this,当前实例
public synchronized void method() {}
// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {}
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
synchronized (this) { //同步代码块 }
//锁Class对象 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){}
math.round(-11.5)=-11
原因:四舍五入的原理是在参数上加0.5 然后进行下取整。
访问修饰符
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
- public : 对所有类可见。使用对象:类、接口、变量、方法
内部类
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
new 类/接口{
//匿名内部类实现部分
}.方法
为什么java只有值传递?
Java方法传参,都是对所传变量进行拷贝,对基本数据类型来讲,拷贝的是实际数值,对引用数据类型来讲拷贝的是引用地址;
Java内存伪共享
伪共享:变量A和B,同处于一块CPU cache line中,线程A需要用变量A,线程B需要用变量B,但因为读取都是读一整个line的内容,因此对A和B的修改会导致另一个线程也要修改,降低效率,本来没有共享内存,但却伪共享了
@Contented修饰类的某个成员变量x,代表x会被放在另外的cache line中,避免伪共享。
获取当前时间
LocalDateTime
LocalDateTime dateTime = LocalDateTime.now();
String nowTime = dateTime.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"));
try
try catch finally捕获代码段的异常。
finally 即使在try catch中执行了 return操作,也会执行。
finally会先保存try尝试返回的值,然后运行finally代码块,如果finally内有return,会直接返回,无视try尝试返回的值。try的返回值又分为如下两种情况:
int num = 1;
try{
return num;
}finally{
num = 2;
}
//无法修改num的值,返回值仍然是1
//因为是基本数据类型,try备份的返回值是数据本身
-------------------------------------------------
String str = "123";
try{
return str; //备份好str的地址值后,就去执行finally,然后才返回出去
}finally{
str = "456"; //通过地址值去修改数据
}
//可以修改数据,return的地址值对应的数据是"456"
//地址就可以修改指向的对象
当然,JVM宕机了,finally块也不会执行。
Intern方法
String.intern()
返回字符串的引用,如果不在常量池就放到常量池,然后取出常量池中的String的引用。
JAVA8新特性
函数式编程
Stream中常用方法如下:
stream()
,parallelStream()
filter()
findAny()
findFirst()
sort
forEach
voidmap(), reduce()
flatMap()
- 将多个Stream连接成一个Streamcollect(Collectors.toList())
distinct
,limit
count
min
,max
,summaryStatistics
stream并不会修改数据源,也就是不会影响你传入的list。
比如执行filter,会生成一个新的stream
asList方法
String[] atp = {"Nadal", "Djokovic", "Wawrinka"};
List<String> players = Arrays.asList(atp);
foreach方法打印
players.forEach((player) -> System.out.print(player + "; "));
filter&Predicate
// 根据条件过滤
public static void filter(List names, Predicate condition) {
names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
System.out.println(name + " ");
});
}
// 输出以J开头的字符串
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
filter(languages, (str)->str.startsWith("J"));
拼接多个Predicate
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print(n) );
// .and拼接
Predicate细节
支持 .and / .or拼接条件
// Predicate.negate()方法,取反
Predicate<String> startWithA = x -> x.startsWith("A");
List<String> collect = list.stream()
.filter(startWithA.negate())
// 筛出不以A开头的字符串
.collect(Collectors.toList());
map
map是将原元素映射成新元素,抛弃旧元素(不是K-V形式那种)
// 转成大写
List<String> list3=list1.stream().map(s->s.toUpperCase).collect(Collectors.toList());
// String::toUpperCase 也可以
// 将对象的list转成了 string的list
List<Student> list1=Arrays.asList(new Student("古力娜扎",18),new Student("迪丽热巴",19));
List<String> list3=list1.stream().map(s->s.getName()).collect(Collectors.toList());
collect
再将流转回某个集合
// list
.collect(Collectors.toList());
// set
.collect(Collectors.toSet());
// map
.collect(Collectors.toMap(Product::getId, Product::getName));
// 分组 groupingBy
Map<String, List<Product>> groupListMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory));
// 拼接 joining
String nameJoin = prodList.stream().map(Product::getName).collect(Collectors.joining(","));
// 计数 counting
Long count = prodList.stream().collect(Collectors.counting());
flatmap&of 拼接stream
List<Integer> result= Stream.of(Arrays.asList(1,3),Arrays.asList(5,6))
// 两个流
.flatMap(a->a.stream()).collect(Collectors.toList());
// return 1,3,5,6
distinct
去重
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
.distinct().collect(Collectors.toList());
count
计数
persons.stream().filter(p -> p.getAge() > 18).count();
min/max
Person a = lists.stream().max(Comparator.comparing(t -> t.getId())).get();
sorted
排序
.sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
limit
.limit(3) //只取前三个,同sql limit0,3
reduce
reduce
方法来计算集合的累加和。
reduce
做的是:从前两个元素开始,进行某种操作(这里进行的是加法操作)后,返回一个结果,然后再拿这个结果跟第三个元素执行同样的操作,以此类推,直到最后的一个元素。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.reduce((a, b) -> {
return a + b;
})
parallel
利用ForkJoinPool.commonPool-worker来工作,实现并行计算。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.parallel()
.reduce((a, b) -> {
return a + b;
})
.ifPresent(System.out::println);
上面这个例子它的输出结果如下:
ForkJoinPool.commonPool-worker-1: 3 + 4 = 7
ForkJoinPool.commonPool-worker-4: 8 + 9 = 17
ForkJoinPool.commonPool-worker-2: 5 + 6 = 11
ForkJoinPool.commonPool-worker-3: 1 + 2 = 3
ForkJoinPool.commonPool-worker-4: 7 + 17 = 24
ForkJoinPool.commonPool-worker-4: 11 + 24 = 35
ForkJoinPool.commonPool-worker-3: 3 + 7 = 10
ForkJoinPool.commonPool-worker-3: 10 + 35 = 45
// 而不用parallel 会按部就班的计算,如下:
main: 1 + 2 = 3
main: 3 + 3 = 6
main: 6 + 4 = 10
main: 10 + 5 = 15
main: 15 + 6 = 21
main: 21 + 7 = 28
main: 28 + 8 = 36
main: 36 + 9 = 45
45