Java基础(查缺补漏)

2023-11-24 09:40
文章标签 java 基础 补漏 查缺

本文主要是介绍Java基础(查缺补漏),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java01

Java缺点:编译速度慢,语法不灵活,人工智能不适合Java

Java有个缺点是运行速度慢,为什么?

Java是编译型语言,不能直接运行,需要先编译才能运行

写代码->编译->运行

Java运行机制:

1、编写源代码 .java

2、将源代码编译成字节码文件(16进制) .class

3、运行字节码文件

在这里插入图片描述

JDK(Java Development Kit):Java 开发工具包,包括 JRE 和 Java 编译器(将源文件编译成字节码文件)

JRE(Java Runtime Environment):Java 运行环境,包括 JVM 和 Java 基础类库

如果只是需要运行 Java 程序,安装 JRE 即可,如果要开发 Java 程序,需要安装 JDK。

完整的 Java 程序的组成:关键字 + Java 类库 + 开发者自定义的标识符

Java02

问题:内存地址是系统随机分配的,16进制的数据,很难记忆。如何解决?引入变量名,通过变量名来读取数据

给数值起别名,可以通过别名来替代内存地址操作数据

boolean类型所占的空间:1/8个字节

&&短路与、||短路或。效率更高!

以二进制为单位进行运算(位运算):

  • &:每位数字一一对应,都为1则该位记作1,否则为0
  • |:每位数字一一对应,只要有一个为1该位记作1
  • ^:每位数字一一对应,相同记作0,不同记作1
  • A << B:变量 A 乘以2的 B 次方,2 << 3即2*2^3(左移)
  • A >> B:变量 A 除以2的 B 次方,2 >> 3即2/2^3(右移)

Java03

可以直接把数值赋值给 char,但是不能把整数类型的变量赋值给 char

Java04

Java05

Switch语句不能用于比较数据类型为long、float、double型的

Java06

数组:使用一个变量名即可访问多个数值

基本数据类型的变量不是对象,跟 Object 没有关系,输出基本数据类型,是直接输出它的值

只有输出对象的时候,才会调用 toString 方法

int num = 1;

先在栈中开辟空间存储变量 num,直接将 1 存入 num 中,跟堆没有关系

在这里插入图片描述

Test test = new Test();

堆中专门用来存储对象

在这里插入图片描述

int[] array = new int[3];

在这里插入图片描述

Java07

int[][] array = {{1,2,3},{4,5,6},{7,8,9}}

在这里插入图片描述

int[][] array = new int[3][];// 二维的长度可以不定义

对象的特征:

  • 属性,对象的静态特征
  • 方法,对象的动态特征

使用有参构造创建对象,只需要调用一次方法,就可以同时完成创建和赋值

Java08

byte[] a1,a2[];// a2是二维数组
// 上面的代码等价于下面的代码
byte[] a1;
byte[] a2[];

Java09

使用 static 修饰的成员方法叫做静态方法,静态方法中不能使用 this 关键字,静态方法也不能访问类的实例变量和实例方法(非 static 修饰的)

static 除了可以修饰成员变量和成员方法,还可以修饰代码块,被 static 修饰的代码块叫做静态代码块,特点是只执行一次,当类被加载到内存的时候执行,不需要手动进行调用,会自动执行。

创建子类对象之前,会自动创建父类对象。默认调用父类的无参构造创建父类对象

this() 和 super() 的区别:

super() 调用父类的构造函数

this() 调用本类的构造函数

构造函数不能进行递归,会导致无限次创建对象,堆内存溢出

重写的规则:

  • 方法名相同
  • 参数列表相同
  • 子类方法的返回值与父类方法的返回值类型相同或者是其子类
  • 子类方法的访问权限不能小于父类(public > protected > 默认 > private(如果父类方法的访问权限是private,则已经无法被子类重写))

Java10

多态:一种事物可以有多种不同的表现形态,一个对象在不同的业务场景中以不同的形式出现,根据不同的业务场景,对象呈现出不同的形式。

抽象的前提是多态,多态的前提是继承

Object 是所有 Java 类的共同父类,为什么?

因为 Java 中所有对象有一些共性,比如 hashCode 获取地址,getClass 获取类信息

Object类中的toString()、equals()、hashCode()方法一般会被重写

