0%

刷题路线和资料来自:代码随想录知识星球 | 代码随想录 (programmercarl.com)

理论基础

数组在Java中的存储与赋值机制Java中基本数据类型赋值机制与数组的赋值机制 | 偷掉月亮 (moonshuo.cn)

可以知道在Java中数组值存储在堆中,而在栈空间中存储的是数组的地址,与基本数据类型不同

image-20220331123609262

二分查找

704. 二分查找 - 力扣(LeetCode) (leetcode-cn.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
while (left<=right){
int mid =(left+right)/2;
if (nums[mid]==target){
return mid;
}
if (target>nums[mid]){
left=mid+1;
}else{
right=mid-1;
}
}
return -1;
}
}

上面的二分查找用法是两边都闭合的,所以可以肯定int right=nums.length-1;如果是两边的闭合不同,需要使right值取值不同。

移除元素

27. 移除元素 - 力扣(LeetCode) (leetcode-cn.com)

巧妙交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
每一次找到之后,将找到的数与最后一个数交换,并将length--,防止死循环,同时i--,将交换过来的数在检查一次!!!
class Solution {
public int removeElement(int[] nums, int val) {
int length=nums.length;
for (int i=0;i<length;i++){
if (nums[i]==val){
int temp=nums[length-1];
nums[nums.length-1]=nums[i];
nums[i]=temp;
i--;
length--;
}
}
return length;
}
}

双指针法

快慢指针都从0开始,如果遇到目标的数,看快指针增加一,满指针不改变,但是一旦遇到不是目标的数,慢指针立即接受快指针的数,将的目标数覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int removeElement(int[] nums, int val) {

// 快慢指针
int fastIndex = 0;
int slowIndex;
for (slowIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;

}
}

有序数组的平方

977. 有序数组的平方 - 力扣(LeetCode) (leetcode-cn.com)

暴力解法

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int[] sortedSquares(int[] nums) {
int length=nums.length;
for (int i=0;i<length;i++){
nums[i]=nums[i]*nums[i];
}
Arrays.sort(nums);
return nums;

}
}

双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int[] sortedSquares(int[] nums) {
int length=nums.length;
int []array=new int[length];
int right=array.length-1;
int flag=array.length-1;
int left=0;
while (right>=left) {
if (Math.abs(nums[right]) >= Math.abs(nums[left])) {
array[flag]=nums[right]*nums[right];
flag--;
right--;
}
else {
array[flag]=nums[left]*nums[left];
flag--;
left++;
}

}
return array;
}
}

长度最小的子数组

暴力解法

这里是求连续的数组,所以可以确定开始的位置,和结束的位置,就可以确定所有的情况,不需要去考虑太多情况

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
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int length=nums.length;
int result=length;
boolean flag=false;
for (int i=0;i<length;i++){
int sum=0;
for (int j=i;j<length;j++){
sum=sum+nums[j];
if (sum>=target){
if (result>=j-i+1){
flag=true;
result=j-i+1;
break;
}
}
}
}
if(flag){
return result;
}else{
return 0;
}
}
}

滑动窗口

