Java基础

Vue集成天地图:从定位到交互绘图的完整实践指南 本文详细介绍了如何在Vue项目中集成天地图API,从基础配置到高级功能实现,包括定位、标点、画线等交互绘图功能。通过实战代码示例和性能优化技巧,帮助开发者快速掌握Vue与天地图的结合应用,提升地图开发效率。 阅读详情

参考JavaGuide及其他开源资料,自用留存无商业目的

Java编译原理

1. Java语言的编译方式是什么,与其他语言有什么区别

Java是 编译解释型 语言。

编译语言有三种方式:

1)编译型语言,例如C。是使用编译器将源码一次性翻译为机器码,再执行。例如C语言代码将.c源码编译为机器码并保存在可执行文件中(exe或.o)。这种方式开发效率低,但执行效率高。

2)解释型语言,例如python。是在运行时将源码一句句进行解释执行。这种方式开发效率高,但执行效率低。

3)编译解释型语言。先将源码翻译为字节码文件,再对字节码文件进行解释,包含了编译和解释两个阶段,因此叫编译解释型语言。

2. 字节码文件是什么,有什么好处

字节码文件即.class文件,Java编译的方式是先翻译为.class文件,然后.class在JVM虚拟机中运行。

使用字节码文件的好处:

1)与编译型语言相比实现了平台无关性:编译型语言(例如C)在不同平台需要重新编译,例如C语言代码在Windows系统下要编译为exe文件,而换了Linux系统要重新编译为.o文件才能执行。

2)与解释型语言相比效率较高:解释型语言在运行时需要一句句进行解释,效率较低。而Java先将源码翻译为字节码文件,再对字节码文件进行解释,效率较高。

3. JVM、JRE、JDK分别是什么

JVM:Java虚拟机,用于运行字节码文件。JVM的主要作用就是将字节码文件翻译为不同系统的机器码然后执行,不同系统需要安装不同版本的JVM,JVM是Java实现平台无关性的关键。

JRE:JVM+Java类库,是Java应用程序的运行环境。

JDK:JRE+Java开发工具,在JRE的基础上增加了Java开发调试所需的工具,是程序员进行Java编程所需要的环境。

Java9以后,取消了单独的JRE,而是使用jlink工具生成Java程序所需要的最小运行环境。

4. Java即时编译

即时编译(Just in Time Compilation),或称为JIT。JIT会将热点代码编译为机器码存储在codeCache中,当下次执行遇到这段代码,就从codeCache中直接读取机器码运行,来提高性能。

引入JIT后的JVM流程:

1)将源码编译为字节码文件,这一过程包括词法分析、语法分析、语义分析等

2)判断是否是热点代码(判断方式是设置一个阈值,如果方法或代码块在一段时间内调用次数超过阈值,就认为它是热点代码)

3)如果是热点代码,在codeCache中读取机器码执行。

4)如果不是热点代码,直接解释执行。

5)解释执行后如果代码调用次数超过阈值,触发编译,并将编译后的机器码存入codeCache中

除了缓存热点代码的机器码以后,JIT还会进行一系列优化:

1)中间表达形式

2)方法内联:在遇到方法调用时,将目标方法的方法体纳入编译范围,并取代原有的方法调用的手段。

3)逃逸分析:逃逸分析指分析对象在哪些地方被调用,从而判断对象是否逃逸出线程或方法。JIT在内联方法的基础上进行逃逸分析,进而使用锁消除、栈上分配、标量替换等方法进行优化。

锁消除:如果JIT分析锁并没有逃逸出当前线程,那么加锁解锁就没有意义,就会直接删除该锁。

栈上分配与标量替换:如果JIT经过逃逸分析发现对象没有逃逸出方法,就可以不分配在堆中,而是分配在栈上。JIT使用的栈上分配方法实际上是通过标量替换实现的,即对于一个对象的创建,直接用它其中包括的多个变量去替换这个对象。

4)循环展开:通过展开循环减少循环次数。

5. Java静态编译

静态编译(Ahead of Time Compilation),或称为AOT、提前编译,即在程序执行前将Java代码编译为机器码,避免了JIT预热的开销,减少了内存占用。

但AOT不支持Java的一些动态特性,例如反射、动态代理等。

Java语法

1. 什么是标识符和关键字

