Java基础精选面试题

目录

JAVA基础精选面试题

JDK JRE 和 JVM的关系

八大基本类型和取值范围

冒泡排序实现原理

重载和重写的区别

四种权限修饰符

形参和实参的区别

JAVA中的==和equals的区别

为什么重写equals()必须重写hashcode()

面向对象的特征

接口与抽象类的区别

面向对象和面向过程的区别和特点

this与super的区别

static关键字与final关键字

序列化与反序列化

JVM如何判断一个对象已经死亡

java手动调用System.gc()会发生什么?

接口和抽象类有哪些区别

String类的常用方法

比较下String与StringBuilder的区别

StringBuilder和StringBuffer的区别

hashMap和hashTable的区别

单线程与多线程的区别

线程中为什么需要重写run()方法?

run()方法和start()方法的区别是什么?

线程的状态和是如何切换的?

进程、程序、线程的区别

并行与串行的区别

LinkedList在JDK1.7前后的区别

ArrayList和LinkedlList区别

浅谈二叉排序树的操作

lambda表达式

java中重写和重载有什么区别

JAVA代理的几种实现方式


JAVA基础精选面试题

JDK JRE 和 JVM的关系

JDK--(Java Development Kit):是java程序开发工具包,包含JRE和开发人员所使用的工具,其中的开发工具:编译工具(javac.exe)和运行工具(java.exe),我们想要开发一个java程序就必须安装JDK

JRE--(Java Runtime Environment):是java程序的运行时环境,包含了JVM和运行时所需的核心类库,我们想要运行一个已有的java程序,那么只需要安装JRE即可.

JVM--(Java Virtual Machine):即java虚拟机, java运行时的环境,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

八大基本类型和取值范围

冒泡排序实现原理

import java.util.Arrays;

public class BubbleSort {
    public static void main(String[] args) {
        //1.创建一个无序数组
        int[] a = {27, 96, 73, 25, 21};
        //2.创建并调用自定义排序方法
        sortArray(a);
    }
        private static void sortArray (int[] a){
            //1.外层循环:控制的是比较的论数,假设有n个数,最多比较n-1轮
            //外层循环的循环变量代表的是轮数
            //比如5个数,最多比较4轮,<=a.length-1,最多取到1234共4个值
            for (int i = 1; i <= a.length - 1; i++) {
                //2.内层循环:相邻比较+互换位置
                //内层循环的循环变量代表的是数组下标
                for (int j = 0; j < a.length - 1; j++) {
                    if (a[j] > a[j + 1]) {
                        //交换数据
                        int t = a[j];
                        a[j] = a[j + 1];
                        a[j + 1] = t;
                    }
                }
            }
            System.out.println(Arrays.toString(a));
    }
}

重载和重写的区别

方法的重载:

在同一个类中出现多个方法名相同,但是参数的类型,参数的个数,参数具体的内容不同,与参数的名字无关

注意:方法是否构成重载,取决于参数列表中参数的个数与参数的类型,与参数的名字无关

重载的意义:重载不是为了程序员方便,而是为了方便外界调用这个名字的方法时,不管传入什么类型的参数,都可以匹配到对应的方法来执行,程序会更加的灵活.

方法的重写:

子类继承了父类以后,想要在不改变父类代码的情况下,实现功能的修改与拓展重写遵循的规则:

一大:子类方法的修饰权限符>=父类方法的权限修饰符

两同:方法名与参数列表与父类保持一致

两小:子类的方法的返回值类型<=父类方法的返回值类型,注意:这里说的是继承关系,不是值得大小,子类抛出的异常类型<=父类方法抛出的异常类型

四种权限修饰符

形参和实参的区别

方法的传值:如果方法的参数类型是基本类型,传入的是实际的字面值,如果是引用类型,传入的是地址值

形参:形式意义上的参数,比如方法参数列表的参数名,光看参数名是无法确定这个变量的值是多少的

实参:实际意义上的参数,比如我们的局部变量,比如成员变量,比如调用方法时传入的数字