我的错误解法,忽略的窗口滑动过程中窗口的逐渐缩小的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minSubArrayLen(int target, int[] nums) {
int slowIndex;
int fastIndex=0;
int result=nums.length;
int sum=0;
boolean flag=false;
for (slowIndex=0;fastIndex<nums.length;fastIndex++){
sum=sum+nums[fastIndex];
if (sum>=target&&result>=fastIndex-slowIndex+1){
flag=true;
sum=sum-nums[slowIndex]-nums[fastIndex];
result=fastIndex-slowIndex+1;
slowIndex++;
fastIndex--;
}

}
if(flag){
return result;
}else{
return 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {

// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
//利用result的赋值巧妙赋值避开result=num.length的错误
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}

代码随想录 (programmercarl.com)

螺旋矩阵

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Arrays;

public class Solution {
/*我们
1.首先需要确定在何时转弯
2.向什么方向放生转弯*/
public int[][] generateMatrix(int n) {
int [][]array=new int[n][n];
//代表当前填充的数
int num=1;
//代表每一组已经填充的数
int count=0;
//flag代表圈数
int flag=0;
//代表方向
int position=1;
int hang=n;
int temp=n;
if (n%2!=0){
array[n/2][n/2]=hang*hang;
}
while (num<=hang*hang&&temp>0){
count=0;
if (position==1){
while (count<temp-1){
array[flag][flag+count]=num;
num++;
count++;
}
position++;
}
else if (position==2){
while (count<temp-1) {
array[count+flag][n - flag-1] = num;
num++;
count++;
}
position++;
}
else if (position==3){
while (count<temp-1){
array[n-flag-1][n-count-flag-1]=num;
count++;
num++;
}
position++;
}
else if (position==4){
while (count<temp-1){
array[n-count-flag-1][flag]=num;
num++;
count++;
}
position=1;
flag++;
temp=temp-2;
}
else {break;}

}

return array;
}



public static void main(String[] args) {
Solution solution=new Solution();
System.out.println(Arrays.deepToString(solution.generateMatrix(3)));
//System.out.println(solution.minSubArrayLe2(4, new int[]{1,4,4}));
}
}

在判断时候,一定要对其进行分组,并且注意二维数组的行列等等

总结

  1. 对于数组要删除的元素,是不能完成删除的,但是可以将这个进行覆盖,利用双指针方法可以巧妙将删除的数进行覆盖(图片来自代码随想录 (programmercarl.com)),当然也可以将其和最后的一个交换。27.移除元素-双指针法

  2. 而对于要求数组连续的值,可以使用双指针的变形,滑动窗口方法进行破解。图片来自代码随想录 (programmercarl.com)

    209.长度最小的子数组

  3. 而对于螺旋矩阵,需要思路清晰,分析好每一个方向的情况,同样比较复杂的其他情况要进行分类。

举例

1
2
3
4
public interface TestService {
void sayHi();
void sayGood();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class TestServiceImpl implements TestService {

@Override
public void sayHi() {
System.out.println("HI");
}

@Override
public void sayGood() {
System.out.println("GOOD");
}
}

main

1
2
3
4
5
6
7
8
9
10
public class AppTest 
{
@Test
public void test01() {
TestService testService=new TestServiceImpl();
testService.sayHi();
testService.sayGood();
}

}

需求

现在我需要在以上的两个方法之前分别要求输出现在的时间,以及在执行完之后输出“完成”。我们拥有以下几个方法

  1. 直接在sayHi,与saygood方法中添加
  2. 写一个工具类,调用工具类进行添加

以上的两个方法虽然有效,但是如果需要我们更改100个这样的方法,那样岂不是要更改100行

动态代理实现

动态代理可以在程序的执行过程创建代理对象,通过代理对象执行方法,增加额外方法

实现IncocationHandler,功能增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.zss.handle;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class MyIncation implements InvocationHandler {
//动态代理的目标对象
private Object object;

public MyIncation(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res=null;
System.out.println(new Date());
//通过代理对象执行方法shi,会执行这个invoke
res=method.invoke(object,args);
System.out.println("完成");
return res;
}
}

创建代理

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppTest 
{
@Test
public void test01() {
//创建目标对象
TestService testService=new TestServiceImpl();
InvocationHandler invocationHandler=new MyIncation(testService);
//使用Proxy创建代理
TestService proxy=(TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(), testService.getClass().getInterfaces(),invocationHandler);
proxy.sayGood();
proxy.sayHi();
}
}

image-20220505154812603

当然如果我们不想要sayhi方法执行一些代码,我们可以在实现IncocationHandler通过判断方法的名称,来选择执行哪一些代码

JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。

参考:

动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的**$Proxy0.class**文件发现:

class类定义为:为何jdk动态代理必须有接口,不支持仅实现类的代理 - 简书 (jianshu.com)

public final class $Proxy0 extends Proxy implements Interface {

​ *public $Proxy0(InvocationHandler paramInvocationHandler) {*

​ *super(paramInvocationHandler);*

​ ***}***

原理

原理不清楚,插眼!!

什么是单例设计模式

所谓单例设计模式,就是采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

目的:使用着在main方法中就不可以自己创建实例对象以及定义一些变量。

单例设计模式的两种方法:饿汉式,懒汉式

饿汉式

  1. 将我们的构造器私有化,防止我们在类的外部可以直接new一个对象
  2. 在类的内部直接创建该静态对象(否则下面的static方法无法调用)
  3. 提供一个公共的static方法,返回我们想要的对象
  4. 实现代码

疑问:

为什么getInsatnce方法要用静态static???

如果我们不使用静态方法,想要调用我们的方法时,就必须首先new一个实例对象,与我们的最初使用的目的相违背。

为什么叫饿汉式??

我们可能在以后的使用中不会使用到dog的名字,但是我们的类的内部已经给出了相应的属性元素。

查看以下一段代码:

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
public class Test {
public static void main(String[] args) {
Dog dog=Dog.getInstance();
System.out.println(dog.toString());
}
}
class Dog{
private String name;
private static Dog ming=new Dog("小狼");
/**创建一个私有的构造器*/
private Dog(String name){
this.name=name;
}
/**创建一个公共的静态方法*/
public static Dog getInstance(){
return ming;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

image-20220322090055669

懒汉式

基于上述的饿汉式的缺点:创建对象却可以没有被使用,下面我们看看懒汉式

  1. 构造器私有化
  2. 定义一个静态属性,但是不去创建
  3. 提供一个公共的静态方法
  4. 只有用户使用对象时,才会返回这个对象,如果我们再次去调用这个函数,我们会返回上一次我们创建的对象。

我们来查看以下代码:可以发现我们在使用dog.age时,并未创建新的实例对象,也就没有占用空间,如果我们不使用name就不会去调用instannce方法。

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
32
public class Test {
public static void main(String[] args) {
System.out.println(Dog.age);
Dog dog=Dog.getInstance();
System.out.println(dog.toString());
}
}
class Dog{
private String name;
public static int age=10;
/** 创建一个私有的对象却不进行初始化*/
private static Dog ming;
/**创建一个私有的构造器*/
private Dog(String name){
System.out.println("构造器被调用");
this.name=name;
}
/**创建一个公共的静态方法*/
public static Dog getInstance(){
if (ming==null){
ming=new Dog("小狼");
}
return ming;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

image-20220322090118858

小结

饿汉式:在类加载时创建,可能存在资源浪费的问题

懒汉式:线程安全问题,多个线程同时访问,会发生冲突

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
package com.zss.reflact;


/**
* @author zss
*/
public class Cat {
public String name="asac";
public int age=0;
private String sex="娜娜";
int tall=15;
public void cry(){
System.out.println("HI");
}
void hello(){
System.out.println("NIAO");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
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
32
33
34
35
package com.zss.reflact;

import java.util.Arrays;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class cla=Class.forName("com.zss.reflact.Cat");
//获得全类名
System.out.println(cla.getName());
//获得简单类名
System.out.println(cla.getSimpleName());
//获得所有公共的属性
System.out.println(Arrays.toString(cla.getFields()));
//获得本类所有的属性
System.out.println(Arrays.toString(cla.getDeclaredFields()));
//获得public方法
System.out.println(Arrays.toString(cla.getMethods()));
//所有方法
System.out.println(Arrays.toString(cla.getDeclaredMethods()));
//public构造器
System.out.println(Arrays.toString(cla.getConstructors()));
//所有构造器
System.out.println(Arrays.toString(cla.getDeclaredConstructors()));
//包名
System.out.println(cla.getPackage());
//父类
System.out.println(cla.getSuperclass());
//接口
System.out.println(Arrays.toString(cla.getInterfaces()));

}
}

image-20220321205903318

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
32
33
package com.zss.reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class cla=Class.forName("com.zss.reflact.Cat");
//输出属性的修饰符,0代表默认,1代表public,2代表private,4代表protected,static是8,final是16,如果重复则会相加
//得到属性的类名信息
Field[] fields= cla.getFields();
for (Field f:fields){
System.out.println(f.getName()+f.getModifiers()+f.getType());
}



//方法的修饰符,返回方法修饰的对象
Method[] methods= cla.getMethods();
for (Method m:methods){
System.out.println(m.getName()+m.getModifiers()+m.getReturnType());
}

//构造器
Constructor []constructors= cla.getConstructors();
System.out.println(constructors[0].getName()+constructors[0].getModifiers()+ Arrays.toString(constructors[0].getTypeParameters()));
}
}

image-20220321211933358

Class类

  1. Class也是类,因此继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因此类只加载一次
  4. 每一个类的实例都会记得自己由那个Class实例化生成
  5. 通过Class可以得到一个类的完整结构
  6. Class对象是放在堆中的
  7. 注意在方法区中,会存在cat类的二进制字节码数据

传统对象debug与我们反射对象的debug可以发现都是通过ClassLoader类的loadClass加载到堆中,而且我们cat.class对象只会存在份,不管我们new多少次,还是会存在一次,类不同才会不同

class拥有许多的方法,可以得到对象的很多的信息。

Class类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zss.reflact;


/**
* @author zss
*/
public class Cat {
public String name="asac";
public int age=0;
public String sex="娜娜";

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
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
32
33
34
35
36
37
38
39
package com.zss.reflact;

import java.io.IOException;
import java.lang.reflect.Field;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classPath="com.zss.reflact.Cat";
//?代表不确定的类型
Class<?> cla=Class.forName(classPath);
//尝试输出cla,这里显示那个类的Class对象
System.out.println(cla);
//得到其运行类型
System.out.println(cla.getClass());
//得到包名
System.out.println(cla.getPackage().getName());
//得到全类名
System.out.println(cla.getName());
//创建对象实例
Cat cat=(Cat) cla.newInstance();
System.out.println(cat);
//通过反射获取属性
Field name=cla.getField("name");
System.out.println(name.get(cat));
//通过反射设置值
name.set(cat,"sec");
System.out.println(cat);
//得到所有的字段属性
Field[] fields=cla.getFields();
//System.out.println(fields);
for (Field f:fields){
System.out.println(f.getName());
}
}
}

image-20220321172447804

动态和静态加载

静态加载

编译时加载相关的类,如果没有则报错,依赖性太强。

下面的代码,我们不会使用到Dog这个类,但是编译会给我们直接报错,依赖性太强,要求所有的类必须被加载好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.zss.reflact;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
int a=0;
if (a==1){
Dog dog=new Dog();
dog.cry();}
else {
System.out.println("你对的");
}
}
}

image-20220321193758779

动态加载

运行时需要加载的类,如果运行时不要该类,则不报错,降低了依赖性.下面的这一段代码,路径根本不存在,但是在运行过程中不会使用到该代码,所以不会本加载,当使用时才会被加载。反射属于动态加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zss.reflact;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
int a=0;
if (a==1){
Class cls=Class.forName("java//不存在");
Object O=cls.newInstance();
Method method=cls.getMethod("HI");
method.invoke(O);
}
else {
System.out.println("你对的");
}
}
}

image-20220321194532855

类加载的时机

  1. 创建对象时new
  2. 子类被加载时,父类也被加载
  3. 调用类中静态成员时
  4. 通过反射时

类加载的过程

image-20220321200304845

在加载中,将类的class文件读入内存,并且为之创建一个java.lang.Class对象,此过程由类加载器完成。验证与文件安全性(字节码等)进行验证,准备阶段分配空间,解析阶段把符号引用转成字节引用。初始化过程中可以由程序员进行控制,JVM负责对类进行初始化,这里主要指静态成员

image-20220321200557645

类加载的五个阶段

加载阶段

JVM在该阶段主要目的是将字节码从不同的数据源(.class文件,jar包,网络文件等)转换为二进制字节流加载到内存中,并且生成一个代表该类的java.lang.Class对象

验证阶段

  1. 保证Class为文件的字节流中包含信息符合虚拟机的要求,并且不会危害虚拟机自身安全
  2. 文件格式验证(是否以魔数oxcafebabe开头)、元数据验证,字节码验证和符号引用验证
  3. 可以考虑使用-Xverify:none 参数关闭大部分验证措施,缩短虚拟机加载的时间

准备阶段

JVM对在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始化值比如int为0,String为null等),这些变量所使用的内存都将在方法区中进行分配。在这里只会对静态变量分配内存,实例变量不会分配,而对于final的常量会一次性分配

