JAVA基础(2021.4.6)
大量参考了https://www.cnblogs.com/bailing80/p/11443409.html
JDK 和 JRE 有什么区别、以及JVM
JDK(Java Development Kit )的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
JRE(Java Runtime Environment) 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。
JAVA面向对象的三大特性:封装、继承、多态
封装:
核心思想就是“隐藏细节”、“数据安全”:将对象不需要让外界访问的成员变量和方法私有化(
private
),只提供符合开发者意愿的公有方法(public
的set
和get
方法)来访问这些数据和逻辑,保证了数据的安全和程序的稳定。把只在本类内部使用的方法使用private
,这就是封装的思想,是面向对象最基本的开发规范之一。
Java 的访问权限修饰关键字: private、protected、public 和 default
public :具有最大的访问权限,可以访问任何一个在 CLASSPATH 下的类、接口、异常等。
protected: 保护子类,子类可以访问这些成员变量和方法,其余类不可以
default :本包的类可以访问(子类在本包也可以使用)
private :访问权限仅限于本类内部,实际开发大多成员变量和方法都是使用private 修饰的
本类 | 本包 | 子类 | 外部包 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
default | √ | √ | (子类在本包就可以) | |
private | √ |
继承:类只能单继承,接口可以多继承(extends),这跟继承的原理有关,因为接口方法未实现,所以不会出现方法未知
1)单继承多实现
2)可以super调用父类方法,前提是权限不为private
3)子类的构造函数一定会隐式调用父类的无参构造函数,如果没有无参构造函数,则要显示调用其他的构造函数
4)构造函数无法覆盖,父类构造函数的代码一定执行
5)重写父类方法时,权限必须大于父类
6)继承提高了程序的复用性、扩展性,是多态特征的前提
7)实际开发中,是先有子类再根据子类的共同特性提取出父类
多态:
分为重载时多态(编译时多态)、【主】重写时多态(运行时多态)
多态指允许不同类的对象对同一“消息”做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。可以用于消除类型之间的耦合关系,Spring 的核心就是多态和面向接口编程。
1)Java 中可以使用父类、接口变量引用子类、实现类对象;
2)在这个过程中,会对子类、实现类对象做自动类型提升,其特有功能就无法访问了(向上转型),如果需要使用,可以做强制类型转换(向下转型)。
讲多态之前先讲一下重载和重写的区别
重载(返回值可以不同)和重写(返回值相同)的要素构成(区别)
重载:在编译时可以知道具体的执行逻辑
1)发生在同一个类中
2)相同的方法名
3)参数列表不同
4)不看返回值,如果出现了只有返回值不同的“重载”,是错的。
重写:在运行时可以知道具体的执行逻辑
1)发生在子类与父类中
2)相同的方法名
3)相同的参数列表
4)返回值相同 或者 子类方法的返回值是父类方法返回值类型的子类
5)访问修饰符相同 或者 子类方法的修饰符范围 大于 父类
6)抛出的异常相同 或者 子类方法抛出的异常 小于父类
多态的三种实现方式:重写、向上转型、继承/实现
向上转型:将子类对象赋给父类对象
特别注意:向上转型之后还是调用的子类方法,可以理解为新建了一个引用指向子类
public class Animal{
void hello(){
System.out.println("嗷呜");
}
}
public class Pig extends Animal{
void hello(){
System.out.println("我饿了");
}
}
//使用
Animal a = new Pig();
//Pig p = new Pig();
//Animal a = (Animal)p;//会提示强制转换是多余的,因为跟上面是一样的写法
a.hello();
a = new Animal();
a.hello();
//结果
我饿了
嗷呜
//可以看到,都是a.hello(),但是却有了不同的反应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
向下转型:将父类对象强制转换为子类对象,前提是父类对象之前必须是对应的子类对象
//还是拿这个例子,不过我们改一下,如果这个时候子类有一个专属的技能
public class Animal{
void hello(){
System.out.println("嗷呜");
}
}
public class Pig extends Animal{
void hello(){
System.out.println("我饿了");
}
void canEat(){
System.out.println("不够吃,再来点");
}
}
//使用
Animal a = new Pig();
a.hello();
a.canEat();//会报错,提示找不到这个方法
//有两种解决办法
//第一种,如果你知道它之前初始化是Pig,你可以强制转换(向下转型)
((Pig)a).canEat();
//第二种,如果不知道,就要判断确定后再强制转换
if(a instanceof Pig){
((Pig)a).canEat();
}
//结果
我饿了
不够吃,再来点
不够吃,再来点
总结
这里的父类不一定是实体类,也可以是接口实现的方式
8种基本数据类型
六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型
byte | short | int | long | float | double | char | boolean | |
---|---|---|---|---|---|---|---|---|
位数(bit) | 8 | 16 | 32 | 64 | 32 | 64 | 16 | 1 |
默认值 | 0 | 0 | 0 | 0L | 0.0f | 0.0d | / | false |
最值 | -27~27-1 | / | / | / | / | / | / | / |
String为引用类型,Integet是包装类型,为什么有Integer?
因为从数据库读取的int数据有可能为Null,所以
Integer f1 = 100, f2 = 100; f1 == f2为true
String s1 = “hello”, s2 = “hello”; s1 == s2为true
==只有在String比较的时候比较特殊
引用类型
强引用:发生 GC的时候不会被回收。
Object object= new Object();
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
Object object= new Object();
SoftReference<Object> softReference = new SoftReference<>(object);
Object result = softReference.get();
//通过SoftReference的get方法来获取对象。软引用,在jvm内存不足的情况下会被回收
弱引用: 有用但不是必须的对象,在下一次GC时会被回收。
Object object= new Object();
WeakReference<Object> weakReference= new WeakReference<>(object);
Object result = weakReference.get();
//通过WeakReference的get方法来获取对象。在gc的时候就会被回收,不管内存是否充足。
虚引用(幽灵引用/幻影引用): 无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 GC时返回一个通知。
Object object= new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference pr = new PhantomReference(object, queue);
//虚引用和没有引用是一样的,需要和队列(ReferenceQueue)联合使用。当jvm扫描到虚引用的对象时,会先将此对象放入关联的队列中,因此我们可以通过判断队列中是否存这个对象,来进行回收前的一些处理。
普通for循环和foreach区别
1、for与foreach都可以遍历数组/集合,不过for则在较复杂的循环中效率更高。
2、foreach不可以删除/修改集合元素,而for可以
3、foreach和for都可以修改元素里面的属性
throw 和 throws 的区别
throw:是真实抛出一个异常。
throws:是声明可能会抛出一个异常。
try-catch-finally 中哪个部分可以省略
try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。
== 和 equals 的区别
基本类型 | 引用类型(String ,Integer) | Object实体类型 | |
---|---|---|---|
== | 值相同 | 引用相同(地址) | 引用相同(地址) |
equals | 值相同 | 值相同(String ,Integer重写了equals方法) | 引用相同(地址) |
String和Integer重写的equals方法代码如下
//String public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i])//区分大小写 return false; i++; } return true; } } return false; } //Integer public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
String str="i"
与String str=new String("i")
一样吗
不一样,因为内存的分配方式不一样。
String str = "i"
的方式,Java 虚拟机会将其分配到常量池中;而String str = new String("i")
则会被分到堆内存中。JDK1.6及以前,常量池在方法区,这时的方法区也叫做永久代;
JDK1.7的时候,方法区合并到了堆内存中,这时的常量池也可以说是在堆内存中;
JDK1.8及以后,方法区又从堆内存中剥离出来了,但实现方式与之前的永久代不同,这时的方法区被叫做元空间,常量池就存储在元空间。
Java内存分配详解(堆内存、栈内存、常量池)
https://blog.csdn.net/jian_sheng_tan/article/details/78323327
String x = "string";
String y = "string";//发现常量池存在,所以指向同一个引用
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
代码解读:因为 x 和 y 指向的是同一个引用(地址),所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。
String、StringBuffer、StringBuilder的区别
操作字符串的类有:String、StringBuffer、StringBuilder
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
如何将字符串反转?
使用 StringBuilder 或者 StringBuffer 的 reverse() 方法。
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
String 类的常用方法都有那些
function() | 说明 |
---|---|
indexOf() | 返回指定字符的索引。 |
charAt() | 返回指定索引处的字符 |
replace() | 字符串替换 |
trim() | 去除字符串两端空白 |
split() | 分割字符串,返回一个分割后的字符串数组 |
getBytes() | 返回字符串的 byte 类型数组 |
length() | 返回字符串长度 |
toLowerCase() | 将字符串转成小写字母 |
toUpperCase() | 将字符串转成大写字符 |
substring() | 截取字符串 |
equals() | 字符串比较 |
final 有什么作用
1)final 修饰的类叫最终类,该类不能被继承。
2)final 修饰的方法不能被重写。
3)final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。
abstract class Cat {
public static void sayHi() {
System. out. println("hi~");
}
}
普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
接口和抽象类的区别:接口其实是一种特殊的抽象类(所有方法都抽象就是接口)
实现: 抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
实例化: 抽象类不可以实例化,接口可以通过向上转型、匿名内部类实例化。
构造函数: 抽象类可以有构造函数;接口不能有。
数据成员: 抽象类可以有数据成员,接口不可以。
实现数量: 类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符: 接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
方法实现: 抽象类可以有成员方法的实现代码,但是接口不能有。
什么是内部类?内部类的作用
将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
作用:
1)每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
2)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3)方便编写事件驱动程序
4)方便编写线程代码
匿名类
线程的使用就是匿名内部类
//继承方式 XXX extends Thread{ public void run(){}}
new Thread(){
@Override
public void run() {
super.run();
}
}.start();
//实现接口方式 XXX implements Runnable{ public void run(){}}
Runnable r = new Runnable(){
public void run(){
System.out.println("###");
}
};
new Thread(r).start();
//使用继承方式和接口实现方式
new Thread(new Runnable(){
public void run(){
System.out.println("@@@");
}
}).start();
Array (数组)和 Arrays 有何区别
Array是
java.lang.reflect
下的一个类,并且不能被new出来,也就是不能被实例化,它的构造器被private所修饰,且由于类被final修饰因此不能被继承。
Arrays是
java.util
包下的一个工具类,提供了许多对数组操作的静态方法,包括数组的排序,对比,转化等等系列方法,十分的好用。
数组转字符串Arrays.toString
public static void main(String[] args) {
int[] array = {3,2,1,4,5};
System.out.println(array); // 输出 [I@61bbe9ba
System.out.println(Arrays.toString(array)); // 输出 [3, 2, 1, 4, 5]
}
数组排序Arrays.sort
从小到大排序
public static void main(String[] args) {
int[] array = {3,2,1,4,5};
Arrays.sort(array); // 从小到大排序
System.out.println(Arrays.toString(array)); // 输出 [1, 2, 3, 4, 5]
}
查看了一下Arrays的方法,并没有提供从大到小的排序方法,那么如何从大到小排序呢?我们就需要重写compare方法来实现从大到小。看到这里,经常使用python来进行编程的朋友可能要骂娘了,我靠,这么麻烦,python中一个sorted函数不就搞定啦?那也没办法,java就是这个亚子,接受吧!
从大到小排序
public static void main(String[] args) {
Integer[] array = {3,2,1,4,5};
Comparator<Integer> cmp = new Comparator<Integer>() {
public int compare(Integer i1, Integer i2) {
return i2-i1;
}
};
Arrays.sort(array,cmp); // 从大到小排序
System.out.println(Arrays.toString(array)); // 输出 [5, 4, 3, 2, 1]
}
其实还有一种方法是这个亚子的,偷偷告诉你们,要牢记于心,不然就要像上面一样重写方法啦,懒人才不会去重写!
public static void main(String[] args) {
Integer[] array = {3,2,1,4,5};
Arrays.sort(array, Collections.reverseOrder()); // 从大到小排序
System.out.println(Arrays.toString(array)); // 输出 [5, 4, 3, 2, 1]
}
数组比较:Arrays.equals 比较一维数组,Arrays.deepEquals 比较多维数组
public static void main(String[] args) {
int[] array1 = {1,2,3};
int[] array2 = {1,2,3};
int[] array3 = {1,2,4};
System.out.println(Arrays.equals(array1,array2)); // 数组的比较,返回true
System.out.println(Arrays.equals(array1,array3)); // 数组的比较,返回false
}
public static void main(String[] args) {
Integer[][] array1 = {{1,2,3}};
Integer[][] array2 = {{1,2,3}};
Integer[][] array3 = {{1,2,4}};
System.out.println(Arrays.equals(array1,array2)); // 多维数组的比较,返回false
System.out.println(Arrays.equals(array1,array3)); // 多维数组的比较,返回false
System.out.println(Arrays.deepEquals(array1,array2)); // 多维数组深比较,判断是否是同一个对象,返回true
}
数组和ArrayList互转:toArray,Arrays.asList
public static void main(String[] args) {
Integer[] array = {1,2,3};
List<Integer> arrayList = new ArrayList<>(Arrays.asList(array)); // 数组转ArrayList
System.out.println(arrayList); // 输出 [1, 2, 3]
arrayList.toArray(); // ArrayList转数组
}
数组填充:Arrays.fill()
public static void main(String[] args) {
Integer[] array = new Integer[5];
Arrays.fill(array,0,3,9); // 右开区间[0,3),给数组下标 0~2的元素填充9
System.out.println(Arrays.toString(array)); // 输出[9, 9, 9, null, null]
}
数组拷贝:Arrays.copyOf()
public static void main(String[] args) {
Integer[] array = {1,2,3,4,5};
Integer[] arrayCopy = Arrays.copyOf(array,10); // 拷贝并生成一个新的数组对象,并设置长度为10
System.out.println(Arrays.toString(arrayCopy));
}
JAVA数组转List的三种方式及对比
1)Arrays.asList(strArray)
通过
Arrays.asList(strArray)
方式,将数组转换List后,不能对List增删,只能查改,否则抛异常。原因解析:
Arrays.asList(strArray)
返回值是java.util.Arrays
类中一个私有静态内部类java.util.Arrays.ArrayList
,它并非java.util.ArrayList
类。java.util.Arrays.ArrayList
类具有 set(),get(),contains()等方法,但是不具有添加add()
或删除remove()
方法,所以调用add()
方法会报错。使用场景:
Arrays.asList(strArray)
方式仅能用在将数组转换为List后,不需要增删其中的值,仅作为数据源读取使用。
2)ArrayList的构造器
通过ArrayList的构造器,将
Arrays.asList(strArray)
的返回值由java.util.Arrays.ArrayList
转为java.util.ArrayList
。关键代码:
ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量不大的情况下,可以使用。
3)Collections.addAll(arrayList, strArray)
通过
Collections.addAll(arrayList, strArray)
方式转换,根据数组的长度创建一个长度相同的List,然后通过Collections.addAll()
方法,将数组中的元素转为二进制,然后添加到List中,这是最高效的方法。关键代码:
ArrayList< String> arrayList = new ArrayList<String>(strArray.length); Collections.addAll(arrayList, strArray);
使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量巨大的情况下,优先使用,可以提高操作速度。
如何实现数组和 List 之间的转换?
数组转 List: 使用 Arrays. asList(array) 进行转换。
List 转数组: 使用 List 自带的 toArray() 方法。
// list to array
List<String> list = new ArrayList<String>();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String[] array = new String[]{"王磊","的博客"};
Arrays. asList(array);
Java异常体系
Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。其中异常类Exception又分为**运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。**下面将详细讲述这些异常之间的区别与联系:
1、Error与Exception
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOf MemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
2、运行时异常和非运行时异常
运行时异常都是RuntimeException类****及其子类异常
如:NullPointerException - 空指针引用异常
IndexOutOfBoundsException - 下标越界异常
ClassCastException - 类型强制转换异常
NumberFormatException - 数字格式异常等
这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
**非运行时异常(编译时异常)**是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
如IOException、
SQLException等
以及用户自定义的Exception异常,一般情况下不自定义检查异常。
IO 流分为几种
按功能来分:输入流(input)、输出流(output)
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
BIO、NIO、AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
反射
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
什么是 Java 序列化?什么情况下需要序列化?
Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。
以下情况需要使用 Java 序列化:
- 想把的内存中的对象状态保存到一个文件中或者数据库中的时候;
- 想用套接字在网络上传送对象的时候;
- 想通过RMI(远程方法调用)传输对象的时候。