KYLO的 Java 基础知识点总结(其一)

什么是Java

Java是一门面向对象的编程语言,Java吸收了C++语言的各种优点,摒弃了复杂的多继承和指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好的实现了面向对象理论。除此之外,Java支持网络编程以及多线程,Java具有平台无关性,编译与解释并存。

JVM、JRE、JDK的关系

JVM

Java虚拟机(JVM)是运行字节码文件的虚拟机。JVM并不是只有一种,Java实现平台无关性就是靠JVM实现,平台无关性是指,同一份字节码文件,到不同的系统上有相同的效果。所以会针对不同的系统(Mac,Windows,Linux)实现不同的JVM,达到“一次编译,处处运行”。

(我们平时常用的JVM是 HotSpot VM)

JRE

JRE(Java Runtime Environment) Java运行时环境,如果你只需要运行Java程序JRE就够了,JRE是运行已编译Java程序所需要的所有内容的集合,包括 JVM,Java类库、Java命令、和其他基础构件。但是JRE不能创建新程序。

JDK

JDK(Java Development kit) Java开发语言工具包,是一款功能齐全的 Java SDK(语言工具包),他包含JRE所拥有的一切,除此之外,他还包括, 编译器(Javac),和工具(Javadoc和jdb)等。他能用于创建程序。

javadoc是Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档。

jdb(Java调试工具)

Java语言有哪些特点

  1. 简单易学(Java语法和C++语言很接近,但是去除了C++中多继承和指针)
  2. 面向对象(封装、继承、多态)
  3. 平台无关性
  4. 支持网络编程而且很方便
  5. 支持多线程
  6. 编译与解释并存
  7. 健壮性(Java语言是强类型语言,支持异常处理,以及垃圾的自动回收)
  8. 安全性

什么是字节码?采用字节码的好处是什么

字节码是Java程序经过编译以后得到的,即.class文件,他运行在JVM上,不面向任何系统,只面向JVM,又由于平台无关性,所以可以做到一次编译,处处运行

语言类型分为编译型语言(C++、GO、Rust)和解释型语言(Python、PHP、JavaScript),Java从编译到运行的过程是,先用Javac进行编译生成字节码,字节码经过JVM解释,生成机器码,机器码即为机器可以理解的代码,就可以运行了。Java以半编译,半解释的方法解决了传统解释型语言的执行速度较慢的问题,这就是字节码的好处。

除此之外Java中还有JIT(just-in-time compilation)根据28原则,消耗大部分性能的只有一小部分代码,所以JIT编译器会将热点(所以JVM叫HotSpot VM 哈哈:)代码编译成机器码并保存起来。

编译器和解释器的区别

为什么Java又编译又解释,却比传统解释型语言性能好呢。传统解释型语言是一行一行的将程序解析成机器码,这一步是很耗性能的。编译是直接吧所有程序一次性解析成机器码。而Java先将程序编译成字节码,这里字节码你可以理解成一种半机器码,JVM进行解释的时候,比直接解释程序要快的多。所以Java比传统解释型语言性能好。

*什么是Java程序的主类?应用程序和小程序的主类有何不同?

一个程序中可以有多个类,但是只能有一个主类。在Java应用程序中,这个主类是指包含main()方法的类。在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类,应用程序的主类不一定要求是public类,但是小程序的主类要求必须是public类。主类是Java程序执行的入口点。

Java应用程序从主线程启动(也就是main方法),applet小程序没有main方法,主要是嵌入在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。

Java和C++的区别

  • Java不提供指针来访问内存,程序内存更加安全。
  • Java的类是单继承的,C++支持多继承,但是Java的接口可以多继承
  • Java有垃圾回收机制,不需要手动释放垃圾(嘎嘎方便:)
  • Java是编译与解释共存,C++是编译型语言

基础语法(简单的直接过)

Java有哪些数据类型

Java是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

基础数据类型:

  • 数值型:(整型)byte、short、int、long (实型)float、double
  • 布尔型:   boolean
  • 字符型:char

引用数据类型:

  • 接口
  • 数组

Java基本数据类型图

类型关键字类型名称占用内存取值范围默认值
整型byte字节型        1字节-128(-2^7)~127(2^7-1)0
整型short短整型2字节-2^15~2^15-10
整型int整型4字节-2^31~2^31-10
整型long长整型8字节-2^63~2^63-10L
实型float单精度浮点型4字节-3.4*10^38~3.4*10^380.0F
实型double双精度浮点型8字节-1.79*10^308~-1.79*10^3080.0D
字符型char字符型2字节0~65535'\u0000'
布尔型boolean布尔型未明确false 、 truefalse