解析阶段

虚拟机将常量池的符号引用变成直接引用,直接引用利用地址进行相互引用

初始化阶段

  1. 初始化阶段才是真正执行类中定义的java程序代码,此阶段是执行()方法的过程

  2. () 方法是由编译器按语句在源文件出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句并进行合并,比如会将其进行合并,而最终num=200

    1
    2
    int static num=100;
    num=200;
  3. 虚拟机会保证一个类()方法在多线程中被正确的枷锁,同步,如果多个线程同时初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕

反射的作用

反射可以使我们通过外部文件配置,在不修改源码的情况下,来控制程序,也符合设计模式ocp的原则(在不修改源码的条件下,扩容功能)

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.zss.reflact;

/**
* @author zss
*/
public class Cat {
public void hi(){
System.out.println("你好啊小猫猫");
}
public void cry(){
System.out.println("小猫咪哭泣");
}

}

image-20220321151517509

我想要通过配置文件另外一个程序调用Cat的对象,并且调用其cry方法,接下来我们只需要通过更改配置文件的method方法指向的方法即可

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
package com.zss.reflact;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Properties properties=new Properties();
properties.load(new FileReader("src/com/zss/reflact/test.properties"));
String classPath=properties.getProperty("class");
String method=properties.getProperty("method");
//首先我们得到了类名称以及方法,但是在这里我们无法创建新的类和调用方法
//使用反射机制,第一步加载类,返回Class了类型的对象clas
Class clas=Class.forName(classPath);
//通过clas得到加载类,Cat的实例对象
Object o=clas.newInstance();
System.out.println("运行类型为"+o.getClass());
//通过clas得到加载类的方法,在反射机制中将方法视为对象(万物皆对象)
Method method1=clas.getMethod(method);
//通过方法对象类调用对象
method1.invoke(o);

}
}