可以看一下String类中重写的equals()方法,逻辑结构十分清晰!多锻炼分析源码的能力!

Java11

基本类型可以直接用双等号判断

引用类型如果用双等号判断,比较的是内存地址。所以引用类型的比较一般用重写后的equals()方法判断

在这里插入图片描述

hashCode 和 equals 的关联

  • 都是用来判断两个对象是否相等
  • hashCode 的效率更高
  • hashCode 的值不相等,则两个对象一定不是同一个对象
  • hashCode 的值相等,两个对象不一定是同一个对象,不能确定对象的关系

在集合框架中,数据量大,有的不允许存储重复数据,考虑效率的问题,常见处理如下:

1、先用 hashCode 判断两个对象是否相等,如果能判断出来,则直接给结果

2、如果判断不出来,再使用 equlas 进行确认

Java12

接口和抽象类的关系

  • 接口是一个极度抽象的抽象类,接口中的方法全部都是抽象方法。
  • 抽象类中允许有非抽象方法的存在

Java中有两种错误:

  • 1、编译时错误。IDEA 会提示,可以避免
  • 2、运行时错误。语法没有问题,逻辑有问题,可以通过编译,但是运行就会报错

错误也可以抽象成对象,用对象来表示,Java 中专门有一些用来表示各种各样的错误,Java 中就专门有一组类来生成各种各样的错误对象,这组类就是异常类

Java 可以结合异常类提供一种处理错误的机制:当程序中出现运行时错误的时候,会创建一个包含错误信息的异常对象,并将该对象提交给系统,由系统转交给能处理该异常的代码进行处理。

Java13

try-catch 结构来手动处理异常

try 代码块中就是要运行的代码,如果一旦出错,会自动创建异常对象并抛出

catch 代码块会捕获这个异常对象,可以在 catch 代码块中进行相应的处理

finally 会和 try-catch 结合起来使用

finally 的作用:无论程序是否抛出异常,finally 代码块中的程序一定会执行

我们不手动使用try-catch结构来处理异常的话,JVM也会帮我们兜底处理异常,但是我们不能把全部工作都交给虚拟机

异常类是一组树状结构的类,最顶端的类是 Throwable

Throwable 有两个子类:

  • Error
  • Exception

Error 描述的是系统错误,无法处理,Exception 描述的逻辑错误,可以处理

try-catch 是一种防范机制,如果程序有异常则抛出进行处理,如果没有异常发生则不用处理。

throw 是开发者主动抛出异常的一种方式

throws 用来修饰方法的,表示该方法有可能会抛出异常,那么该方法的调用者就需要注意处理异常。

使用 throws 抛出的异常分两种:

  • 可不处理异常:RuntimeException
  • 必须处理异常:Exception 及其子类

可不处理异常,可以不用手动进行处理(JVM会帮我们处理),必须处理异常是指开发者必须手动进行处理

Java14

方法中的局部变量不能使用 static 进行修饰

修饰符当前类同包子类其他包
public可以可以可以可以
protected可以可以可以不能使用
默认可以可以不能使用不能使用
private可以不能使用不能使用不能使用

基本数据类型间存在自动转型,包装类之间不存在自动转型

final:

  • 被 final 修饰的类不能被继承
  • 被 final 修饰的方法不能被重写
  • 被 final 修饰的变量不能修改值

接口之间是可以继承的,同时接口是支持多继承的

抽象类和接口都不能被实例化

接口中的属性用 public static final 修饰

Java15

提升程序的运行效率,性能,让程序充分利用 CPU 资源,提高 CPU 资源的使用效率,从而解决高并发带来的负载均衡问题。

进程:计算机正在运行的一个独立的应用程序,动态概念,程序必须在运行状态下才是一共进程。

线程:线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成的。

Java16

Thread类源码分析:

1、Thread 类本身有一个 Runnable 的成员变量 target,默认值为 null

2、Thread 提供了一个带参构造,参数是 Runnable,会将外部传入的 Runnable 赋值给成员变量 target

3、Thread 类本身的 run 方法,首先判断 target 是否为 null,如果不为 null,则直接调用 target 的 run 方法

  • 创建状态:实例化一个新的线程对象,还未启动。
  • 就绪状态:调用 start 方法完成启动,进入线程池等待抢占 CPU 资源。
  • 运行状态:线程对象获取了 CPU 资源,在一定的时间内执行任务。
  • 阻塞状态:正在运行的线程暂停执行任务,释放 CPU 资源。
  • 终止状态:线程执行完毕或者因为异常导致线程终止运行。

