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继承

继承: 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新 的数据或新

的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用 继承我们能够非常方便地复用

以前的代码。

继承的细节:

  1. 基类发生变化,子类同样发生变化
  2. 子类继承父类方法,通过重写实现不同功能
  3. 子类继承父类所有属性和方法,但私有属性不能在子类直接访问,要通过公有方法进行访问
  4. 子类调用构造器,第一步是子类必须调用父类的构造器,完成父类的初始化。默认情况会总会先调用父类无参构造器,如果父类没有无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则会报错。
  5. 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重写与重载

重载:同一类,方法名相同,参数列表不同。

  1. 可以改变返回值类型
  2. 可以改变访问修饰符
  3. 可以声明新的或更广的检查异常
  4. 可以在同一个类或子类中重载

重写

  1. 必须继承关系
  2. 必须方法名相同
  3. 必须参数列表相同
  4. 必须返回值类型相同
  5. 子类访问修饰符限制大于等于父类的
  6. 子类不能抛出新检查异常或更广的检查异常

3.变量与方法

3.1.变量的分类

image-20220815093307248

变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。
全局变量(成员变量):类里,函数,静态语句块外的变量
局部变量:方法体内
private String name;//实例变量
private static int i;//类变量(静态变量)

3.2成员变量与局部变量的区别

名字 初始值 作用域 生命周期 存储位置
全局变量(成员变量) 有默认初始值 整个类 随对象创建而存在,随对象消失而消失 随着对象的创建而存在,堆内存
局部变量 没有默认初始值,使用前必须赋值 方法、语句体内 方法调用完或语句结束后,自动释放 在方法被调用,或者语句被执行的时候存在,存储在栈内存中

3.3静态变量和实例变量的区别

静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会 有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。

实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量 是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

3.4静态变量与普通变量区别

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有 的对象所共享,在内存

中只有一个副本,它当且仅当在类初次加载时会被初始 化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副 本,各个对象拥有的副本互不影响。

还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。

3.5在调用子类构造方法之前会先调用父类没有参数的构造方法,其 目的是?

帮助子类做初始化工作。

3.6一个类的构造方法的作用是什么?若一个类没有声明构造方法, 改程序能正确执行吗?为什么?

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构 造方法也会有默认的不带参数的构造方法。

3.7构造方法有哪些特性?

  1. 名字与类名相同;

  2. 没有返回值,但不能用void声明构造函数;

  3. 生成类的对象时自动执行,无需调用。

3.8静态方法和实例方法有何不同?

  1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使 用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调 用静态方法可以无需创建对象。

  2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量 和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此 限制

4.关键字

4.1 final

final可以修饰数据、方法、类。final修饰数据是恒定的

final 修饰 基本数据类型,不能对基本类型变量重新赋值,因此基本类型变量不能被改变(只能赋值一次,编译阶段放到常量池)
final 修饰 引用类型 ,对象的引用不能改变(引用的地址不会改变),可以对 对象的内容进行改变(如属性值等)
final 修饰 方法 ,方法不能被重写
final 修饰 类 ,类不能被继承

4.2 static

static被称为静态的

  1. 被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享
  2. 在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
  3. static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
  4. 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

static常见应用场景

  1. 修饰成员变量
  2. 修饰成员方法
  3. 静态代码块
  4. 修饰类【只能修饰内部类也就是静态内部类】
  5. 静态导包
import static com……ClassName
可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用

static注意事项

  1. 静态只能访问静态。
  2. 非静态既可以访问非静态的,也可以访问静态的。

4.3 this

  1. this.属性:调用本对象属性
  2. this.方法:调用本对象方法
  3. 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中方式

  1. 使用new关键字
Object object = new Object();
  1. 使用Class类的newInstance方法---类对象.newInstance()
Object o = Object.class.newInstance();
  1. 使用Constructor类的newInstance方法
Object o = Object.class.getConstructor().newInstance();
  1. 使用clone方法--克隆
  1. 使用反序列化