boolean类型的占用内存,在java规范中并没有指出,但是在JVM规范中指出,boolean单独使用时,编译之后做int处理,即4字节,当以boolean[] 数组使用时,作为byte[]数组处理,即1字节。

基本类型和包装类型的区别

  • 包装类型不赋值就是null,基本类型不赋值不是null
  • 包装类型可以用于泛型,基本类型不行
  • 基本类型比包装类型占用的空间少
  • 存储的区域不同,基本类型的全局变量存放在堆中(因为全局变量属于某一个对象实例,实例在堆中,所以该变量在堆中),基本类型的局部变量存放在栈中的局部变量表中。包装类型的变量几乎都存在于堆中

为什么说几乎都存在于堆中呢?

JVM中引入了JIT之后,会对对象进行逃逸分析,如果该变量没有逃逸到方法外部,那会就会通过标量替换来实现栈上分配内存

包装类型的缓存机制了解吗?

缓存机制即常量池,8中基本数据类型的包装类型,Integer,Byte,Short,Long,这四种包装类在常量池中存储了[-128~127]的对象。Boolean保存了True,False两个对象,Character保存了[0~127]的对象。Float和Double没有(小数嘛,无穷无尽 qwq)

缓存机制的出现是因为有些基础数据类型会被频繁使用,每次使用都新创建是很消耗性能的,所以将常用的数据放到常量池中,这种思想在String常量池中也有体现。

Integer i1=new Integer(40);

Integer i2=40;

但凡有new XXX() 格式出现,都一定会在堆中初始化一个对象,但是下面这种格式,就会先去常量池中找,如果没有在到堆中创建对象

说到底,这种机制就是性能和资源之间的权衡,(亘古不变的时间空间问题:)

**所有的包装类型都用equals方法进行比较

自动装箱和拆箱了解吗?

自动装箱就是将 基础数据类型转换成包装数据类型,本质上就是 Integer.valueOf()等 方法的使用

自动拆箱就是将包装数据类型转换成基础数据类型,本质上就是  xx.intValue()等方法的使用

switch是否可以作用在byte上,是否能作用在long上,是否能作用在String上?

Java5以前,byte,short,int,char都可以作为switch中,从java5开始switch中引入了枚举类型,枚举类型也可以作用在switch中,java7开始,String也可以作用在switch上,但是long从始至终都不能作用在switch上。(因为switch底层是使用int来做判断的);

float f=3.4是否正确

结论:不正确,3.4是Double类型的,3.4F才是float类型的,用float f=3.4,会导致向下转型,会导致精度丢失,建议使用

  • float f = (float) 3.4;
  • float f = 3.4F;

short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1+=1; 有错吗?

结论:前者有错,后者没有,1默认是int类型数据,所以这里s1+1向下转型成为int类型,所以需要将s1+1转换成short类型即

s1 = (short)(s1 + 1);

s1 +=1;没有错,因为s1+=1;底层代码其实就是 s1 = (short) (s1+1)

为什么浮点数运算的时候会有精度丢失的风险?

根据计算机系统基础上的知识,我们知道浮点型的数据和整性的数据的表示方式不一样,整性直接就是带符号位的二进制表示。而浮点数使用的是一种 符号位(1位)指数(8/11位)尾数(23/52位)三种数组合而成

具体是 (-1)^符号位*尾数*2^指数

尾数的位数可以代表浮点数的精度,尾数越长,浮点数的精度越大

指数越长,浮点数可以表示的范围越大。尾数的长度始终是有限的,只用23/52位表示一个数字,可能会发生舍入,这就导致尾数不能表示全部的小数,当出现那种情况时,就会出现精度丢失。

浮点数_计算机系统基础

如何解决浮点数运算的精度丢失问题

BigDecimal可以实现对浮点数的运算,且不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过BigDecimal来做的。

BigDecimal使用方式

超过long整性的数据应该如何表示

使用BigInteger来表示,BigInteger底层使用int[]数组来存储任意大小的整性数据,但是相对于常规的整型运算来说,BigInteger运算效率会相对较低。

访问修饰符public、private、protected、默认的区别

修饰符当前类当前包子类其他包
privateTFFF
defaultTTFF
protectedTTTF
publicTTTT

这四个修饰符都可以被用于修饰 类,接口,方法,变量

**注意 private和protected不能被用于修饰外部类

private修饰的外部类是私有的,这个类不能用于创建实例,那这个类又有什么用呢?

protected,我们知道protected修饰的类A可以被同包下,或者非同包下但是是其子类的一个类访问,那么当B类不属于其包下,如果B类只有继承了A类才可以访问A类,但是想要继承A类前提你得可以访问A类,这是冲突的 (玄妙无比:)。

运算符

& 与 &&的区别

