JAVA面向对象基础
1、说一下什么是面向对象
面向对象是一种编程思想,它是相对于面向过程而言的,从执行者变为了指挥者,通过这种思想来将生活中复杂的事情简单化
面向对象就是把一个对象抽象成类,具体来说就是把一个对象的静态特征和动态特征抽象成属性和方法,也就是把一类事物的算法和数据结构封装在一个类中,程序就是多个对象之间相互通信组成的
面向对象三大特征 1、封装 2、继承 3、多态
封装
封装是指隐藏对象的属性和实现的细节,仅仅对外提供公共的访问方式,提高了安全性。
继承
提高复用性,只要继承父类,就能拥有父类的功能
多态
1、多态是指同一个实体同时具有多种形式,可以把不同的子类对象都当做父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一的调用标准。
2、提高了程序的扩展性和可维护性
多态的三大必要条件:
1)继承 2)重写 3)父类引用指向子类对象 向上转型
2、说一下类和对象
类
1)Java语言最基本的单位就是类
2)类是一类事物的抽象
对象
对象具有三大特点:对象的状态、对象的行为、对象的标识
1)对象的状态是用来描述对象的基本特征
2)对象的行为用来描述对象的功能
3)对象的标识是指对象在内种中都有一个唯一的地址
类和对象的区别
类:如手机类,抽取相同的行为和属性 行为作为成员方法 属性作为成员变量
对象:可以按照类模板生产出多个手机,1号手机对象、2号手机对象
属性:颜色、尺寸、品牌、价格
行为:打电话,发短信
对象在内存中的存储
1)局部变量存在栈中,方法执行完毕内存就被释放
2)对象(new出来的东西)存在堆中,对象不再使用时,内存才会被释放
3)每个堆内存中的元素都有地址值
4)对象中的属性都是有默认值的
3、说一下构造方法
1)构造方法的方法名必须与类名相同
2)构造方法没有返回值类型 也不能定义为void (定义为void后则是一个普通方法)
3)可以有多个构造方法,如果没有定义系统会创建一个无参默认构造方法
4、构造代码块、局部代码块
构造代码块
1)构造代码块在类中方法外
2)用来提取构造方法中通用代码,每次调用构造方法时都会先执行构造代码块
3)优先于构造方法加载
局部代码块
1)在方法中的代码块
2)通常用于控制变量的作用范围,出了括号就失效
5、this关键字
1)构造方法中,this()必须放在第一行(子类构造方法第一行默认调用父类super()关键字,所以调用this()则必须放在第一行,否则则会出现super() 其他输出语句 this() 此时则调用了2次super() 会报错)
6、super关键字
1)如果用,必须出现在调用位置的第一行 (因为子类继承了父类的属性和方法,所以在先初始化父类的属性和方法,这样子类才可以初始化自己特有的,因为java中不允许调用没有初始化的成员)
7、this和super的区别
1)this代表本类对象的引用,super代表父类对象的引用
2)this用于区分成员变量和局部变量
3)super用于区分本类变量和父类变量
4)this.成员变量 this.成员方法() this(【参数】),代表调用本类内容
5)super.成员变量 super.成员方法() super(【参数】),代表调用父类内容
6)this和super不可以同时出现在同一个构造方法中,它们两个只要出现必须在第一行
8、访问权限修饰
本类 同包 子类 任意
public √ √ √ √
protected √ √ √
default √ √
private √
9、方法重写
规则:子类重写父类方法(方法名、参数列表、返回值)完全一致才算重写
1)父类中的私有方法不能被重写
2)子类重写父类方法时,访问修饰符要大于等于父类
10、重载和重写的区别
重载:同一个类中的多个方法具有相同的名字,但它们的参数列表不同,即参数的数量,类型,顺序不同
重写:是存在于父子类之间的,子类定义的方法与父类中的方法具有相同的名字,相同的的参数列表和返回值类型(子类的访问修饰符要大于等于父类的)
11、static
1)可以修饰成员变量和成员方法
2)随着类的加载而加载,优先于对象加载
3)只加载一次,就会一直存在,不再开辟新空间
4)可以直接被类名调用
5)静态只能调用静态,非静态可以随意调用
6)static不能和this或者super共用,因为有static时可能还没有对象
7)全局唯一,全局共享
12、final
1)final修饰的变量是个常量,值不能被更改
2)final修饰的方法不能被重写
3)final修饰的类不能被继承
13、异常
Java中异常主要分为两类
Error:系统错误,无法修复
Exception:可修复的错误
Exception中常见异常类型:
1)RuntimeException
2)ClassCastException
3)ClassNotFoundException
异常的两种处理方式:
捕获或向上抛出
14、静态代码块
执行顺序:静态代码块→构造代码块→局部代码块
1)在类加载时就加载,并且只被加载一次,一般用于项目的初始化
15、静态变量和实例变量的区别
实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
静态变量不属于某个实例对象,而是属于类,所以也被称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用
1)静态变量需要用static来修饰
2)实例变量需要创建对象后才可以通过这个对象来使用
3)静态变量可以直接使用类名来引用
16、抽象类
Java中没有方法体的方法,该方法由其子类来具体的实现。该没有方法题的方法我们称之为抽象方法,含有抽象方法的类我们称为抽象类
2)抽象类中可以没有抽象方法,如果类中有抽象方法,那该类必须定义为一个抽象类
3)子类继承了抽象类以后,要么也是一个抽象类,要么就把所有的抽象方法都重写
4)抽象类类不能被实例化
抽象类中:
1)既可以有变量,也可以有常量
2)既可以有普通方法,也可以有抽象方法
抽象方法:
1)抽象方法要被实现,所以不能是静态的,也不能是私有的
2)构造方法:抽象类中也有构造方法,但是本身无法被实例化
问题:
1)抽象类的构造方法有什么用?
一般用于给子类实例化,当创建子类对象调用子类构造方法时会先自动调用父类构造方法
2、abstract可以和static一起使用吗
用static声明方法表明这个方法在不生成类的实例时可直接被类调用,而抽象方法不能被调用,两者矛盾,所以,不能
17、接口
Java中由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现
提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口的编程,面向抽象的编程
特点:
1)接口中都是抽象方法
2)通过interface关键字创建接口,通过implements让子类来实现
3)接口之间可以多实现,接口和接口之间可以多继承
构造方法:
1)接口中没有构造方法
2)在创建实现类的对象时默认的super(),是调用的Object的无参构造
重点:
1)接口中没有成员变量,都是常量,所以,接口中的变量没有写修饰符时都会默认加上
public static final
2)接口里的方法,默认就是抽象的,如果你不写abstract,则会默认补齐
18、Java设计模式
Java中共有23种设计模式
单例模式:
常见的Spring默认创建的bean就是单例模式的
其中单例模式最重要的是确保对象只有一个(简单来说,保证一个类在内存中的对象就一个)
1)饿汉式
1)私有化构造方法
2)在类的内部创建好对象
3)对外界提供一个公共的get(),返回一个已经准备好的对象
public class Test{
private Test(){ }
private static Test t = new Test();
public static Test get(){
return t;
}
}
2)懒汉式
1)私有化构造方法
2)在类的内部创建好一个对象,不赋值
3)提供一个公共的get(),在中判断对象是否已有地址值,没有则对象进行赋值,并返回
public class Test{
private Test(){ }
private static Test t = null;
public static Test get(){
if(t == null){
t= new Test();
}
return t;
}
}
19、抽象类和接口的区别
1)抽象类中有普通方法,接口中没有
2)抽象类中可以有普通变量,接口中只能是公共的静态常量
3)接口可以继承接口,并可多继承接口,但类只能单继承
4)抽象类中有构造方法,接口中没有
20、StringBuffer和StringBuilder
StringBuffer 线程安全
StringBuilder 非线程安全
内部维护 char[ ] value ,默认初始化数组容量为16
1)StringBuffer和StringBuilder有一定的缓冲区,当字符串没有超过容量时,不会分类新的容量,当字符串大小超过容量时,会自动增加容量
2)StringBuilder速度更快,线程不安全,StringBuffer线程安全
与String的区别是:
1)StringBuffer和StringBuilder的对象能够被多次的修改,并且不产生新的对象,String每次操作都会产生新的对象
2)由于 StringBuilder 相较于 StringBuffer 有速度优势,多数情况下建议使用StringBuilder。如果应用程序要求线程安全的话则必须使用StringBuffer
21、IO流
流:是指一串连续的数据(字符串或节),是以先进先出的方式发送信息的通道
1)先进先出:最先写入输出流的数据最先被输出流读取到
2)顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。
3)只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
IO流分类:
1)按数据流的方向:输入流、输出流
2)按处理数据单位:
字节流:处理一切
字符流:处理纯文本
3)按功能:节点流、处理流
节点流和处理流:
1)节点流:直接操作数据读写的流类,比如FileInputStream FileOutputStrean FileReader FileWriter
2)处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)
22、BIO、AIO、NIO的区别
同步:发起一个调用后,被调用者未处理完请求,调用不返回
异步:发起一个调用后,被调用者立即回应已经接收到请求,但是被调用者并没有返回结果,此时我们可以去处理其他的请求,被调用者通常通过事件、回调机制来通知调用者返回其结果
阻塞:发起一个请求后,调用者一直等待请求结果返回,无法干其他事情
非阻塞:发起一个请求后,调用者不用一直等待结果返回,可以先干其他事情
BIO同步阻塞I/O模式:
用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行
NIO同步非阻塞I/O模式:
用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费
AIO异步非阻塞I/O模式:
用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了
23、Collection
Collection父接口常用的两个子接口
List子接口:数据有序,可以重复
-- ArrayList子类
-- LinkedList子类
-- Vector子类
Set子接口:不可以重复
-- HashSet子类
-- TreeSet子类
Collection常用方法
boolean add(E e):添加元素。
boolean addAll(Collection c):把小集合添加到大集合中 。
boolean contains(Object o) : 如果此 collection 包含指定的元素,则返回 true。
boolean isEmpty() :如果此 collection 没有元素,则返回 true。
Iterator<E> iterator():返回在此 collection 的元素上进行迭代的迭代器。
boolean remove(Object o) :从此 collection 中移除指定元素的单个实例。
int size() :返回此 collection 中的元素数。
Objec[] toArray():返回对象数组
24、List
ArrayList
通过数组实现,查询快,增删慢,支持随机访问,线程不安全,初始容量是10(jdk1.8以前是10,jdk1.8以后为0,新增数据才扩容为10),每次扩容到当前容量的1.5倍
LinkedList
1)通过双向链表实现,查询慢(移动指针),增删快,线程不安全
2)链表维护了一个size(链表长度)、Node<E> first(链表头),Node<E> last(链表尾),Node的结构中有个item、next、prev充分说明了此链表是一个双向链表
//链表的长度
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
//链表的头
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
//链表的尾
transient Node<E> last;
//链表维护的Node的结构
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector:
底层数组实现,查询快,增删慢,由于线程安全,所以效率低,扩容为当前2倍
子类:stack 栈
特点:先进先出,对Vector进行了扩展
25、ArrayList和LinkedList的区别
1)底层数据结构不同:ArrayList底层是数组 LinkedList底层是双向链表
2)ArrayList支持随机访问而LinkedList不支持
ArrayList源码中它实现了一个RandomAccess接口,然而这个接口是个空接口,实现这个空接口的意义就是标记这个类具有一个随机访问的特性。
--最根本原因是ArrayList是根据数组下标去访问里面的数据
3)插入和删除的效率不同
ArrayList:是采用数组实现的,插入和删除受到元素位置的影响,会产生数据迁移
LinkedList:采用的是双向链表存储数据,所以不会收到元素位置影响,直接改变指针位置即可,所以比较快
4) ArrayList:执行频繁查找效率比LinkedList高
LinkedList:执行频繁的插入和删除效率比ArrayList高
25、HashSet和TreeSet
HashSet:通过HashMap实现,无序,数据不可重复,可以存储一个null,线程不安全
TreeSet:通过TreeMap实现,自然排序,数据不可重复,可存储一个null,线程不安全
26、Map
HashMap:
HashMap是一个无序的K,V结构容器,jdk1.8以前为数组+链表,以后为数组+链表+红黑树
初始化容量为16,默认的加载因子为0.75,每次扩容为当前容量的一倍,当链表长度大于8且数组
长度大于64时会转换为红黑树,线程不安全
TreeMap:
底层通过红黑树实现,自然排序,线程不安全
HashTable:
底层为哈希表,主要是线程安全,键和值都不允许为null,扩容机制为原来的2n+1
27、ConcurrentHashMap
28、多线程
线程的4种创建方式:
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、创建线程池
线程的6种状态:新建状态→可运行状态→阻塞状态→无限等待状态→超时等待状态→终止状态
或
下面详细介绍了线程的6种状态,如按照5种状态理解则将无限等待状态+超时等待状态+阻塞状态理合为阻塞状态,将可运行状态分为就绪状态和运行中状态)如下:
线程的5种状态:新建状态→就绪状态→运行状态→阻塞状态→终止状态
新建状态:new 了一个线程对象,但是还没有调用start()方法时
可运行状态:可运行状态包含 就绪状态 和 运行状态
就绪状态:当调用了线程对象的start()方法,等待被线程调度选中,获取CPU的使
用权
运行状态:就绪状态的线程获得CPU时间片之后变成运行中状态
阻塞状态:
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
1)线程对象调用sleep时,当前线程进入阻塞状态
2)运行在当前线程里的其它线程调用join()方法,当前线程进入阻塞状态
3)等待用户输入的时候,当前线程进入阻塞状态
无限等待状态:
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
超时等待状态
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知,带有超时参数的常用方法有Thread.sleep、锁对象.wait()
终止状态:
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
线程中常用的方法及概念
wait():Object中的方法,必须在Synchronized修饰的方法或块中使用,调用A.wait()的线程将加入到该对象A的等待池中,此时该线程进入阻塞状态,并且释放掉持有的当前对象A的锁
sleep():Thread中的静态方法,可以在任何地方使用,让当前正在执行的线程暂停一段时间,让出cpu资源,进入等待状态,不会释放锁,休眠时间结束以后进入可运行状态
yield():Thread中的方法,作用是将执行该方法的线程对象回到可运行状态(放弃当前拥有的cpu资源),给优先级相同或更高(如没有相同优先级或更高的则不生效)的其他线程来获取运行机会,实际结果中不能够保证一定会调用其他线程,调用yield()的线程有可能再次被调度程序选中
join():Thread中的方法,会使主线程(或调用t.join()的线程)进入等待状态,等该线程执行完毕后才被唤醒,进入可运行状态,并不影响同一时刻处于运行中状态的其他线程
PS:join()方法的底层是利用wait()方法实现的,但是join()源码中只调用了wait()方法,并没有调用notify()方法,是因为线程在终止的时候会自动调用自身的notifyAll()方法,来释放所有的资源和锁
notify():Object中的方法,当使用某个对象的notify()方法时,将从该对象的等待池中选择一个等待的线程唤醒,唤醒的线程将进入锁池争夺对象锁
PS:使用了notify方法进行唤醒,而notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态,假设调用 increment 方法的线程执行完后,所有的线程都处于等待状态,此时 又执行了notify方法,这时如果唤醒的是一个increment方法的调度线程,那么while循环等于true,则此唤醒的线程也会处于等待状态,此时所有的线程都处于等待状态,那么也就没有了运行的线程来唤醒它们,这就发生了死锁。
如果使用notifyAll方法来唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(就是increment方法执行完后,唤醒了所有等待该锁的状态,注:不是wait状态),那么此时,即使再次唤醒一个increment方法调度线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态
总结:notify方法很容易引起死锁,除非你根据自己的程序设计,确定不会发生死锁,notifyAll方法则是线程的安全唤醒方法
notifyAll():Object中的方法,当使用某个对象的notify()方法时,将该对象的等待池中所有等待的线程唤醒,唤醒的线程将进入锁池争夺对象锁
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized代码块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中,等待争夺锁的拥有权
等待池/等待队列:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池
29、Synchronized
在静态方法上加锁:锁的是当前Class对象
在普通方法上加锁:锁的是当前实例对象
在代码块上加锁:括号中指定的对象
Synchronized的原理有2个:
- 内置锁
- 互斥锁
内置锁:每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁(Intrinsic Lock)或者监视器锁(Monitor Lock)。线程进入同步代码块或方法的时候会自动获得该锁,并且在退出同步代码块时(正常返回,或者是异常退出)会自动释放锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法
互斥锁:而Java的内置锁又是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程B尝试去获得线程A持有的内置锁时,线程B必须 等待(WAITING)或者阻塞(BLOCKED),直到线程A释放这个锁,如果A线程不释放这个锁,那么B线程将永远等待下去
30、反射
反射就是在运行状态中,对于任何一个类都可以知道他的属性和方法,对于任何一个对象都可以调用它的属性和方法
反射对象的三种创建方式:
1、类.class
2、对象.getClass()
3、Class.forName(全路径名)
框架
1、spring springMVC springboot