JAVA中的==和equals的区别

equals和==最大的区别就是一个方法一个是运算符.

==:如果比较的对象是基本数据类型,则比较的是数值是否相等,如果比较的是引用数据类型,则比较的是对象的地址值是否相等.注意:如果我们在使用引用类型时,如果两个引用类型保存在同一个常量池中,输出结果也为true.

 

equals:用来比较方法两个对象的内容是否相等.注意:equals()不能用于基本数据类型的变量,如果没有对equals()进行重写,则比较的时引用类的变量所指向的对象地址.

为什么重写equals()必须重写hashcode()

两个对象若equals的结果为true,此时两个对象的hashcode值也一定相同的,所以是一起重写的

面向对象的特征

封装

1.前提:为了保证数据的安全,也为了程序的使用者能够按照我们预先设计好的方式来使用资源

2.封装属性:用private修饰我们的属性,然后为属性提供对应的getXxx()[获取属性值]与setXxx()[属性设置值]

3.封装方法:用private修饰方法,被修饰的方法只能在本类中使用,所以我们在本类的公共方法里调用这个私有方法,外界如果想用这个私有方法的功能,只能调用这个公共方法就可以了

继承

1.前提:继承可以实现程序的复用性,减少代码的冗余.

2.我们通过extends关键字建立子类与父类的继承关系,格式:子类extends父类

3.继承相当于子类把父类功能复制一份,包括私有资源

4.继承是可以传递的:爷爷的功能会传递给爸爸,爸爸的功能可以传递给孙子

5.Java的类是单继承的:一个子类只能有一个父类,但是一个父类可以有多个子类

6.子类继承了父类以后,如果对父类的功能不满意,可以在不修改父类功能的[满足OCP原则]的前提下,在子类中,重写继承过来的这个方法,重写需要满足的规则:两同 两小 一大,要谨慎使用

7.继承是一种is a的关系,强耦合,关联性特别强,而且类的继承机会只有一次,需要谨慎使用

8.子类可以直接使用父类的所有非私有资源

多态

1.前提:为了忽略子类型之间的差异,统一看做父类类型,写出更加通用的代码,比如:把Cat看做Animal,把Dog看做Animal,如果方法需要设置传入的参数.可以buy(Animal a),比如:把算术异常、输入不匹配异常都都看作是Exception,统一捕获处理,只写一个解决方案

2.概念:"在同一时刻,同一对象,代表的类型不同,拥有多种形态

3.多态的要求:继承+重写

4.多态的口诀1:父类引用指向子类对象:父类型的引用类型变量保存的是子类对象的地址值

5.多态口诀2:编译看左边,运行看右边:父类中定义的功能,子类才能使用,否则报错,多态中,方法的定义看的是父类的.

6.多态中资源的使用:

1)成员变量:使用的是父类的

2)成员方法:对于方法的定义看的都是父类的,对于方法实现,重写后使用的是子类的

3)静态资源:静态资源属于类资源,不存在重写的概念,在哪个类中定义的,就属于哪个类

7.向上造型与向下造型

1)这两种都属于多态,只不过是多态的两种表现形式

2)向上造型[最为常用]

可以把不同的子类都看做是父类型,比如Parent p=new Child();

比如:花木兰替父从军,看做是父类型,并且花木兰在从军的时候,不能使用自己的特有功能,比如化妆

3)向下造型

前提:必须得先向上造型,才能向下造型

子类的引用指向子类的对象,但是这个子类之前被看作是父类类型,所以需要强制类型转换

Parent p=new Child();然后Child c=(Child)p;

比如:花木兰已经替他爸打完仗了,想回家织布,name这个时候,一致被看做是父类型的花木兰必须经历"解甲归田"[强制类型转换]这个过程,才能重新被看作成子类类型,使用子类的特有功能

为什么向下造型:之前被看做事父类类型的子类对象,想使用子类的特有功能,那就需要向下造型.

抽象

1.抽象的关键字时abstract

2.被abstract修饰的方法是抽象方法,抽象方法没有方法体