image-20220321152608448

反射机制

反射机制允许程序在执行期间借助ReflectionAPI得到任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象属性以及方法。加载完类之后,在堆中产生了一个Class类型的对象(一个类只能有一个),这个对象包含类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,通过镜子看到类的结构

image-20220321154203831

  1. 首先,通过Javac 方法编译文件,编译成为.class的文件,包含其方法,构造器等信息。
  2. 当我们在运行阶段new一个对象的时候,类会进行加载,将.class字节码文件通过类加载机制加载到class类对象堆中,以数据结构的方式进行存储,而且方法等不为一个,所以以数组的方法进行保存。
  3. 而生成的Cat对象,知道他是属于哪一个class对象的
  4. 而我们得到class对象时,我们可以创建对象,调用方法等等

Cat示范类

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
32
33
34
35
36
37
38
39
package com.zss.reflact;


/**
* @author zss
*/
public class Cat {
public String name="asac";
private int he=10;
public int age=0;
int tall=15;
public void cry(String name){
System.out.println("HI"+name);
}
public void hello(){
System.out.println("NIAO");
}
@Override
public String toString() {
String sex = "娜娜";
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}

public Cat(String name, int age, int tall) {
this.name = name;
this.age = age;
this.tall = tall;
}
private Cat(String name){
this.name=name;
}

public Cat() {
}
}