在这里插入图片描述

lamdba 表达式的本质是将接口中唯一的抽象方法的具体实现作为参数进行传递

Java17

线程调度就是通过调用线程的方法从而实现对线程的控制

线程合并:将指定的线程加入到当前线程中,将两个线程合并为一个线程,由交替执行任务变成了顺序执行任务(join方法)

线程礼让:只是在某个时间节点让一次,之后继续和其他线程争夺 CPU 资源。

Java18

多个线程同时访问一个资源的时候,可能会导致数据不准确。

本质原因:线程的任务是多步,CPU 分配的资源持续时间如果足够线程完成它的任务,就不会出错,如果不够,就会出错。

同步的关键在于判断多个线程锁定的是否为同一个资源,如果锁的是同一个就会同步,数据不会出现问题,如果锁定的是多个,数据就会出现问题

常量池:字符串常量池,包装类常量池

Java19

多线程下安全的单例模式:

public class SingletonDemo {private static SingletonDemo singletonDemo = null;private SingletonDemo(){System.out.println("创建了SingletonDemo对象");}public synchronized static SingletonDemo getInstance(){if(singletonDemo == null) {singletonDemo = new SingletonDemo();}return singletonDemo;}
}

一个线程在访问内存数据的时候,拿到的不是数据本身,而是将该数据复制保存到工作内存中,相当于取出来一个副本,对工作内存中的数据进行修改,修改完成之后再保存到主内存中,即主内存对线程不可见。

volatile 关键字的作用是使主内存中的数据对线程可见

在这里插入图片描述

百分百安全的单例模式:

public class SingletonDemo {private volatile static SingletonDemo singletonDemo = null;private SingletonDemo(){System.out.println("创建了SingletonDemo对象");}public static SingletonDemo getInstance(){if(singletonDemo == null){synchronized (SingletonDemo.class) {if(singletonDemo == null) {singletonDemo = new SingletonDemo();}}}return singletonDemo;}
}

synchronized 在解决线程安全问题的同时也会带来一个隐患,并且是比较严重的隐患,死锁。

Java20

并发编程,为什么公司很看重?

  • 并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到最大。
  • 提高效率,降低成本。

并发:多线程操作同一个资源,但不是同时操作,而是交替操作,单核 CPU 的情况下,CPU 按时间段分配给多个线程。

并行:并行是真正的多个线程同时执行,多核 CPU,每一个线程使用一个 CPU 资源。

高并发编程不是具体的技术,而是提高程序性能,充分发挥硬件设备性能的一种设计结构。

高并发的标准:

1、QPS:每秒响应的请求数。

2、吞吐量:单位时间内处理的请求数,由 QPS 和并发数决定。

3、平均响应时间:系统对每一个请求做出的平均响应时间。

QPS = 并发数 / 平均响应时间

4、并发用户数:系统同时承载正常使用系统功能的用户数量。

提高系统并发能力的两种方式

1、垂直扩展:提升单机处理能力

  • 增强单机的硬件性能,提升内存,硬盘扩展。
  • 提升单机软件的架构性能,使用 Cache 来提高效率,使用异步请求提升服务的吞吐量,使用 NoSQL 提升数据库的访问性能。

2、水平扩展

集群和分布式都是水平扩展的方案

  • 站点层扩展
  • 服务层扩展
  • 数据层扩展

Callable 和 Runnable 的区别:

  • Callable 可以在任务结束之后提供一个返回值,Runnable 没有这个功能。
  • Callable 的 call() 可以抛出异常,Runnable 的 run() 不能抛出异常。
  • Callable 需要结合 FutureTask 使用,Callable 是对 Runnable 的一种继承和扩展。

Java21

wait 的功能和 sleep 类似,都是让线程暂停执行任务,都是让线程进行休眠,但是作用的主体不同,sleep 直接作用于线程对象,wait 作用于资源对象。资源是无法休眠的,只能让正在访问该资源的线程对象进行休眠

sleep和wait的区别

1、sleep 和 wait 来自不同的类,sleep 是定义在 Thread 类中的,wait 是定义在 Object 类中的。

2、wait 释放锁,sleep 不释放锁

