JAVA基础(持续更新)
1.重写和重载的区别
重写就是子类继承父类方法时,需要对父类方法进行覆盖,
重载是指在同一个类中,方法名相同,参数列表的类型/个数/顺序不同,方法返回值和访问修饰符可以不同。
发生在编译期
方法的重写遵循(两同两小一大):发生在运行期
“两同”即⽅法名相同、形参列表相同;
“两⼩”指的是⼦类⽅法返回值类型应⽐⽗类⽅法返回值类型更⼩或相等,⼦类⽅法声明抛出
的异常类应⽐⽗类⽅法声明抛出的异常类更⼩或相等;
“⼀⼤”指的是⼦类⽅法的访问权限应⽐⽗类⽅法的访问权限更⼤或相等。
2. 创建对象的过程
Person p = new Person();//短短这行代码发生了很多事情
1.把Person.class文件加载进内存
2.在栈内存中,开辟空间,存放引用变量p
3.在堆内存中,开辟空间,存放Person对象
4.对成员变量进行默认的初始化
5.对成员变量进行显示初始化
6.执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
7.堆内存完成
8.把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
3. java面向对象三大特性
封装:
封装是把一个对象的属性私有化,同时可以提供一些可以被外界访问的属性的方法,如果属性不想被访问,我们可以不提供方法给外界方法,但一般来说,一个类中都有供外界访问的方法
继承:
就是使用已存在的类的定义为基础创建新的类,可以增加新的属性和方法,,还可以使用父类的功能,但不能有选择的继承父类,通过继承可以提高代码的复用性。
ps:1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性
和⽅法⼦类是⽆法访问,只是拥有。
2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。(以后介绍)。
多态:
父类引用指向子类对象,举个栗子:有一个动物类、小猫类、小狗类,Animal a = new Cat();此处便是多态,(多态的前提是:继承+重写)
【编译看左边 运行看右边
* 解释:必须要父类定义这个方法,才能通过编译,编译时,把多态对象看作是父类类型
* 必须要子类重写这个方法,才能满足多态,运行时实际干活的是子类】
在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并
覆盖接⼝中同⼀⽅法)。
**总结三大特性的优点:**封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性.。
4. StringBuffer和StringBuilder的区别是什么?String为什么不可变?
可变性:String是不可变的,StringBuilder、StringBuffer是可变的
StringBuilder、StringBuffer都继承于AbstractStringBuilder,在 AbstractStringBuilder 中
也是使⽤字符数组保存字符串 char[]value 但是没有⽤ final 关键字修饰,所以这两种对象都是可
变的。StringBuilder 与 StringBuffer 的构造⽅法都是调⽤⽗类构造⽅法AbstractStringBuilder 实
现的。
AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
* (字符储存)
*/
char[] value;
/**
* The count is the number of characters used.
* (字符数计数)
*/
int count;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
安全性:
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是
StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、
append、insert、indexOf 等公共⽅法。StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同
步锁,所以是线程安全的。StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的。
性能:
每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String对象。
StringBuffer、 StringBuilder能够多次被修改,且不产生新的未使用的对象。
StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。
相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。
总结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
【String是不可变的原因】
String被final修饰字符串数组来保存字符串,因此不可变(private final char
value[] ,在java9后改成private final byte value[])
5.在一个静态方法内调用一个非静态变量为什么是非法的?
静态方法是属于类的,随着类的加载而加载,在加载类时,程序就会为静态方法分配内存,而非静态方法时属于对象的,对象是在类加载之后创建的,也就是说静态方法先于对象存在,当你创建一个对象时,程序为其在堆中分配内存,一般是通过this指针来指向该对象。静态方法不依赖对象的调用,它是通过“类名.方法”来调用的。而对于非静态方法,在对象创建的时候程序才会为其分配内存,然后通过类的对象去访问非静态方法。因此,在对象未存在时非静态方法也不存在,静态方法自然不能调用非静态方法。
6.接⼝和抽象类的区别是什么?
- 抽象类是一个特殊的类,特殊在抽象类中可以包含没有方法体的方法(抽象方法)
- 接口可以理解为一个特殊的抽象类。特殊在接口里都是抽象方法,没有普通方法
- 接口会为方法自动拼接public abstract,还会为变量自动拼接public final static
- 抽象类中可以有成员变量,接口中只能有静态常量
- 抽象类可以有构造方法–用来给子类创建对象,接口中没有构造方法
- 抽象类和接口都不能实例化(创建对象)
- 接口可继承接口,并可多继承接口,但类只能单继承
- 抽象方法只能声明,不能实现,接口是设计的结果,抽象类是重构的结果
7.成员变量和局部变量的区别是什么?
8.对象实体和对象引用的区别?
java数据类型可以分为基本数据类型和引用数据类型两大类,java中有堆内存和栈内存之分,基本数据类型可以在栈中直接分配内存,但引用数据类型是对象引用在栈内存中,对象实体在堆内存中。
栈内存中存放的变量生命周期结束就会释放内存,堆内存中存放的对象实体需要被垃圾回收机制不定时回收。
当我们创建一个对象 比如 Weapon wea = new Weapon();
这句话包含了三个动作:
a. Weapon weapon在栈内存中创建了一个名为wea的Weapon类型引用变量,它可以指向Weapon类型的实体对象;
b. new Weapon()在堆内存中创建了一个Weapon类型的实体对象;
c. 最后用“=”将对象引用wea指向对象实体new Weapon()。
综上所述,对象引用就是一个可以直接访问的有名字的变量,它存储的是一个地址,这个地址指向堆内存中某一个该类型的实体对象;而对象实体没有名字,我们只能通过引用变量来间接访问;且只有new才会创建对象实体,才会在堆内存中开辟新的空间
9.⼀个类的构造⽅法的作⽤是什么? 若⼀个类没有声明构造⽅法,该程序能正确执⾏吗? 为什么?
主要作用是完成类对象的创建或者初始化。能正确执行,因为有默认的无参构造方法
10.构造⽅法有哪些特性?
特点:方法名与类名相同,且没有返回值类型
执行时机:创建对象时立即执行
默认会创建无参构造,但是,如果自定义了含参构造,默认的无参构造会被覆盖,注意要手动添加
11.静态方法和实例方法有什么不同?
在外部调用静态方法时,可以通过"类名.方法名"的方式,也可以使用"对象名.方法名"的方式,而实例方法只可通过后者进行调用。即在调用静态方法时可以不创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法无此限制
12.对象的相等与指向他们的引⽤相等,两者有什么不同?
对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等
13.在调⽤⼦类构造⽅法之前会先调⽤⽗类没有参数的构造⽅法,其⽬的是?
子类无条件的继承父类中不含参数的构造方法,如果子类自己没有构造方法,它将继承父类无参构造方法作为自己的构造方法;如果子类定义了构造方法,则在创建对象时,它将先执行继承自父类的无参构造方法,帮助子类做初始化工作,然后再执行自己的构造方法。
14.== 与 equals的区别(⭐️⭐️⭐)
== : 它的作⽤是判断两个对象的地址是不是相等。对于基本数据类型来说,**比较的是具体数值。对于引用数据类型来说,**比较的是对象的内存地址。
当使用equals()方法进行比较时,其比较结果取决于equals()方法的具体实现
任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,
而在Object类的默认实现中,即没有对equals()方法进行重写,那么equals()方法等价于==的作用,。
15.hashCode ()与 equals() (⭐️⭐️⭐)
1)hashCode()的作用
hashCode()的作用是获取哈希码,也成为了散列码;返回一个int整数。哈希码的作用是确定该对象在哈希表中的索引位置
hashCode()定义在JDK的Object类中,这就意味着Java中的任何类都包含有hashCode()函数。
散列表存储的是键值对(key-value),它的特点是:能够根据键快速的检索处对应的值,可以快速找到所需要的对象
hashCode()和equals()都是用来比较两个对象是否相等。
2)为什么要有hashCode()?
在java中有一些哈希容器,如HashMap等。当调用容器的get(Object obj)方法时,容器内部需要判断一些当前obj对象在容器中是否存在,然后再进行后续操作。当判断存在是,要将obj对象和容器中的每个元素–进行比较,要使用equals()才是正确的。但如果哈希容器中有很多元素时,那么equals()比较起来就会很慢。这时就引入了hashCode(),当我们调用哈希容器中的get(Object obj)方法时,它会首先查看当前容器中是否有相同的哈希值对象,如果不存在,直接返回null;如果存在,再调用当前对象的equals()方法比较一下看哈希处的对象是否与要查找的对象相同,如果不同,返回null;如果相同,则返回该哈西处的对象
hashCode()被设计用来使得哈希容器能高效的工作。也只有在哈希容器中,才使用hashCode()来比较对象是否相等,但要注意这种比较是一种弱的比较,还要利用equals()方法最终确认。
区别:性能和可靠性
重写的equals()里一般比较的比较全面和复杂,这一效率就比较低,而利用hashCode()只需要生成一个hash值进行比较就可以了,效率较高。但hashCode()并不是完全可靠,有时候不停对象生成的hashCode也会一样,所以hashCode()在大部分时候可靠,并不是绝对可靠。
equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
注:
重写equals()方法时候一定要重写hashCode()方法。
hashCode()相等是两个对象相等的必要非充分条件(即哈希值相等的两个对象,不一定相等;哈希值不相等的两个对象,必定不相等)。
equals()相等是两个对象相等的充要条件
15.为什么 Java 中只有值传递?
Java 程序设计语⾔对对象采⽤的不是引⽤调⽤,实际上,对象引⽤是按
值传递的。
⼀个⽅法不能修改⼀个基本数据类型的参数(即数值型或布尔型)。
⼀个⽅法可以改变⼀个对象参数的状态。
⼀个⽅法不能让对象参数引⽤⼀个新的对象。
16.简述线程、程序、进程的基本概念
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位。一个进程在执行过程中可以产生多个线程。同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或者在各个线程键切换工作时,负担要比进程小得多,由此,线程也被称为轻量级进程。
程序时值含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序时静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程是进程划分成的更⼩的运⾏单位。线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响。从另⼀⻆度来说,进程属于操作系统的范畴,主要是同⼀段时间内,可以同时执⾏⼀个以上的程序,⽽线程则是在同⼀程序内⼏乎同时执⾏⼀个以上的程序段。
17.线程有哪些基本状态
三态模型:
就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
五态模型:
我们可以再添加两种状态:
创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
线程生命周期,主要有五种状态:
新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
18.Java中的异常处理
在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类
有两个重要的⼦类 Exception (异常)和 Error (错误)。 Exception 能被程序本身处理( trycatch ), Error 是⽆法处理的(只能尽量避免)。
Exception :程序本身可以处理的异常,可以通过 catch 来进⾏捕获。 Exception ⼜可以分
为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
Error : Error 属于程序⽆法处理的错误 ,我们没办法通过 catch 来进⾏捕获 。例如,
Java 虚拟机运⾏错误( Virtual MachineError )、虚拟机内存不够错误
( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发⽣时,Java
虚拟机(JVM)⼀般会选择线程终⽌。
受检查异常
Java 代码在编译过程中,如果受检查异常没有被 catch / throw 处理的话,就没办法通过编译
。⽐如下⾯这段 IO 操作的代码。
除了 RuntimeException 及其⼦类以外,其他的 Exception 类及其⼦类都属于检查异常 。常⻅的受
检查异常有: IO 相关的异常、 ClassNotFoundException 、 SQLException …。
不受检查异常
Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其⼦类都统称为⾮受检查异常,例
如: NullPointExecrption 、 NumberFormatException (字符串转换为数
字)、 ArrayIndexOutOfBoundsException (数组越界)、 ClassCastException (类型转换错
误)、 ArithmeticException (算术错误)等。
异常处理总结
try 块: ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟
⼀个 finally 块。
catch 块: ⽤于处理 try 捕获到的异常。
finally 块: ⽆论是否捕获或处理异常, finally 块⾥的语句都会被执⾏。当在 try 块或
catch 块中遇到 return 语句时, finally 语句块将在⽅法返回之前被执⾏。
在以下 3 种特殊情况下, finally 块不会被执⾏:
- 在 try 或 finally 块中⽤了 System.exit(int) 退出程序。但是,如果 System.exit(int) 在异常
语句之后, finally 还是会被执⾏ - 程序所在的线程死亡。
- 关闭 CPU。
19. Java 序列化中如果有些字段不想进⾏序列化,怎么办?
对于不想进⾏序列化的变量,使⽤ transient 关键字修饰。
transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化
时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅
法。
20. 获取⽤键盘输⼊常⽤的两种⽅法
方法一:通过Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法二:通过BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
21.Java中IO流
Java 中 IO 流分为⼏种?
按照流的流向分,可以分为输⼊流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的⻆⾊划分为节点流和处理流。
InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。