反射创建实例

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
package com.zss.reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> cla= Class.forName("com.zss.reflact.Cat");
//通过无参构造器
Object o=cla.newInstance();
System.out.println(o);
//有参构造器
Constructor<?> constructor=cla.getConstructor(String.class,int.class,int.class);
constructor.newInstance("java",12,56);
System.out.println(constructor);

//通过私有构造器
Constructor<?> constructor1=cla.getDeclaredConstructor(String.class);
//进行爆破,造成私有可以调用,使用反射可以访问私有的构造器
constructor1.setAccessible(true);
constructor1.newInstance("HAHA");
System.out.println(constructor1);


}
}

image-20220321214920927

反射访问类的成员

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
package com.zss.reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> cla= Class.forName("com.zss.reflact.Cat");
Object o=cla.newInstance();
Field name=cla.getField("name");
//如果name为静态static,则下面的o也可以是null,静态也是对象的
name.set(o,"lili");
//通过反射得到真实内容
System.out.println(name.get(o));


Field he=cla.getDeclaredField("he");
//爆破
he.setAccessible(true);
System.out.println(he.get(o));




}
}

image-20220321220059624

反射访问方法

当然私有方法也可以爆破调用,其余用法与成员相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.zss.reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> cla= Class.forName("com.zss.reflact.Cat");
Object o=cla.newInstance();
Method method=cla.getMethod("cry",String.class);
method.invoke(o,"你好啊啊");

}
}