标识符是程序员给变量和方法起的名字,关键字是Java已经赋予其含义的单词,不能作为标识符。

2. 访问控制修饰符有哪些,分别什么范围

public:所有类都能访问

protected:当前类、同一包、子类能访问

默认(default):当前类、同一包能访问

private:只有当前类能访问

3. 成员变量和局部变量的区别

成员变量是在类中定义的,局部变量是在方法或代码块中定义中。

成员变量可以被访问修饰符或static、final修饰,局部变量不能。

成员变量会自动赋对应类型的默认值,局部变量不会自动赋值,必须显式赋值。

4. static、final

static修饰的成员变量是静态变量,它是属于类的,而不是属于某个对象的,被类的所有实例共享。

如果成员变量被static final修饰,那么它是一个常量,不能被改变,必须显式赋值。

5. 静态方法为什么不能调用非静态成员

静态方法是属于类的,在类加载的时候分配内存,此时非静态成员还不存在,所以不能调用。

非静态成员是在实例化以后才存在。

6. 异常处理

异常Exception和错误Error的区别:

Throwable有两个子类,一个是Exception,一个是Error。Exception是程序可以捕获并处理的,Error不能捕获并处理,遇到了程序就会停止。

受检查异常和不受检查异常的区别:

受检查异常(Checked Exception):必须处理,不处理不能通过编译,例如IOException、除零异常等;不受检查异常(Unchecked Exception):可以不处理,不处理也能通过编译,例如空指针异常NullPointException。

try-catch-finally语法:

try {
    // 可能抛出异常的代码
    throw new RuntimeException("抛出异常");
} catch(Exception e) {
    System.out.println("捕获异常");
} finally {
    System.out.println("finally");
}

当程序中有多种类型异常时,使用多个catch字段

throw、throws:

throw用于抛出异常,throws用于声明方法可能抛出异常

public static void method throws IOException {
    throw IOException("hi");
}

7. 重写和重载有什么区别

重载是不同的方法有相同的方法名和不同的参数列表,当调用方法时,会根据参数列表匹配对应的方法。

重写是子类继承父类后,编写与父类方法具有相同方法名、参数列表的方法,当调用方法时,会根据所属的类自动调用对应重写后的方法。

重写用@Override注解修饰

例如重写priorityQueue的排序方法

Queue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
    @Override
    public int compare(int[] a, int[] b){
        return a[0] != b[0] ? b[0] - a[0] : b[1] - a[1];
    }
});

8. 引用拷贝、浅拷贝、深拷贝

引用拷贝:新建一个引用,指向同一个对象

浅拷贝:在堆上新建一个对象,但对于对象内部的引用类型的变量,直接指向原来的

深拷贝:在堆上新建一个对象,对象内部的引用类型变量也新建

9. Java注解

注解(Annotation)是一种特殊的注释,可以修饰类、方法、字段。

注解有两种解析方式:

编译时解析,例如@Override,在编译时扫描到这个注解就会处理。

运行时解析,例如@Value、@Component,在运行时通过反射动态解析。

10. Java值传递

Java方法调用采用的是值传递,也就是实参传递给方法的形参时,是复制一个副本传递过去,而不是直接将引用传递过去。

当参数是引用类型时,复制的是引用的地址,仍然是值传递。

例如

public void test() {
    int a = 1;
    System.out.println(a); // 1
    change(a);
    System.out.println(a); // 仍然是1
}
public void change(int a) {
    a++;  // 对形参的改变不影响实参
}

public void test() {
    int[] a = {1, 2, 3, 4, 5};
    System.out.println(a[0]); // 1
    change(a);
    System.out.println(a[0]); // 变为0,传递的是复制的地址
}
public void change(int[] a) {
    a[0] = 0;
}


public class Item {
    int a;
    int b;
}
public void test() {
    Item item = new Item(1, 2);
    change(item);
    System.out.println(item.a); // 2
}
public void change(Item item) {
    item.a++;
}

public void test() {
    String str1 = "aaa", str2 = "bbb";
    swap(str1, str2);
    System.out.println(str1);  // 仍然是aaa
    System.out.println(str2);  // 仍然是bbb
}
public void swap(String str1, String str2) {
    String temp = str1;
    str1 = str2;
    str2 = temp;
    System.out.println(str1);  // bbb  形参str1指向bbb的地址,形参str2指向aaa的地址,但实参没有发生改变
    System.out.println(str2);  // aaa
}