3.如果一个类中出现了一个抽象方法,那么这个类必须是被abstract修饰的

4.关于抽象方法的特点:

1)抽象类中的方法不做限制:全普/全抽/半普半抽

2)如果一个类中的方法都是抽象方法,还要声明成抽象类,为什么?为了不让外界创建本类的对象

3)抽象类不可以创建对象,所以常用于多态

4)抽象类中包含构造方法,但是不是为了自己创建对象时使用,而是为了子类的super()

5)抽象类中也可以定义成员变量的

5.如果一个子类继承了一个抽象父类,有两种结局方案

1)作为抽象子类:不实现/实现部分 抽象父类中的抽象方法:"躺平"

2)作为普通子类:实现抽象父类中所有的抽象方法:"负债子偿"

6.面向抽象进行编程:后天重构的结果 

接口与抽象类的区别

1.接口是一种用interface定义的类型,抽象类是一种用class定义的类型

2.接口中的方法都是抽象方法(还有default和static),抽象类中可以写普通方法

3.接口中的都是静态常量,抽象类中可以写不同成员变量

4.接口是先天设计的结果,抽象是后天重构的结果

5.接口没有构造方法,不可以实例化,抽象类有构造方法,但是也不可以实例化

6.接口可以多继承,抽象类只能单继承

面向对象和面向过程的区别和特点

面向对象和面向过程最本质的区别在于考虑问题的出发点不同,面向过程是以事件流程为考虑问题的出发点,而面向对象则是以参与事件的角色(对象)为考虑问题的出发点,所以面向对象在处理问题时更加灵活。目前,面向过程的语言更多被用于处理底层业务,而面向对象编程则更多用于实现一些业务逻辑复杂的大型系统。

从结构上来说,面向过程的特点是模块化和流程化,而面向对象的特点是封装、继承和多态,这里面就有本质的区别了.

什么是⾯向对象编程
	我要抓老鼠
		我买只猫,猫有抓⽼⿏的⽅法,对象本身具有的⽅法
		放进房⼦⾥⾯就⾏了
		复⽤(放到别的房⼦⾥⾯)
什么是⾯向过程编程
	我要抓老鼠
		买⽼⿏笼
		放诱饵
		等⽼⿏进⼊笼⼦
		把笼⼦关起来

this与super的区别

1.this代表的是本类,super代表的是父类

2.当本类的成员变量与局部变量同名时,我们可以通过this.变量名类指定本类的成员变量,当父类的成员变量与子类的变量名同名时,我们可以通过super.变量名指定父类的成员变量

3.我们可以在本类构造函数的第一行使用this();调用本类的无参构造/使用this(参数),调用本类对应参数的构造方法,构造函数的调用只能有这一种方法,或者创建对象时被动触发,不能在外面自己触动调用构造函数直接不能相互调用,否则会死循环

4.我们可以在子类构造函数的第一行,使用super();调用父类的无参构造/使用super(参数);调用父类对象的参数构造方法.注意:子类默认调用super();父类的无参构造,如果父类没有无参构造,需要手动指定调用哪个含参构造

static关键字与final关键字

static

1.被static修饰的资源统称为静态资源,可以用来修饰变量、方法、代码块、内部类

2.静态资源属于类资源,随着类的加载而加载,优先于对象进行加载,只加载一次

3.静态资源可以不通过对象,使用类名直接调用,不需要创建对象

4.静态的调用关系,被全局所有对象共享

5.静态的调用关系:静态资源只能调用静态资源

6.静态资源是优先于对象的,所以静态资源不能与this和super公用

final

1.final表示最终

2.被final修饰的类也是最终类,也称作叶子结点,所以不能被继承

3.被final修饰的方法是这个方法的最终实现,不能被重写

4.被final修饰的是常量,值不可以被修改,注意常量定义必须赋值

序列化与反序列化

序列化与反序列化的作用就是对象的保存与传输

序列化:把内存中的对象通过序列化流输出到磁盘中,使用的流是:ObjecrtOutPutStream

