Java基础
Java基础
1.面
向对象
1.1面向对象和面向过程的区别
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:一种编程风格,把问题分解成一个个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象的调用,组合解决问题。
优点:易维护、易复用、易扩展;具有封装、继承、多态的特性,设计出低耦合的系统
缺点:性能比面向过程低
1.2面向对象的三大特征:
1. 封装 1. 继承 1. 多态
面向对象的五大基本原则(OOD原则):
1. 单一职责原则(SRP) 1. 开放封闭原则(OCP) 1. 里氏替换原则(LSP) 1. 接口隔离原则(DIP) 1. 依赖导致原则(ISP)
单一职责原则:类只有单一功能,不要为类实现过多功能,保证只有一个引起变化的原因。
优点:高内聚、低耦合
开放封闭原则:软件实体应该是可扩展且不可修改的
里氏替换原则:子类必须可以替换基类。
实现方法:面向接口编程,将公共部分抽象为基类或抽象类,通过继承,在子类中复写父类的方法。
注意:如果违背了里氏替换原则,则也违背了开发封闭原则。
接口隔离原则:接口内聚,不要“胖”。使用多个小接口,而不要使用一个大的接口。
分离接口的方法:(1)委托分离 (2)多重继承分离,通过接口多继承来实现客户的需求
依赖倒置原则:程序依赖于抽象接口,而不是具体实现。也就是说,对抽象进行编程,而不是实现
原因:在面向过程的开发中,上层依赖于下层,当下层发生重大变化时,上层也要跟着变动,这会导致模块复用性降低,大大提高了开发成本。
2. 封装、继承、多态、接口
2.1 封装
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
将 方法 和 属性 写到同一个类中,并将属性 私有化,生成 get set方法,外部访问属性需要通过get和set方法,内部可以直接访问属性,这样的一个类我们认为它完成了封装。(提高代码复用性和安全性)
2.2继承
继承: 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新 的数据或新
的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用 继承我们能够非常方便地复用
以前的代码。
继承的细节:
- 基类发生变化,子类同样发生变化
- 子类继承父类方法,通过重写实现不同功能
- 子类继承父类所有属性和方法,但私有属性不能在子类直接访问,要通过公有方法进行访问
- 子类调用构造器,第一步是子类必须调用父类的构造器,完成父类的初始化。默认情况会总会先调用父类无参构造器,如果父类没有无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则会报错。
- super和this只能放在构造器第一行,因此两个不能共存
2.3 接口
接口:interface修饰的类
2.4 抽象类
2.5多态
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提 高了程序的拓展性。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口 (实现接口并覆盖接口中同一方法)。
多态实现的3个必要条件:继承、重写、向上转型。
2.6 抽象类与接口的区别
相同点:
(1)接口和抽象类都不能实例化
(2)都位于继承的顶端,用于被其他实现或继承
(3)都包含抽象方法,其子类都必须覆写这些抽象方
不同点:
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
实现 | 子类使用extends关键字来继承抽象类。如果子类 不是抽象类的话,它需要提供抽象类中所有声明 的方法的实现 | 子类使用implements关键字来实现 接口。它需要提供接口中所有声明的 方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且 不允许定义为 private 或者 protected |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
2.8普通类与抽象类的区别
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
- 抽象方法是一个模板或约束,避免了子类的随意性,需要实现它的类必须重写它的抽象那个方法。
2.9抽象类能用final修饰吗?
不能,final修饰类不能被继承,抽象类定义就无意义
2.7重写与重载
重载
:同一类,方法名相同,参数列表不同。
- 可以改变返回值类型
- 可以改变访问修饰符
- 可以声明新的或更广的检查异常
- 可以在同一个类或子类中重载
重写
:
- 必须继承关系
- 必须方法名相同
- 必须参数列表相同
- 必须返回值类型相同
- 子类访问修饰符限制大于等于父类的
- 子类不能抛出新检查异常或更广的检查异常
3.变量与方法
3.1.变量的分类
变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。 全局变量(成员变量):类里,函数,静态语句块外的变量 局部变量:方法体内
private String name;//实例变量 private static int i;//类变量(静态变量)
3.2成员变量与局部变量的区别
名字 | 初始值 | 作用域 | 生命周期 | 存储位置 |
---|---|---|---|---|
全局变量(成员变量) | 有默认初始值 | 整个类 | 随对象创建而存在,随对象消失而消失 | 随着对象的创建而存在,堆内存 |
局部变量 | 没有默认初始值,使用前必须赋值 | 方法、语句体内 | 方法调用完或语句结束后,自动释放 | 在方法被调用,或者语句被执行的时候存在,存储在栈内存中 |
3.3静态变量和实例变量的区别
静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会 有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量 是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
3.4静态变量与普通变量区别
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有 的对象所共享,在内存
中只有一个副本,它当且仅当在类初次加载时会被初始 化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副 本,各个对象拥有的副本互不影响。
还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。
3.5在调用子类构造方法之前会先调用父类没有参数的构造方法,其 目的是?
帮助子类做初始化工作。
3.6一个类的构造方法的作用是什么?若一个类没有声明构造方法, 改程序能正确执行吗?为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构 造方法也会有默认的不带参数的构造方法。
3.7构造方法有哪些特性?
-
名字与类名相同;
-
没有返回值,但不能用void声明构造函数;
-
生成类的对象时自动执行,无需调用。
3.8静态方法和实例方法有何不同?
-
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使 用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调 用静态方法可以无需创建对象。
-
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量 和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此 限制
4.关键字
4.1 final
final
可以修饰数据、方法、类。final
修饰数据是恒定的
final 修饰 基本数据类型,不能对基本类型变量重新赋值,因此基本类型变量不能被改变(只能赋值一次,编译阶段放到常量池) final 修饰 引用类型 ,对象的引用不能改变(引用的地址不会改变),可以对 对象的内容进行改变(如属性值等) final 修饰 方法 ,方法不能被重写 final 修饰 类 ,类不能被继承
4.2 static
static
被称为静态的
- 被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
- 在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
- static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
- 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
static
常见应用场景
- 修饰成员变量
- 修饰成员方法
- 静态代码块
- 修饰类【只能修饰内部类也就是静态内部类】
- 静态导包
import static com……ClassName 可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用
static
注意事项
- 静态只能访问静态。
- 非静态既可以访问非静态的,也可以访问静态的。
4.3 this
- this.属性:调用本对象属性
- this.方法:调用本对象方法
- this():调用本对象的构造方法
4.4 super
super.属性:在子类中,调用父类非私有化属性
super.方法:在子类中,调用父类非私有化方法
super():在子类中,调用父类非私有化构造方法
注意:super与this不能同时出现
5.访问修饰符区别
private:同类可见
default:同包可见
protected:同包及所有子类可见(同包可见,不同包子类可见)
public:所有类
6.&与&&,I 与 II 区别
&(按位与):第一个为真,true(短路)。
&&(逻辑与):左右同时为真,true
I(按位或):第一个条件满足,就不判断后面的
||(逻辑或):所有条件都要判断
7. Object类
所有类的超类,Object可以显示继承,也可以隐式继承
显示继承:
public Person extend Object{ }
隐式继承:
public Person{ }
8.创建对象的5中方式
- 使用new关键字
Object object = new Object();
- 使用Class类的newInstance方法---
类对象.newInstance()
Object o = Object.class.newInstance();
- 使用Constructor类的newInstance方法
Object o = Object.class.getConstructor().newInstance();
- 使用clone方法--克隆
- 使用反序列化
[Java中创建对象的5种方式 - _1900 - 博客园 (cnblogs.com)](https://www.cnblogs.com/wxd0108/p/5685817.html#:~:text=Java中创建对象的5种方式 1 使用new关键字 2 使用Class类的newInstance方法,3 使用Constructor类的newInstance方法 4 使用clone方法 5 使用反序列化)
9. final、finally、finalize区别
final
:看4.1
finally
:作用于try-catch代码块中,处理异常的时候,表示不管是否出现异常,我们一定会执行该方法
finalize
属于Object类的一个方法,子类可以覆盖该方法以实现资源清理工作,GC 在回收对象之前调用该方法。
10. continue、break 和 return 的区别是什么?
continue
:指跳出当前的这一次循环,继续下一次循环。break
:指跳出整个循环体,继续执行循环下面的语句。return
用于跳出所在方法,结束该方法的运行。
11.内部类
- 一个类的内部又嵌套了另一个类结构,被嵌套的类被称为内部类
- 基本语法
public class Outer{ class Inner{ } } class Other{ }
内部类的分类:
- 定义在外部类的局部位置上
- 局部内部类---有类名
- 匿名内部类---无类名
- 定义在外部类的成员位置
- 成员内部类
- 静态内部类
11.1局部内部类
- 定义在外部类的局部位置上的类,如方法中
特征:
- 可以直接访问外部类所有成员
- 不能添加访问修饰符
- 作用域:仅仅在定义它的方法或代码块中
- 当外部类和局部内部类的属性或方法重名时,默认遵循就近原则。this调用本类方法和属性,来区分外部类的方法和属性
局部内部类访问外部类成员------直接调用
外部类访问局部内部类成员------创建对象,在访问(必须在作用域内)
public class LocalInnerClass { public static void main(String[] args) { Outer outer = new Outer(); oute1r.m1(); } } class Outer{ private int a =100; private void m2(){ System.out.println("m2"); } public void m1(){ } //在局部内部类的作用域中,创建局部内部类对象 Inner inner = new Inner(); inner.f1(); } }
11.2 匿名内部类
- 定义在外部类的局部位置,比如方法中,无类名
- 基本语法
.new 类或接口(){ 类体 };
- 使用场景:
- 只使用一次或少数次数接口时,因为传统实现接口会写个类继承实现,如果只实现一次接口,用继承的话很繁琐,可以使用匿名内部类来简化
- 使用细节
- 不能添加访问修饰符,因为匿名内部类的地位就是局部变量
- 作用域仅在定义的方法中或代码块中
- 可以直接访问外部类的所有成员,包含私有的
- 如果外部类和匿名内部类的成员重名时,默认就近原则。如果想访问外部类成员用 外部类名.this.成员 去访问
实例:
/** * * 演示匿名内部类的使用 */ public class AnonymousInnerClass { public static void main(String[] args) { Outer outer = new Outer(); outer.m1(); } } class Outer{ private int i =0; public void m1(){ IA anonymous = new IA() { @Override public void cry() { System.out.println("匿名内部类"); } }; anonymous.cry(); } } interface IA{ public void cry(); }
11.3 成员内部类
-
定义在外部内的成员位置
-
使用细节
- 可以直接访问外部类的所有成员,包含私有
- 可以添加任意访问修饰符,因为它是成员变量
- 作用域:和成员变量一样
- 成员内部类访问外部类成员,直接访问
- 外部类访问成员内部类,创建对象,再访问
- 外部其他类访问成员内部类
/** * * 演示匿名内部类的使用 */ public class AnonymousInnerClass { public static void main(String[] args) { Outer.Inner inner = new Outer().new Inner(); inner.say(); } } class Outer{ private int i =0; private String name="张三"; class Inner{ private int i = 1; public void say(){ System.out.println("i="+i+"name="+name); } } }
11.4 静态内部类
- 静态内部类是定义在外部类的成员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态的成员
- 可以添加任意访问修饰符
- 作用域:同其他的成员,为整个类体
- 静态内部类访问外部类,直接访问
- 外部类访问静态内部类,创建对象,再访问
public class StaticInnerTest { public static void main(String[] args) { Outer1 outer1 = new Outer1(); outer1.m1(); } } class Outer1{ private int n1=10; private static String name = "张三"; private static class Inner{ public void say(){ System.out.println("静态内部类"); System.out.println(name); } } public void m1(){ Inner inner = new Inner(); inner.say(); } }
11.5内部类的优点
-
一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
-
内部类不为同一包的其他类所见,具有很好的封装性;
-
内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
-
匿名内部类可以很方便的定义回调
11.6内部类有哪些应用场景
-
一些多算法场合
-
解决一些非面向对象的语句块。
-
适当使用内部类,使得代码更加灵活和富有扩展性。
-
当某个类除了它的外部类,不再被其他的类使用时。
11.7局部内部类和匿名内部类访问局部变量的时候,为什么变量必须 要加上final?
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢? 它内部原理是什么呢?
是因为生命周期不一致, 局部变量直接存储在 栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变 量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final, 可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题
12.对象的判断
12.1 ==与equals
(1):基本数据类型比较值;引用类型==比较地址
(2)equals:默认比较两个对象地址,如果要比较对象内容相等,可以重写equals和hashcode方法。
-
String中的equals方法是被重写过的,因为object的equals方法是比较的对象的 内存地址,而
String的equals方法比较的是对象的值。
-
当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要 创建的值相同的对
象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建 一个String对象。
12.2 hashCode 与equals (重要)
hashCode 与equals 属于Object类,即每个对象都有该方法
hashCode()
:作用是获取哈希吗,也被称为散列码;它实际是返回int整数,哈希吗作用是确定该对象在哈希表中的索引位置。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出 对应的“值”。
为什么重写equals时,必须重写hashCode方法?
每当重写该方法时,通常都需要重写hashCode方法,以维护hashCode方法的一般约定,即相等的对象必须具有相等的哈希码,因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的 内存地址是否相等。
13.值传递
13.1当一个对象被当作参数传递到一个方法后,此方法可改变这个对 象的属性,并可返回变化后的结果,那么这里到底是值传递还是 引用传递
是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一 个参数被传递到方法中
时,参数的值就是对该对象的引用。对象的属性可以在被 调用过程中被改变,但对对象引用的改变是不会影响到调用者的
13.2 为什么Java中只有值传递
因为按值调用表示方法接收的是调用者提供的值,而按引用调用 表示方法接收的是调用者提供的变量地址。一个方法可以 修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的 一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
13.3 值传递与引用传递的区别
值传递
:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷 贝,也就是说传递后就互不相关了。
引用传递
:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引 用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说 传递前和传递后都指向同一个引用(也就是同一个内存空间)。
14. 数据类型
14.1 Java有哪些数据类型
类型名称 | 关键字 | 占用内存 |
---|---|---|
字节型 | byte | 1字节 |
短整型 | short | 2字节 |
整型 | int | 4字节 |
长整型 | long | 8字节 |
单精度浮点型 | float | 4字节 |
双精度浮点型 | double | 8字节 |
字符型 | char | 2字节 |
布尔型 | boolean | 1字节 |
从小到大:byte < short int < float < long < double
14.2. switch的作用范围
JDK1.5 以前,switch(expr),expr只能是 byte 、short、char、int;
JDK1.5 以后 引入了枚举
JDK1.7 以后 字符串(String)
14.3 用最有效率的方法计算2乘以8
2<<3
14.4 Math.round(11.5)等于多少?Math.round(-11.5)等于多少
Math.round(11.5) =12;
Math.round(-11.5) = -11
14.5 short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才
能赋值给 short 型。
而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其
中有隐含的强制类型转换。
15.IO流
IO框架 - snail05 - 博客园 (cnblogs.com)
15.1 IO流的分类
-
按流的方向分:
- 输入流:文件读出到程序
- 输出流:程序写入到文件
-
按操作单元分:
- 字节流:以字节为单位
- 字符流:以字符为单位
-
按功能分:
- 节点流:可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流
- 处理流:对一个已存在的流进行连接或封装,在节点流的基础之上增强功能
IO流的4大基类
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream |
Reader |
输出流 | OutputStream |
Writer |
15.2 BIO,NIO,AIO 有什么区别?
简答
-
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并
发处理能力低。
-
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过
Channel(通道)通讯,实现了多路复用。
-
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO
,异步 IO 的操作基于事件和回调机制。
详细回答
-
BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动
连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
-
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio包,提供了 Channel , Selector,Buffer等抽象。NIO中的 N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和ServerSocket 相对应的SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两
种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
-
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非
阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵
塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,
虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
15.3 Files的常用方法都有哪些?
-
Files. exists():检测文件路径是否存在。
-
Files. createFile():创建文件。
-
Files. createDirectory():创建文件夹。
-
Files. delete():删除一个文件或目录。
-
Files. copy():复制文件。
-
Files. move():移动文件。
-
Files. size():查看文件个数。
-
Files. read():读取文件。
-
Files. write():写入文件。
15.4 字节流与字符流得选择
字符流:只能处理文本文档(.txt
)
字节流:任意文件
15.5 IO模型得分类
I/O模型可以分为:阻塞I/O模型、非阻塞I/O模型、I/O复用模型、信号 驱动式I/O模型和异步I/O模型。
- 按照POSIX标准来划分只分为两类:
- 同步I/O
- 异步I/O
- 同步I/O与异步I/O的区别
- I/O操作分为两步:发送I/O请求与实际的I/O操作
16. 反射
16.1什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
静态编译和动态编译
静态编译:在编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象
反射机制优缺点
优点: 运行期类型的判断,动态加载类,提高代码灵活度。
缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
16.2 Java获取反射的三种方法
- 通过new对象实现反射机制---
对象.getClass()
//方式一(通过建立对象) Student stu = new Student(); Class classobj1 = stu.getClass(); System.out.println(classobj1.getName());
- 通过路径实现反射机制---
Class.forName(类的全路径名)
Class classobj2 = Class.forName("fanshe.Student");
- 通过类名实现反射机制----
类名.class
Class<Person> clazz = Person.class;
反射 - snail05 - 博客园 (cnblogs.com)
16.3 反射中,Class.forName和classloader的区别
class.forName()和classLoader都可用来对类进行加载。
-
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static
块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在
newInstance才会去执行static块。
16.4通过反射创建对象
-
获取Class对象
对象.getClass()
类名.class
Class.forName(类的全路径名)
-
获取构造参数
Class对象.getConstructor()
:获取无参构造Class对象.getConstructor(String.class, int.class)
:获取有参构造Class对象.getConstructors()
:获取所有构造方法Class对象.getDeclaredConstructor()
:获取无参构造(私有)Class对象.getDeclaredConstructor(String.class)
:获取有参构造(私有)Class对象.getDeclaredConstructors()
:获取所有构造方法(包括私有)
-
创建对象
构造器.newInstance()
:创建类的对象私有构造器.setAccessible(true)
:获取私有构造器时,必须先开启访问权限
16.5 通过反射获取类的属性
-
Filed
的获取Class对象.getField(String name)
:获取指定名称的属性对象Class对象.getFields()
:获取所有的属性对象Class对象.getDeclaredField(String name)
:获取任意权限的指定名称的属性对象Class对象.getDeclaredFields()
:获取任意权限的所有的属性对象
//1.获取Class对象 Class<Admin> clazz = Admin.class; //2.获取Admin类所有的public修饰的属性包括继承过来的 Field[] fields1 = clazz.getFields(); //3.获取Admin类所有的属性包括private修饰的 Field[] fields2 = clazz.getDeclaredFields(); System.out.println(Arrays.toString(fields1)); System.out.println(Arrays.toString(fields2)); //获取gender属性 Field genderField = clazz.getDeclaredField("gender"); //获取bbb属性 Field bbbField = clazz.getField("bbb"); -
Filed
的操作Class对象.set(Object obj, Object value)
:将指定的值赋值给指定对象的指定属性Class对象.get(Object obj)
:获取指定对象的指定属性的值
16.6 通过反射获取类的方法
-
Method
的获取Class对象.getMethod(String 方法名,形参列表):
获取指定方法(不能获取私有方法)Class对象.getDeclaredMethod(String 方法名,形参列表)
: 获取指定方法(可以获取私有方法)
-
Method
的操作方法对象.invoke(Java对象,形参列表)
:invoke
方法的返回值,就是指定的方法的返回值。
如果指定的方法没有返回值,则invoke
方法返回null
16.7 通过反射获取注解
-
Annotation[] getAnnotations()
:获取所有注解(不包括私有) -
Annotation[] getDeclaredAnnotations()
:获取所有注解(包括私有) -
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
:获取指定注解 -
自定义注解
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestMapping { String value(); // 这是注解的一个属性字段,也就是在使用注解时填写在括号里的参数 } -
使用注解
@MyRequestMapping("/test") public class TestController { public void test() { System.out.println("进入Test方法"); } } -
通过反射获取TestController的注解
public class Test { public static void main(String[] args) { Class<?> c = TestController.class; //获取该类中@MyRequestMapping 注解对象 MyRequestMapping baseRequestMapping = c.getAnnotation(MyRequestMapping.class); System.out.println(baseRequestMapping.value()); // 输出value的值 //获取该类中所有注解对象 Annotation[] atnsArray = c.getAnnotations(); for (Annotation an : atnsArray) { System.out.println(an); } } }
16.8反射的优缺点
缺点:
- 反射代码的可读性和可维护性比较低
- 反射代码执行的性能低
- 反射破坏了封装性
优点:
- 可以在运行期间获取类的信息、并操作一个类的方法,提高程序的灵活性和扩展性
17.常用的 API
17.1 String
length() | 获取字符串的长度 |
---|---|
equals(String s) | 判断两个字符串内容是否相同 |
equalsIgnoreCase(String s) | 不区分大小写判断两个字符串内容是否相同 |
charAt(int index) | 返回下标所在的char值(字符) |
indexOf(String s) | 返回字串第一次出现的位置,没出现则返回-1 |
lastIndexOf(String s) | 返回字串最后一次出现的位置,没出现返回-1 |
starstWith(String prefix) | 判断字符串是否以prefix为前缀开头 |
endsWith(String suffix) | 判断字符串是否以suffix为后缀结尾 |
toLowerCase() | 返回字符串的小写形式 |
toUpperCase() | 返回字符串的大写形式 |
substring(int startindex,int endindex) | 返回从startindex开始到endindex结束的字串 |
contains(String s) | 判断是否包含字串s |
concat(String s) | 字符串拼接,相当于+ |
replaceAll(String oldSrt,String newSrt) | 替换原有字符串中的字串为目标字串 |
split(String split) | 以指定字符串分割后返回字符串数组 |
getBytes() | 返回字符串的字节数组 |
tocharArray() | 将此字符串转换为一个新的字符数组 |
trim() | 返回字符串两边消除空格后的字符串 |
isEmpty() | 判断字符串是否为空 |
compareTo | 将字符串与另一个对象进行比较 |
hashCode() | 返回字符串的哈希值 |
17.1.1 字符型常量和字符串常量的区别?
- 字符型常量是单引号,字符串常量是双引号
- 字符型常量相当于(ASCLL值)可以参加运算,字符串常量代表地址
- 字符型常量只占 1 Byte ,字符串常量占 至少 1 Byte
17.1.2 什么是字符串常量池?
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存 储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回 它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
17.1.3 String有哪些特性?
不变性
:String 是只读字符串,是一个典型的 不可变对象,对它进行任何操作,其实都是创 建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并 频繁访问时,可以保证数据的一致性。常量池优化
:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时, 会直接返回缓存的引用。final
:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
17.1.4 String为什么是不可变的?
- 用private final 修饰字符数组 来保存字符串;
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变
17.1.5 String真的是不可变吗?
- String不可变但不代表引用不可以变;原来String的内容是不变的,但可以改变的引用
- 通过反射是可以修改所谓的“不可变”对象
17.1.6 String常用方法
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
17.1.7 String 和 StringBuffer、StringBuilder的区别是什么?String 为什么是不可变的
- 可变性
- String类中使用字符数组保存字符串,private final char value[],所以 string对象是不可变的。
- StringBuilder与StringBuffer都继承自 AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这 两种对象都是可变的。
- 线程安全性
- String中的对象是不可变的,也就可以理解为常量,线程安全。
- StringBuffer对方法加了同步锁或者对调用 的方法加了同步锁,所以是线程安全的。
- StringBuilder并没有对方法进行加同步锁,所以是非线程安全 的。
- 性能
- String < StringBuffer < StringBuilder
总结使用场景:
String | StringBuffer | StringBuilder | |
---|---|---|---|
使用场景 | 操作少量数据 | 多线程操作大量数据 | 单线程操作大量数据 |
17.1.8 String str = new String("xdclass.net"); 创建了⼏个对象?
答案: 创建⼀个对象:常量池存在,则直接new⼀个对象; 创建两个对象:常量池不存在,则在常量池创建⼀个对象,也在堆⾥⾯创建⼀个对象
17.2 Date
17.3 包装类
- 自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱::将包装类型转换为基本数据类型
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
- Integer a= 127 与 Integer b = 127相等吗?
对于对象引用类型:==比较的是对象的内存地址。
对于基本数据类型:==比较的是值。
public static void main(String[] args) { Integer a = new Integer(3); Integer b = 3; // 将3自动装箱成Integer类型 int c = 3; System.out.println(a == b); // false 两个引用没有引用同一对象 System.out.println(a == c); // true a自动拆箱成int类型再和c比较 System.out.println(b == c); // true Integer a1 = 128; Integer b1 = 128; System.out.println(a1 == b1); // false Integer a2 = 127; Integer b2 = 127; System.out.println(a2 == b2); // true //自动拆箱 int i = a.intValue();
自动装箱和拆箱的原理
- 自动装箱原理 valueOf()
只有double和float的自动装箱代码没有使用缓存,每次都是new 新的对象,其它的6种基本类型都使用了缓存策略。 在Integer类中本身其实存在一个cache机制,它本身是一个常量,存在于Java的方法区中,当我们给定的整数在[-128,127]之间的话,那么会直接从cache中返回对应的Integer类的引用(或者说地址),如果整数的大小不在这个范围内的话,那么valueof()方法会新new一个Integer类型的对象,并返回。
- 自动拆箱:intValue()
18.异常
18.1 异常的分类
- Throwable: Java 语言中所有错误或异常的超类
- Error:java 运行时系统的内部错误和资源耗尽错误。如系统崩溃,内存不足,堆栈溢出 等,编译器不会对错误进行检测,应用程序也不会对错误进行捕获处理;一旦这类错误发生,通常应用程序会被终 止,仅靠应用程序本身无法恢复;
- StackOverflowError:深递归导致栈被耗尽而抛出的异常
- OutOfMemoryError:内存溢出异常
- Exception:可以在应用程序中进行捕获并处理的,通常遇到这种错 误,应对其进行处理,使 应用程序可以继续正常运行。
- CheckdException(编译时异常):一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序 去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch。
- IOException:输入输出异常。
- SQLException:操作数据库异常
- FileNotFoundException:文件不存在异常
- ClassNotFoundException:找不到类异常
- InterruptedException: (中断异常-调用线程睡眠时候)
- RuntimeException(运行时异常):程序错误导致。
- NullPointerException:空指针异常
- ArithmeticException:算术异常。
- ArrayOutOfBoundsException:数组索引越界异常。
- IndexOutOfBoundsException:下标越界异常
- ClassCastException:类型强制转换异常。
- BufferOverflowException: (缓冲区溢出异常)
- CheckdException(编译时异常):一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序 去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch。
- Error:java 运行时系统的内部错误和资源耗尽错误。如系统崩溃,内存不足,堆栈溢出 等,编译器不会对错误进行检测,应用程序也不会对错误进行捕获处理;一旦这类错误发生,通常应用程序会被终 止,仅靠应用程序本身无法恢复;
- Throwable 类常用方法
- public string getMessage():返回异常发生时的详细信息
- public string toString():返回异常发生时的简要描述
- public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
- public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息
18.2 Error 和 Exception 区别是什么?
Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出 等,编译器不会对这类错 误进行检测,JAVA 应用程序也不应对这类错误进行捕 获,一旦这类错误发生,通常应用程序会被终 止,仅靠应用程序本身无法恢复;
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错 误,应对其进行处理,使 应用程序可以继续正常运行。
18.3 运行时异常与一般异常(编译时异常)区别?
- RuntimeException:JVM 在运行期间可能出 现的异常。 Java 编译器 不会检查运行时异常。
- 编译时异常:是Exception 中除 RuntimeException 及其子类之外的异常;Java 编 译器会检查受检异常。
区别:是否强制要求调用者必须处 理此异常,如果强制要求 调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲, 如果没有特殊的要求,我们建 议使用RuntimeException异常。
18.4 异常的处理方式
- 遇到问题不进行具体处理,而是继续抛给调用者 (throw,throws)抛出异常有三种形式,一是 throw, 一个 throws,还有一种系统自动抛异常。
- try catch 捕获异常针对性处理方式
18.5 Throw和 throws 的区别?
- 位置不同
- throws 用在方法上,后面跟的是异常类,可以跟多个;throw 用在方法体内,后面跟的是异常对 象。
- 功能不同
- throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw 抛出具体的问题对象,执行到throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛 给调用者。
- throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象。
18.6 final、finally、finalize 有什么区别?
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方 法不能被重写、修 饰变量表示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执 行的代码方法 finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用 来存放一些关闭资源的代 码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类, Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的 清理工作。
18.8 . try-catch-finally 中哪个部分可以省略?
catch 可以省略
原因:try只适合处理运行时异常,try+catch适合处理运行时 异常+普通异常。也就是 说,如果你只用try去处理普通异常却不加以catch处 理,编译是通不过的,因为编译器硬性规定,普通 异常如果选择捕获,则必须用 catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定, 所以 catch可以省略,你加上catch编译器也觉得无可厚非。
18.9 . try-catch-finally 中,如果 catch 中 return 了, finally 还会执 行吗?
答:会执行,在 return 前执行。
注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块, try中的 return 语句不会 立马返回调用者,而是记录下返回值待 finally 代码块 执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会 返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很 大的 困扰
18.12 Java异常处理最佳实践
- 在 finally 块中清理资源或者使用 try-with-resource 语句
- try()⾥⾯可以定义多个资源,它们的关闭顺序与定义的顺序相反
- 优先明确的异常
- 你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异 常。
- . 对异常进行文档说明
- 当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可 以更好地避免或处理异常。
- 使用描述性消息抛出异常
- 在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中, 都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。
- 优先捕获最具体的异常
- 不要捕获 Throwable 类
- 不要忽略异常
- 不要记录并抛出异常
- 包装异常时不要抛弃原始的异常
- 不要使用异常控制程序的流程
- 使用标准异常
- 异常会影响性能
19 注解
19.1什么是注解
- 注解就是给类、属性、方法、参数 加的标记
19.2自定义注解
-
修饰符 @interface 注解名{ ... } -
元注解:
@Target
:注解使用范围ElementType.Type
:类ElementType.Method
:方法ElementType.Field
:属性ElementType.Parameter
:方法参数
@Retention
:注解的生命周期RetentionPolicy.SOURCE
:只在编译期有效RetentionPolicy.RUNTIME
:表示生命周期持续到运行期
@Inherited
:该注解可以被继承@Documented
: 该注解可以被生成到文档中
-
自定义注解
/* 元注解: @Target : 指定自定义注解能标记什么 属性: ElementType.Type 表示能标记类 ElementType.Method 表示能标记方法 ElementType.Field 表示能标记属性 ElementType.Parameter 表示能标记方法参数 @Retention : 指定自定义注解的生命周期 属性: RetentionPolicy.SOURCE 表示生命周期只在编译期有效 RetentionPolicy.RUNTIME 表示生命周期持续到运行期 @Inherited : 指定自定义注解可以被继承 @Documented : 指定自定义注解可以被生成到文档中 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MyAnnotation { /* 声明注解的属性 属性可以有默认值,如果没有为该属性指定值,则使用默认值 */ String name() default ""; String value() default ""; } -
使用注解
@MyAnnotation(name = "jay") public class Demo1 { /* 使用MyAnnotation注解 为value属性赋值(没有指定属性名时,则默认赋值的就是value属性) */ @MyAnnotation("aaa") public void test(){ } } -
读取注解
boolean 反射对象.isAnnotationPresent(注解的类对象)
:判断当前反射对象(类对象、方法对象)是否被指定的注解标记反射对象.getAnnotation(注解对象.Class)
:取出标记该反射对象的注解对象注解对象.属性名()
:取出注解对象的属性值
public class Main { public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException { /* 读取Demo2类中每一个方法, 判断每一个方法是否有MyAnnotation的注解 如果有: 则执行该方法,执行注解的属性那么多次 如果没有: 不处理 */ //1.获取Demo2的类对象 Class<Demo2> clazz = Demo2.class; Demo2 demo2 = clazz.newInstance(); //2.获取该类对象的所有方法对象 Method[] methods = clazz.getDeclaredMethods(); //3.取出每一个方法对象 for (Method m : methods) { //4. 判断该方法是否被InvokeCount注解修饰 if(m.isAnnotationPresent(MyAnnotation.class)){//表示被该注解标记了 //5.取出标记的注解对象 InvokeCount ic = m.getAnnotation(MyAnnotation.class); //6.取出注解的属性 int count = ic.value(); for (int i = 0; i < count; i++) { m.invoke(demo2); } } } } }
20 序列化与反序列化
序列化 :将java
对象写入指定的文件
反序列化 :将文件读取成java
对象
ObjectOutputStream
(序列化):将java
对象写入指定的文件- 构造方法
ObjectOutputStream(OutputStream out)
:创建指向指定路径文件的对象字节输出流对
- 常用方法
writeObject(Object obj)
:将指定的Java对象写入指定的文件
- 构造方法
ObjectInputStream
(反序列化):将文件读取成``java`对象- 构造方法
ObjectInputStream(InputStream in)
:创建指向指定路径文件的对象字节输入流对象
- 常用方法
Object readObject()
:将文件读取成java
对象
案例
21 泛型
- 什么是泛型?
- 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数
- 泛型的好处
- 编译时,检查添加元素的类型,提高安全性
- 减少类型转换次数,提高效率
- 不在提示编译警告
- 标识符
E
- Element (在集合中使用,因为集合中存放的是元素)T
- Type(Java 类)K
- Key(键)V
- Value(值)N
- Number(数值类型)?
- 表示不确定的java类型- S、U、V - 2nd、3rd、4th types
- 注意事项
- 泛型 T、E只能是引用类型
- 使用泛型的数组,不能初始化
- static修饰不能使用泛型
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通