image-20220321220535697

反射相关类

  1. Class代表一个类,Class对象表示某个类加载后在堆中的对象
  2. Method:代表类的方法
  3. Field:代表类的成员变量
  4. Constructor:代表类的构造方法
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.zss.reflact;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Properties properties=new Properties();
properties.load(new FileReader("src/com/zss/reflact/test.properties"));
String classPath=properties.getProperty("class");
String method=properties.getProperty("method");
//首先我们得到了类名称以及方法,但是在这里我们无法创建新的类和调用方法
//使用反射机制,第一步加载类,返回Class了类型的对象clas
Class clas=Class.forName(classPath);
//通过clas得到加载类,Cat的实例对象
Object o=clas.newInstance();
System.out.println("运行类型为"+o.getClass());
//通过clas得到加载类的方法,在反射机制中将方法视为对象(万物皆对象)
Method method1=clas.getMethod(method);
//通过方法对象类调用对象
method1.invoke(o);
//只能得到公有的
Field name=clas.getField("name");
System.out.println(name);
//得到值
System.out.println(name.get(o));
//得到构造器
Constructor constructor=clas.getConstructor();
//得到有形参的构造器
Constructor constructor1 = clas.getConstructor(String.class, int.class);
System.out.println(constructor1);
System.out.println(constructor);


}
}

image-20220321161913538

反射的优化

反射的优点:可以动态创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架机制失去底层支撑。

反射缺点:使用反射基本时=是解释执行,对执行的速度有影响,直接调用方法与与反射调用方法的速度可能会相差上百倍。

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
package com.zss.reflact;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Properties properties=new Properties();
properties.load(new FileReader("src/com/zss/reflact/test.properties"));
String classPath=properties.getProperty("class");
String method=properties.getProperty("method");
Class clas=Class.forName(classPath);
Object o=clas.newInstance();
Method method1=clas.getMethod(method);
long startTime=System.currentTimeMillis();
for (int i=0;i<900000000;i++) {
method1.invoke(o);
}
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
}
}

image-20220321163324656

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
32
33
34
package com.zss.reflact;

import java.lang.reflect.Field;

