JAVA SE基础
JAVA SE(笔记)
基础操作类似其余语言,不多说
- 基本数据类型(数据类型 多了个BigDecimal BigInteger, BigDecimal更适合需要精确计算的场景)
- 运算符
- 流程控制 (块级作用域 ,前端JS是函数作用域,ES6引入了let 才有块级作用域)
- while 语句 不止是whle() {},还可以 do {} while()
- break 如果是嵌套 可以标识跳出哪一层循环
基础操作练习
寻找1000以内水仙花
也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数 ,指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身
1
2
3
4
5
6
7
8
9
10
11
12public class Main {
public static void main(String[] args) {
for (int i = 1; i <1000 ; i++) {
int a = i%10; // 取模获取个位数
int b = (i/10)%10;
int c = (i/100)%10;
if ((Math.pow(a,3)+Math.pow(b,3)+Math.pow(c,3)) == (double) i) {
System.out.println(i);
}
}
}
}斐布拉奇数列 (1000以内的)
1
2
3
4
5
6
7
8
9
10
11
12
13public class Main {
public static void main(String[] args) {
int a = 1;
int b = 1;
System.out.println(a);
while (a+b < 1000){
System.out.println(b);
int temp = a+b;
a =b;
b= temp;
}
}
}
面向对象
基本概念同其他语言,只列出常用不一样地方以及理解
创建对像属性都会存在初始值,如果是基本类型,那么默认是统一为
0(如果是boolean的话,默认值为false)如果是引用类型,那么默认是null某个分支有return 则其余分支也需要return
按值传递
静态方法 静态变量属于类的
我们实际上是将
.class文件丢给JVM去执行的,而每一个.class文件其实就是我们编写的一个类,我们在Java中使用一个类之前,JVM并不会在一开始就去加载它,而是在需要时才会去加载(优化)一般遇到以下情况时才会会加载类:- 访问类的静态变量,或者为静态变量赋值,调用类的静态方法
- new 创建类的实例(隐式加载)
- 子类初始化时
- 反射加载
静态变量-静态代码块-成员变量-普通代码-构造方法
访问权限 共有>不同包子类>包内访问>自己能访问
私有和公有很好理解,私有只能当前类访问,共有所有类都可以访问
默认和protected 默认就同一个包下面的类 protected 就是同一个包下面的类 + 不同包下的子类
问题: 为啥普通类不能有用protected修饰
包和文件夹是一样的 只不过建包可以一次性建好多层目录,另外标注为包后可以2层目录一个包显示,idea的功能
子类转父类,可以自动转(向上转型) 。可以使用强制类型转换,将一个被当做父类使用的子类对象,转换回子类。(向下转型 )
封装-继承-多态,最精髓的多态 ,不同对象对同一个消息做出不同响应
子类重写父类的方法,不能降低可见性
抽象类 ,抽象方法,不能实例化,但是可以方便向上转型
如果一个类中有一个抽象方法,那么当前类一定是抽象类;抽象类中不一定有抽象方法。抽象方法需要有子类实现,如果子类不实现,则子类也需要定义为抽象方法
接口一定是抽象的 ,一定都是public,没有构造方法。接口有点像特殊的抽象类,没有属性,接口重要的特征就是实现类似多继承 一种方案。
包装类的缓存机制,超出缓存范围了就会new ,得到不同对象。从缓存取是同一个对象,具体可以点进去看包装类的源码。实际就是等于包装类.valueOf()
非包装类 数字等 必须要以对象方式操作,像BigInteger 等
看很多代码实现某个接口,就具备了某些功能,但是实际是个空接口,java.lang.Cloneable,告诉java这个类需要具备什么功能,打标识后,底层会做对应的处理
接口中都是抽象方法和常量,不可能有非抽象方法,常量必须被赋值。
常量默认被public static final
方法默认被public static abstractl修饰
JAVA数组内存空间设计连续的,所以长度需要固定,不可以修改
内部类
内部类是定义在另一个类中的类,内部类相比较其他语言 ,语法就有点别扭。内部类可以对同一个包中的其他类隐藏起来,有时候想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
成员内部类
可以无条件访问外部类的所有成员属性和成员方法
外部类必须通过成员内部类的对象来访问内部类的属性和方法
成员内部类是对象的属性,每个对象都有个内部类属性
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class Outer {
String var ="外部类";
public class Inner {
String var ="内部类";
public void print() {
System.out.println("内部类的变量--"+ this.var);
System.out.println("外部类的变量--"+ Outer.this.var);
System.out.println("就近原则--"+ var);
}
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner= outer.new Inner();
inner.print();
}
}
打印结果
内部类的变量--内部类
外部类的变量--外部类
就近原则--内部类
静态内部类
静态内部类就像静态方法和静态变量一样,是属于类的, 内部类如果没指向外部类成员变量,将不会初始化外部类。实际会编译成2个类,有引用才会初始化类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package src.com.entity;
public class OuterStatic {
String var ="外部类";
public static class Inner {
String var ="静态内部类";
// 无法访问外部非静态内容
public void print() {
System.out.println(var);
}
}
}
package src;
import src.com.entity.OuterStatic;
public class Main {
public static void main(String[] args) {
OuterStatic.Inner inner = new OuterStatic.Inner();
inner.print();
}
}局部内部类
方法里面的类,作用范围同其余局部变量,只能方法里面用
匿名内部类(使用频率最高)
正常情况下,要创建一个抽象类的实例对象,只能对其进行继承,先实现未实现的方法,然后创建子类对象。接口也是一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package src.com.entity;
public abstract class AbstractInner {
public void print() {
System.out.println("抽象类");
}
}
package src;
import src.com.entity.AbstractInner;
public class Main {
public static void main(String[] args) {
AbstractInner abstractInner = new AbstractInner() {
@Override
public void print() {
super.print();
}
};
abstractInner.print();
}
}Lambda表达式、
如果接口里面只有一个带实现的抽象方法,那么我们可以将匿名内部类简写为lambda表达式。lambda仅仅支持接口 不支持抽象类
1
2
3
4
5
6
7
8// study 是个接口
Study study = ()-> {
System.out.println("lambada");
};
study.print();
// 方法引用语法
Study study= Interger::sum
Study study= String::new问题
为啥局部内部类和匿名内部类只能访问final
内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁.
当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的”copy”。这样就好像延长了局部变量的生命周期
但是为什么这里用final却又可以访问呢?
因为Java采用了一种copy local variable的方式来实现,也就是说把定义为final的局部变量拷贝过来用,而引用的也可以拿过来用,只是不能重新赋值。从而造成了可以access local variable的假象,而这个时候由于不能重新赋值,所以一般不会造成不可预料的事情发生和js有区别 ,js垃圾回收是引用标志,有引用就会一直存在。
异常处理
异常 Exception
- RuntimeException 数组越界异常,空指针异常,算术异常
- 编译时异常明确指出可能会出现的异常,在编译阶段就需要进行处理(捕获异常)必须要考虑到出现异常的情况
具体右键图表