&有两种用法

  1. 按位与
  2. 逻辑与(短路与)

举个例子 (1!=1)&/&&(2==2) &&的情况下,前表达式为false的时候,后表达式直接不进行判断,&的情况下,无论前表达式的结果为何,都要进行后面表达式的运算。

关键字

final有什么用

用于修饰类、属性和方法

  • 修饰类:该类不可以被继承
  • 修饰方法:该方法不可以被重写
  • 修饰变量:该变量的值不可以被改变,但是当该变量是引用数据类型时,不可以改变的该变量的引用,你可以改变该变量的内容

final、finally、finalize的区别

  • final用于修饰类属性或者方法
  • finally,在进行异常处理时使用,配合try-catch使用,当有部分代码是无论发没发生异常都要处理时,就可以将该代码写在finally块中。例如关闭流。
  • finalize,Object类中的方法,Object类是所有类的父类,所以finalize方法所有的类都有。这个方法是启动垃圾收集时由垃圾收集器调用的方法。调用System.gc()方法时由垃圾收集器调用,是对一个对象是否可以被回收的最后判断


this关键字的用法

this表示当前类,一般用于构造方法,传入参数和属性名一致时用于区分属性和入参。

类方法中引用本类构造器,也可以不写this

qwq没啥用

super关键字的用法

super可以理解成指向父类的指针,这个父类是离当前类最近的父类。

用法:

  • 普通的直接引用,表示父类对象
  • 子类中的成员变量或方法与父类中的成员变量同名时,用super进行区分。
  • 引用父类构造器

static存在的主要意义

static存在的意义是用于创建独立于具体对象的域变量或者方法,以致于即使没有创建对象,也能使用属性和调用方法。

static关键字还有一个关键点是可以形成静态代码块以优化程序性能。static块中可以置于类中的任何地方,类中可以有多个static块,这些static块会在类加载的时候按顺序来执行并且只会执行一次。因此很多初始化操作都在static代码块中进行。

static应用场景

  1. 修饰方法
  2. 修饰成员变量
  3. 修饰内部类
  4. 修饰代码块
  5. 静态导包(该类中可以直接使用导入类的数据,不用用类名访问)

流程控制语句

在Java中,如何跳出当前的多重嵌套循环

在循环语句前定义一个标号 比如 ok:

退出 使用 break ok;即可


面向对象基础

面向对象和面向过程的区别

两种的主要区别在于解决问题的方式不同

  • 面向过程以过程为核心,将要解决的问题拆分成一个一个方法,按顺序执行方法,强调问题的流程、顺序
  • 面向对象以对象为核心,以解决问题的的角色出发,强调问题的主体,角色,以对象执行的方式解决问题。

面向过程的优点是:根据事情的目的分解出过程,一步一步的解决,对于不复杂的事情解决效率高。

面向对象的优点是:具有更高的复用性和拓展性。缺点:解决简单单一功能时,面向对象的设计过于繁琐

面向过程比面向对象的性能高?

面向过程性能比面向对象高是因为,面向对象,需要进行实例化操作,开销比较大,比较消耗资源,所以当性能时最重要的考量因素的时候,比如单片机、嵌入式开发、Linux\Unix 等一般就采用面向过程开发

创建一个对象用什么运算符?对象实体和对象引用有何不同