/**
* @author zss
*/
public class Cat {
public static void main(String[] args) {
Cat cat=new Cat();
long startTime =System.currentTimeMillis();
for (int i=0;i<900000000;i++){
cat.hi();
}
long endTime =System.currentTimeMillis();
System.out.println(endTime-startTime);
}
public String name="阿华";
private int age=2;
public void hi(){
System.out.println("你好啊小猫猫");
}
public void cry(){
//System.out.println("小猫咪哭泣");
}

public Cat() {
}

public Cat(String name, int age) {
this.name = name;
this.age = age;
}
}

image-20220321163403286

优化

在反射之中,拥有一个方法可以关闭反射的安全检查机制,提高运行的速度

1
method1.setAccessible(false);

image-20220321163737496

获取Class对象的方式

方式一:在代码编译阶段

使用Class.forName(),但是此时已经知道了一个类的全类名和该类在类路径下。可以通过Class类的静态方法获取 。多用于全类名,了解类的全路径,加载类等

1
2
String classPath="com.zss.reflact.Cat";
Class<?> cla=Class.forName(classPath);

方式二:在类加载阶段

类.class,多用于参数的传递,比如通过反射得到对应构造器的对象

1
2
Class cla2=Cat.class;
System.out.println(cla2);

方式三:在运行阶段

对象.getClass(),此时已经知道了实例化对象,将其对应的运行类型进行传递。而我们学到这里也可以了解到所谓的运行类型其实也是通过反射指向了我们.class文件加载之后真正的类

1
2
3
Cat cat=new Cat();
Class cla3=cat.getClass();
System.out.println(cla3);

方式四:在类加载器中

1
2
3
4
5
6
Cat cat=new Cat();
//第一步:先得到类加载器
ClassLoader classLoader=cat.getClass().getClassLoader();
//通过类加载其得到Class对象,得到路径
Class cla4=classLoader.loadClass(classPath);
System.out.println(cla4);

那些类拥有Class对象

  1. 外部类,内部类
  2. 接口,数组
  3. 枚举,注解
  4. 基本数据类型
  5. void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zss.reflact;


import java.io.Serializable;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Class<String> cls=String.class;
Class<Serializable> cls2=Serializable.class;
Class<Integer[]> cls3=Integer[].class;
Class<Deprecated> cls4=Deprecated.class;
System.out.println(cls);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);

}

}

简介

REST:Representational State Transfer,表现层资源状态转移。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。

实现

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。

传统方式 REST风格
查询操作 getUserById?id=1 user/1–>get请求方式
保存操作 saveUser user–>post请求方式
删除操作 deleteUser?id=1 user/1–>delete请求方式
更新操作 updateUser user–>put请求方式

由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?

HiddenHttpMethodFilter

SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求

HiddenHttpMethodFilter 处理put和delete请求的条件:

a>当前请求的请求方式必须为post

b>当前请求必须传输请求参数_method

满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式

在web.xml中注册HiddenHttpMethodFilter

1
2
3
4
5
6
7
8
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

我们进入到其中源码中,接受到post请求,并且带有_method的方法请求会发生转换

image-20220425153400442

即在请求的方法中加入

1
2
3
4
5
6
在这里发送请求的时候,会将方法转换为PUT请求,并且在controller中需要限制该method,才可以正确的进行。如果没有说明,在controller中没有找到Put请求,那么就很有可能进入到保存操作
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="text" name="name" value="name">
<input type="submit" value="修改">
</form>
1
2
3
4
5
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String updateuser(String name){
System.out.println(name);
return "target";
}

删除方法的实现

首先设置点击表单的超链接与实践响应,实践响应的时候调用另外一个表单执行真正的删除操作,发送删除的请求,同时禁止跳转,这样可以实现本页

创建处理delete请求方式的表单

1
2
3
4
5
<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post">
<!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>

删除超链接绑定点击事件

引入vue.js

1
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>

删除超链接

1
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>

通过vue处理点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>

控制器方法

1
2
3
4
5
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}