反序列化:通过反序列化流将磁盘中的数据恢复成对象,使用的流失ObjectInputStream

注意1:一个类的对象如果想要被序列化,name这个类必须实现可序列化接口,实现这个接口的目的是相当于给这个类做一个标记,标记这个类可以序列化

注意2:序列化时会生成一个UID,表示当前序列化输出的对象版本信息,反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致会报错

注意3:所以,标准操作是一次序列化对应一次反序列化,如果目标对象所在的类没有做任何修改,一次序列化可以对应多次反序列化(根本原因是UID没变)

JVM如何判断一个对象已经死亡

JAVA堆中几乎存放着所有的对象实例,垃圾回收在堆进行垃圾回收前,首先要判断这些对象哪些还活着,哪些已经死去,判断死去有如下几种算法

1.可达性算法

通过一系列的称为"GC Roots"的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象的GC Root没有任何引用链相连时,则证明此对象是不可用的.JAV/C#等语言就是使用可达性算法进行垃圾回收的.

2.引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是不可能再被使用的,即对象已死;但是,主流的JVM中没有选用引用计数算法来管理内存,最主要的原因就是引用计数算法无法解决对象的循环引用问题.Python/ActionScript等语言都是基于引用计数算法

java手动调用System.gc()会发生什么?

java手动调用System.gc(),不能立刻让程序马上回收内存,这个调用相当于建议执行垃圾回收,但是什么时候不能确定

接口和抽象类有哪些区别

不同:

抽象类:

1.抽象类中可以定义构造器

2.可以又抽象方法和具体方法

3.接口中的成员都是public的

4.抽象类中可以定义成员变量

5.有抽象方法的类必须被声明为抽象类,而抽象类未必有抽象方法

6.抽象类中可以包含静态方法

7.一个类只能继承一个抽象类

接口:

1.接口中不能定义构造器

2.方法全部都是抽象方法

3.抽象类中的成员可以是private,默认,protected,public

4.接口中定义的成员变量实际上都是常量

5.接口中不能有静态方法

6.一个类可以实现多个接口

相同:

1.不能够实例化

2.可以将抽象类和接口类型作为引用类型

3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类

String类的常用方法

int hashCode() 返回此字符串的哈希码。
boolean equals(Object anObject) 将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() 返回此对象本身(它已经是一个字符串!)。

int length() 返回此字符串的长度。
String toUpperCase() 所有字符都转换为大写。
String toLowerCase() 所有字符都转换为小写
boolean startsWith(String prefix) 测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) 测试此字符串是否以指定的字符串结束。

char charAt(int index) 返回指定索引/下标处的 char 值/字符
int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) 将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) 根据给定元素来分隔此字符串。

String trim() 返回去除首尾空格的字符串
byte[] getBytes() 把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) 返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) 返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) 把int转成String

比较下String与StringBuilder的区别

String

特点:创建之后长度内容是不可变的,因为String底层是被final修饰的,每次拼接字符串,都会产生新对象

1.如果是直接""或者字符串常量拼接产生的,保存在字符串常量池中

2.如果直接new则保存在堆中

创建方式:

String() String(String s) String(char[] c) String(byte[] b) String s = “abc”;

优缺点:

1.优点:String类提供了丰富的关于操作字符串的方法,比如拼接,获取对应下标处的字符,截取子串等等

2.缺点:在进行字符串拼接+=的时候,效率比较低

StringBuilder

StringBuilder是一个长度可变的字符串序列,在创建时,会有一个长度为16的默认空间,当拼接字符串的时候,实则是在原对象的基础之上进行拼接,如果长度不够就扩容,所以StringBuilder在创建之后,对应操作的是一个对象.

创建方式:

StringBuilder sb = new StringBuilder();//创建一个长度为16的StringBuilder对象
StringBuilder sb = new StringBuilder(“abc”);//以指定字符串内容为“abc”的方式创建一个StringBuilder对象

优缺点:

1.优点:在拼接的时候,不会产生新对象,就避免了因为拼接频繁生产对象的问题,提高了程序的效率