new运算符,new创建一个对象(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。

对象引用可以理解成一个对象的一个标记,这个对象可以有多个标记,每个标记都可以找到,使用这个对象。当然这个标记也可以不标记对象。

面向对象三大特征

封装

封装是指把一个对象的属性隐藏在对象内部,由对象来向外提供访问这些属性的方法。对象里的变量,方法,构造器,都可以用访问修饰符修饰,来控制外界访问的权限。比如用private修饰全局变量,然后提供get,set方法。如果禁止外界修改,就不申明set方法。所以封装提高了代码的可维护性和安全性

*有一说是封装可以提高代码复用性,我觉得是因为封装是继承的基础,继承提高了复用性,所以说封装提高了复用性.

继承

继承是指这样一种能力,他可以使用现有类的所有功能,并在无需重新编写原来类的代码的基础上进行功能的拓展.

通过继承创建的类叫做子类或者派生类,被继承的类叫做超类或者父类,继承的过程其实就是一般到特殊的过程.

继承可以提高代码的复用性,维护性,简化代码书写,提高开发效率.

**

  1. 子类拥有父类所有的属性,即使是private访问修饰符修饰的属性,虽然无法访问,但是拥有
  2. 子类可以在父类的基础上进行属性的拓展,也就是添加属性或者方法
  3. 子类可以对父类的属性进行修改,即重写方法等.

多态

多态按字面意思,表示一个对象具有多种状态,多态是指同一个接口,使用不同的实例而执行不同的操作,具体表现为父类引用指向子类的实例.

实现多态得满足三个条件 1继承 2重写 3父类引用子类对象

多态的特点

  • 要使用多态得满足,一个类被继承了,或者一个接口被实现了.
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定
  • 如果子类重写了父类的方法,真正执行的是子类重写的方法,如果没有重写,则执行父类的方法.

多态的优点

提高了代码的可维护性(继承实现),提高了代码的可拓展性(多态实现),你可以理解成,父类向外提供一个接口,访问这个接口的可以是继承了这个父类的不同子类,不同的子类可以针对父类方法实现不同的效果.

面向对象五大基本原则

  • 单一职责原则SRP: 类的功能要单一。
  • 开放封闭原则OCP: 即开闭原则,即只提供一个模块的拓展功能而不提供修改功能。
  • 里氏替换原则LSP: 子类可以替换父类出现在父类出现的所有地方。
  • 依赖倒置原则DIP:高层次的模块不应该依赖低层次的模块,他们应该都依赖于抽象,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
  • 接口分离原则ISP: 设计时一个接口的只支持一种功能。

深拷贝和浅拷贝的区别了解吗?什么是引用拷贝?

引用拷贝其实就是两个对象引用指向同一个对象,就叫引用拷贝

我们知道对象内部是可以包含成员变量的,而这些成员变量可以是实例对象。

浅拷贝就是只拷贝表层的对象,不拷贝内部的对象,内部的对象指向的依然是原来的对象。

深拷贝就是既拷贝表层的对象,也拷贝内部的对象。

类与接口

抽象类和接口的对比

抽象类是用来捕捉子类的通用特性的,接口是抽象方法的集合。

从设计层面来说,抽象类是对类的抽象,是一种模板设计模式,接口是行为的抽象,是一种行为的规范。

相同点

  • 抽象类和接口都不可以实例化
  • 都位于继承的顶端,用于被其他类实现或继承
  • 都包含抽象方法,其子类都必须覆盖重写这些抽象方法

不同点

抽象类接口
声明abstract classinterface
实现子类使用extends关键字来继承抽象类,如果子类不是抽象类的话,需要实现抽象类的所有抽象方法子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类中可以有构造器接口不能有构造器
访问修饰符抽象类中的方法可以是任意访问修饰符接口方法默认访问修饰符是public。并且不允许定义为private或者protected
多继承一个类只能继承一个抽象类一个类可以实现多个接口
字段声明抽象类的字段申明可以是任意的接口的字段默认都是static和final的

java8中引入了默认方法和静态方法,一次来减少抽象类和接口之间的差异。

现在我们可以为接口提供默认实现的方法了,并且不用强制子类来实现他,接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则。

  • 行为模式应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
  • 选择抽象类的时候通常是一下情况:需要定义子类行为,又要为子类提供通用的功能。

普通类和抽象类有哪些区别

  • 抽象类不可以实例化,普通类可以实例化
  • 抽象类用abstract class创建,普通类用 class
  • 抽象类中可以有抽象方法,普通类不能有

抽象类可以用final修饰吗

结论:不可以,抽象类设计出来就是一种要被使用的模板,而被final修饰的类是不能被继承的,不能被使用的模板是没用意义的。

变量与方法

成员变量与局部变量有哪些区别

变量:在程序执行过程中,在某个范围内其值可以改变的量。从本质上来说,变量其实是内存中的一小块区域,成员变量:方法外部,类内部定义的变量。局部变量:类的方法和非静态代码块中的变量。

成员变量和局部变量的区别:

成员变量局部变量
作用域针对整个类有效只在方法体内部有效
存储位置无论是基本数据类型还是引用数据类型,都随着实例本身创建而创建一起存储在堆内存中。静态成员变量目前也在堆内存中。局部变量随着方法被调用出现,基本数据类型的变量引用和变量都存储在栈中,引用数据类型的变量引用和地址值存储在栈中,引用对象实体存储在堆中,如果出现对象逃逸,则用标量替换存储在栈中。
生命周期随着实例创建时创建,随着实例释放而释放方法被调用入栈时创建,方法出栈时释放
初始值基本数据类型会被赋值为默认初始值,引用数据类型赋值为null没有初始值

在Java中定义一个不做任何事且没有参数的构造方法的作用

即无参构造,每个类被声明时都会默认有个无参构造(当你重载第一个构造时,该无参构造就失效了)该无参构造提供类的初始化功能,你可以通过该构造器创建一个该类的实例。

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

  • 静态方法提供不需要创建实例的访问,即可以用类名.方法名的方式调用,而实例方法必须要创建出实例才能调用。
  • 静态方法只可以使用类的静态成员,实例方法则都可以使用。

什么是方法的返回值?返回值的作用是什么

方法的返回值,是方法执行得到的结果,前提是该方法可以产生结果,作用是用该方法产生的结果进行下一步操作。

内部类

什么是内部类

内部类,即定义在一个类内部的类,就叫做内部类,内部类本身就是一个类的属性。

内部类的分类有哪些

内部类有四种,静态内部类,匿名内部类,成员内部类和局部内部类。

静态内部类

static 关键字修饰的内部类,只可以使用类的静态成员,而不可以使用类的非静态成员。

创建方法为 new 外部类.内部类

成员内部类

定义在类内部,成员位置上的非静态类就是成员内部类,成员内部类可以访问外部类的所有变量和方法,包括静态和非静态,私有和共有。成员内部类依赖于外部的实例。

创建方法为 外部实例.new 内部类

局部内部类

定义在方法或者代码块中,定义在实例方法中的局部内部类可以访问外部类的所有成员,定义在静态方法中的所有局部内部类只能访问外部类的静态成员。

该内部类只可以在对应方法体内创建。

匿名内部类

最常用的内部类,可以声明在类的方法以及方法体中,叫匿名内部类是因为该类实际上不存在,一般是实现形式是,创建一个实现了抽象方法的接口/抽象类的实现类/子类。没用给这个类起名,所以叫匿名内部类。

匿名内部类的特点

  • 匿名内部类必须继承一个抽象类或者一个接口
  • 匿名内部类不能定义任何静态成员和静态方法(因为该类没用类名)
  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为final
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现接口的所有抽象方法

内部类的优点

  • 一个内部类的实例,可以访问创建他的外部类实例的所有成员,包括私有数据(这一点是不是很像一个继承了父类的子类能干的事)
  • 内部类不为同一包的其他类所见,具有很好的封装性
  • 内部类有效的实现了“多继承”(依靠第一点),优化了Java单继承的缺陷
  • 匿名内部类可以很方便的定义回调

*回调是一种调用方式,其核心是回调方将本身传给调用方。调用方完成操作后,会调用回调方的方法。关于回调

局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final

这个问题非常有意思。反编译局部内部类和匿名内部类,得到的类文件的构造器是有参构造器,其中的参数就是包含着内部类的方法的参数,和外部类的实例本身。

 这里为什么必须要要final修饰变量(这里的变量是指外部类方法中的变量)就很好理解了,我们创建局部内部类的对象和匿名内部类的对象时,会给内部类实例的成员变量赋值(这里反编译没有显示成员变量不知道为啥,但实际上是有的),我们创建了内部类实例之后,将外部变量的数值改变,在调用内部类的方法,此时内部类成员变量指向的还是原来的外部变量的值。就形成了冲突,所以要将变量设置为final。阻止这种冲突的产生(也就是保护数据一致性)。(实际上也无法进行外部变量的重新赋值,java会报编译时异常);

 为什么要加final

重写与重载

构造方法有哪些特点?是否可以被override?

构造方法的特点:

  • 没有返回值,但是也不用写void
  • 名字和类名相同
  • 生成类时自动执行,无需调用

override,即重写,我们知道重写,发生在实现接口,或者是继承一个类的时候,重写一个父类或者接口的方法。当你满足继承一个类,或者是实现一个接口的时候,你本身就是一个类,而每个类的构造方法都必须与类名相同,所以重写父类构造器本身就是矛盾的。但是构造器可以overload,即重载。

重载和重写的区别。重载的方法能否根据返回类型进行区分

方法的重载和重写都是多态的一种体现,(我觉得重载是多态有待商榷),区别在于前者实现的是编译时的多态,而后者实现的时运行时多态。

重载:发生在同一个类内,方法名相同参数列表不同(参数类型,个数,顺序),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

重写:发生在父类子类之间,方法名,参数列表相同,子类重新实现父类该方法的逻辑。其中返回值可以小于等于父类返回值,抛出异常小于父类,访问修饰符大于等于父类(这是因为有多态存在,父类引用子类对象,如果子类方法修饰符小于父类方法,可能出现没法调用的情况)。

Java常见类

Object

所有类的祖先类,所有的类都继承自Object类,该类实现了以下方法

  • toString()    //用于输出类的数据,如果没有重写该方法,输出的是该对象的地址
  • equals()   //判断数据的地址是否相同,可以重写支持数据的判断
  • hashcode()   //用于获取对象的哈希值
  • clone()   //用于对象的克隆操作
  • getClass()   //用于获取类的类对象
  • wait()  //用于当前线程的暂停操作有多种重载
  • notify()  //用于已暂停线程的重启
  • notifyAll()   //唤醒所有暂停的线程
  • finalize()   //垃圾回收器调用,进行对象时候可以被回收的判断

其中,被native修饰的方法,是提供的原生方法,不可以被重写。

==和equals的区别

equals方法是Object类中的方法,又因为Object类是所有类的父类,所以所有的类都实现了equals方法,==是Java提供的一种运算符,用于判断两个变量是否相等,原生Object类实现的equals方法实际上就是使用了==进行比较,如果不对equals方法进行重写那么equals方法就是==。

Java中有基本数据类型和引用数据类型,基本数据类型直接使用==就可以比较,但是引用数据类型用==比较,实际上比的是数据的地址,这样即使是两个数值相同的数据,也不会得到相等这个结果,所以要重写equals方法进行引用数据类型的比较。

一般进行引用数据类型比较时,推荐用equals

hashCode有什么用

hashCode,即哈希值,也称散列码,通过不同的哈希算法,每个对象都可以有他对应的hash值,哈希值的作用是确定该对象再哈希表中的索引位置,一般实在哈希表中,做判重的作用,每个对象都有一个hashCode,虽然两个不同的对象可能生成一样的hashCode(概率非常小,即使出现了,也可以使用equals方法),但是如果hashCode不相等那么这两个对象一定不相等,这种情况就可以不使用equals方法,

散列表存储的是键值对,它的特点是能根据键快速的检索出对应的值。这其中就利用了hashCode,可以快速找到所需要的对象。

String、StringBuffer、StringBuilder的区别

String,即字符串,引用数据类型,是一种不可变的数据类型,说他不可变有以下几个原因

  • 首先,String底层是一个char[]实现的,这个char[] 的修饰符是final ,我们知道被final修饰符修饰的变量不可以被改变。
  • 其次,被final修饰并不是String不可变的根本原因,char[] 是一个引用数据类型,被final修饰不可变的是指向char[] 对象的指针,我们可以对char[]内部的值进行改变,但是String类的封装中,没用提供给我们改变char[] 的方法,这是String不可变的根本原因

*JDK9中String和StringBuffer和StringBuilder底层的char[] 变成了byte[]因为JDK9提供的Latin-1字符集包含绝大多数字符,其中的字符用一字节就可以表示。使用空间小于char[]

StringBuffer、StringBuilder

String是不可以改变的,我们需要可以进行修改的字符串类型,所以Java提供了以上两个类,给我们进行字符串的修改,这两个类都继承自AbstractStringBuilder类,都拥有修改字符串的基本方法,如append()等。

而StringBuilder和StringBuffer的区别就是,StringBuffer是线程安全的类,StringBuilder线程不安全,但是其性能要好于StringBuffer.至于StringBuffer为什么是性能安全的,是因为,StringBuffer的方法使用了synchronized进行修饰。

我们再进行代码编写时,发现是可以对String进行修改的,不是说String不可以被修改吗?为什么可以能,其实是java底层在你修改String对象的时候,自动创建了StringBuffer对象,然后进行修改,而且每次修改都会创建StringBuffer对象,这样是非常影响性能的,所以不推荐直接用String进行字符串的修改操作。

在使用HashMap的时候,用String做key有什么好处

HashMap的存储是基于计算key的HashCode值的,String对象在实例化后,会将自己的hashcode存在实例的hash属性上,HashMap存储时,不必再计算,相比其他对象更快。

字符串常量池

和包装类型的常量池一样,字符串常量池都是Java提供用于提高性能的工具,将使用的字符串存到常量池中,以便于再次使用。

String s1 = new String("abc");创建了几个对象?

我们知道 但凡出现了new 就一定会在堆上创建一个新的对象(此处排除对象逃逸)。

如果此时字符串常量池中没用该字符串对象,就会再常量池中创建一个对象,所以答案是一个或两个。

intern()方法,使用该方法,如果常量池中有该字符串对象,直接返回常量池中该对象的引用,如果没有,则创建一个然后返回该对象的引用。(这里我有点疑惑,创建字符串的两种方法,都会直接在常量池中创建对象,那怎么会出现常量池中没用该字符串的情况呢)

String str = "str"+"ing"; 本质就是 String str = "string"; 因为经过编译之后,编译之后的代码会将其合并(这个操作叫做常量折叠),所以这种情况也只会创建一个对象。

常量折叠,并非所有对象都可以。只有八种基本数据类型和String,以及final修饰的基本数据类型和字符串变量,基本数据类型之间的加减乘除以及移位操作

值传递

当一个对象被当作参数传递到一个方法后,次方法可改变这个对象的属性,并可以返回变化后的结果,那么这里是值传递还是引用传递

首先,Java只支持值传递,当一个参数是一个对象实例时,参数的值就是该对象的引用。对象的属性可以被调用过程中改变,但对对象引用的改变是不会影响到调用者的。

为什么Java中只有值传递

值传递:表示方法接收的是调用者提供的值

引用传递:表示方法接收的是调用者提供的变量的地址,当你提供了地址时,方法就可以改变这个地址上的数据。

Java所提供的就是值传递。方法的参数中,无非两种类型,基本数据类型和引用数据类型。

情况1、实例中有两个基本类型的实例变量,num1=1,num2=2,调用方法 add(int a,int b);

此时传入方法体内a,b的是num1、num2的拷贝,我们进行swap(a,b) 并不会改变实例变量的数值。

情况2、实例中有一个数组类型的实例变量nums,我们调用方法add(int[] a);此时  a是nums引用对象的拷贝,我们知道此时实例中的nums也不是数组本身,数组再堆中,是一个对象,而nums只是一个指向数组的一个引用。我们调用 ,swap(a[0],a[1]);会发现,数组的值改变了,难道这是引用传递吗?并不是,此时a(引用对象),依然指向的是数组对象,这个值是没有改变的。

情况3、实例中有两个Object类型的实例变量,o1,o2,调用方法add(Object ob1,Object ob2);

此时o1,o2,同理也并不是对象本身,而是两个对象的引用,ob1,ob2是o1和o2引用的拷贝,我们调用swap(ob1,ob2)会发现,o1和o2指向的对象并没用改变。

这三种情况得到值传递和引用传递的本质区别,

值传递,传入的参数是值的拷贝,当是基本数据类型时,我们可以改变参数,但是不可以改变值本身,当是引用数据类型时,我们可以改变参数所指向的对象,也可以改变参数所指向的对象的数据,但是我们不能改变引用对象和对象的映射关系。

引用传递,我们可以改变值,同时也可以改变引用数据类型指向的实例对象,这一点是值传递做不到的

值传递和引用传递有什么区别

值传递:值的是在方法调用时,传递的时按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。

引用拷贝:指的是在方法调用时,传递的参数是按引用进行传递,器传递的引用地址,也就是变量所对应的内存空间的地址,传递的是值的引用,也就是说,传递前和传递后都指向同一个引用。

Java包

JDK中常用的包有哪些

  • java.lang :系统基础类
  • java.io :有关输入输入的包,文件操作等
  • java.nio :完善io包功能,提高io性能
  • java.util :java的系统辅助类,特别是集合类
  • java.net :网络相关的类
  • java.sql :数据库操作相关的类

import java和javax有什么区别

刚开始的时候,JavaAPI所必须的包是java开头的包,javax当时只是拓展API包来使用,然而随着时间的推移,javax逐渐拓展为JavaAPI的组成部分,但是将javax包移动到java包中,会破坏源有代码,所以就将javax作为javaAPI的一部分了。

IO流

java中IO流分为几种

按照流的流向分为两类,输入流和输出流。按照操作单元划分,分为字符流和字节流。按照流的角色划分为节点流和处理流。Java io共涉及40多个类,彼此之间关系紧密,基本都是一下四个类派生出来的。

  • InputStream:字节输入流的基类
  • Reader:字符输入流的基类
  • OutputStream:字节输出流的基类
  • Writer:字符输出流的基类。

BIO、NIO、AIO有什么区别

前置:同步异步/阻塞非阻塞

同步:一个请求发送,等待一个响应,这是同步过程。

异步:一个请求发送,返回一个空响应,或者不返回响应,等待被调用方进行回调或者触发事件,进行后续操作。

阻塞:阻塞即等待,指在获取数据时,线程会等待数据处理,期间不进行其他操作

非阻塞:对应阻塞,在获取数据时,不等待,去进行其他操作,期间轮询查看数据是否加载完成,完成则进行后续操作

IO操作:一个IO操作其实分成两个操作,1、发起IO请求,2、实际的IO操作。

同步IO和异步IO的区别在于,第二步是否阻塞,所以异步IO不存在阻塞,即阻塞的IO都是同步IO

阻塞IO和非阻塞IO的区别是第一步发起IO请求是否阻塞,如果阻塞直到完成IO就是传统的阻塞式IO

异步不存在阻塞,同步阻塞操作时,发送一个请求,发起请求的人等待响应,这是一个同步过程,假设等待很久,那么此时这个发起请求的人就被阻塞了。

同步非阻塞操作时,发送一个请求,发起请求的人等待响应,期间他去干其他的事,是不是看一眼响应来了吗,来了就去干接下来的事。

异步操作,异步即发送一个请求,不等待响应,或者没有响应。发完请求后我们就去干其他事(这个过程是不是就没用等待,是不是就不存在阻塞)。接受请求方处理完毕,进行回调,或者触发事件,然后请求方收到,进行接下来的事。

简答:

  • BIO 同步阻塞式IO:就是我们平常使用的传统IO,他的特点是模式简单使用方便,并发处理能力低。
  • NIO 同步非阻塞式IO:是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用
  • AIO 异步非阻塞式IO:是NIO的升级,异步IO的操作基于事件和回调机制。

详细回答:

  • BIO(Blocking I/O):同步阻塞式IO,就是我们传统使用的IO,数据的IO必须阻塞在一个线程内等待其完成。在活动连接数不是特别高的情况下,这种模型是比较不错的,可以让每一个线程专注于自己的IO,并且编程模型简单,也不用考虑系统的过载、限流等问题。线程池,本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或者请求。但是面对十万甚至百万连接的时候,BIO就无法处理了(为什么没法处理了,BIO模式,一次请求就会创建一个线程(没用线程池的情况),不管这个请求有没有后续了,这个线程都只会为这个请求服务,所以,当请求数量上来以后,线程就会不限制的上升,直到系统无法处理。)                                        例:比如一家饭店,每次一个客户来了,都会喊一个服务员为他们服务,服务员可以理解成线程,客户可以理解成请求,直到客户吃完饭了,服务员才可以为下一个客户服务(这种情况是线程池的情况,如果没有线程池,就是一个客户来了就招一个服务员,更蠢:),当客户非常多的时候,假设一共十个服务员,来了50个客户,此时这个饭店就无法处理了,还有可能出现,客户只是来了,但是一直不点菜(抽象成,一个连接对应一个线程,然后这个线程中一直没有IO请求)。                                                                                                                           这个模式下,被阻塞的是IO线程
  • NIO(Non-Blocking I/O): 同步非阻塞式IO,是Java针对传统IO的升级,在Java.nio包中,提供了,Selector(多路复用器),Channel(通道),Buffer(缓冲区)等抽象,NIO是面向缓冲的,基于通道的IO操作方法。可以更好的解决高并发,高负载的情况。但是编程模型比较复杂。具体的处理模式是,由一个多路复用器,注册多条通道,每个连接对应一条通道,通道既支持读也支持写,当一个IO请求到达时,多路复用器轮询通道,发现一个通道中有IO请求,多路复用器创建一个线程处理这个请求,这种情况就有别于BIO,不是一个连接一个线程,而是正真出现IO时才创建一个线程。                                                                                                                    例:还是那家饭店,除了十个服务员(线程)之外,还有一个大堂经理(Selector线程),客户来了,他们会先到大堂经理处报道,正真要点菜了,大堂经理才会派一个服务员去服务,解决了BIO模式下,客户进来一直不点菜的情况。                                                                              这个模式下IO线程依然会被阻塞,但是Selector线程不会被阻塞,所以叫非阻塞式IO,所以当并发量到一定程度时还是不能承受。
  • AIO(Asynchronous I/O):AIO也就是NIO2,也叫异步非阻塞式IO,异步是基于回调机制,或者事件监听实现,,应用操作后会直接返回,不会等待。具体是,一个请求发送来了,程序直接将IO操作交给内核,让系统进行IO,这期间去进行其他操作,系统IO完成,会进行回调,程序收到,则进行IO之后的操作。                                                                                              例:还是那家饭店,一个客户来了,服务员不为他服务,客户自己坐下来,从菜单里选菜,选好了喊服务员(回调),服务员为他服务。

Files的常用方法有哪些

  • Files.exists() 检测文件路径是否存在
  • Files.createFile() 创建文件
  • File.createDirectroy() 创建文件夹
  • File.delete() 删除一个文件或者目录
  • File.copy() 复制文件
  • File.move() 移动文件
  • File.size() 查看文件个数
  • File.read() 读取文件
  • File.write() 写入文件

反射

什么是反射

Java反射机制实在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

静态编译和动态编译

静态编译:在编译时确定类型,绑定对象

动态编译:在运行时确定类型,绑定对象

反射机制优缺点

优点: 反射是运行时判断类型,动态加载类,代码灵活性非常高

缺点:运行时加载类,比编译时加载类要慢很多,因为需要进行一系列解释操作,有性能瓶颈,反射可以无视访问权限,无视类型检测,安全性会降低。

反射机制的应用场景有哪些

反射机制是框架的灵魂。

在我们平时的项目开发中,很少用到反射,但是实际上,很多设计开发都与反射机制有关,例如模块化开发,通过反射区调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的各种框架,也大量使用到了反射机制。

Java获取反射的三种方法

  1. 类名.class
  2. 实例对象.getClass()
  3. Class.forName("类的绝对路径")

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KYLO_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值