10. Java SPI

SPI(Service Provider Interface),即给服务提供者的接口。

API(Application Programming Interface)。

API是服务提供者编写好代码后,向外提供调用的接口。而SPI是服务调用方提供统一的接口后,服务提供方可根据接口进行不同的实现。

11. 泛型

泛型即类型参数,指用T、E等占位符作为类型,不指定具体的类型,而是在编译时进行类型检查。

泛型类:

public class Test<T> {
    private T name;
    
    public Test(T name) {
        this.name = name;
    }
    
    public T getName() {
        return name;
    }
}
// 使用时传入类型
Test<String> test = new Test();

泛型接口:

public interface Test<T> {
    public T getName();
}

泛型方法:

public T printList(T[] list) {
    for(T item : list) {
        System.out.println(item);
    }
    return list[0];
}

Integer[] intList = {1, 2, 3, 4, 5};
String[] strList = {"aa", "vv", "cc"};
printList(intList);  // 可以传入不同类型的数组
printList(strList);  

泛型的类型擦除:

Java的泛型是一种“伪泛型”,在编译时,会动态地擦除泛型,并修改为Object或指定的其他类型。

既然会擦除,为什么还要使用泛型?

泛型能够提高代码灵活性,如果没有泛型,只能用Object类型代替,需要手动进行强制类型转换,比较麻烦而且容易出错。泛型在编译期就进行了类型检查和类型擦除,也就是只要能通过编译,就确保了类型安全。而使用Object进行强制类型转换,哪怕通过编译,在运行期间也可能报错。

桥方法:

继承泛型类后,如果没有重写类的泛型方法,编译器会自动创建对应的桥方法来保证多态。

12. 通配符

通配符是<?>,表示不确定的类型。分为三种:无界通配符<?>可以传入任何类型,下边界通配符<? extends FatherClass>只能传入FatherClass及其子类,上边界通配符<? super SonClass>只能传入SonClass及其父类。

通配符表示不确定传入的会是什么类型,所以List<?>不能向其中添加元素,只能将其作为Object对象进行处理。

与泛型相同,通配符也会在编译时被类型擦除,编译后都变成了Object或指定类型。

Java数据类型

1. 八种基本数据类型和对应的包装数据类型分别是什么

基本数据类型:boolean、byte、short、int、long、float、double、char

包装数据类型:Boolean、Byte、Short、Integer、Long、Float、Double、Character

2. 基本数据类型和包装数据类型有什么区别

存储位置:基本数据类型定义的局部变量存储在栈中,非静态成员变量存储在堆中,静态成员变量存储在元空间中;包装数据类型实际上是对象实例,几乎都存储在堆中。(这里几乎的含义:Hotspot虚拟机引入JIT优化后,会对对象开启逃逸分析,如果对象没有逃逸到方法外部,就分配在栈上,而不会在堆中分配内存)

默认值:基本数据类型有默认值,包装数据类型默认值是null

比较方式:基本数据类型可以用==比较,包装数据类型用==比较的是内存地址,要用equals函数比较,才是比较的实际值。

3. 包装数据类型的缓存机制

Byte、Short、Integer、Long默认创建[-128, 127]的缓存,Character默认创建[0, 127]的缓存。当创建新对象的值在这个范围内时,直接返回缓存池中的对象,而不会创建新对象,以此来提升性能。

Integer i1 = 2;
Integer i2 = 2; 
Integer i3 = new Integer(2);  // 显式创建新对象,不用缓存
System.out.println(i1==i2); // 输出true
System.out.println(i2==i3); // 输出false

4. 什么是自动拆装箱,什么时候会发生自动拆装箱,底层是怎么实现的,为什么自动拆箱可能带来NPE风险

自动拆箱指包装数据类型转换为基本数据类型,自动装箱指的是基本数据类型转换为包装数据类型。

Integer i1 = 10;  // 发生自动装箱
int i2 = i1;  // 发生自动拆箱

自动拆箱的原理是intValue()、charValue()等,自动装箱的原理是valueOf(),上面两行代码就等价于

Integer i1 = Integer.valueOf(10);
int i2 = i1.intValue();

NPE也就是NullPointException(空指针异常),在自动拆箱时调用对应的intValue()、charValue()方法,然而,如果包装数据是null,调用这些方法就会导致NPE。如下

