Java, the world's best programming language
1.Java 基础
1.关键词
1.return
Java 中 return 用法小结:
- return:必须放在方法中
- return 的主要作用有两点:
- 1.返回方法指定类型值
- 2.用于方法结束的标志,return 后面的语句不会被执行
- 无返回类型
- 有返回类型
package test;
public class Test001 {
public static void main(String[] args) {
int i;
System.out.println("return语句之前"+getInfo());
for (i = 0; i < 5; i++) {
if(i==3){
return;//无返回类型,用于方法的结束
}
System.out.println(String.format("i=%d",i));
}
//return 之后的语句将不会被执行
System.out.println("return语句之后"+getInfo());
}
public static int getInfo(){
return 1;//有返回类型,返回方法指定类型的返回值
}
}
2.进制转换
10进制转化其他进制 对应的方法,参数:n(原10进制数据),r(进制), 返回值
10进制转2进制 Integer.toBinaryString(n); 一个二进制字符串.
10进制转8进制 Integer.toOctalString(n); 一个八进制字符串
10进制转16进制 Integer.toHexString(n); 一个16进制字符串
10进制转 r 进制 Integer.toString(100, 16); 一个r进制字符串
3.实参与形参
形式参数 :是在**定义函数名 和函数体 的时候使用的参数**,目的是用来接收调用该函数时传入的参数。public void sout(String name) { //形式参数为 name
System.out.println(name);
}
==实际参数==:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数 ”。
public static void main(String[] args) {
ParamTest pt = new ParamTest();
pt.sout("Hollis");//实际参数为 Hollis
}
实际参数是调用有参方法 的时候真正传递的内容,而形式参数是用于接收实参内容的参数
4.值传递与引用传递
值传递 (pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递 (pass by reference) 是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。总结:
- Java 中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
- 无论是值传递还是引用传递,其实都是一种求值策略 (Evaluation strategy) 。在求值策略中,还有一种叫做按共享传递 (call by sharing)。其实 Java 中的参数传递严格意义上说应该是按共享传递,而按共享传递其实只是按值传递的一个特例罢了。
按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。
- 在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。
- 如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。
- 如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
5.构造方法能重载,不能重写
概述:构造方法 存在于类中,给对象数据(属性)初始化;
特点:方法名与类名一样;无返回值无 void;
默认构造方法:
我们不创建一个构造方法时,系统默认提供一个无参构造 ;
当我们创建一个构造方法时,系统不再提供无参构造,所以在实际项目中,全部手动给出无参构造
- 重载 :存在于在一个类中,方法名相同,方法参数的数据类型 或方法的形式参数 个数不同
- 重写 :存在于子父类中,方法名 、方法参数 、返回值 全部相同
6.抽象类、抽象方法
1. 总结表述
- 抽象类 和抽象方法 的产生是为了维护继承链的逻辑 ,即抽象类相对于那些普通的类处于**继承树的根部**。
- 抽象方法的产生完全是为了迎合抽象类的存在:抽象方法只能写在抽象类中!
- 抽象类的字段(反正也没有 abstract 抽象字段一说)只要像正常的继承关系那样使用就好了。
- 被 abstract 关键字修饰的类叫抽象类。
- 被 abstract 关键字修饰的方法叫抽象方法。
2. 核心思想
- 抽象类(abstract class)不能被实例化!!
- 抽象类是有构造器的(所有类都有构造器)
- 抽象类以有抽象方法,也可以没有抽象方法;但是抽象方法只能存在于抽象类中。
- 抽象类中的非抽象方法如同在非抽象类中一样,正常继承使用。
- 抽象方法(abstract method)只能存在于抽象类中!!
- 抽象方法所在的类,一定是抽象类
- 因为抽象方法是没有方法体的,如果所在的类不是抽象类,那么该类可以实例化对象,调用抽象方法,然后无方法体去具体实现功能,则矛盾
- 不存在所谓的抽象静态方法
- 因为静态方法 总是和一个类相绑定的,也因为这样我们使用类名而不是实例来调用某个类的静态方法;(反证法)如果一个抽象类的抽象方法被修饰为 static 的,我们推荐使用类名来调用该方法,即调用抽象类的静态方法(在「类」上面划着重号),可是又因为这个方法还被 abstract 修饰,是没有方法体的,我们不能直接调用(必须要现在子类中具体实现后才能调用)
3. 抽象类的使用:
抽象类不能实例化(不能直接创建对象)。抽象类是用来被继承的,继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类,使用 abstract 关键字修饰。
抽象类也是类,因此原来类中可以有的成员,抽象类都可以有,那么抽象类不能直接创建对象,为什么还有构造器呢?供子类调用,子类创建对象时,需要为从父类继承的属性初始化。
抽象类不能使用 final 修饰。
4. 抽象类注意事项
(1) 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
(2) 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的 super(),需要访问父类构造方法。
(3) 抽象类中,可以有成员变量。
理解:子类的共性的成员变量 , 可以定义在抽象父类中。
(4) 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,声明为抽象类目的就是不想让使用者创建该类的对象,通常用于某些特殊的类结构设计。
(5) 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
5.abstract 关键字:
可以用来修饰的结构:类、方法;不能用来修饰变量、代码块、构造器。
不能和 abstract 一起使用的修饰符:
- 外部类可用修饰符:abstract、final
- 两种访问修饰符:public 和缺省。
- 方法可用修饰符,不能共存的:4 种访问修饰符
- 不能被重写:static、final、abstract
- 没有方法体:native
7.构造函数 和 静态(static)
1.构造方法
构造方法 :创建对象,给对象中的成员进行初始化格式:
- 1.方法名与类名相同
- 2.没有返回值类型,连 void 都没有
- 3.没有具体的返回值
2.静态
静态:共享数据 ,只存一份,并使其共享
static 是一个修饰符,用于修饰成员(成员变量、成员函数)。
当成员被静态修饰后(多了一种调用方式),除了可以被对象调用外,还可直接被类名调用(类名.静态成员)
1.static 的特点
- 随着类的加载而加载 当类加载到内存中,静态成员以及值也加载到内存中,也就是说,静态成员会随着类的消失而消失,他的生命周期最长
- 优先于对象存在
- 被所有对象所共享
- 可以被类名所调用
被静态修饰的成员变量被称为态成员变量 静或者叫类变量
没有被静态修饰的成员变量被称为成员变量 或者叫实例变量
2. 实例变量和类变量的区别
- 存放位置
类变量随着类的加载存在于方法区 中
实例变量随着对象的建立存在于堆内存 中 - 生命周期
类变量生命周期最长,随着类的消失而消失
实例变量生命周期随着对象的消失而消失
3.静态使用的注意事项
- 静态方法 只能访问静态成员 ,非静态方法既可以访问静态也可以访非静态
原理:生命周期 决定调用关系
非静态成员随着对象的创建而存在,静态方法优先于非静态存在,所以不可以调用未存在的方法和变量。而当非静态存在时,静态已经存在 静态方法中不可以定义 this,super 关键字
因为静态优先于对象存在,所以静态方法中不可以出现 this 关键字
优点: 1.对对象的共享数据进行单独空间的存储,节省堆内存空间 2.可以直接被类名调用
缺点: 1.生命周期过长,占用内存 2.访问出现局限性,静态只能访问静态
8.Cookie 和 Session 的区别
1.共同之处:
cookie 和 session 都是用来跟踪浏览器用户身份的会话方式。
2.工作原理:
1.Cookie 的工作原理
(1)浏览器端第一次发送请求到服务器端
(2)服务器端创建 Cookie,该 Cookie 中包含用户的信息,然后将该 Cookie 发送到浏览器端
(3)浏览器端再次访问服务器端时会携带服务器端创建的 Cookie
(4)服务器端通过 Cookie 中携带的数据区分不同的用户
2.Session 的工作原理
(1)浏览器端第一次发送请求到服务器端,服务器端创建一个 Session,同时会创建一个特殊的 Cookie(name为JSESSIONID的固定值 ,value为session对象的ID ),然后将该 Cookie 发送至浏览器端
(2)浏览器端发送第 N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID 的Cookie 对象
(3)服务器端根据 name 为 JSESSIONID 的 Cookie 的 value(sessionId),去查询 Session 对象,从而区分不同用户。
- name 为 JSESSIONID 的 Cookie不存在(关闭或更换浏览器),返回 1 中重新去创建 Session 与特殊的 Cookie
- name 为 JSESSIONID 的 Cookie存在,根据value中的SessionId去寻找session 对象
- value 为 SessionId 不存在(Session 对象默认存活 30 分钟),返回 1 中重新去创建 Session 与特殊的 Cookie
- value 为 SessionId 存在,返回 session 对象
3.区别
cookie 数据保存在客户端,session 数据保存在服务端。
session 过期与否,取决于服务器的设定。cookie 过期与否,可以在 cookie 生成的时候设置进去
1.session:
简单的说,当你登陆一个网站的时候,如果 web 服务器端使用的是 session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话 sessionid,服务器根据当前 sessionid 判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。
2.cookie:
sessionid 是服务器和客户端连接时候随机分配的,如果浏览器使用的是 cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了 cookie 用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为 cookie 变量,所以只要不关闭浏览器,那么 cookie 变量一直是有效的,所以能够保证长时间不掉线。
4.区别对比:
(1)cookie 数据存放在客户的浏览器上,session 数据放在服务器上
(2)cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗,如果主要考虑到安全应当使用 session
(3)session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面应当使用 COOKIE
(4)单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 COOKIE 不能 3K。
(5)所以:将登陆信息等重要信息存放为 SESSION;其他信息如果需要保留,可以放在 COOKIE 中
2.数组
1.List
1.List 数据的指定修改
package ArrayForiter;
import java.util.*;
import java.util.stream.*;
import org.junit.jupiter.api.Test;
public class ListItemRemoveTests {
public List<String> initList = Arrays.asList("360","aliyun","baidu","bilibili","amazon","bytedance","Tencent");
/*
* 增强for循环删除 ConcurrentModificationException
*/
public void test1() {
List<String> list = new ArrayList<String>(initList);
for(String element : list) {
if(element.startsWith("b")) {
list.remove(element);
}
}
System.out.println(list);
}
@Test
public void test1_1() {
List<String> list = new ArrayList<String>(initList);
list.forEach ((e) -> {
if(e.startsWith("b")) {
list.remove(e);
}
});
System.out.println(list);
}
/* 低级for size, 删除不全。(功能不完善的 bug */
public void test2() {
List<String> list = new ArrayList<String>(initList);
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if(str.startsWith("b")) {
list.remove(i);
}
}
System.out.println(list);
}
/*
* 角标越界
* 无法阻止 for的i增大
*/
public void test3() {
List<String> list = new ArrayList<String>(initList);
int size = list.size();
for (int i = 0; i < size; i++) {
String str = list.get(i);
if(str.startsWith("b")) {
list.remove(i);
}
}
System.out.println(list);
}
/*
* 逆序删除
* 从后面开始,可以改变 for 增大导致的角标越界
*/
public void test4() {
List<String> list = new ArrayList<String>(initList);
for(int i=list.size()-1;i>0;i--) {
String str = list.get(i);
if(str.startsWith("b")) {
list.remove(i);
}
}
System.out.println(list);
}
/*
* 调用 iteration 提供的方法
*/
public void test5() {
List<String> list = new ArrayList<String>(initList);
for(Iterator<String> iterator = list.iterator();iterator.hasNext();) {
String str = iterator.next();
if(str.startsWith("b")) {
iterator.remove();
}
}
System.out.println(list);
}
public void test5_1() {
List<String> list = new ArrayList<String>(initList);
for(Iterator<String> iterator = list.iterator();iterator.hasNext();) {
String str = iterator.next();
if(str.startsWith("b")) {
list.remove(str); // 用的 iterat 方法 但没在关键点用iterator.remove();
}
}
System.out.println(list);
}
/*
* filter 过滤
* 得到一定量的反向元素
*/
public void test6() {
List<String> list = new ArrayList<String>(initList);
list = list.stream().filter(e -> !e.startsWith("b")).collect(Collectors.toList());
System.out.println(list);
}
public static void main(String[] s) {
ListItemRemoveTests a = new ListItemRemoveTests();
/*
* 原因: Iterator迭代时不能remove。
* 是同步操作问题 两者修改次数不一致
*/
//a.test1();
//a.test1_1();
//a.test2();
//a.test3();
a.test4();
a.test5();
/*
* 用的 iterat 方法 但没在关键点用iterator.remove();
*/
//a.test5_1();
a.test6();
}
}
3.Java 高级特性
1. 迭代器(Iterators)和列表迭代器(ListIterators)
迭代器是集合元素的顺序访问的算法,它允许遍历集合而不需要暴露集合的内部表示。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
列表迭代器提供了额外的 nextIndex
和 previousIndex
方法,以及 set
和 add
方法,允许在遍历过程中修改列表。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
ListIterator<String> listIterator = myList.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
if ("Banana".equals(element)) {
listIterator.set("Mango"); // 替换元素
}
}
2. 流(Streams)
流操作 Java 8 引入了流 API,它提供了一种高级方式来处理集合中的元素,支持顺序和并行处理。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
myList.stream()
.filter(s -> s.startsWith("A")) // 过滤
.map(String::toUpperCase) // 映射
.forEach(System.out::println); // 消费
3. 不可变性(Immutability)
不可变集合一旦创建,其内容就不能被修改。这提供了线程安全性和不可变数据的安全性。
List<String> myImmutableList = Collections.unmodifiableList(Arrays.asList("Apple", "Banana", "Cherry"));
// myImmutableList.add("Mango"); // 编译错误,因为 myImmutableList 是不可变的
4. 并发集合(Concurrent Collections)
并发集合类,如 ConcurrentHashMap
、ConcurrentLinkedQueue
和 CopyOnWriteArrayList
,提供了线程安全的集合实现。
ConcurrentHashMap<String, String> myConcurrentMap = new ConcurrentHashMap<>();
myConcurrentMap.put("Key", "Value");
// myConcurrentMap.putAll(otherMap); // 安全地添加其他映射
5. 函数式接口(Functional Interfaces)
函数式接口允许你使用 Lambda 表达式来提供实现。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
myList.forEach(s -> System.out.println(s)); // 使用 Lambda 表达式
6. 自定义集合操作
自定义集合操作
Java 8 引入了 Collection
接口的默认方法,允许你在不改变集合接口的情况下添加新功能。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
myList.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); // 使用 Lambda 表达式进行排序
7. 并行集合(Parallel Collections)
并行集合是一种特殊的集合,它允许集合的并行处理。
List<String> myList = Arrays.asList("Apple", "Banana", "Cherry");
myList.parallelStream() // 转换为并行流
.filter(s -> s.startsWith("A")) // 过滤
.map(String::toUpperCase) // 映射
.forEach(System.out::println); // 消费
- 映射(Maps)的高级操作
高级映射操作Map
接口提供了丰富的方法,如putIfAbsent
、remove
、replace
、replaceAll
和compute
等。Map<String, String> myMap = new HashMap<>(); myMap.put("Key1", "Value1"); myMap.putIfAbsent("Key2", "Value2"); // 如果 Key2 不存在,则添加 myMap.remove("Key1"); // 移除 Key1 myMap.replace("Key2", "New Value2"); // 替换 Key2 的值 myMap.replaceAll((key, value) -> value.toUpperCase()); // 替换所有值
- 集合的工具类(Collections Utilities)
集合工具类Collections
类提供了一系列静态方法,用于操作集合,如排序、填充、替换和查找。List<String> myList = Arrays.asList("Apple", "Banana", "Cherry"); Collections.sort(myList); // 排序 Collections.fill(myList, "Mango"); // 填充 Collections.replaceAll(myList, "Apple", "Kiwi"); // 替换所有元素
2.MySQL 基础
1.数据库类型
1.五大类型
1.整数类型:
BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT
2.浮点数类型:
FLOAT、DOUBLE、DECIMAL
3.字符串类型:
CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB
4.日期类型:
Date、DateTime、TimeStamp、Time、Year
5.其他数据类型:
BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection 等
6.相似数据间的区别
1.int(1)与 int(10)的区别?
规定类型之后,储存是定长的。从本身长度还是储存方式都是一样的,最大值都是-2^31 到 2^31-1,没有区别。
唯一区别就是设置 zerofill,如:int(4) 设置之后会自动左边补零显示:0001。
2.tinyint(1)与 tinyint(3)的区别?
基本与 int 区别一样,唯一区别就是设置 zerofill,如:tinyint(6) 设置之后会自动左边补零显示:000001,但最大值还是 255。
3.char 与 varchar 的区别?
char 是固定长度,不管实际存储数据的长度,按 char规定长度分配空间,会截断尾部的空格。
varchar 是可变长度,会根据实际存储数据的长度分配最终的存储空间,实际占用的字节+1。
char(n)、varchar(n)中的 n 都是代表字符的个数,超过最大长度 n 的限制之后,字符串会被截断。
4.BLOB 与 TEXT 的区别?
BLOB 是一个二进制对象,可以容纳可变数量的数据。有四种类型的 BLOB:TINYBLOB、BLOB、MEDIUMBLO 和 LONGBLOB。
TEXT 是一个不区分大小写的 BLOB。四种 TEXT 类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。
BLOB 保存二进制数据,TEXT 保存字符数据。
3. Redis 基础
1.token 过期后,如何自动续期?
JWT token 的 payload 部分是一个 json 串,是要传递数据的一组声明,这些声明被 JWT 标准称为 claims。
JWT 标准里面定义的标准 claim 包括:
- iss(Issuser):JWT 的签发主体;
- sub(Subject):JWT 的所有者;
- aud(Audience):JWT 的接收对象;
- exp(Expiration time):JWT 的过期时间;
- nbf(Not Before):JWT 的生效开始时间;
- iat(Issued at):JWT 的签发时间;
- jti(JWT ID):是 JWT 的唯一标识。
除了以上标准声明以外,我们还可以自定义声明。以 com.auth0 为例,下面代码片段实现了生成一个带有过期时间的 token.
String token = JWT.create()
.withIssuer(ISSUER)
.withIssuedAt(new Date(currentTime))// 签发时间
.withExpiresAt(new Date(currentTime + EXPIRES_IN * 1000 * 60))// 过期时间戳
.withClaim("username", username)//自定义参数
.sign(Algorithm.HMAC256(user.getPassword()));
其中:
- withIssuer() 设置签发主体;
- withIssuedAt() 设置签发时间;
- withExpiresAt() 设置过期时间戳,过期的时长为 EXPIRES_IN (单位秒);
- withClaim() 设置自定义参数。
JWT 设置了过期时间以后,一定超过,那么接口就不能访问了,需要用户重新登录获取 token。如果经常需要用户重新登录,显然这种体验不是太好,因此很多应用会采用token 过期后自动续期的方案,只有特定条件下才会让用户重新登录。
1.token 过期的续期方案
解决 token 过期的续期问题可以有很多种不同的方案,这里举一些比较有代表性的例子。首先我们看一个单 token 方案,这个方案除了可以实现 token 续期以外,还可以实现某些条件下的强制重新登录。
1.单 token 方案
将 token 过期时间设置为 15 分钟;
前端发起请求,后端验证 token 是否过期;
- 如果过期,前端发起刷新 token 请求,后端为前端返回一个新的 token;
- 前端用新的 token 发起请求,请求成功;
如果要实现每隔 72 小时,必须重新登录,后端需要记录每次用户的登录时间;
- 用户每次请求时,检查用户最后一次登录日期
- 如超过 72 小时,则拒绝刷新 token 的请求,请求失败,跳转到登录页面。
另外后端还可以记录刷新 token 的次数,比如最多刷新 50 次,如果达到 50 次,则不再允许刷新,需要用户重新授权。
2.双 token 方案
- 登录成功以后,后端返回access_token 和 refresh_token ,客户端缓存此两种token;
- 客服端
- 使用 access_token 请求接口资源,成功则调用成功;
- 如果token 超时,客户端携带 refresh_token 调用 token 刷新接口获取新的 access_token ;
- 服务端
- 后端接受刷新 token 的请求后,检查 refresh_token 是否过期。
- 如果过期,拒绝刷新,客户端收到该状态后,跳转到登录页;
- 如果未过期,生成新的 access_token 返回给客户端。
- 后端接受刷新 token 的请求后,检查 refresh_token 是否过期。
- 客户端携带新的 access_token 重新调用上面的资源接口。
- 客户端退出登录或修改密码后,注销旧的 token,使 access_token 和 refresh_token 失效,同时清空客户端的 access_token 和 refresh_token 。
微信网页授权是通过 OAuth2.0 机制实现的,也使用了双 token 方案。
3.微信网页授权方案
- 客服端
- 用户在第三方应用的网页上完成微信授权以后,第三方应用可以获得 code(授权码)。code 的超时时间为 10 分钟,一个 code 只能成功换取一次access_token 即失效。
- 服务端
- 第三方应用通过 code 获取网页授权凭证access_token 和刷新凭证 refresh_token 。
4.Redis 缓存 token
后端实现 token 过期还可以利用 Redis 来存储 token,设置 redis 的键值对的过期时间。如果发现 redis 中不存在 token 的记录,说明 token 已经过期了。
通过使用 Redis 的字符串数据结构,我们可以轻松地存储和管理 Token。同时,我们展示了如何在每次验证时延长 Token 的过期时间,以实现 Token 的自动过期和续期。使用 Redis 作为 Token 存储和管理的解决方案,可以提高系统的性能和可扩展性。
2.token 失效处理方案
1.主动处理
1.思路
- token 产生(存入本地)时的时间戳 :用户成功登录,存 token 时记下此刻的时间戳 A
- token 使用的时间戳:axios 的请求拦截器中,请求会自动携带 token,这就是使用 token 的时候,记下此刻的时间戳 B
- 检查是否过期:时间差 = 时间戳 B - 时间戳 A ,将时间差与指定的 token有效时长对比。如果大于有效时长,表示已经过期;如果小于有效时长,表示没过期
- 不同情况的处理
- 已经过期:
- 退出登录–清空 token、当前用户信息,跳转到登录页
- 更换 token
- 没过期:
- 业务照常进行
- 已经过期:
- 不同情况的处理
2.代码
/* src/utils/auth.js */
import Cookies from 'js-cookie'
// 定义时间戳的key
const timeKey = 'hr-timestamp'
// 存时间戳
export function setTimeStamp() {
return Cookies.set(timeKey, Date.now())
}
// 读时间戳
export function getTimeStamp() {
return Cookies.get(timeKey)
}
到Vuex中的登录函数中,记下登录成功时的时间戳
import { setTimeStamp } from '@/utils/auth.js'
const actions = {
/* 一、定义函数:用户登录 */
/* 调用处:点击登录按钮时 */
async login(context, payload) {
// 1.发请求
const res = await loginApi(payload)
// 2. 存登录成功后的token ,token在响应数据res.data中
context.commit('SET_TOKEN', res.data)
// 3. token过期的主动处理:记下存token时的时间戳
setTimeStamp()
}
}
定义过期时长、判断过期函数
/* src/utils/request.js */
import store from '@/store'
import { getTimeStamp } from '@/utils/auth'
import router from '@/router'
// 定义过期时长
const TimeOut = 3600 // 单位:秒
// 定义判断过期函数
function checkTimeOut() {
// 当前时间
const currentTime = Date.now()
// 读取存token时的时间戳
const timeStamp = getTimeStamp()
// 转换为秒后再比较
const flag = ((currentTime - timeStamp) / 1000) > TimeOut
// 返回布尔值
return flag
}
请求拦截器中,编写业务逻辑
service.interceptors.request.use(
(config) => {
const token = store.getters.token // 尝试读取token
if (token) {
/* token过期的主动处理 */
if (checkTimeOut()) {
/* return的flag为false,表示过期 */
// 触发actions中的logout函数,清空当前过期的token(防止页面跳转错误)、清空用户信息
store.dispatch('user/logout')
// 跳转到登录页
router.push({ path: '/login' })
// return抛出一个执行错误, 用于终止promise的执行链
return Promise.reject(new Error('token超时,请重新登录'))
}
// 如果token存在,就自动添加到请求头上
// 注意:使用动态添加属性的形式。
// 原因:如果headers中的Authorization之前不存在,这样能添加一个新属性;如果headers中的Authorization之前存在,这样能覆盖以前的token值
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
2.被动处理
1.思路
- 每次请求成功发送后,都会得到服务器的响应。经过后端的判断,如果当前的 token 失效,那么一定会在响应的数据中携带一个标识。或者说返回一个错误状态码,例如 code:233333
- token 过期属于响应失败,axios 响应拦截器中的第二个回调会被触发
- 在响应拦截器的第二个回调中,编写 token 失效的业务逻辑:
- 清除当前的失效 token(防止页面无法跳转);
- 跳转到登录页;return 一个执行错误,用于终止当前的 promise 执行链
2.代码
service.interceptors.response.use(
(response) => {
// dosomething
},
(error) => {
// ! 服务器响应失败时,干些事情: 导致响应失败的原因有很多,其中之一是 token 过期
// 响应失败时的error(错误对象),它经过了axios的2层包装,服务器响应的真实数据在 error.response.data 中。
// axios包装的提示信息是:error.message,与服务器响应的真实数据是两回事
const realData = error.response.data
/* 处理token失效---后端处理 */
if (error.response && realData && realData.code === 233333) {
// 以上三个条件全部满足时,才说明token超时
// 1. 触发actions中的logout函数,清除无效token、当前用户信息
store.dispatch('user/logout')
// 2. 跳转到登录页面
router.push({ path: '/login' })
// 3. return 一个执行错误,用于终止当前的promise执行链
return Promise.reject(error)
} else {
/* 如果token未失效,则是其他错误 */
// 1. 提示错误信息
Message.error(realData.message)
// 2. return 一个执行错误
return Promise.reject(error)
}
}
)
4.Java 策略模式
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)
Java 模式设计关系图
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 设计模式优点
- 提供了一种共享的设计词汇和概念,使开发人员能够更好地沟通和理解彼此的设计意图。
- 提供了经过验证的解决方案,可以提高软件的可维护性、可复用性和灵活性。
- 促进了代码的重用,避免了重复的设计和实现。
- 通过遵循设计模式,可以减少系统中的错误和问题,提高代码质量
设计模式的六大原则
1.开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2.里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3.依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4.接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5.迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6.合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
1.创建型模式
1.工厂模式(Factory Pattern)
工厂模式提供了一种将对象的实例化过程封装在工厂类中的方式。通过使用工厂模式,可以将对象的创建与使用代码分离,提供一种统一的接口来创建不同类型的对象。
1.意图
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
2.工厂模式包含以下几个核心角色:
- 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
- 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。
3.工厂模式实现
工厂模式
创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。
FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
1.创建接口
Shape.java
public interface Shape {
void draw();
}
2.创建实现接口的实体类
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
3.创建一个工厂,生成基于给定信息的实体类的对象
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
4.使用该工厂,通过传递类型信息来获取实体类的对象
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
//获取 Circle 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape("CIRCLE");
//调用 Circle 的 draw 方法
shape1.draw();
//获取 Rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape("RECTANGLE");
//调用 Rectangle 的 draw 方法
shape2.draw();
//获取 Square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape("SQUARE");
//调用 Square 的 draw 方法
shape3.draw();
}
}
2.抽象工厂模式(Abstract Factory Pattern)
1.意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
主要解决:主要解决接口选择的问题
2.抽象工厂模式实现
抽象工厂模式实现
我们将创建 Shape 和 Color 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactory 和 ColorFactory,这两个工厂类都是扩展了 AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer。
AbstractFactoryPatternDemo 类使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 Shape(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 Color(RED / GREEN / BLUE),以便获取它所需对象的类型。
public interface Shape {
void draw();
}
//创建实现接口的实体类。
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public interface Color {
void fill();
}
// 创建实现接口的实体类
public class Red implements Color {
@Override
public void fill() {
System.out.println("Inside Red::fill() method.");
}
}
// 为 Color 和 Shape 对象创建抽象类来获取工厂。
public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape);
}
// 创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。
public class ShapeFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
@Override
public Color getColor(String color) {
return null;
}
}
// 创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。
public class ColorFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType){
return null;
}
@Override
public Color getColor(String color) {
if(color == null){
return null;
}
if(color.equalsIgnoreCase("RED")){
return new Red();
} else if(color.equalsIgnoreCase("GREEN")){
return new Green();
} else if(color.equalsIgnoreCase("BLUE")){
return new Blue();
}
return null;
}
}
// 创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂
public class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
}
if(choice.equalsIgnoreCase("COLOR")){
return new ColorFactory();
}
return null;
}
}
// 使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。
public class AbstractFactoryPatternDemo {
public static void main(String[] args) {
//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
//获取形状为 Circle 的对象
Shape shape1 = shapeFactory.getShape("CIRCLE");
//调用 Circle 的 draw 方法
shape1.draw();
//获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
//获取颜色为 Red 的对象
Color color1 = colorFactory.getColor("RED");
//调用 Red 的 fill 方法
color1.fill();
}
}
3.单例模式(Singleton Pattern)
1.意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
2.单例模式实现
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。
public class SingleObject {
//创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();
//让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}
//获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
//从 singleton 类获取唯一的对象。
public class SingletonPatternDemo {
public static void main(String[] args) {
//不合法的构造函数
//编译时错误:构造函数 SingleObject() 是不可见的
//SingleObject object = new SingleObject();
//获取唯一可用的对象
SingleObject object = SingleObject.getInstance();
//显示消息
object.showMessage();
}
}
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
//这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public enum Singleton {
INSTANCE;
public void whateverMethod(){}
}
4.建造者模式(Builder Pattern)
1.意图:
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决:主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
2.建造者模式实现
我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo 类使用 MealBuilder 来创建一个 Meal。
//创建一个表示食物条目和食物包装的接口。
public interface Item {
public String name();
public Packing packing();
public float price();
}
public interface Packing {
public String pack();
}
//创建实现 Packing 接口的实体类。
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
//创建实现 Item 接口的抽象类,该类提供了默认的功能。
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float price();
}
//创建扩展了 Burger 和 ColdDrink 的实体类
public class VegBurger extends Burger {
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "Veg Burger";
}
}
public class ChickenBurger extends Burger {
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "Chicken Burger";
}
}
public class Coke extends ColdDrink {
@Override
public float price() {
return 30.0f;
}
@Override
public String name() {
return "Coke";
}
}
public class Pepsi extends ColdDrink {
@Override
public float price() {
return 35.0f;
}
@Override
public String name() {
return "Pepsi";
}
}
//创建一个 Meal 类,带有上面定义的 Item 对象
import java.util.ArrayList;
import java.util.List;
public class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}
//创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象
public class MealBuilder {
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
//BuiderPatternDemo 使用 MealBuilder 来演示建造者模式(Builder Pattern)
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}
5.原型模式(Prototype Pattern)
1.意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
2.原型模式实现
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypePatternDemo 类使用 ShapeCache 类来获取 Shape 对象
原型模式实现
//创建一个实现了 Cloneable 接口的抽象类。
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
//创建扩展了上面抽象类的实体类
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
//创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。
import java.util.Hashtable;
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.put(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}
//PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}
2.结构型模式
这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。
1.适配器模式(Adapter Pattern)
2.桥接模式(Bridge Pattern)
3.过滤器模式(Filter、Criteria Pattern)
4.组合模式(Composite Pattern)
5.装饰器模式(Decorator Pattern)
6.外观模式(Facade Pattern)
7.享元模式(Flyweight Pattern)
8.代理模式(Proxy Pattern)
3.行为型模式
这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。
1.责任链模式(Chain of Responsibility Pattern)
2.命令模式(Command Pattern)
3.解释器模式(Interpreter Pattern)
4.迭代器模式(Iterator Pattern)
5.中介者模式(Mediator Pattern)
6.备忘录模式(Memento Pattern)
7.观察者模式(Observer Pattern)
8.状态模式(State Pattern)
9.空对象模式(Null Object Pattern)
10.策略模式(Strategy Pattern)
11.模板模式(Template Pattern)
12.访问者模式(Visitor Pattern)
4.J2EE 模式
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
1.MVC 模式(MVC Pattern)
2.业务代表模式(Business Delegate Pattern)
3.组合实体模式(Composite Entity Pattern)
4.数据访问对象模式(Data Access Object Pattern)
5.前端控制器模式(Front Controller Pattern)
6.拦截过滤器模式(Intercepting Filter Pattern)
7.服务定位器模式(Service Locator Pattern)
8.传输对象模式(Transfer Object Pattern)
public interface IStack<T> {
/**
* 初始化栈
* @param maxSize: 数组的最大长度。栈的最大长度。
*/
public void initStack(int maxSize);
/**
* 销毁栈
*/
public void destroyStack();
/**
* 添加数据,进栈。
* @param data 要被添加的数据.类型不确定,所以用泛型。
*/
public void push(T data);
/**
* 删除栈顶元素
*/
public void pop();
/**
* 判断栈是否为空
* @return true or false.
*/
public boolean isEmpty();
/**
* 获取栈顶元素
* @return 栈顶数据元素。数据元素的类型不确定,所以是泛型。
*/
public T getTop();
}
public class Stack<T> implements IStack<T> {
private int length;// 元素个数。栈的长度。
private T arr[];// 用于存放数据的数组。因为是顺序栈,所以用数组。数组取值是用角标 arr[0] arr[1] ...
private int maxSize;
/**
* 构造函数
*
* @param maxSize
* 数组的最大长度
*/
public Stack(int maxSize) {
this.maxSize = maxSize;
initStack(maxSize);
}
@Override
public void initStack(int maxSize) {
this.maxSize = maxSize;
// 初始化数组。
//为什么要初始化数组,因为数组不初始化,就没有分配内存空间,
//是一个空对象,也就是空指针。
arr = (T[]) new Object[maxSize];
}
@Override
public void destroyStack() {
// TODO Auto-generated method stub
}
// 添加值 1判断 原有数值长度 是否 已满于最大数组长
// 2 数值 赋予 原有数组末,数组长增加
@Override
public void push(T data) {
if(this.length >= this.maxSize){
System.out.println("已满");
return;
}
arr[this.length] = data;
this.length++;
}
// 添加值 1判断 原有数值长度 是否 为零
// 2 数值 赋予 原有数组 次末,数组长减少
@Override
public void pop() {
if(this.length == 0){
System.out.println("已经到底,无法pop");
return;
}
arr[this.length-1] = null;
this.length--;
}
// 指针 1判断长度 如果为零返回 否则 返回数组 次长度
@Override
public T getTop() {
if(this.length == 0){
return null;
}
return this.arr[this.length-1];
}
@Override
public boolean isEmpty() {
return this.length == 0;
}
}
public class Test {
public static void main(String[] args) {
Stack<Integer> s = new Stack<>(1024); // 指定栈的长度最大值是1024.
s.push(1);
s.push(2);
s.push(3);
s.pop();
s.pop();
Integer top = s.getTop();
System.out.println(top);
}
}