错误比异常更严重,异常就是不同寻常,但不一定会导致致命的问题,而错误是致命问题,一般出现错误可能JVM就无法继续正常运行了,比如内存溢出
当程序没有按照我们理想的样子运行而出现异常时(默认会交给JVM来处理,JVM发现任何异常都会立即终止程序运行,并在控制台打印栈追踪信息)现在我们希望能够自己处理出现的问题,让程序继续运行下去,就需要对异常进行捕获
泛型
- 泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译!因为是具体使用对象时才会明确具体类型,所以说静态方法中是不能用的
- 引入泛型,可以提高代码的可重用性
- 泛型类型限定(泛型类和泛型方法都可以使用类型限定)
- 泛型通配符?形如<? extends 类名><? super 类名>
- 编译器在编译时会进行泛型擦除,未定义上下限的就是Object,但是会有一个signature弱标记,这也是能通过反射拿到泛型具体类型的原因
函数式编程工具接口
Supplier 生产者
1
2
3
4Supplier <Integer> supplier = ()-> {
return 55;
};
System.out.println(supplier.get());Consumer 很常用, 其实和JS里面的回调有点类似,可以把参数给第三方函数去消费
1
2
3
4
5Consumer<Integer> consumer = (a)-> {
System.out.println(a);
};
consumer.andThen(x-> {
}).accept(6666);Function 先执行compose apply 在执行andThen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Function <Number, String> nfunction = new Function<Number, String>() {
@Override
public String apply(Number number) {
return number.toString();
}
};
String i = "444";
String n = nfunction
.compose(String::length)
.andThen(s-> {
System.out.println(s.toUpperCase());
return s.concat("拼接");
})
.apply("3333");
System.out.println(n);Predicate .test() 返回bool值 .and .nagate .or 等逻辑运算,语义化
optional 判断空
1
2
3Optional.ofNullable(i).ifPresent(s -> {
System.out.println(s);
}).orElse(s-> {}).orElseGet()
集合
一般使用接口List<E>,方便后面Array改Linked等 切换
ArrayList 底层是数组实现的 用size记录长度,动态改变size和存储内容
tips: ArrayList 实现里面有个 transient Object[] elementData
java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据 成员,我们不想用serialization机制来保存它。****为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字 transient****。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的