2.缺点:对于字符的操作,不太方便,所以在使用的时候,如果拼接很多的时候,先将String转为StringBuilder进行拼接,拼接完成之后再转为String.

StringBuilder转String:
StringBuilder sb = new StringBuilder();
sb.append(“abc”);
String s = sb.toString();

StringBuilder和StringBuffer的区别

相同点:

StringBuilder和StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder也是使用字符数组保存字符串char[]value 但是没有用final修饰,所以这两种对象都是可变的.

不同点:

1.StringBuffer对线程加了同步锁或者对调用方法加了同步锁,所以是线程安全的,StringBuilder并没有对方法进行加同步锁,所以是非线程安全的.

2.相同情况下使用StringBuilder相比使用StringBuffer获得10%~15%左右的性能提升,但是要冒着多线程不安全的风险

3.单线程操作字符串缓冲区下操作大量数据建议使用StringBuilder,多线程操作字符串缓冲区下操作大量数据建议使用StringBuffer 

hashMap和hashTable的区别

1.底层结构不同,JDK1.7都是数组+链表,但是JDK1.8 hashMap加入了红黑树.

2.hashTable不允许K或V值为null,但是hashMap都可以

3.添加K或V的hash算法不同,hashMap添加元素时,是使用自定义的哈希算法,而hashTbale是直接采用key的hashCode()

4.实现方式不同,hashTable继承的是Dictionary类,而hashMap继承的是abstractMap类

5.初始化容量不同,hashMap的初始容量是16,而hashTable初始容量是11,两者的负载因子都是0.75

6.扩容机制不同,当已用容量>总容量*负载因子时,hashMap扩容规则为当前容量翻倍,hashTable则为当前容量翻倍+1

7.支持遍历的种类不同,hashMap只支持Itrator遍历,而hashTable支持Itator和Enumereation两种遍历方式

8.由于hashTable是线程安全的也是Synchronized,所以在单线程的情况下它比hashMap满,如果你不需要同步,只需要单一线程,那么使用hashMap性能好与hashTable

单线程与多线程的区别

线程:是进程中的单个顺序控制流,是一条执行路径

例如:扫雷程序,当点击第一个格的时候,时间开始动,无论点的快与慢时间都在动,则有两个线程

单线程:一个进程如果有一条执行路径,则称为单线程程序.

多线程:一个程序如果有多条执行路径,则称为多线程程序.

线程中为什么需要重写run()方法?

因为run()是用来封装被线程执行的程序

run()方法和start()方法的区别是什么?

run():封装线程执行的代码,直接调用,相当于调用普通方法

start():启动线程,然后JVM调用此线程的run()方法

线程的状态和是如何切换的?

1)新建状态:创建线程对象,申请PCB,对应的是new线程对象

2)就绪状态/可运行状态:万事俱备,只欠CPU,刚刚创建好的线程对象所有资源已经准备好,并且加入到了就绪队列之中

唯有等待操作系统的调度,只要分配了CPU,也就是时间片,当前线程可立即执行,对应的是start()

注意:调用start()并不会立即执行线程对象,这个是由OS的调度规则决定的。我们控制不了

3)执行/运行状态:就绪队列中的线程对象被OS选中,分配了时间片,正在执行

注意:只有就绪状态才能变成运行状态

4)阻塞状态:线程在执行过程中遇到了问题,比如锁阻塞、休眠阻塞、等待阻塞…

注意:我们的阻塞状态,等问题解决了以后/获取了临界资源【要抢占的公共资源】后

是加入到就绪队列中的,转为就绪状态,而不是转为运行状态直接执行

5)终止状态:线程成功执行完毕,释放资源,归还PCB

6)线程的挂起:正在运行中的线程,由于CPU分配的时间片已经用完,所以需要冻结当前线程运行的状态与各项信息

把它插入到就绪队列中,直到下次这个线程被调度执行时,重新恢复现场,继续执行

进程、程序、线程的区别

1.程序:数据与指令的集合,而且程序是静态的