public void test() {
    int value1 = getValue(1);
    int value2 = getValue(-1); // 空指针异常
}
Integer getValue(int key) {
    if(key > 0) return key + 1;
    else return null;
}

5. String类型:String、StringBuilder、StringBuffer有什么区别

String是不可变的,StringBuilder和StringBuffer是可变的,其中StringBuffer对方法加了同步锁,所以是线程安全的,StringBuilder是线程不安全的。

因此,操作少量数据使用String,单线程操作大量数据使用StringBuilder,多线程操作大量数据使用StringBuffer。

String的不可变:

String内部是使用private fianal byte[] value来保存字符串的,因此是不可变的。Java重载了String类的“+”和“+=”运算符,当使用“+”来操作String时,实际上是先将其转为StringBuilder,调用其append方法完成加操作后,使用toString方法再转回String。

但当多次使用“+”时,编译器会多次创建StringBuilder,会增加内存消耗,因此如果要在循环中大量操作字符串,最好还是直接使用StringBuilder。

6. 什么是字符串常量池

字符串常量池是JVM为了减少内存消耗而专门开辟的一块区域,当字符串第一次创建时,会保存在常量池中,当下一次创建相同的字符串时,会直接返回常量池中的字符串,而不会重新创建,如下:

String a1 = "aaa";
String a2 = "aaa";
System.out.println(a1 == a2);  // 输出true

String a3 = "aaa";  // 直接返回字符串常量池中的对象
String a4 = new String("aaa");  // 创建一个新的String对象,并用字符串常量池中的"aaa"对象初始化它

问:当显式new一个String对象时,例如String a1 = new String(“abc”);会创建几个对象

1)如果字符串常量池中没有"abc",创建两个。具体来说,先创建一个"abc"字符串,保存在字符串常量池中,再在堆中创建一个新的String对象,并使用"abc"常量进行初始化;

2)如果字符串常量池中已经存在"abc"常量,只在堆中创建一个String对象。

7. 如何改变一个String的值

问如何改变一个String的值,如果回答直接赋值是不对的,因为String内部的char数组/byte数组是final修饰的,不可变的。如果直接赋值的话,其实是创建了一个新的String对象,然后将引用指向了这个对象,如果打印前后的hashcode,是不一样的。

那么如何不创建新对象,原地改变这个String的值呢?

正确的回答是用反射机制。

String str = "abc";
System.

Field field = String.class.getDeclaredField("value");  // 获取String内部的value字段(也就是char数组/byte数组)
field.setAccessible(true);  // 绕过访问修饰符
field.set(str, new char[]{'d', 'e', 'f'});  // 将str对象的field字段的值修改为def
System.out.println(str.hashCode());  // 前后两个哈希值是一样的,说明是同一个对象

8. 常量折叠

对于编译期常量的运算,javac编译器会进行常量折叠的优化,也就是将常量的简单操作计算出来,直接存储结果。八种基本数据类型常量和string常量都可以进行常量折叠。而对于变量和运行时常量,由于编译期间无法确定其值,所以不会进行常量折叠优化。例如:

String str3 = "abc" + "def";  // 是编译期常量,常量折叠后,在字符串常量池中创建"abcdef"对象,而不会创建"abc"和"def"对象
String str4 = "abcdef";  // 常量池中的"abcdef"对象
System.out.println(str3 == str4);  // true

String str1 = "abc";
String str2 = "def";
String str5 = str1 + str2;  // 变量,这里做的操作是字符串对象拼接,底层原理是先将String转为StringBuilder,使用其append方法进行拼接,最后再将其转回String,所以它返回的不是字符串常量池中的对象
final String str6 = str1 + str2;  // 是运行时常量,虽然由final修饰,但右边表达式是变量,编译期无法确定值,也就无法进行常量折叠优化
System.out.println(str3 == str5);  // false
System.out.println(str3 == str6);  // false

9. String的intern方法

String的intern方法是一个native方法,用于返回String类型在字符串常量池中的引用。

如果字符串常量池中有相同内容的对象,就返回其引用。如果没有,就在字符串常量池中创建,再返回其引用。

String s1 = new String("hiii");  //  在字符串常量池中创建"hiii"对象,然后创建一个新的String对象,并用常量初始化它
String s2 = s1.intern();  // 字符串常量池中的对象
System.out.println(s1 == s2);  // false
System.out.println(s2 == "hiii");  // true


