javaSE 温故而知新
重温 javaSE
前言:有地基才能有高楼大厦
认识java
-
java是一门强类型的语言
1.1 什么叫强类型什么叫弱类型呢?-
强类型:
强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整形变量 a,那么程序根本不可能将 a 当作字符串类型处理。强类型定义语言是类型安全的语言。 -
弱类型
数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。
强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。例如:JavaScript
1.2 强类型和弱类型各有什么优势?
- 强类型:
强类型定义语言严谨性能避免不必要的错误 - 弱类型:
编译速度快,开发敏捷
-
-
java是静态语言
2.1 什么叫静态语言什么叫动态语言?-
静态语言
(1)变量的类型在编译的时候确定
(2)变量的类型在运行时不能修改
(3)这样编译器就可以确定运行时需要的内存总量 -
动态语言
(1)变量的类型在运行的时候确定
(2)变量的类型在运行可以修改
-
-
java是跨平台的语言
一套代码跑各个平台 -
java是面向过程的语言
4.1 面向过程和面向对象的优缺点- 面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
- 面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
Java基础
1、数据类型
1.1 基本数据类型:
byte short char int float double long boolean
注意:整形默认为int,如果数值不属于int范围,会报错,整形后面加L代表是long数据。浮点型显性默认为double,浮点数高精度类型不能直接赋值给低精度类型,会造成精度损失,编译不通过,必须加F,加F代表是float数据
例如:
long value = 9223372036854775807; //错误写法
long value = 9223372036854775807L; //正确写法相等于long value = (long)9223372036854775807L;
float f = 1000.1; // 错误写法编译不通过
float f = 1000.1F; // 正确写法相等于 float f = (float)1000.1;
1.2 引用数据类型
所有的非基本数据类型都是引用数据类型
1.3 基本数据类型的包装类
1.4 基本类型与包装类互相转换
// 基础类型转成包装类
Long l = new Long(1000L);
Long l2 = Long.valueOf(100L); // 包装类.valueOf(基本数据类型变量)
// 包装类转成基本类型
long basicL = l.longValue(); // 包装类变量.基本数据类型value()
1.5 自动装箱与拆箱
Integer i = 10; //装箱 相当于Integer i = Integer.valueOf(10)
int n = i; //拆箱 相当于int n = i.intValue()
1.6 Byte,Short,Integer,Long缓存
对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,是同一个对象则为true,如果不在这区间,则为false
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
答案在源码中很容易找到,装箱是调用的valueOf方法,源码如下
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
我们看到如果是在IntegerCache.low和IntegerCache.high之间,都是返回的同一个对象,这些对象在加载类的时候就已经创建好了
注意:Float,Double并没有实现常量池技术。
2、访问修饰符
名称 | 中文简称 | 类内部是否可以访问 | 同包是否可以访问 | 不同包是否可以访问 | 子类是否可以访问 |
---|---|---|---|---|---|
privalte | 私有的 | 可以 | 不可以 | 不可以 | 不可以 |
default | 默认不写 | 可以 | 可以 | 不可以 | 不可以 |
protected | 受保护的 | 可以 | 可以 | 不可以 | 可以 |
public | 公开的 | 可以 | 可以 | 可以 | 可以 |
3、final关键字
- final类,不可被继承
- final方法,不可被重写,可以被重载,private 方法是隐式的 final
- final参数,不可改变参数的指向,如下:
- final变量,不可改变,被称为常量
4、static关键字
static静态、可以修饰属性,方法,还可以定义代码块,被修饰的元素隶属于类,被类的所有对象共享。
- static方法:一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用
- static变量:也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
- static代码块:static代码块可以置于类中的任何地方,类中可以有多个static块。 在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次== 。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
- static类:static类只能是内部类
5、异常
顶层类 Throwable 派生出两个重要的子类, Error 和 Exception
Error:Error的错误是致命的,一但发生Error JVM就会停止
Exeption:Exeption不会停止JVM。
5.1 Exception
异常又分为,运行时异常,和编译异常
5.2 处理异常
-
try-catch-finaly(捕获)
try (如果有资源需要在代码块执行完释放可以把变量声明在这里,前提是资源要实现AutoCloseable接口) { // 可能发生异常的代码 } catch (Exception e ) { // 发生异常处理的代码 } finally { // 不管发不发生异常都会执行的代码块 }
注意点:
如果catch里面有return,return语句会在finally代码块的最后执行
catch可以有多个 -
throw throws (抛出异常,交给上层处理)
throw主动抛出异常
if () { throw new NumberFormatException("主动抛出") }
throws 定义可能会发生的异常
public Integer test() throws NumberFormatException() { }
5.3 自定义异常
在我们日常开发中,大部分异常是够用的,但是又些时候需要我们自己去继承RuntimeExecption类定义一个自己的异常来满足需求
6、逻辑运算符
逻辑运算符
:先走一遍,在判断
&、|、^、!
短路逻辑运算符
:一但符合条件,后面的判断不走
&&、||
7、泛型
泛型不支持基本数据类型,用包装类代替
为什么要使用泛型?
-
保证了类型的安全性。
-
消除强制转换
List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); // 使用泛型 List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast
泛型通配符:
- 上界通配符:<? extends T> 只能是T或者是T的子类
- 下界通配符:<? super T> 只能是T或者是T的父类
- 无界通配符:<?> 任何类型
8、重载和重写
重载:方法名称都相同,方法参数顺序、参数类型、参数个数不一样
重写:只有继承才会有重写,方法名称,方法参数顺序、参数类型、参数个数都一致。重写的访问修饰符必须大于等于父类
9、abstract关键字
abstract类:天生就是为了规范来被子类继承的,不能被实例化,不能用final修饰(因为可以abstract类就是要用来继承的),与接口不同的是,abstract可以有普通成员变量、方法。
abstract方法:abstract方法必须存在abstract类中,不能用private、static修饰(因为子类必须要重写),abstract方法必须被重写
10、interface关键字(接口)
接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。interface只能用在类上,接口中的所有方法都是abstract的,子类必须重写。
10.1 interface和abstract的区别
interface是强调实现功能可以多实现,interface里面的方法都是abstract的,实现的子类全部要重写,没有构造器
abstract是强调所属关系只能单继承,abstract类可以存在非abstract的方法,子类可以选择是否要重写、有构造器
11、继承
11.1 为什么要使用继承?
- 提高代码的重用性。
- 类与类之间产生了关系,是多态的前提
11.2 实现继承
关键字extends,我们可以让一个类和另一个类建立起父子关系.例如
// Student称为子类(派生类),People称为父类(基类或超类)
// 当子类继承父类后,就可以直接使用父类公共的属性和方法了
public class Student extends People {}
注意:
final 的方法子类不可重写
private default 方法、属性、子类不可访问
11.3 this和super
this:是自身的一个对象,代表对象本身
- 普通直接引用当前对象本身
- 形参和成员名重名,用this来区分
- 引用构造方法,this(参数) ,应该为构造函数中的第一条语句,调用的事1本类中另外一种形式的构造方法
super:可以理解为是指向自己超(父)类对象,这个超类指的是离自己最近的一个父类
- 普通的直接引用,与this类似,只不过它是父类对象,可以通过它调用父类成员
- 子类中的成员变量或方法与父类中的成员变量或方法同名,可以使用super区分
- 引用构造方法,super(参数):调用父类中的某一个构造方法(应该为构造方法中的第一条语句)
12、implements
implements用来实现接口,可以多实现,以逗号分割
13、多态
实现多态的条件
- 必须要有子类继承父类的继承关系
- 子类需要对父类中的一些方法进行重写,然后调用方法时就会调用子类重写的方法而不是原本父类的方法
- 在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法
例如:
public class Duo {
public void test () {
System.out.println("Duo");
}
}
public class Duo2 extends Duo {
@Override
public void test() {
System.out.println("Duo2");
}
}
public class Demo {
public static void main(String[] args) {
Duo duo = new Duo2();
duo.test(); // 输出 Duo2
Duo duo = new Duo();
duo.test(); // 输出 Duo
}
}
14、String类
String不是基本数据类型,它在内部存储了一个 final char数组
String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了,如果需要对字符串做很多修改,那么应该选择使用 StringBuffer / StringBuilder
14.1 丰富的构造函数
14.2 不可变有什么好处?
- 字符串常量池的设计需要
String常量池是方法区的一个特殊的储存区。当新建一个字符串(双引号方式)的时候,如果此字符串在常量池中早已存在,会返回一个已经存在字符串的引用,而不是新建一个对象。 - hashCode不会变
在开发中,我们经常用String去做Map的Key,String的hashCode不会变,Map就不用重新获取hashCode
14.3 双引号创建方式和其他创建方式的不同
String str1 = "abc";
String str2 = "abc";
String str3 = "abc";
String str4 = new String("abc");
String str5 = new String("abc");
双引号这种方式创建String,会判断字符串常量池中,有没有这个常量,如果有的话,直接引用,如果没有创建常量
其他的创建方式会在堆里创建对象,堆再引用常量池的常量,返回堆里的引用
14.4 intern方法
我们看到这是个native方法,被native修饰的方法,都是和操作系统的打交道的方法,我们看不到,不能修改
调用intern方法,会判断字符串常量池中是否用,有的话直接返回字符串常量池中的引用,如果没有就创建,返回字符串常量池的引用,证明如下:
String a = "a";
String b = new String("a");
System.out.println(a == b); // false
String c = b.intern();
System.out.println(a == c); // false
14.5 StringBuffer和StringBuilder
这两者相同点:可变字符串类,内部都维护了一个char[] value;
,有扩容机制,capacity()方法可以返回实际的长度
这两者区别:StringBuffer内部使用synchronized 关键字进行修饰,所以是线程安全的,StringBuilder线程不安全
15、内部类
public class Demo {
// 成员内部类
class inner1 {}
// 静态内部类
static class inner2 {}
public void demoMethod() {
// 方法内部类
class inner3 {}
// 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
}
});
}
}
1、成员内部类特点:
成员内部类实例需要通过外部类,如new Demo().new inner1()
,成员内部类可以访问外部类的所有属性方法
2、局部内部类特点:
局部内部类:作用域只能是在方法内
3、静态内部类特点:
不依赖外部类实例,外部类.new 静态内部类()
new创建对象,只能访问外部类的静态属性,可以拥有自己的成员属。通俗意思就是,定义了一个类,这个类可以访问外部类static的成员属性和方法
4、匿名内部类特点:
匿名内部类不能定义任何静态成员、方法和类,通常用来实现一个接口,访问局部变量,这个局部变量必须是final
16、java 是值传递
首先看下面代码
public class Employee {
public String name=null;
public Employee(String n){
this.name=n;
}
//将两个Employee对象交换
public static void swap(Employee e1,Employee e2){
Employee temp = e1;
e1 = e2;
e2 = temp;
System.out.println(e1.name+" "+e2.name); //打印结果:李四 张三
}
public static void main(String[] args) {
Employee worker = new Employee("张三");
Employee manager = new Employee("李四");
swap(worker,manager);
System.out.println(worker.name + " " + manager.name); //打印结果仍然是: 张三 李四
}
}
通过上面的代码,发现形参上的交换,并没有改变实参,所以一定不是引用传递。实际它是地址copy,在swap方法中,我们实际上是改变了地址值,不会影响原实参的地址引用。
我们再来看看一段代码
public class Employee {
public StringBuilder name=null;
public Employee(StringBuilder n){
this.name=n;
}
//将两个Employee对象交换
public static void swap(Employee e1,Employee e2){
e1.name.append("交换");
e2.name.append("交换");
}
public static void main(String[] args) {
Employee worker = new Employee(new StringBuilder("张三"));
Employee manager = new Employee(new StringBuilder("李四"));
swap(worker,manager);
System.out.println(worker.name.toString());
System.out.println(worker.name.toString()+" "+manager.name.toString()); //张三交换 李四交换
}
}
结果是改变了实参,用通俗的话就是,相当于有形参实参参数是指向同一个地址的,打个比方就是有两把钥匙,两把钥匙都可以进入房子,备用钥匙进去,把电冰箱换成了洗衣机,然后主要钥匙再进去肯定是电冰箱换成了洗衣机。但是第一段代码的情况是这样的,有两把钥匙,备用钥匙又重新进锅炉,融化,变成了可以打开其他房子的钥匙,把其他房子的电冰箱换成了洗衣机,肯定是不会影响到主要钥匙的房子的。判断是否发生改变的核心就是看看是否发生了地址的改变。
17、类的初始化顺序
- 初始化父类的静态成员
- 初始化父类的静态代码块
- 初始化子类的静态成员
- 初始化子类的静态代码块
- 初始化父类的非静态成员
- 初始化父类的非静态代码块
- 初始化父类的构造方法
- 初始化子类的非静态成员
- 初始化子类的非静态代码块
- 初始化子类的构造方法
(静态成员 -> 静态代码块) ->( 非静态成员变量 -> 非静态代码块 - > 构造方法)
18、++a 和 a++
++a:先自己+1,再参与运算
a++:参与运算再自己+1
我们看看++a和a++的操作数栈,上代码:
public class Demo {
public static void main(String[] args) {
int a = 1;
// 1 + 3
int b = a++ + ++a;
System.out.println(b); // 4
System.out.println(a); // 3
}
}
操作数栈:
0 iconst_1 // 入栈一个1的值
1 istore_1 // a存入局部变量表第一个位置
2 iload_1 // 加载局部变量表第一个位置到栈 a = 1
3 iinc 1 by 1 // 局部变量表第一个位置自增+1,增加前a=1,增加后a=2
6 iinc 1 by 1 // 局部变量表第一个位置自增+1,此时 a=2,再自增+1, a=3
9 iload_1 // 加载局部变量表第一个位置 a=3
10 iadd // 相加
11 istore_2 // b存入到局部变量表2的位置
12 getstatic #2 <java/lang/System.out> //打印
15 iload_2
16 invokevirtual #3 <java/io/PrintStream.println>
19 return
18、jdk8特性
Lambda
JDK7之前有些情况,我们需要实现一个接口作为参数传入,我们不得不,写一个类来实现接口,而这个接口我们只需要用一次,代码会变得臃肿,lambda的到来,就是为了解决这个问题。
在lamdba没有出现之前,我们想要实现一个接口只有一个方法的时候,从繁到简的演变过程:
@FunctionalInterface
public interface LambdaInterface {
void test();
}
public interface Run {
void test(LambdaInterface);
}
1、定义一个专门的类来实现
public class LamdbaImpl implements LambdaInterface {
@Override
public void test() {
}
}
public class LamdbaDemo {
public static void main(String[] args) {
Run.test(new LamdbaImpl())
}
}
2、静态成员内部类
public class LamdbaDemo {
static class LamdbaInner implements LambdaInterface {
@Override
public void test() {
}
}
public static void main(String[] args) {
Run.test(new LamdbaInner())
}
}
3、局部内部类
public class LamdbaDemo {
public static void main(String[] args) {
class LamdbaInner implements LambdaInterface {
@Override
public void test() {
}
}
Run.test(new LamdbaInner())
}
}
4、匿名内部类
public class LamdbaDemo {
public void test() {
LambdaInterface lambdaInterface = new LambdaInterface() {
@Override
public void test() {
}
};
Run.test(lambdaInterface)
}
}
5、lamdba
public class LamdbaDemo {
public void test() {
Run.test(()->{
})
}
}
使用lamdba会可以让我们的代码简洁很多,但是同时也会降低可读性,阿里巴巴开发手册中,规定了不允许用lamdba。
tips:在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错,可以拥有若干个默认方法。
注意点:lamdba内部访问外部方法内的变量必须是final的
例如:
public class LamdbaDemo {
public static void main(String[] args) {
int i = 1;
new Thread(()-> {
i++; // error
System.out.println(i);
}).start();
}
}
lamdba方法引用
-
引用静态方法:
引用静态方法,接口 = Class名::静态方法名,如果接口的返回类型为void,静态方法的返回类型可以为任意的,如果接口的返回类型不为void,静态方法的返回类型需要和接口一致。接口和静态方法的参数类型,顺序必须一样。
public class LamdbaDemo { public static void main(String[] args) { // Person person = food -> { // LamdbaDemo.eat(food); // }; Person person = LamdbaDemo::eat; person.eat("1"); } static String eat(String food) { System.out.println(food); return food; } } interface Person { void eat(String food); }
-
引用实例方法:
引用实例方法,接口 = Class名::成员方法名,如果接口的返回类型为void,成员方法的返回类型可以为任意的,如果接口的返回类型不为void,静态方法的返回类型需要和接口一致。接口和方法的参数类型,从第二开始算起,顺序必须一样,因为第一个参数是实例对象。
public class LamdbaDemo { public static void main(String[] args) { Person person = LamdbaDemo::eat; person.eat(new LamdbaDemo(), "niu"); } public String eat(String food) { System.out.println(food); return food; } } interface Person { void eat(LamdbaDemo lamdbaDemo, String food); }
-
引用this
引用this和实例方法的区别就是,实例就是this,可以省略不写public class LamdbaDemo { public void test() { Person person = this::eat; person.eat("niu"); } public String eat(String food) { System.out.println(food); return food; } } interface Person { void eat(String food); }
Stream API
stream是优化集合处理的优化,它是链式的,对于处理数据比较大的时候,效率很高,对于数据量比较小,直接使用普通的for效率比较高
Stream和Collection集合的区别:
- Collection是一种静态的内存数据结构,而Stream是有关计算的
- 前者是主要面向内存的,存储在内存中;后者主要是面向CPU,通过CPU实现计算.
- 集合讲的是数据,Stream讲的是计算
串行Stream:
这位老哥写的还可以,点击跳转
并行Stream:
多个线程来处理流,将一个大任务,分而治之分成很多个线程去执行(fork/join原理)
获取并行流的方式:
-
通过已有的串行流获取并行流
List<Integer> integers = Arrays.asList(1,2,3,4); Stream<Integer> stream = integers.stream(); // 通过已有的串行流获取并行流 Stream<Integer> parallel = stream.parallel();
-
直接获取
List<Integer> integers = Arrays.asList(1,2,3,4); // 直接获取 Stream<Integer> stream = integers.parallelStream();
并行流的线程安全问题:
ArrayList<Integer> integers = new ArrayList<>();
// 添加一万个数据到integers
for (int i = 0; i < 10000; i++) {
integers.add(new Random().nextInt(1000));
}
ArrayList<Integer> integers2 = new ArrayList<>();
// 获取integers的并行流
integers.parallelStream().forEach(x -> {
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
integers2.add(x);
});
System.out.println(integers2.size());
解决方式:
- 加锁
- 使用并发容器
- 使用collect生成List
并行Stream 原理(frok join pool):
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架, 核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果
Fork/Join框架使用一个巧妙的算法来平衡线程的负载,称为工作窃取(work-stealing)算法。工作窃取的运行流程图如下:
fork join属于并发编程系列:看这篇文章,点击跳转
Optional类
optional类是为了解决我们日常开发中多嵌套判断是否为null而存在的,看一个例子:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
return isocode.toUpperCase();
}
}
}
}
这样的代码非常的难维护,如果我们换成Optional
首先,重构类,使其 getter 方法返回 Optional 引用:
public class User {
private Address address;
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
// ...
}
public class Address {
private Country country;
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
// ...
}
现在我们可以这样写
User user = new User("anna@gmail.com", "1234");
String result = Optional.ofNullable(user)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("default");
方法:
日期时间API
简单来说就是。jdk7之前的日期api不健全,线程不安全,不丰富,现在新增了几个类,都是线程安全的
- LocalDate
- LocalTime
- LocalDateTime
- Instant
- Duration 计算时间差
- Period 计算日期差
- TemporalAdjuster 时间调节器。比如,获得下个星期日,当月的最后一天或下一年的第一天的日期
- ZonedDateTime 支持时区的日期时间类
可以很清晰的看出ZonedDateTime相当于LocalDateTime+ZoneId
这篇文章写的很好:点击跳转
19、枚举类
如果我们需要定义一组常量,最好是是使用枚举类
特点:可以进行排序,构造方法是私有的,不能再继承类。
方法:
java集合
文章太长了,放在我另一篇博客中了,点击跳转
Java多线程
待更新
java IO
待更新
java 反射
待更新
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?