2.进程:运行中的程序,给程序加入了时间的概念,不同时间进程有不同的状态,进程是动态的,代表OS中正在运行的程序,进程有独立性、动态性、并发性

并行与串行的区别

1.并行:相对于资源比较充足,多个CPU同时并发处理多个不同的进程

2.串行:相对资源不太充足时,多个资源同时去抢占公共资源,比如CPU

LinkedList在JDK1.7前后的区别

JDK1.7之前,LinkedList采用的是双向循环链表,从JDK1.7开始,LinkedList采用的是双向链表.

注意点:

1.在双向循环链表中只有head一个头结点,查询元素时,永远都是从head开始,顺序或者逆序查找.

2.双向循环链表中的head并不是添加进来的第一个元素节点,而是一个元素为null的节点,该head不计入链表的长度

ArrayList和LinkedlList区别

ArrayList和LinkedList都实现了List接口,他们有以下的不同点:

ArrayList是基于索引的数据接口,他的底层是数组.他可以以O(1)时间复杂度对元素进行随机访问,与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n).

相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要想数组那样重新计算大小或者更新索引.

LinkedList比ArrayList更占内存,因为LinkedList为每一个节点储存了两个引用,一个指向前一个元素,一个指向下一个元素.

ArrayList VS LinkedLIst

1.因为Array是基于索引(index)的数据结构,他使用索引在数组中搜索和读取数据时很快的,Array获取数据的时间复杂度是O(1),但是要删除数据开销是很大的,因为这需要重排数组中的所有数据.

2.相对于ArrayList,LinkedList插入是更快的,因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候将所有数据重新装入一个新的数据,这是ArrayList最坏的一个情况,时间复杂度是O(n),而LinkedList中插入或者删除时间复杂度仅为O(1).ArrayList在插入数据中还需要更新索引(除了插入数组的尾部).

3.类似于插入数据,删除数据时,LinkedList也优于ArrayList.

4.LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中每个节点中存储的实际数据的前后节点的位置(一个LinkedList实际存储了两个值:Node<E>first和Node<E>last分别表示链表的起始节点和尾节点,每个Node实际存储了三个值:E item,Node next,Node pre).

什么场景下更适宜使用LinkedList,而不是ArrayList

1.你的应用不会随机访问数据.因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据.

2.你的应用更多的插入和删除元素,更少的读取数据,因为插入和删除元素不涉及重拍数据,所以它要比ArrayList更快 

换句话说,ArrayList的实现用的是数组,LinkedList的基于链表,ArrayList适合查找,LinkedList适合增删

以上就是关于ArrayList和LinkedList的差别,你需要一个不同步的基于索引的数据访问时,请尽量使用ArrayList,ArrayList很快,也很容易使用,但是要记得给定一个合适的初始大小,尽可能的减少更改数组的大小

浅谈二叉排序树的操作

1.添加元素:

1.1若root为null,则将新节点成为root.

1.2若root不为null,与root进行大小比较.

1.2.1小于:将新节点添加到左子树(若left为null,让新节点成为left即可.若left不为null,继续让left进行大小比较).小于则添加到left的左子树上,大于则添加到left的右子树上.

1.2.2大于:将新节点添加到右子树(若right为null,让新节点成为rright,若right不为null,集合和right进行大小比较).小于,添加到right的左子树上,大于则添加到right的右子树上

2.遍历元素

        先序遍历:根左右

        中序遍历:左根右(重点掌握)--结果为元素升序排列

        后序遍历:左右根

3.查询元素:和添加方式相似.从root开始,判断当前节点是否为查询元素(通过compareTo方法的返回值是否为0判断相等)若不是查找的元素,与root比较大小,小于则到左子树上继续查找,大于则到右子树上继续查找,每次比较都会排除几乎一般的数据,所以其查询效率较高,其查询效率比单向链表要高

4.删除元素

        4.1删除的是叶子节点,将父节点指向该节点的引用置位null.

        4.2删除的节点只有一颗子树,让其父节点的引用指向删除节点的子节点

        4.3删除的节点有两颗子树,让其前驱节点或后继节点替换掉当前节点(前驱和后继节点为中序排列后删除节点的前一个和后一个节点)