String s1 = new String("hi") + new String("hy");  // 创建hi常量、hy常量,创建两个新String对象分别用两个常量初始化,然后用String重载后的+运算符完成字符串拼接
String s2 = s1.intern();  // 在字符串常量池中创建hihy对象
System.out.println(s1 == "hihy");  // false
System.out.println(s2 == "hihy");  // true

Java面向对象

1. 面向对象和面向过程的区别

面向过程是将解决问题的过程拆分为一个个方法的执行,面向对象是先抽取出对象,然后用对象调用方法的方式去解决问题。

2. 面向对象的三大特征

封装:将一个对象的属性隐藏在内部,不允许直接访问,而是通过暴露的方法接口访问。

继承:子类可以使用extends关键字继承父类,拥有父类全部的方法和属性(但无法访问private的方法和属性)

多态:即一个对象拥有多种状态,当子类继承父类,且重写父类的方法后,当调用该方法时,会自动根据具体的类型调用对应的方法

3. 抽象类和接口

使用abstract修饰,只有方法名,没有方法体,子类继承后重写抽象方法

abstract public class Animal {
    // 抽象方法
    abstract void eat();
    // 非抽象方法
    void sleep(){
        System.out.println("dog sleeping");
    }
}
public class Dog {
    @Override
    void eat(){
        System.out.println("dog eatting");
    }
}

接口中的方法都是只有方法名,没有方法体,使用implements关键字实现接口后,使用@Override注解重写

接口中的变量必须是public static final的,而且必须有初始值

以前接口中的方法必须是抽象方法,但现在允许接口中有方法的默认实现,用default关键字修饰,当实现接口的类需要重写时,只需用@Override注解重写即可

public interface Animal {
    public static final name = "animal";
    public void eat();
    default public void bark(){
        System.out.println("barking");
    }
}
public class Dog implements Animal {
    @Override
    public void eat(){
        System.out.println("dog eatting");
    }
    
    @Override
    public void bark(){
        System.out.println("dog barking: wangwangwang");
    }
} 

抽象类和接口有什么区别:

  1. 目的不同:抽象类主要是为了实现代码复用,接口主要是为了规定必须有的能力(即实现这个接口,就必须实现对应的方法)
  2. 要求不同:Java是单继承的,只能继承一个类,但可以实现多个接口

Java反射机制

反射机制含义及优缺点

Java反射机制是指在运行时动态获取类信息并操作类或对象。

反射的优缺点:

优点:使方法调用更灵活

缺点:

  1. 反射调用比普通调用性能低

反射之所以性能相对较差,主要是因为它在运行时进行了大量的查找和验证操作。比如,通过名字查找方法、字段等,这都增加了额外的开销。

优化反射性能的一些常见方法有:

1)缓存反射操作的结果,避免重复查找。

2)尽量减少反射的使用,只在必要的时候使用。

3)对于频繁使用的反射操作,可以考虑使用动态代理来提高性能。

  1. 可能造成安全风险。例如会破坏封装性,反射机制可以绕过访问控制符,访问private的字段和方法;恶意代码可能利用反射来执行不被允许的操作。

防范方法:

1)设计严格的权限控制,只允许受信任的代码使用反射;

2)对输入的反射相关参数进行检查和过滤。

反射的使用步骤

  1. 获取类的Class对象
Class<?> clazz = MyClass.class;
Class<?> clazz = object.getClass();
Class<?> clazz = Class.forName("com.example.MyClass");
  1. 获取类的构造方法、字段、方法
// 获取构造方法
Constructor<?> constructor = clazz.getDeclareConstructor();
// 获取字段
Field field = clazz.getDeclaredField("fieldName");
// 获取方法
Method method = clazz.getMethod("methodName");
  1. 操作构造方法、字段、方法
// 创建一个该类的新对象
Object object = constructor.newInstance("arg");
// 访问字段
field.setAccessible(true); // 绕过访问控制权限检查
field.set(object, "value");  // 设置object对象field字段的值为"value"
// 调用方法
method.invoke(object, 123);

Spring框架中应用反射的例子

依赖注入

对于@Autowired注解标记的变量,扫描其类路径,找到有@Component注解标记的类,使用反射机制创建对应实例,并通过反射注入到变量中。