[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 的区别是什么?

  1. continue :指跳出当前的这一次循环,继续下一次循环。
  2. break :指跳出整个循环体,继续执行循环下面的语句。
  3. return 用于跳出所在方法,结束该方法的运行。

11.内部类

  • 一个类的内部又嵌套了另一个类结构,被嵌套的类被称为内部类
  • 基本语法
public class Outer{
class Inner{
}
}
class Other{
}

内部类的分类:

  • 定义在外部类的局部位置上
    • 局部内部类---有类名
    • 匿名内部类---无类名
  • 定义在外部类的成员位置
    • 成员内部类
    • 静态内部类

11.1局部内部类

  • 定义在外部类的局部位置上的类,如方法中

特征:

  1. 可以直接访问外部类所有成员
  2. 不能添加访问修饰符
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 当外部类和局部内部类的属性或方法重名时,默认遵循就近原则。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修饰
  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态的成员
  2. 可以添加任意访问修饰符
  3. 作用域:同其他的成员,为整个类体
  4. 静态内部类访问外部类,直接访问
  5. 外部类访问静态内部类,创建对象,再访问
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内部类的优点

  1. 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!

  2. 内部类不为同一包的其他类所见,具有很好的封装性;

  3. 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。

  4. 匿名内部类可以很方便的定义回调

11.6内部类有哪些应用场景

  1. 一些多算法场合

  2. 解决一些非面向对象的语句块。

  3. 适当使用内部类,使得代码更加灵活和富有扩展性。

  4. 当某个类除了它的外部类,不再被其他的类使用时。

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只能是 byteshortcharint

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

image-20221009104813911

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获取反射的三种方法

  1. 通过new对象实现反射机制---对象.getClass()
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
  1. 通过路径实现反射机制---Class.forName(类的全路径名)
Class classobj2 = Class.forName("fanshe.Student");
  1. 通过类名实现反射机制----类名.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反射的优缺点

缺点:

  1. 反射代码的可读性和可维护性比较低
  2. 反射代码执行的性能低
  3. 反射破坏了封装性

优点:

  1. 可以在运行期间获取类的信息、并操作一个类的方法,提高程序的灵活性和扩展性

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为什么是不可变的?

image-20221009112335771

  • 用private final 修饰字符数组 来保存字符串;
    • 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变
17.1.5 String真的是不可变吗?
  1. String不可变但不代表引用不可以变;原来String的内容是不变的,但可以改变的引用
  2. 通过反射是可以修改所谓的“不可变”对象

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()
只有doublefloat的自动装箱代码没有使用缓存,每次都是new 新的对象,其它的6种基本类型都使用了缓存策略。
Integer类中本身其实存在一个cache机制,它本身是一个常量,存在于Java的方法区中,当我们给定的整数在[-128,127]之间的话,那么会直接从cache中返回对应的Integer类的引用(或者说地址),如果整数的大小不在这个范围内的话,那么valueof()方法会新new一个Integer类型的对象,并返回。
  • 自动拆箱:intValue()

18.异常

18.1 异常的分类

image-20221010100935718

  • Throwable: Java 语言中所有错误或异常的超类
    • Error:java 运行时系统的内部错误和资源耗尽错误。如系统崩溃,内存不足,堆栈溢出 等,编译器不会对错误进行检测,应用程序也不会对错误进行捕获处理;一旦这类错误发生,通常应用程序会被终 止,仅靠应用程序本身无法恢复;
      • StackOverflowError:深递归导致栈被耗尽而抛出的异常
      • OutOfMemoryError:内存溢出异常
    • Exception:可以在应用程序中进行捕获并处理的,通常遇到这种错 误,应对其进行处理,使 应用程序可以继续正常运行。
      • CheckdException(编译时异常):一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序 去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch。
        • IOException:输入输出异常。
        • SQLException:操作数据库异常
        • FileNotFoundException:文件不存在异常
        • ClassNotFoundException:找不到类异常
        • InterruptedException: (中断异常-调用线程睡眠时候)
      • RuntimeException(运行时异常):程序错误导致。
        • NullPointerException:空指针异常
        • ArithmeticException:算术异常。
        • ArrayOutOfBoundsException:数组索引越界异常。
        • IndexOutOfBoundsException:下标越界异常
        • ClassCastException:类型强制转换异常。
        • BufferOverflowException: (缓冲区溢出异常)
  • 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修饰不能使用泛型
posted @   snail05  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示