学习笔记——javaSE
一、学习重点

二、学习内容
练习:
我们之前写过的超级数组,链表,队列,栈
他们4个有一个公共的泛型接口,这4个结构都实现这个泛型接口,重写里面的抽象方法。
设计的就是一个工具集。
package com.jsoft.morning; /** * 现在,这个超级数组的类就是一个泛型类 * * 当我们在一个类后面加上了泛型<T>,这个T就是一个数据类型。 * 既然T就是一个数据类型,那我们就可以拿过来用。 * * */ public class SuperArray<T> { // 声明了一个T类型的变量t private T t; // 维护一个数组,要考虑的是怎么存 private Object [] array; // 超级数组的长度 private int size; // 数组当前的容量 private int capacity; public SuperArray(){ // array = new Integer[10]; this(10); // capacity = 10; } public SuperArray(int capacity){ array = new Object[capacity]; this.capacity = capacity; } // 添加数据,默认添加,在数组的尾部添加 public void add(T data) { // 添加时要确保容量足够,如果不够,就需要扩容 ensureCapacity(size + 1); // 真正的添加数据 array[size++] = data; } // 添加数据,传入两个参数 // 在指定位置添加 public void add(int index,T data){ if(rangeCheck(index)){ ensureCapacity(size + 1); System.arraycopy(array,index,array,index + 1,size - index); // 真正的添加数据 array[index] = data; size++; } } // 删除最后一个数据 public Object remove(){ if(size > 0){ return array[--size]; } return null; } // 删除指定下标位置的元素 public Object remove(int index){ if(rangeCheck(index)){ Object res = array[index]; System.arraycopy(array,index + 1,array,index,(--size - index)); return res; } return null; } // 修改 public boolean set(int index,T data) { if(rangeCheck(index)){ array[index] = data; return true; } return false; } // 获取超级数组的长度 public int size(){ return size; } // 获取指定下标的元素 public Object get(int index) { // 判断一下index和合法性 if(rangeCheck(index)){ return array[index]; } return null; } private boolean rangeCheck(int index) { // index >= 0 // index <= size - 1 return (index >=0 && index <= size - 1); } // 这个方法只在当前类使用,所以声明成private private void ensureCapacity(int needCapacity) { // System.out.println(needCapacity + "-----" + capacity); if(needCapacity > capacity){ // 1.5倍 capacity = capacity + (capacity >> 1); // 创建一个新的扩容好的数组 Object [] newArray = new Object[capacity]; // 把原数组的数据拷贝过来 /* src:原数组 srcPos:拷贝原始数组的起始位置 dest:目标数组 destPos:目标数组的起始位置 length:拷贝数据的长度 */ System.arraycopy(array,0,newArray,0,array.length); array = newArray; } } }
泛型类
/** * 接口中存储的一定是大家都能用的。 * removeFirst * removeLast * * 以后我们面向接口编程,接口里的方法不能出现实现类中方法体为空的情况。 * * * @param <T> */ public interface DataContainer<T> { void add(T t); void update(Integer index, T t); T remove(Integer index); T get(Integer index); }
/** * 现在,这个超级数组的类就是一个泛型类 * * 当我们在一个类后面加上了泛型<T>,这个T就是一个数据类型。 * 既然T就是一个数据类型,那我们就可以拿过来用。 */ public class SuperArray<T> implements DataContainer<T> { // 声明了一个T类型的变量t private T t; // 维护一个数组,要考虑的是怎么存 private Object [] array; // 超级数组的长度 private int size; // 数组当前的容量 private int capacity; public SuperArray(){ // array = new Integer[10]; this(10); // capacity = 10; } public SuperArray(int capacity){ array = new Object[capacity]; this.capacity = capacity; } // 添加数据,默认添加,在数组的尾部添加 public void add(T data) { // 添加时要确保容量足够,如果不够,就需要扩容 ensureCapacity(size + 1); // 真正的添加数据 array[size++] = data; } @Override public void update(Integer index, T t) { set(index,t); } @Override public T get(Integer index) { // 判断一下index和合法性 if(rangeCheck(index)){ return (T) array[index]; } return null; } // 添加数据,传入两个参数 // 在指定位置添加 public void add(int index,T data){ if(rangeCheck(index)){ ensureCapacity(size + 1); System.arraycopy(array,index,array,index + 1,size - index); // 真正的添加数据 array[index] = data; size++; } } // 删除最后一个数据 public T remove(){ if(size > 0){ return (T) array[--size]; } return null; } @Override // 删除指定下标位置的元素 public T remove(Integer index){ if(rangeCheck(index)){ T res = (T) array[index]; System.arraycopy(array,index + 1,array,index,(--size - index)); return res; } return null; } // 修改 private boolean set(int index,T data) { if(rangeCheck(index)){ array[index] = data; return true; } return false; } // 获取超级数组的长度 public int size(){ return size; } private boolean rangeCheck(int index) { // index >= 0 // index <= size - 1 return (index >=0 && index <= size - 1); } // 这个方法只在当前类使用,所以声明成private private void ensureCapacity(int needCapacity) { // System.out.println(needCapacity + "-----" + capacity); if(needCapacity > capacity){ // 1.5倍 capacity = capacity + (capacity >> 1); // 创建一个新的扩容好的数组 Object [] newArray = new Object[capacity]; // 把原数组的数据拷贝过来 /* src:原数组 srcPos:拷贝原始数组的起始位置 dest:目标数组 destPos:目标数组的起始位置 length:拷贝数据的长度 */ System.arraycopy(array,0,newArray,0,array.length); array = newArray; } } public static void main(String[] args) { SuperArray<String> superArray = new SuperArray<>(); superArray.add("a"); superArray.add("d"); superArray.add("c"); superArray.add("b"); for (int i = 0; i < superArray.size(); i++) { System.out.println(superArray.get(i)); } } }
三、笔记内容
面试八股文
JavaSE高级部分
1.泛型
2.枚举
3.多线程 前3年(难)
4.集合(数据结构,树,二叉树,红黑树,B+树,B-树)
5.IO流(文件)
6.反射和注解
7.网络通信Socket(tomcat)
泛型:(Generics)
之前咱们写的超级数组中要么只能存数字,要么啥都能存。
有一些鸡肋。利用泛型可以解决问题
什么是泛型?
泛型广泛的、普通的类型。
泛型能够帮助我们把【类型明确】的工作推迟到创建对象或者调方法的时候。
意思就是:我定义类的时候,不需要考虑这个数组到底要存什么类型。
创建这个超级数组对象的时候把里面要存的数据的类型确定下来。
泛型的修饰:
1、泛型类
2、泛型方法
3、泛型接口
泛型类把泛型定义在类上,用户在使用类的时候才能把类型给确定。
具体的使用方法使用<>加上一个未知数。通常用T K V E等大写字母表示。
实际上用啥都行,只要是个单词就可以。
public static void main(String[] args) { // 如果一个泛型类,在创建对象的时候没有指定泛型类型 // 默认还是Object // 在使用这个类的时候,去确定泛型类型 // 现在这个超级数组就只能存String类型 // 不规范 SuperArray<String> superArray = new SuperArray(); superArray.add("a"); // superArray.add(1); // 这个超级数组superArray1就只能存Employee类型 // 在JDK7以后,可以省略等号右边的泛型的声明,<>必须得写,规范 // 类型推断 SuperArray<Employee> superArray1 = new SuperArray<>(); superArray1.add(new Employee()); superArray1.add(new Employee()); superArray1.add(new Employee()); superArray1.add(new Employee()); // 完整写法,JDK7以前 SuperArray<Employee> superArray2 = new SuperArray<Employee>(); }
泛型方法:
我们如果只关心某个方法,可以不定义泛型类。
只定义泛型方法。
泛型方法是不是一定要在泛型类里?不是
泛型类里是不是一定要有泛型方法?不是
在定义泛型方法时,要首先定义泛型类型。
定义在方法中间,泛型的使用处之前
使用泛型方法,最好要结合返回值,和Object一样。
我们到后面学习反射的时候再说!!!
public <T> T show(T t) { // 拿着这个t在方法中做好多事情,再把t返回回去 // 调用另一个方法 System.out.println(t); return t; } public static void main(String[] args) { new Ch03().show("哈哈"); }
继承关系
泛型类在继承时:
1.父类是一个泛型类,子类要不要是泛型类?
2.永远记住,泛型的声明只能在当前类名后或者方法中间,而且声明的泛型是自己的。
3.在子类继承父类时,子类泛型和父类泛型都写出来的情况下,父跟子
4.如果在继承时,没有写出任何泛型,当前子类就不是泛型类。
class Father<T> { T t; } // 在确定子类泛型的时刻,父类的泛型和子类一样 class Son<T> extends Father<T> { } // class Son2 extends Father { } public class Ch04 { public static void main(String[] args) { Son<Employee> son = new Son<>(); son.t = new Employee(); Son2 son2 = new Son2(); son2.t.notify(); } }
如果在一个泛型类中,尽量就不要再使用泛型方法。
泛型方法多数都是出现在非泛型类。
静态泛型方法:
1.静态方法如果是泛型方法,泛型的声明必须写。
2.因为静态结构是属于类的,不属于某个对象。
@param <T>
interface Inter<T> { T show(T t); static <T> T info(T t){ return t; } } class Demo01<T> implements Inter<T> { // @Override // public Object show(Object o) { // return null; // } @Override public T show(T t) { return t; } } public class Ch05 { public static void main(String[] args) { Inter.info(1); } }
?通配符
可以接受任何类型。
如果使用Object类型,别写泛型。
泛型约束类的数据类型。
JDK1.5之后泛型。JDK5
@param superArray
class Animal { } class Dog extends Animal { } class Teddy extends Dog { public static void show(SuperArray<? super Teddy> superArray) { } } public class Ch06 { public static void main(String[] args) { SuperArray<Animal> superArray = new SuperArray<>(); Teddy.show(superArray); } }
类型参数化。
类型擦除:
为了兼容性,使用原始类型(没有泛型)是可以的。
泛型刚刚出现的时候,还存在大量的不适用泛型的代码。
保证代码的兼容性,将参数化类型的实例传递给设计用于原始类型的方法必须是合法的。
为了保持兼容性,Java泛型中,其实有一种类似伪泛型,因为Java在编译期间,
所有的泛型都会被擦掉。
Java的泛型语法是在编译期这个维度上实现的。
正常来说在生成的字节码文件中,不包含泛型的类型信息的。
在JVM中看到的只是SuperArray,由泛型附加的类型信息对JVM是看不到的。
可以理解为,泛型的本质就是让程序员在编写代码时遵守的一个规则。
比如SuperArray:在确定了泛型之后,这个超级数组中就统一只放同一类型的数据。
如果放入其他类型,编译不通过。
经验:开发中,能够在业务上解决的问题,尽量不要在技术上解决。
1.泛型不能是基本数据类型。(原则上来说,数组可以作为泛型,语法角度,不可以)
<>里面放的就应该是类名。数组是在编译后才会生成一个类($xxxx)
2.方法重载:a.同一个类里 b.方法名相同 c.参数不同
原理:类型擦除。
3.多态上。
public static void show(SuperArray<Employee> superArray){ } public static void main(String[] args) { SuperArray<Object> superArray = new SuperArray<>(); superArray.add(10); }
泛型的应用场景:
1.父类(接口),起到的是一个规范的作用,对里面的数据类型没有明确要求。
2.容器类。(超级数组,链表,队列,栈)
当类型无法确定时,使用泛型。
开发中,我们更多的是会使用到一些泛型类或泛型接口。
可以在参数中传递接口类型,利用多态来实例化。
枚举类型
应用场景:
在某些情况下,一个类的对象的个数是有限的,
如季节,春夏秋冬,比如24节气,星期。。。
规定这个类的对象的个数。
public static void main(String[] args) { Ch01 ch01 = new Ch01(); Calendar instance = Calendar.getInstance(); instance.get(Calendar.YEAR);// int类型 }
SeasonEnum
/** * 大更新 * JDK1.5更新了枚举类 * * 枚举类中: * 把需要用到的对象声明出来 * 写对应的构造器 * 可以有set,get方法 */ public enum SeasonEnum { SPRING(1,"春天"), SUMMER(2,"夏天"), AUTUMN(3,"秋天"), WINNER(4,"冬天"); // public static final Season SPRING = new Season(1,"春天"); private Integer value; private String name; SeasonEnum(Integer value, String name) { this.value = value; this.name = name; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
SeasonEnum1
public enum SeasonEnum1 { SPRING(1,"春天1"); private Integer value; private String name; SeasonEnum1(Integer value, String name) { this.value = value; this.name = name; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
枚举在switch中使用
JDK1.5之后
枚举类的命名规则:所有的枚举类要以Enum结尾。
public static void main(String[] args) { SeasonEnum season = SeasonEnum.SPRING; switch (season) { case SPRING: case SUMMER: case AUTUMN: case WINNER: } }
枚举的静态导入
*号代表导入枚举类的所有对象
import static com.jsoft.afternoon.SeasonEnum.*; /** * 枚举的静态导入 */ public class Test { public static void main(String[] args) { System.out.println(SPRING.getName()); } }
枚举的优势
1.int类型不具备安全性。假如某个程序员在定义int时少写了个final,
会存在被他人修改的风险。枚举类,它天然就是一个常量类
2.使用int类型,语义不够明确。如果说在控制台打印输入1。
public static void main(String[] args) { // Integer seasonStr = 1; // if(seasonStr == SeasonConstant.SPRING) { // System.out.println(1); // } // 枚举里面都是常量,静态 // 推荐枚举的比较使用 == SeasonEnum spring = SeasonEnum.SPRING; String name = "春天"; SeasonEnum1 spring1 = SeasonEnum1.SPRING; // if(name.equals(spring.getName())){ // System.out.println("春天"); // } if(spring1.equals(spring)) { System.out.println(123); } }
单例模式
使用枚举类实现单例模式。
高效Java
单元素的枚举类型已经成为实现单例模式的最佳方案。
class Singleton { // 私有化构造器 private Singleton() {} // 提供公有的获取实例的静态方法 public static Singleton getInstance(){ return SingletonHolder.INSTANT.instant; } // 声明一个枚举类(内部类) private enum SingletonHolder{ INSTANT; private final Singleton instant; SingletonHolder() { instant = new Singleton(); } } } public class Test { public static void main(String[] args) { System.out.println(Singleton.getInstance() == Singleton.getInstance()); } }
MyException
public class MyException extends RuntimeException { private ResultMessage message; public MyException(ResultMessage message) { this.message = message; } public MyException(String message, ResultMessage message1) { super(message); this.message = message1; } public String getMessage() { return message.getMessage(); } }
ResultMessage
public enum ResultMessage { USERNAME_NOT_EXIST(101,"用户名不存在"), USERNAME_ALREADY_EXIST(102,"用户名已存在"); private Integer code; private String message; ResultMessage(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
多线程前置
我们要学习多线程,需要学习一些计算机组成的一些知识。
cpu8核以上和内存16G。
开一个虚拟机,开一个数据库,开git,开xshell,开idea,
开Nginx,开redis,启动N多个微服务。
为了完成特定的任务,用某种编程语言写一个软件(程序)。
程序要想运行就必须加载到内存中执行。
在执行程序的时候,实时的指令加载到cpu内存的指令寄存器中执行。
执行过程中产生的数据要加载到数据寄存器中。ALU负责进行算术逻辑运算的操作。
系统总线(System Bus):连接计算机系统的主要组件,用来降低成本和促进模块化。
可以通过软件来控制硬件。
进程:
一个正在执行中的程序就是一个进程,系统就会为这个进程发配独立的【运行资源】
进程是程序的一次执行过程,它有自己的生命周期
它会在启动程序时产生
运行程序时存在
关闭程序时消亡
比如:QQ,idea,腾讯会议,PDF
早期的计算机,单进程,同一时间只能执行一个进程。
计算器。同一时间只能执行一段代码。
随着计算机的发展,CPU的计算能力大幅提升,
按照时间线交替执行不同继承的方式。
每个执行一点点时间,感官上觉得这么多的进程是同时在运行。
如果一个进程有多个任务
打开浏览器,下载游戏(好几个小时,实况足球2022),另一个标签在看视频
另一个标签在写博客,另一个标签在听音乐。。。
16核,4个任务
如果为每一个任务分配单独的进程去执行,进程间通信。CPU在调度。
病毒,熊猫烧香。操作你的运行中的进程,修改了你的数据。
引入了线程。
线程是由进程创建,是进程的一个实体,是具体干活的人
一个进程有多个线程。线程不独立分配内存,而是共享进程的内存资源,
线程可以共享cpu资源。
进程更强调的是【内存资源分配】,线程更强调的是【计算的资源的分配】
因为有了线程的概念,一个进程的线程不能修改另一个线程的数据,线程之间是相互隔离的,
安全性更好。
我使用浏览器打开两个腾讯视频,他们同时播放视频,
我一个浏览器也可以同时下载多个文件,
同时播放两个视频,在多线程的作用下。
下载,网络资源,IO问题。
我的电脑有2个核心,又有4个逻辑处理器。2核心4线程。
8核心16线程啥意思?
理论上,一个核在一个时间点只能跑一个线程,但是cpu同一个时间能跑16线程
1.物理cpu内核,每颗物理CPU可以有1个或多个物理内核,
通常情况下物理CPU内核数都是固定,单核CPU就只有1个物理内核
2.逻辑CPU内核,操作系统可以使用逻辑CPU来模拟真实CPU。
在没有多核心CPU情况下,单核CPU只有一个核,可以把一个CPU当做多个
CPU使用,逻辑CPU的个数就是作用的CPU物理内核数。英特尔研发的一种技术
超线程,2002发布。和硬件有关,了解。
几个问题:
1.我们的进程直接创建、调度线程。不能。
2.QQ执行了一会,不执行,等其他线程执行完毕之后。
在上一次执行间歇期,会记录位置。
不同进程间的线程切换,耗费极大资源空间。