注解解析

使用getAnnotations()获取注解,并使用.value、.number等解析注解的内容

// 获取类的注解
Class<?> clazz = MyClass.class;  // 获取类对象
Annotation[] annotations = clazz.getAnnotations();  // 获取注解
MyAnnotation myAnnotation = clazz.getAnnotation(MyAnnotation.class);  // 获取指定注解

// 获取方法注解
Method method = clazz.getMethod("myMethod");  // 获取指定方法
Annotation[] methodAnnotations = method.getAnnotations();  // 获取该方法的注解
MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);

// 获取字段注解
Field field = clazz.getField("myField");
Annotation[] fieldAnnotations = field.getAnnotations();
MyAnnotation fieldAnnotation = field.getAnnotation(MyAnnotation.class);

// 解析注解的内容
String value = myAnnotation.value();
int number = myAnnotation.number();

动态代理:

使用反射类Method调用方法,实现方法的代理

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

public class UserServiceInvocationHandler implements InvocationHandler {
    private final Object target; // 目标对象

    public UserServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强逻辑:在方法调用前执行
        System.out.println("Before method: " + method.getName());

        // 调用目标对象的方法
        Object result = method.invoke(target, args);

        // 增强逻辑:在方法调用后执行
        System.out.println("After method: " + method.getName());
        return result;
    }
}

Java代理模式

代理模式就是用代理对象代替真实对象来进行访问,在不修改真实对象的前提下,为对象提供某些扩展和增强功能。

代理模式有两种,一种是静态代理,一种是动态代理。

1. 静态代理

静态代理是为每一个目标类手动编写代理类,并在编译时将其翻译为字节码文件。

动态代理是编写代理类,并在运行时传入目标类,动态为目标类生成代理类。

与静态代理相比,动态代理的优点是更加灵活,不必为每个目标类都单独生成代理类,避免了代码冗余。

动态代理有两种实现方式:

2. JDK动态代理

JDK动态代理是基于接口实现的,要求目标类必须实现某个接口。如下:

// 目标类接口
public interface TargetInterface {
    public void method();
}
// 目标类具体实现
public class TargetImpl implements TargetInterface {
    public void method() {
        System.out.println("方法实现");
    }
}
// 代理类
public JDKProxyHandler implements InvocationHandler {
    private Object target;  // 目标对象
    
    public JDKProxyHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法执行前的代理代码
        Object result = method.invoke(target, args);
        // 方法执行后的代理代码
        return result;
    }
}

public class Test {
    public static void main(String[] args) {
        // 目标类,必须由接口实现
        TargetInterface target = new TargetImpl();
        // 代理类,由Proxy类的newProxyInstance方法实现,并类型转换为目标类接口
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxyHandler(target)
        );
        // 调用代理增强后的method方法
        proxy.method();
    }
}

3. CGLIB动态代理

CGLIB动态代理不要求实现接口,而是生成目标类的子类来实现的。

public class Target {
    public void method() {
        System.out.println("目标类方法");
    }
}
// 自定义方法拦截器(通过实现MethodInteceptor接口)
public class CGLIBProxyInterceptor implements MethodInteceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 方法执行前的代理代码
        Object result = proxy.invokeSuper(obj, args);
        // 方法执行后的代理代码
        return result;
    }
}
public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperClass(Target.class);
        enhancer.setCallback(new CGLIBProxyInterceptor());
        Target proxy = (Target) enhancer.create();
        proxy.method();
    }
}

JDK动态代理和CGLIB动态代理的区别:

JDK动态代理要求目标类必须实现接口,CGLIB动态代理不要求,而是通过生成目标类的子类实现的。因此,CGLIB不适用于final类型的类。另外JDK动态代理的性能要比CGLIB动态代理高。在Spring AOP中,实现了接口的类使用JDK动态代理,没有实现接口的使用CGLIB动态代理。

一文掌握DrissionPage的详细使用 是一个基于 Python 的网页自动化工具,主要用于网页数据的抓取、自动化操作和测试。它结合了 Selenium 和 Requests 的优点,提供了更简洁、高效的 API,适用于各种网页自动化任务。DrissionPage 是一个功能强大且易于使用的网页自动化工具,适用于各种网页自动化任务。希望本文的 DrissionPage 的基本使用方法和一些高级功能能够帮助到你。 阅读详情