lambda表达式

1.用处:用来简写代码

        1.1用法:

                1.1.1实现功能性接口(接口只有一个抽象方法)@FunctionalInterface

                1.1.2对集合进行遍历

2.语法:()->{}

            ():实现接口中抽象方法的参数列表

            {}:具体实现

            规则:

                1.若体(可以是方法体也可以是循环体)中只有一行代码,{}可以省略

                2.若体重只有一行代码,且有return,此时return也可以省略

                3.()参数列表中参数的数据类型均可以省略

                4.()参数列表中若只有一个参数,此时()可以省略,注意:若参数列表中无参或者多参,此时这个小括号是不可以省略的

java中重写和重载有什么区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态,而后者实现的是运行时的多态性.重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同,参数个数不同或者二者都不同)则视为重载;重写要求子类被重写方法与父类重写方法有相同的返回类类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则).重写对返回类型没有特殊要求.

方法重载的规则:

1.方法名一直,参数列表中参数的顺序,类型,个数不同.

2.重载与方法的返回值无关,存在于父类和子类同类中.

3.可以抛出不同的异常,可以有不同修饰符.

方法重写的规则:

1.参数列表必须完全与被重写方法的一致,返回类型必须完全被重写方法的返回类型一致.

2.构造方法不能被重写,声明为final的方法不能被重写,声明为static的方法不能被重写,但是能够再次被声明.

3.访问权限不能比父类中被重写的方法的访问权限更低.

4.重写的方法能够抛出任何非限制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以.

JAVA代理的几种实现方式

第一种:静态代理,只能静态的代理某些类或者某些方法,不推荐使用,功能比较弱,但是编码简单.

第二种:动态代理,包含Proxy代理和CGLIB动态代理

Proxy代理是JDK内置的动态代理

特点:面向接口的,不需要导入第三方依赖的动态代理,可以对多个不同的接口进行增强,通过反射读取注解时,只能读取到接口上的注解

原理:面向接口,只能对实现类在实现接口中定义方法进行增强.

定义接口和实现

package com.proxy;

public interface UserService {
    public String getName(int id);

    public Integer getAge(int id);
}
package com.proxy;

public class UserServiceImpl implements UserService {
    @Override
    public String getName(int id) {
        System.out.println("------getName------");
        return "riemann";
    }

    @Override
    public Integer getAge(int id) {
        System.out.println("------getAge------");
        return 26;
    }
}
package com.proxy;

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

public class MyInvocationHandler implements InvocationHandler {

    public Object target;

    MyInvocationHandler() {
        super();
    }

    MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getName".equals(method.getName())) {
            System.out.println("++++++before " + method.getName() + "++++++");
            Object result = method.invoke(target, args);
            System.out.println("++++++after " + method.getName() + "++++++");
            return result;
        } else {
            Object result = method.invoke(target, args);
            return result;
        }
    }
}
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main1 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        InvocationHandler invocationHandler = new MyInvocationHandler(userService);
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),invocationHandler);
        System.out.println(userServiceProxy.getName(1));
        System.out.println(userServiceProxy.getAge(1));
    }
}

CGLIB动态代理

特点:面向父类的动态代理,需要导入第三方依赖

原理:面向父类,底层通过子类继承父类并重写方法的形式实现增强

proxy和CGLIB是非常重要的代理模式,是springAOP底层实现的主要两种方式

CGLIB的核心类

net.sf.cglib.proxy.Enhancer – 主要的增强类 net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现 net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用: Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法 public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。

package com.proxy.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        System.out.println(method.getName());
        Object o1 = methodProxy.invokeSuper(o, args);
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        return o1;
    }
}
package com.proxy.cglib;
 
import com.test3.service.UserService;
import com.test3.service.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;
 
public class Main2 {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(cglibProxy);
 
        UserService o = (UserService)enhancer.create();
        o.getName(1);
        o.getAge(1);
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值