1 | |
- LinedList

- iterator 迭代器
1 | |
队列 Dueue 队列接口 Deque 双端队列接口。通常将LinkedList当队列使用

add 会报异常
offer 同add 返回bool
remove 移除队首 会报异常
poll 移除队首 队列为空会返回null
element 获取队首元素 会报异常
peek 获取队首 为空会返回null
Deque 和前端有点像 子接口 功能更加强大

优先级队列 PriorityQueue
1
2
3
4
5
6
7
8
9Queue<Integer> q = new PriorityQueue<>((a,b)-> {
return b-a;
});
q.offer(3);
q.offer(1);
q.offer(2);
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());Set 同js 不重复 不支持随机访问(不允许下标访问)
底层是hash散列,所以无序存储的,HashSet ,
如果需要遍历顺序保持和插入时候一致 用TreeSet 和 LinkedHashSet
TreeSet 可以传入一个compare 指定排序规则
顺便记录下测试时候写的 一个bug,预估的是打印3个,结果吞掉一个,

原因是 底层采用了HashMap,用Map的key保存set的value值,但是Map的value是Present,finale new Object(),
add 时候 进行比较 结果是0,只更新了相同key的 value值,未更新key值 。 最终set取的是map的key值,所以导致丢失

reeSet/TreeMap集合 自平衡二叉树 中序遍历

- Map 键值对存储 具体查文档
集合Stream 流以及集合工具类
集合流和IO流是2种东西,集合流是对集合增强的一个东西,总体流程
集合 -> 数据流->转换操作->终点操作 或者短路操作
转换操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel
终点操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny
短路操作: anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit.
demo
1 | |
有点类似Javascript的 函数式编程,确实方便许多,API查文档
文件IO
输入流是读取 输出流是write
输入、输出字节流 FileInputStream FileOutputStream
输入、输出字符流 FileReader FileWriter
缓存文件流 BufferedInputStream BufferedOutputStream close或者flush或者缓存区满了 就会保存到数组里面
转换流 InputStreamReader和OutputStreamWriter本质也是Reader和Writer,可以转化为其他流
打印流 PrintStream也继承自FilterOutputStream类因此依然是装饰我们传入的输出流,但是它存在自动刷新机制 类似还有个Scanner 输入
数据流 DataInputStream 最大的不同是它支持基本数据类型的直接读取
对象流 ObjectOutputStream ObjectInputStream 不仅支持基本数据类型,通过对对象的序列化操作,以某种格式保存对象,来支持对象类型的IO
网上找到图片 很全

1 | |
多线程
多线程入门 Thread, 构造函数入参 传入一个Runnable接口的实现类对象
1 | |
线程状态
新生状态(New) 就绪状态(Runnable) 运行状态(Running) 阻塞状态(Blocked) 死亡状态(Terminated)
转化如下

每一个Thread对象中,都有一个
interrupt()方法,调用此方法后,会给指定线程添加一个中断标记以告知线程需要立即停止运行或是进行其他操作,由线程来响应此中断并进行相应的处理,我们前面提到的stop()方法是强制终止线程,interrupted 复位,清楚标识继续运行Thread.currentThread().suspend(); Thread.currentThread().resume(); //恢复此线程 //暂停此线程 次方法不推荐了
1 | |
线程的优先级
Java采用的是抢占式调度方式,优先级越高的线程,优先使用CPU资源
- MIN_PRIORITY 最低优先级
- MAX_PRIORITY 最高优先级
- NOM_PRIORITY 常规优先级
- thread.setPriority(Thread.MIN_PRIORITY);
- 优先级越高的线程,获得CPU资源的概率会越大,并不是说一定优先级越高的线程越先执行!
线程的礼让和加入
thread.yield(); 将CPU资源让位给其他线程
thread.join(); 加入到当前线程 一个线程等待另一个线程执行完成后再继续进行,
线程锁和线程的同步