相关推荐

数据本体论如何指导数仓建模?

数据本体论指导数仓建模的核心在于构建业务语义与数据结构的桥梁。该方法从业务域中抽象标准化知识体系,包含核心概念、属性、关系和规则五大要素,并将其映射为分层数仓模型。通过"业务本体梳理→本体建模→模型映射→治理迭代"的流程,实现数仓从"结构存储"到"语义载体"的升级。相比传统建模,该方法能解决语义不一致、跨域融合难等问题,使数仓具备更好的可扩展性和业务对齐性。实践表明,以数据本体论为指导的数仓建模能显著提升数据资产的可理解性和复用价值。

石榴姐yyds 346

Java基础语法

1. Java概述 1.1 Java语言发展史 语言:人与人交流沟通的表达方式 计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言 Java语言是美国Sun公司(Stanford University Network)在1995年推出的计算机语言 Java之父:詹姆斯·高斯林(James Gosling) 2009年,Sun公司被甲骨文公司收购,所以我们现在访问oracle官网即可:https://www.oracle.com 当前,我们课程使用的JDK版本:11.0 1.2 Java语言跨平台原理

terence的博客 10万+

封装POD与PinMap文件总结学习-20250516

芯片封装外形图(POD)和PinMap是芯片设计和制造中的关键文档。POD详细描述了芯片的物理尺寸、引脚布局和封装类型,而PinMap则提供了芯片封装上各个引脚的位置、名称和功能的详细信息。这些文档在芯片设计、制造和应用过程中起到“地图”的作用,帮助设计人员理解芯片的物理和电气特性。在PCB设计中,POD用于确定socket的footprint和pogo的选型,而PinMap则用于器件的合理布局和布线。这些信息对于确保芯片的正确安装和功能至关重要。

歌颂苦难的行为是无耻的; 2280

Java基础总结

时光不负赶路人,星光不负追梦人

Pandafz的博客 7702

Java基础看这个就足够了【Java基础内容总结】

Java基础内容总结一、创建第一个工程二、Java基本数据类型三、Java的基本流程控制语句1、If-else2、Switch3、For4、While5、Break 和 Continue6、Return四、对象五、方法六、访问权限七、继承和多态1、toString()继承和重写实践2、多态3、接口4、抽象类八、容器九、异常1、运行时异常2、检查性异常3、自定义异常十、I/O1、控制台I/O2、文件...

harry_c的博客 8847

Java必备基础一——一文读懂的Java基础中的基础

Java语言基本格式 Java语言基本元素 Java书写规范

Pandafz的博客 1663

Java基础重要吗?如何提升自己的 Java 基础

基础真心是精通Java的基石,越到后面用的技术越高级,但是你按照知识点一拆分发现全是基础知识,所以如何提升自己Java基础也是小伙们非常关心的问题。简单说说自己的观点 Java基础薄弱原因如下: 1、前期学习Java的时候基本都是懵懂状态,代码基本都是照着视频,笔记,源码艰难的理解的,但是学习基本停留在用的阶段,有的甚至不会用全靠自己强大记忆力就行学习的。 2、越到后面学习的技术基本都是针对就业需要的技术就行定向的学习的,所有基础方面就直接略过,所以仍然停留在用的层面。 3、在往后走,就是新的技术.

java9875的博客 943

Java基础选择题

习题均在网上摘抄,总结

qq_48019875的博客 6268

JAVA基础

该文章讲解了Java的基本语法和面向对象的基本概念,参考动力节点

weixin_74090771的博客 1159

在学习Java中——Java基础重要吗?

很明确肯定重要且异常重要,用一个简单的比喻基础就是地基,有多深厚的地基就会有多高的建筑;相反地基建造的没有那么深厚,所建造出的建筑肯定受到地基的影响; 当然也有很多人觉得学习Java基础部分掌握几个简单的知识点往后学就完啦! 请注意这需要分两种情况: 第一种代码搬运工、编程IT农民工这种偏业务型的工作,并不注重基础的工作,往往自己的知识上限已经被限制住啦,在IT领域常常做的工作比较浅层; 第二种就是技术型工种注重基础知识的学习,培养夯实自己的基础,提高自己的学习上限。通常游走在底层,结实的底层知识就

LSFZ88888的博客 832