3、sleep 需要指定休眠时间,wait 可以不指定休眠时间,一直休眠

使用 wait 让线程休眠之后,唤醒线程有两种方式

1、指定 wait 休眠的时间,调用 wait(long millis),时间一到即自动解除阻塞,和 sleep 功能类似。

2、通过调用 notify 方法唤醒线程。调用 notify 方法的目标方法必须添加 synchronized 关键字才能调用,notify 必须获取锁才能调用,否则抛出异常。

如果 synchronized 修饰非静态方法,则锁定的是方法调用者

Java22

synchronized 只能锁定对象,不能锁定基本数据类型

每个类都有一个 class,不是属性,叫做类字面量,用来获取该类的运行时类的。该类的运行时类本身就是一个对象。(例如:String.class、Object.class、等等)

Lock 是对 synchronized 的升级,是一个接口,常用的实现类是 ReentrantLock

synchronized 是通过 JVM 实现上锁的,ReentrantLock 是通过 JDK 实现上锁的

ReentrantLock:重入锁,可以给同一个资源添加多把锁,解锁方式和 synchronized 有区别

synchronized 的锁是线程执行完毕之后自动释放,ReentrantLock 的锁必须手动释放

synchronized 和 Lock 的区别

1、synchronized 自动上锁,自动释放锁,Lock 手动上锁,手动解锁。

2、synchronized 无法判断是否获取到锁,Lock 可以判断是否拿到了锁。

3、synchronized 拿不到锁就会一直等待,Lock 可以不一直等待。

4、synchronized 是 Java 关键字,Lock 是接口。

ReentrantLock 具备限时性的特点:判断某个线程在一定时间内能否获取到锁,会返回 boolean 表示是否可以拿到锁

Java23

ConcurrentModificationException:并发修改异常。处理集合,多个线程同时并发操作一个集合的时候,会出现该异常

ArrayList 线程不安全,效率高

Vectory 线程安全,效率低

写时复制:当我们向容器添加元素的时候,不是直接对容器进行操作,而是先将容器进行复制,对新的容器进行操作,添加完成之后,再将原容器的引用指向新的容器。

通过进行读写分离来解决并发修改异常的问题,保证在读的时候,没有线程对该集合同时进行写操作

在这里插入图片描述

Java24

线程池:预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完之后再还回到线程池中,重复利用。

线程池的优势:

  • 提高线程的利用率
  • 提高响应速度
  • 便于统一管理线程对象
  • 可控制最大的并发数

7 个参数

  • corePoolSize:核心池的大小,初始化线程个数
  • maximumPoolSize:最大线程数,扩容时的最大上限
  • keepAliveTime:没有任务的线程最多保持多久
  • unit:keepAliveTime 时间单位,TimeUnit
  • workQueue:存储等待执行的任务
  • threadFactory:线程工厂,创建线程对象
  • handler:拒绝任务的策略

Java25

递归:去的过程叫 递,回来的过程叫 归

递推公式:

f(n) = f(n-1) + 1;
f(1) = 1;

由递推公式推导出递归代码:

public int f(int n){if(n == 1) return 1;return f(n-1) + 1;
}

递归需要 3 个条件:

  • 一个父问题可以拆分成若干个子问题,并且若干个子问题的结果汇总起来就是父问题的结果
  • 父问题和子问题,解题思路完全一致,只是数据规模不同
  • 存在终止条件

保存一系列对象的时候,使用数组存在以下问题

1、数组长度固定

2、数组的数据类型统一的,无法修改

为什么要使用集合?

集合可以简单理解为一个长度可变的,可以保存任意数据类型的动态数组

Java 中的集合不是一个类,而是由一组类和接口共同构成的一个框架体系。

接口描述
Collection最基础的接口
ListCollection 的子接口
SetCollection 的子接口
Map独立于 Collection 的另外一个接口,存储一组键值对
Iterator输出集合元素的,遍历集合
ListIteratorIterator 的子接口
Enumeration输出接口,已经被 Iterator 所取代
SortedSetSet 的子接口
SortedMapMap 的子接口
Queue队列接口
Map.EntryMap 的内部接口,描述 Map 中的一组键值对元素

Java26

数组的下标都是从 0 开始的,为什么?

从 0 开始,寻址公式可以减少一步运算,为了提升效率到极致