多线程共同读写主内存里面的东西,由于有缓存,每个线程都从主内存copy到工作内存,在工作内存修改后同步到主内存。会导致A线程已经修改了某个变量,但是没同步到主内存,而 B线程已经读取这个变量到缓存里面,缓存里面的这个变量就不一致了,写回主内存就不一样
于是 引入了缓存一致性问题,
于是引入 synchronized关键字来创造一个线程锁
synchronized代码块,它需要在括号中填入一个内容,必须是一个对象或是一个类,本质上都是对象锁,类锁实际也是锁的类的Class对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class SynchronizedThread {
static int i = 0;
static int itempI = 0;
public static void main(String[] args) throws InterruptedException {
Thread Thread1=new Thread(()->{
synchronized (SynchronizedThread.class) {
for (int j=0;j<10000;j++) {
i++;
}
}
});
Thread Thread2=new Thread(()->{
synchronized (SynchronizedThread.class) {
for (int j=0;j<10000;j++) {
i++;
}
}
});
Thread1.start();
Thread2.start();
sleep(3000);
System.out.println(i);
}
}锁还可以加在方法上 如果是静态方法,就是使用的类锁,而如果是普通成员方法,就是使用的对象锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SynchronizedThread {
static int i = 0;
static int itempI = 0;
public static synchronized void add1() {
i++;
}
public static void main(String[] args) throws InterruptedException {
Thread Thread1=new Thread(()->{
for (int j=0;j<10000;j++) {
add1();
}
});
Thread Thread2=new Thread(()->{
for (int j=0;j<10000;j++) {
add1();
}
});
Thread1.start();
Thread2.start();
sleep(3000);
System.out.println(i);
}
}死锁,如下图,两个线程相互持有对方需要的锁,但是又迟迟不释放

要在synchronized里面使用
await: 当前线程进入等待状态,并且释放对象锁 可以设置最大等待时间
notify: 随机唤起一个 且在notify后,当前线程不会马上释放锁,要等到当前线程被synchronized修饰的代码执行完,才会释放锁;
notifyall: 唤起所有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class LockThread {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread Thread1=new Thread(()->{
synchronized (o) {
System.out.println("执行Thread1 流程");
System.out.println("开始释放锁");
try {
o.wait(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行完毕Thread1结束");
}
});
Thread Thread2 = new Thread(()->{
synchronized (o) {
System.out.println("开始执行Thead2");
System.out.println("通知不用等待了");
o.notify();
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行完毕Thread2 流程");
}
});
Thread1.start();
Thread2.start();
}
}ThreadLocal
- 可以使用ThreadLocal类,为当前线程存值
- InheritableThreadLocal 只是继承初始值 作用域和ThreadLocal一样
定时任务 timeTask是个实现了Runnable的抽象类,不是个接口 ,没法用lambda表达式。。。感觉好累赘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Timer {
public static void main(String[] args) {
java.util.Timer timer = new java.util.Timer();
final Integer[] a = {0};
timer.schedule(new TimerTask() {
@Override
public void run() {
a[0]++;
if(a[0] == 10) {
timer.cancel();
}
System.out.println("延迟1s执行 后面每隔2s执行一次");
}
},1000,2000);
}
}守护线程
Daemon英文意思是希腊神话中半人半神的精灵,守护神。在java中,”精灵守护Daemon线程”就是运行在程序后台的线程,一般被用于在后台为其它线程提供服务。既然它在后台运行,当前台线程(前几节学习的线程)运行完,主体程序就结束了,理所当然该后台线程也应该随之结束了
当一个应用程序的所有非精灵线程停止运行时,应用程序也将终止,守护线程自动停止
t.setDaemon(true); //设置为守护线程(必须在开始之前,中途是不允许转换的)
并行方法
1
2
3
4
5
6
7
8public class parallerlCollect {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("1-fef","2-dewdw","3-frfrfrf","4-fef","5-dewdw","6-frfrfrf","7-fef","8-dewdw","43-frfrfrf"));
list.parallelStream().forEach((ele)-> {
System.out.println(Thread.currentThread().getName() + ele);
});
}
}集合类并发安全( java.util.concurrent.)
JAVA SE练习
- 游戏开发 飞机大战