java基础

java 01 基础语法 1.环境问题 1.1 jdk的配置 JAVA_HOME 配置的就是 JDK 安装的一个根目录 Path 无需新建,只需要添加值(JDK 下的 bin 目录)即可 CLASS_PATH 配置的就是 JDK 的 lib 目录 1.2 JVM的语言特性 JVM是Java语言能够跨平台的前提,但是JVM本身不跨平台 我们编写的文件是以”.java”为后缀的源代码文件,Ctrl+S保存时会触发编译器Eclipse对写好的代码进行编译,编译后生成以”.class.

qq_52930092的博客 1599

JAVA入门基础

1)语法不同:与 while 循环相比,do-while 循环将 while 关键字和循环条件放在后面,而且前面多了 do 关键字,后面多了一个分号。while 循环和 do-while 循环的相同处是:都是循环结构,使用 while(循环条件) 表示循环条件,使用大括号将循环操作括起来。4、 数组属于引用类型,数组可以看成是一个对象,数组中的每一个元素相当于该对象的成员变量,因为数组本身就是对象,instanceof:是Java的一个二元操作符,类似于 ==,>,< 等操作符,也是Java的保留关键字。

Fog_cxz的博客 942

java基础全套_Java全套基础教程

深知Java小白学习有多么的困难,找不到一份好的资源,所以呕心沥血做了一份java基础全套的教程资源。无论你是大牛还是java刚入行的小白,这套视频都可以满足你。Java全套视频教程——Java基础入门本套Java基础入门教程包含四个重点知识点,全套免费视频教程。知识点:环境搭建:(重点掌握)常用DOS命令及快捷键、EditPlus的安装、通用文本编辑快捷键、安装JDK、第一个Java程序的开发、...

weixin_39880301的博客 516

Java基础概念汇总

Java基础

随风而欲的博客 1263

java基础

一、理解变量的值在盒子里存的都是二进制01表示的数,图片和音频也是-127和128与255的关系,256个数代表最大数是255,从0开始连续到255.二、数据存储原理、进制转换,数据类型及转换。

zq021011的博客 2366

怎么巩固JAVA基础语法基础(上)

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar

x496182793的博客 2046

菜鸟Java基础教程 2.Java 基础语法

一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。程序计数器是当前线程执行的字节码指令地址的指示器。每个线程都有自己的程序计数器。确定线程执行的位置,指示下一条要执行的指令。在多线程环境下,确保线程切换后能够正确恢复执行位置。程序计数器通常以寄存器的形式存在于CPU中,用于记录当前线程执行的位置。在执行Java方法时,程序计数器会指示当前执行的方法字节码指令地址。

m0_46211715的博客 1720

Java 基础核心总结

Java 是一种广泛使用的程序设计语言,具有跨平台、面向对象、安全性高、灵活性强等特点,广泛应用于企业级应用程序和移动应用程序等领域。在学习 Java 语言时,需要掌握一些基础核心知识,本文将为您总结 Java 基础核心知识点,以便于您的学习和参考。介绍Java 基础核心知识点包括基本语法、面向对象编程、异常处理、集合框架、IO 流和多线程等方面。

分享是一种积极的生活态度~~ 1870

适合Java基础小白学习的Java基础教程

很多Java基础小白,在刚刚快入门的时候玩命的学习,玩命的记住Java原理,天天早上五点起床背Java的一些英文词汇,然后遇见一些未知的困难,让自己打到癫狂状态,逐渐迷失自我放弃Java,为了解决这个难题,我从100T 的教程资源中寻找到一套适合Java基础小白学习的Java基础教程,只要你耐心看完就一定能够入门。 动力节点Java基础301集 Java基础入...

zzpdljd1991的博客 1694

java基础详解

Dos命令 打开Dos快捷键:windows+R 输入:cmd 开启快速复制:鼠标右键>属性>编辑选项 cd 切换目录 cd.. 返回上层目录 cd\ 退回当前目录 dir 查看当前目录子文件和子文件夹 cls 清屏 exit 退出 Tab 自动补全 ↕ 使用用过的命令 mkdir...

weixin_45116982的博客 523
上一篇: 【北航软院】保研流程和保研经验介绍2022
下一篇: Java集合+并发(部分)
拍坛对酒
博客等级 码龄6年 83粉丝 36原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值