HashSet:存储一组(无序/有序)且唯一的元素

TreeSet:存储一组(无序/有序)且唯一的元素

无序/有序,看你从哪个角度来看:

无序是指元素的存储顺序和遍历顺序不一致

有序是指 TreeSet 内部会自动对元素按照升序进行排列

Java27

Map 中的数据以 key-value 的形式进行存储,key-value 首先会封装成一个 Map.Entry 对象,再把 Map.Entry 存入到 Map 中

在这里插入图片描述

Map 底层是数组 + 链表的形式

Map 兼具了数组和链表的特点,就算它形成了链表,在某种情况下查询速度也比一般的链表要快。

TreeMap 的特点就是集合内部会按照 key 升序对集合中的数据进行排序

HashMap 理想状态下时间复杂度 O(1)

面试题:说一说 HashMap 底层结构是什么?

JDK 7 和 JDK 8 完全不同

JDK 7 使用的是数组 + 链表

JDK 8及后面的版本 使用的是数组 + 链表 + 红黑树

在这里插入图片描述

HashMap 是 key-value 的形式进行存储,key + value 以 Entry 对象的形式进行存储的,存入数组

**存入数组的下标:通过 key 获取对应的 hash,再根据 hash 值与数组最大索引进行按位与运算(都转为二进制,依次对比,都为 1 返回 1,否则返回 0)得到的值就是存入数组的下标。**按位与运算,能够保证下标不越界

当数据量很大的时候,必然会出现下标冲突的问题,如何解决,一个位置需要存储两个以上的对象,就需要使用链表

为什么要使用红黑树?

HashMap 中存储的数据越来越多的时候,必然导致链表越来越长,查询越慢,为了解决这个问题,引入了红黑树。

Java28

红黑树其实就是一个平衡二叉树,可以提高查询效率

当链表长度大于 8 的时候,链表自动转为红黑树,提升查询的效率

平衡二叉树

无论数据是什么顺序的,都可以保证二叉树是平衡。

红黑树

1、结点是红色或黑色

2、根结点是黑色

3、每个叶子结点都是黑色的

4、每个红色结点的两个子结点都是黑色(从每个叶子结点到根结点的所有路径上不能有两个连续的红色结点)

5、从任一结点到其每个叶子结点的所有路径都包含相同数目的黑色结点

在这里插入图片描述

大家在学习源码的时候,一般从构造函数入手

HashMap 源码分析:

**HashMap 的无参构造,只做一件事情 loadFactor(加载因子) = 0.75。并没有创建数组。HashMap 的数组是按需创建的,只有在创建 HashMap 并且添加数据的情况下才会创建数组,否则不会创建数组。数组默认长度是 16 **

public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

当 HashMap 存放的数据大于等于 12 个(12/16=0.75)的时候,就进行数组扩容,扩大一倍,数组长度变为 32。下次当存放的数据达到数组长度的四份之三的时候(3/4=0.75),数组再进行扩容,扩大一倍。数组扩容长度最大为64。

HashMap的put方法的调用源码如下:

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}

获得存放的数组下标:执行如下方法得到的返回值后,再把该返回值与数组最大索引进行按位与运算,最终得到存放的数组下标

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(h = key.hashCode()) ^ (h >>> 16):key 的 hashCode 与自己的高 16 位进行按位异或运算h >>> 16:>>> 无符号右移,取出 h 的高 16 位什么是高16位?举例如下:
0000 0100 1011 0011 1101 1111 1110 0001>>> 160000 0000 0000 0000	0000 0100 1011 0011

putVal方法的源码如下:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

链表长度到达8的时候,会考虑转红黑树,如果此时数组还没有扩容到64的话,则优先进行数组的扩容。如果数组长度为64,则链表进行红黑树的转换

Java29

结合源码分析,HashMap 存值过程如下

1、根据 key 计算 hash 值。

2、在 put 的时候判断数组是否存在,如果不存在用 resize 方法创建默认长度为 16 的数组。

3、确定存入的 Node 对象的索引,根据 hash 值与数组最大索引进行按位与运算得到索引的值。

4、判断该位置是否有元素,如果没有直接创建一个 Node 存入。

5、如果有元素,判断 key 是否相同,如果相同,将原来的 Node 赋给一个变量并返回。

6、如果不相同,需要在原 Node 基础上添加新 Node,首先需要判断该位置是链表还是红黑树。

7、如果是红黑树,将 Node 存入红黑树。

8、如果是链表,就遍历链表,找到最后一位,将 Node 存入。

9、将 Node 存入链表最后一位之后,需要判断此时链表的长度是否超过 8,如果超过 8 ,同时数组容量达到 64 ,需要将链表转为红黑树。如果数组的容量小于 64,会再一次进行数组扩容,当数组容量达到 64 才会进行红黑树的转换。

10、判断数组容量占用是否已经达到了0.75(3/4),如果达到了就进行扩容。

为什么要使用内部类?

通过内部类的形式来隐藏类的相关细节和内部结构,封装性更好,让程序的结构更加合理。

内部类不会单独编译生成.class文件

类中代码执行的顺序:

1、加载类,先加载父类再加载子类,执行静态代码

2、创建对象之前先创建父类对象,执行非静态代码

Java30

泛型可以指代类中的成员变量数据类型,方法的返回值类型以及方法的参数数据类型。

泛型的上限和下限:

泛型上限表示实例化时的具体数据类型,可以是上限类型本身或者它的子类。

类名<泛型标识 extends 上限类名>

泛型下限表示实例化时的具体数据类型,可以时下限类型本身或者它的父类。

类名<泛型标识 super 下限类名>

示例代码:

public class Time<T> {public static void main(String[] args) {test(new Time<Number>());test(new Time<Integer>());test(new Time<Double>());test2(new Time<String>());test2(new Time<Object>());}public static void test(Time<? extends Number> time){}public static void test2(Time<? super String> time){}
}

枚举

package demo01;// 枚举类用enum修饰
public enum Week {// 相当于调用枚举类的有参构造器MONDAY(1,"周一","周一"),// 用逗号分隔TUESDAY(2,"周二","周二"),WEDNESDAY(3,"周三","周三"),TUHRSDAY(4,"周四","周四"),FRIDAY(5,"周五","周五"),SATURDAY(6,"周六","周六"),SUNDAY(7,"周天","周天");// 这个toString的方法,是重写Enum这个抽象类的toString方法,而不是Object的方法@Overridepublic String toString() {return id+"-"+value+"-"+name;}Week(Integer id, String value, String name) {this.id = id;this.value = value;this.name = name;}private Integer id;private String value;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}
}

String 的创建有两种方式:

  • 直接赋值,对象存储在字符串常量池中。(如果字符串经过拼接,那么会存储到堆中)
  • 构造函数,对象存储在堆内存中
package demo01;public class StringDemo01 {public static void main(String[] args) {String str1 = "Hello";String str2 = "Hello";System.out.println(str1 == str2);// trueString str3 = new String("Hello");System.out.println(str1 == str3);// falseString str4 = new String("Hello");System.out.println(str3 == str4);// false}
}

在这里插入图片描述

String 的底层:

  • 底层是用一个 char 数组来存储内容
  • equals 进行重写来确保业务逻辑为判断两个字符串的值是否相等,而不是内存地址

Null和Empty的区别:

  • Null是对象不存在
  • Empty是对象存在,值为空

Java31

String类常用的API:

  • 忽略大小写判断
  • 判断是否以某个值开头或者结尾
  • 截取字符串
  • 根据某个值分割字符串
  • 替换字符
  • 转为字符数组
  • 大小写转换

实际开发中使用 String 存在一个问题,String 对象一旦创建,值是无法修改的。无法修改指的是原地址的 String 对象无法修改,通过拼接之后的新字符串其实是一个重新创建的 String,和之前的 String 是两个完全不同的对象。

因为String底层是数组,数组一旦创建后是无法修改的,并且String类源码中的数组是被final修饰的。所以在字符串被频繁修改的情况下,会存在内存空间占用较大的问题。

空间浪费的问题如何解决呢?

StringBuffer 应运而生了,可以解决这个问题

StringBuffer 底层也是用一个数组来存储字符串的值,并且数组的默认长度为 16,即一个空的 StringBuffer 对象,数组长度为 16。

String 默认数组长度为 0,StringBuffer 默认数组长度为 16。

创建一个有值的 StringBuffer,数组默认长度为 值的长度+16。无论创建的 StringBuffer 是什么内容,都提供了 16 个位置的可修改空间。数组的长度和有效值的长度一定要注意区分开

如果修改的值长度超过了 16,则对数组进行扩容,保持引用不变,在原数组的基础上直接扩容,每次扩容都加16

StringBuffer的最大长度为64

StringBuffer 常用方法

方法描述
public StringBuffer()创建一个空的 StringBuffer 对象
public StringBuffer(String str)创建 str 的 StringBuffer 对象
public synchronized int length()返回 StringBuffer 的有效值的长度
public synchronized char charAt(int index)返回指定下标的字符
public synchronized StringBuffer append(String str)追加字符
public synchronized StringBuffer delete(int start,int end)删除指定区间内的字符
public synchronized StringBuffer replace(int start,int end,String str)替换指定区间内的字符
public synchronized String substring(int start)截取字符串,从指定下标开始截到结尾
public synchronized String substring(int start,int end)截取字符串,指定区间
public synchronized StringBuffer insert(int index,String str)向指定位置插入 str
public int indexOf(String str)找到指定字符的下标
public synchronized StringBuffer reverse()反序输出
public synchronized String toString()返回 StringBuffer 对应的 String 对象

日期类相关:

  • java.util.Date 和 java.util.Calendar
  • java.text.SimpleDateFormat 可以对 Date 进行格式化处理,转换为规定的格式

SimpleDateFormat 模板标记

标记描述
y
M
m分钟
d
H小时,24 小时制
h小时,12 小时制
s
S毫秒

例子:

import java.text.SimpleDateFormat;
import java.util.Date;public class Test {public static void main(String[] args) {Date date = new Date();// 直接创建date对象,输出date对象即为系统时间System.out.println(date);//2022-07-26SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");// 通过SimpleDateFormat的构造器规定格式String format1 = format.format(date);System.out.println(format1);}
}

Calendar类中的静态常量

常量描述
YEAR
MONTH
DAY_OF_MONTH天,以月为单位(月中的第几天)
DAY_OF_YEAR天,以年为单位(年中的第几天)
HOUR_OF_DAY小时
MINUTE分钟
SECOND
MILLISECOND毫秒

Calendar类中的方法

方法描述
getInstance()获取 Calendar 实例化对象
set(int field,int value)给静态常量赋值
get(int field)取出静态常量的值
Date getTime()把Calendar对象转为 Date 对象,然后可以用SimpleDateFormat对Date对象进行格式化处理

Java32

Input 流(输入流)

Output 流(输出流)

输入流:将文件以数据流的形式读取到 Java 程序中,上传

输出流:通过 Java 程序将数据流写入到文件中,下载

Java 是通过 File 类将电脑中的所有文件抽象成一个个对象

方法描述
public File(String path)通过绝对路径创建 File 对象
public String getName()获取文件名
public String getParent()获取文件所在的目录
public File getParentFile()获取文件所在目录的对象
public String getPath()获取文件的路径
public boolean exists()判断对象是否存在
public boolean isDirectory()判断对象是否为目录
public boolean isFile()判断对象是否为文件
public long length()获取文件的大小
public boolean createNewFile()根据当前对象创建新文件
public boolean delete()删除对象
public boolean mkdir()根据当前对象创建目录
public boolean renameTo(File file)为已存在的对象重命名

流:将文件在两个终端之间进行传递,文件本身通过流的形式进行传递

  • 按方向分,可以分为输入流和输出流
  • 按单位分,可以分为字节流和字符流

字节流又可以分为输入字节流和输出字节流

输入字节流 抽象类 InputStream,非抽象子类 FileInputStream

输出字节流 抽象类 OutputStream,非抽象子类 FileOutputStream

字符流又可以分为输入字符流和输出字符流

输入字符流 抽象类 Reader,非抽象子类 FileReader

输出字符流 抽象类 Writer,非抽象子类 FileWriter(FileWriter对象输出字符流要调用一下flush()方法)

UTF-8 编码格式下

Hello World:11个字节,英文、空格、符号都是一个字符占一个字节 1 byte

你好世界:12个字节,中文一个字符占三个字节 3 byte

Java33

文本类型的文件,推荐使用字符流来完成

除了文本类型之外的其他文件,推荐使用字节流来完成

缓冲流(在原有的IO流上加上一个缓冲,目的是加快速度):

  • 输入字节缓冲流 BufferedInputStream
  • 输出字节缓冲流 BufferedOutputStream
  • 输入字符缓冲流 BufferedReader
  • 输出字符缓冲流 BufferedWriter

序列化:将 Java 内存中的对象进行格式化处理存入到本地硬盘中

反序列化:从本地硬盘读取数据还原成 Java 对象

要实现序列化,实体类要实现Serializable这个接口

反射:动态获取类的信息,从而完成某些工作,动态创建对象等等

所谓的动态获取类的信息是指:在程序运行过程中获取类的信息

Java 是面向对象的编程语言,所有的一切都可以用对象来表示,描述类的信息也可以用对象来完成,专门创建一个对象来表示某个类的结构,这个对象的模板就是Class类。

Class类专门创建对象用于表示其它类的结构,每一个Class类的对象都对应某个类的结构

Class类是反射的基础,Class类是专门用来描述其它类的结构的类

Java34

反射的作用:在程序运行过程中动态获取类的结构,从而可以获取对应的构造函数,从而可以动态创建对应的对象。

Java35

暴力反射机制:强行访问私有属性,开启私有属性的访问权限即可!

反射的应用:动态代理

代理:Java 常用的设计模式

委托方 代理方

委托方原本要自己做的事情,现在交给代理方来代替委托方去完成

Java 中实现代理机制:委托方和代理方都具备完成需求的能力,Java 中如何表现?

委托方和代理方需要实现同一个接口

代理模式又可以分为动态代理和静态代理

静态代理:预先写好代理类的代码,如果要修改需要重新编译,灵活性较差

动态代理:不需要预先写好代理类的代码,程序运行期间动态生成,灵活性很好(不需要提前写好代理类的代码,程序运行期间动态生成,必须提前创建一个模板,由这个模板来动态生成代理类,规定动态代理类的执行逻辑。)

创建动态代理对象的方法:

Proxy.newProxyInstance(, , );

参数 1:类加载器 ClassLoader,将类加载到内存中的

参数 2:Class<?>[]

参数 3:InvocationHandler 实例对象

创造代理对象的,创造委托对象的代理对象,实现同一个接口

动态创建代理类,只需要知道委托类实现了哪些接口,就可以创建这个代理类了

需要知道委托类实现了哪些接口

package test;import com.Test;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class MyInvocationHandler implements InvocationHandler {//接收委托方private Object object;//返回代理对象public Object bind(Object object){this.object = object;return Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(),object.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("===开启代理模式===");Object invoke = method.invoke(object, args);return invoke;}
}
package test;import com.Apple;
import com.BMW;
import com.Car;
import com.Phone;public class Test {public static void main(String[] args) {MyInvocationHandler handler = new MyInvocationHandler();
//        Phone proxy1 = (Phone) handler.bind(new Apple());
//        System.out.println(proxy1.salePhone());Car proxy2 = (Car) handler.bind(new BMW());System.out.println(proxy2.test());System.out.println(proxy2.saleCar());}
}

Java36

Java 必须具备开发 Web 应用的能力,JDK java.net 进行 Web 开发的相关的 API

Socket这个类是基于TCP协议的

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket = null;Socket socket = null;serverSocket = new ServerSocket(8080);System.out.println("---服务端---");System.out.println("服务器已启动,等待接收客户端请求...");while (true){socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();DataInputStream dataInputStream = new DataInputStream(inputStream);String request = dataInputStream.readUTF();System.out.println("接收到了客户端请求:" + request);OutputStream outputStream = socket.getOutputStream();DataOutputStream dataOutputStream = new DataOutputStream(outputStream);String response = "Hello World";dataOutputStream.writeUTF(response);System.out.println("给客户端做出响应:" + response);}}
}
import java.io.*;
import java.net.Socket;public class Client {public static void main(String[] args) throws Exception {Socket socket = null;socket = new Socket("127.0.0.1", 8080);System.out.println("---客户端---");String request = "你好!";System.out.println("客户端说:" + request);OutputStream outputStream = socket.getOutputStream();DataOutputStream dataOutputStream = new DataOutputStream(outputStream);dataOutputStream.writeUTF(request);InputStream inputStream = socket.getInputStream();DataInputStream dataInputStream = new DataInputStream(inputStream);String response = dataInputStream.readUTF();System.out.println("接收到了服务器响应:" + response);}
}

这篇关于Java基础(查缺补漏)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/421703

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]