目录
- Java基础知识
- 面向对象
- 字符串
- 数组
Java基础知识
1、Java的优势有哪些?Java有什么优点?
(1)跨平台/可移植性强
(2)安全性强
(3)面向对象编程
(4)简单、高性能
(5)分布式、多线程、健壮性
2、JDK、JRE、JVM三者之间的区别是什么?
(1)JVM:Java虚拟机,是一个虚拟的用于执行bytecode字节码的虚拟计算机。它实现了跨平台,“一次编译,到处运行”,Java虚拟机是实现跨平台的核心机制。
(2)JRE:Java运行环境,Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
(3)JDK:Java开发工具包,Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
3、标识符的命名规则
(1)标识符必须以字母、数字、下划线、美元符号
开
头
;
(
2
)
标
识
符
其
他
部
分
可
以
是
字
母
、
下
划
线
"
"
、
美
元
符
号
"
开头; (2)标识符其他部分可以是字母、下划线"_"、美元符号"
开头;(2)标识符其他部分可以是字母、下划线""、美元符号""和数字的任意组合;
(3)Java标识符大小写敏感,且长度无限制;
(4)标识符不可以是Java关键字;
4、什么是变量、变量的本质是什么?
变量本质上就是代表着一个可操作的存储空间,空间位置是确定的,但是里面放置什么值是不确定的。我们可以通过变量名来访问"对应的存储空间",从而操纵这个"存储空间"存储的值。
Java是一个强类型语言,每个变量都必须声明其数据类型。
(1)每个变量都有类型,类型可以是基本数据类型,也可以是引用数据类型;
(2)变量名必须是合法的标识符;
(3)变量声明是一条完整的语句,因此每一个声明都必须以分号结尾。
5、变量的分类以及作用域
从整体上可将变量划分为局部变量、成员变量(也称为实例变量)和静态变量。
(1)局部变量:声明位置在方法或者语句内部,从属于方法/语句,声明周期从声明的位置开始,直到方法或者语句块执行完毕,局部变量消息;局部变量在使用前必须先声明、初始化(赋初值)再使用。
(2)成员变量:声明位置在类的内部,方法的外部,从属于对象,声明周期为对象创建时,成员变量也跟着被创建,当对象消失的时候,成员变量也会跟着消息;如果对该变量进行初始化,它会自动初始化为该类型的默认初始值。
(3)静态变量:声明位置为类的内部,static修饰,从属于类,生命周期为当类被加载的时候,静态变量就有效,当类被卸载的时候,静态变量消失。如果不对该变量进行初始化,难么它会自动初始化为该类型的默认初始值。
6、常量
常量就是程序运行是不能改变的量,常量只进行一次赋值之后就不能再次赋值,在java中常量使用final关键字来进行声明。
7、Java的基本数据类型
Java是一种强类型语言,每个变量都必须声明其数据类型。Java的数据类型可分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)。
(1)基本数据类型
Java中定义了3类8种基本数据类型,分别是以下:
· 数值型 - byte(1字节)、short(2字节)、int(4字节)、long(8字节)、float(4字节)、double(8字节);
· 字符型 - char(2字节);
· 布尔型 - boolean(1字节或者4字节);
(2)引用数据类型
Java的引用数据类型可分为类(class)、接口(interface)、数组。
引用数据类型的大小统一为4字节,记录的是其引用对象的地址。
7.1 整型
整型用于表示没有小数部分的数值,它允许是负数。整型的范围与运行Java代码的机器无关,这正是Java程序具有很强的移植能力的原因之一。
注意:Java语言的整型常数默认为int类型,当声明long类型的常量时需要在后面加’l’或者是’L’。
如:long a = 55555555555L;
7.2 浮点型
带小数的数据在Java中称为浮点型。浮点型可分为float类型和double类型。
float类型被称为单精度浮点型,尾数可以精确到7为有效数字,在很多情况下,float类型的精度很难满足需求。而double表示这种类型的数值精确约时float类型的两倍,因为被称为双精度类型,绝大部分应用都采用double类型。
float类型的数值有一个后缀F或者f,没有后缀f/F的浮点数值默认为double类型。也可以在浮点数值后添加后缀D或者d,以明确其为double类型的。
如下所示:
float f = 3.14F;//flaot类型赋值时需要添加后缀F/f,不加的话就会报错。
double d1 = 3.14;
double d2 = 3.14D;
注意:浮点数直接进行斤算可能出现精度丢失的问题,这也是Java的弊端,所以我们以后在工作中要避免浮点数直接进行计算或者是比较。
7.3 char字符型
字符型在内存中占2个字节,在Java中使用单引号来表示字符常量。例如’A’是一个字符,它与"A"是不同的,"A"表示含有一个字符的字符串。
char类型用于表示在Unicode编码表中的字符。Unicode编码被设计用来处理各种语言的文字,它占两个字节,可允许有65536个字符。
字符型定义如下:
char ch = 'A';
char ch1 = '好';
Unicode具有从0到65535之间的编码,他们通常从 ‘\u0000’ 到 ‘\uFFFF’ 之间的十六进制值来表示(前缀为u表示Unicode)。
字符型的十六进制表示方法如下:
char c = '\u0061';
Java云烟中还允许使用转移字符’'来将其后的字符转变为其他的含义。常用的zhuanyi9字符及其含义如下所示:
char ch = '\n'; //表示换行符
注意:后面学习的String类,其实是字符序列,本质是就是char字符组成的数组。
7.4 boolean布尔型
boolean类型有两个常量值,分别是true和false,在内存中占用1个字节或4个字节,不可以使用0或者非0的整数代表true和false。boolean类型常用来判断逻辑条件,一般用于程序流程控制。
注意:在《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持,在Java虚拟机中没人任何提供boolean值专用的字节码指令,Java语言表达式所操作的boolean值在编译之后都使用Java虚拟机中的int数据类型来替代,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。也就是说JVM规范支出boolean类型沾了单独使用的44个字节,在数组中是确定的1字节。
8、运算符
计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操作变量。
8.1 算数运算符
算数运算符中+,-,*,/属于二元运算符,二元运算符指的是需要两个操作数才能完成运算的运算符。其中的%是取模运算符,也就是我们常说的求余数的操作。
(1)整数运算:
- 如果两个操作数有一个为long,则结果为long;
- 没有long时,结果为int。及时操作数全为short,byte,结果也是int;
(2)浮点运算 - 如果两个操作数有一个为double,则结果为double;
- 只有两个操作数都时float时,结果采薇float;
(3)取模运算 - 其操作数可以为浮点数,一般使用整数,结果是"余数","余数"符号和左边操作数相同。
如:7%3=1,-7%3=-1,7%-3=1。
算数运算符中的++(自增),–(自减)属于一元运算符,该类运算符只需要一个操作数。
8.2 赋值和赋值扩展运算符
8.3 关系运算符
注意事项:
· =是赋值运算符,而真正的判断两个操作数是否相等的运算符是==。
· ==、!= 是所有(基本和引用)数据类型都可以使用。
· >、>=、<、<=仅针对数值类型(byte/short/int/long,float/double以及char)。
8.4 逻辑运算符
短路与和短路或采用短路的方式。从左到有计算,如果只通过运算符左边的操作数就能够确认该逻辑表达式的值,则不会继续计算运算符右边的操作数,提高了效率。
8.5 位运算符
8.6 字符串连接符
"+"运算符两侧的操作数中只要有一个是字符串(String),系统就会自动将另外一个操作数转换为字符串然后在进行连接。
8.7 条件运算符(三目运算符)
语法格式为: x ? y : z
其中x为boolean类型表达式,先计算x的值,若为true,则整个运算的结果为表达式y的值,否则整个运算结果为表达式z的值。
以上代码输出-1。
8.8 运算符优先级问题
注意:
- 逻辑与、逻辑或、逻辑非的优先级一定要熟悉!(逻辑非>逻辑与>逻辑或)
比如说:a||b&&c的结果是:a || (b&&c)
8.9 &和&&的区别
&运算符有两种用法:(1)按位与;(2)逻辑与。
&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
9 数据类型转换
在八种基本数据类型中除了boolean类型之外的七种基本数据类型都是可以自动转换的。
9.1 自动类型转换
自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型,如下图所示,黑色的实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度丢失的情况。
我们将整型int常量直接赋值给byte、short、char等类型变量,而不需要进行强制类型转换,只要不超过其表数范围即可。
9.2 强制类型转换
强制类型转换,又被称为造型(cast),用于强制的转换一个数值的类型,在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成精度降低或者溢出。
语法格式为:
(type)args
运算符"()"中的type表示将值args转换成的目标数据类型。
浮点型转化为整形时,不会四舍五入,而是舍去小数点后的数。
当一种类型强制转换为另外一种类型,而又超出了目标类型的表数范围,就会被截断成为一个完全不同的值。如下所示:
int x=300;
byte bx = (byte)x; //值为44
byte类型时-256到256
注意:不能在布尔类型和任何数值类型支架你做强制类型转换。
9.3 基本类型转换时常见错误和问题
输出结果为:
10 Scanner键盘输入
11、break、continue、return三者之间的区别是什么?
(1)break:跳出总的上一层循环,不再执行循环(结束当前的循环体);
(2)continue:跳出本次循环,继续执行下次循环(结束正在执行的循环,进入下一个循环);
(3)return:程序返回,不再执行下面的代码(结束当前的方法,直接返回)。
练习:打印一个区间的质数
//输出n-m之间的质数(素数)
public void OutPutZhiShu(int n,int m){
outer:for(int i = n;i<m;i++){
for(int j=2;j<i/2;j++){
if(i%j == 0){
continue outer;
}
}
System.out.print(i + " ");
}
}
12 方法的重载(overload)
方法的重载指的是一个类中可以定义多个方法名相同,单数参数不同的方法。调用时会根据不同的参数自动匹配对应的方法。
注意:重载的放啊实际上时完全不同的方法,只是名称相同而已。
12.1 构成方法重载的条件
(1)不同的含义:形参类型、形参个数、形参顺序不同;
(2)只有返回值不同不构成方法的重载,
如:int a (String str){}和void a(String str)不构成方法的重载;
(3)只有形参的名称不同,不构成方法的重载。
如:int a(String str){}与int a(String s){}不构成方法的重载。
13 递归调用
递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的放啊将会直接或者间接的调用自己。
利用递归可以用简单的程序来解决一些复杂的问题。
递归结构包括两个部分:
- 递归头,递归结束的条件。如果没有递归头,那么程序就会陷入死循环。
- 递归体。
示例代码:使用递归求 n!
//求n的阶乘
static long factorial(int n){
if(n == 1){//递归头
return 1;
}else{ //递归体
return n*factorial(n-1); //n * (n-1)!
}
}
提问:如何不使用第三个变量的前提下完成a=10,b=60的值交换?
//方式一
int a=10,b=60;
a = a+b;//70
b = a-b;//70-60=10
a = a-b;//70-10=60
14、final关键字的作用
final关键字可用于修饰类、属性和方法;
(1)被final修饰的类不可以被继承;
(2)被final修饰的方法不可以被重写;
(3)被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
15、关键字static的独特之处以及应用场景
1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
应用场景:
1、修饰成员变量
2、修饰成员方法
3、静态代码块
4、修饰类【只能修饰内部类也就是静态内部类】
5、静态导包
static的注意事项:
1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。
面向对象
1、面向对象和面向过程的区别
(1)面向过程:
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较小号资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展。
(2)面向对象:
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活,更加容易维护。
- 缺点:性能比面向过程低。
2、面向对象的三大特性
面向对象的三大特性为:封装、继承、多态
(1)封装:
封装就是将一个对象进行私有化,同时提供一些可以被外界访问的属性的方法。
封装隐藏了对象的属性和实现细节,仅对外提供公共访问的方式,将变化隔离,便于使用,提高了安全性和复用性。
(2)继承:
继承就是使用已经存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性的继承父类,通过使用继承我们可以很容易的复用以前的代码。
但是关于继承需要注意以下三点:
- 子类拥有父类非private的属性和方法;
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
- 子类可以用自己的方式实现父类的方法。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。
(3)多态:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
多态的实现:
Java实现多态有三个必要条件:继承、重写、向上转型。
-
继承:在多态中必须存在有继承关系的子类和父类。
-
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
-
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
3、创建对象有哪几种方式?
(1)通过new创建对象,这种方法需要构造器;
Demo demo1=new Demo();
Demo demo2=new Demo(1,“有参构造器”);
(2)通过反射的newInstance()创建对象;
Demo demo2=(Demo) Class.forName(“Demo”).newInstance();
(3)通过Object类的clone方法,需要实现的是Cloneable接口,重写Object的clone方法,使用clone方法创建对象并不会调用任何构造函数(原型模式);
(4)反序列化。Java中常常进行JSON数据跟Java对象之间的转换,即序列化和反序列化;
当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口,虽然该接口没有任何方法。不会调用任何构造函数
4、类和接口
4.1 抽象类和接口的相同点以及区别
(1)相同点:
- 接口和抽象类都不能被实例化;
- 接口和抽象类都位于继承的顶端,用于被其它类实现或者继承;
- 两者都包含抽象方法,其子类都必须重写这些抽象方法。
(2)区别: - 声明:抽象类使用abstract关键字声明,接口使用的是interface关键字声明;
- 实现:抽象类的子类使用的是extends关键字来继承抽象类,而接口的子类使用的是implements关键字来实现接口;
- 是否有构造器:抽象类可以有构造器,但是接口不能有构造器;
- 是够可以多继承:一个类最多只能继承一个抽象类,但是一个类可以实现多个接口;
- 访问修饰符:抽象类中的方法可以是任意访问修饰符,接口中默认是public,并且不允许定义为private或者是protected。
4.2 普通类和抽象类之间的区别
- 普通类可以直接被实例化,但是抽象类不能直接被实例化;
- 普通类不能含有抽象方法,但是抽象类可以含有抽象方法;
- 普通类可以使用final关键字修饰,但是抽象类不能用final关键字修饰。
原因是因为抽象类就是为了让其他类继承的,而被final定义的类不能被继承,相互矛盾了。
5 构造方法
5.1 构造方法有哪些?定义无参构造有什么作用?
构造方法分为有参构造和无参构造,Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
而定义无参构造的主要目的就是为了帮助子类做初始化工作。
总而言之,构造器的主要作用就是为了完成对类对象的初始化工作。
5.2 构造器有四个要点:
- 构造器通过new关键字调用!!
- 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值;
- 如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数,如果已经定义则编译器不会自动添加。
- 构造器的方法名必须和类名一致。
5.3 说出以下代码的输出结果。
public class A {
static{
System.out.println("static in class A");
}
public A(){
System.out.println("class A");
}
}
public class SubA extends A {
static {
System.out.println("static in class SubA");
}
public SubA() {
super();
System.out.println("class SubA");
}
public SubA(String sa) {
System.out.println("class SubA " + sa);
}
}
public class StaticTest {
public static void main(String[] args) {
SubA subA1 = new SubA("111111");
SubA subA2 = new SubA("222222");
SubA subA3 = new SubA("333333");
}
}
输出结果为:
static in class A
static in class SubA
class A
class SubA 111111
class A
class SubA 222222
class A
class SubA 333333
解释:
子类构造方法调用规则:
(1)如果子类的构造方法中没有通过 super 显式调用父类的有参构造方法,
也没有通过 this 显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。
这种情况下,写不写 super(); 语句,效果是一样的
(2)如果子类的构造方法中通过 super 显式调用父类的有参构造方法,
将执行父类相应的构造方法,不执行父类无参构造方法
(3)如果子类的构造方法中通过 this 显式调用自身的其他构造方法,将执行类中相应的构造方法
(4)如果存在多级继承关系,在创建一个子类对象时,以上规则会多次向更高一级父类应用,
一直到执行顶级父类 Object 类的无参构造方法为止
结论:
类的实例化方法调用顺序:
类加载器实例化时进行的操作步骤:加载 -> 连接 -> 初始化
(1)父类静态代变量
(2)父类静态代码块
(3)子类静态变量
(4)子类静态代码块
(5)父类非静态变量(父类实例成员变量)
(6)父类构造函数
(7)子类非静态变量(子类实例成员变量)
(8)子类构造函数
6 内部类
6.1 内部类有哪几种?详细说明一下。
内部类的定义:可以将一个类的定义放在另外一个类的定义内部;
内部类的种类:成员内部类、局部内部类、匿名内部类、静态内部类。
(1)静态内部类:定义在类内部的静态类。
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,new 外部类.静态内部类(),如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
(2)成员内部类:定义在类内部,成员位置上的非静态类。
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
(3)局部内部类:定义在方法的内部,就是局部内部类。
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new 内部类(),如下:
public static void testStaticFunctionClass(){
class Inner {
}
Inner inner = new Inner();
}
(4)匿名内部类:没有名字的内部类,日常开发中使用的比较多。
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名内部类" );
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{
void method();
}
除了没有名字,匿名内部类还有以下特点:
A、匿名内部类必须继承一个抽象类或者实现一个接口。
B、匿名内部类不能定义任何静态成员和静态方法。
C、当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
D、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
匿名内部类创建方式:
new 类/接口{
//匿名内部类实现部分
}
6.2 内部类有哪些使用场景?你在哪些地方使用到了内部类?
(1)在设计模式中使用静态内部类实现单例模式;
public class SingletonDemo03 {
private static class SingletonClassInstance{
private static final SingletonDemo03 instance = new SingletonDemo03();
}
public static SingletonDemo03 getInstance(){
return SingletonClassInstance.instance;
}
private SingletonDemo03(){}
}
(2)
(3)ConcurrentHashMap中的Segment就是它的一个内部类。
6.3 使用内部类的优点
- 内部类有效的实现了“多重继承”,优化了Java单继承的缺陷;
- 内部类具有很好的封装性,因为它不为同一个包的其他类所见;
- 匿名内部类能够很方便的定义回调。
7 重载(Overload)与重写(Override)
7.1 重载与重写的区别
- 重载发生在同一个类,方法名相同但参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值不能根据返回类型进行区分。
- 重写发生在父子类中,方法名、参数列表必须相同。
- 方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,重写实现的是运行时的多态性。
8、==、equals、hashCode
8.1 ==和equals的区别是什么?
(1)== 的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。 (基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
(2)而equals()的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
8.2 euqals和hashCode的理解
(1)两个对象的hashcode相同,则它们的equals()也一定为true对吗?
答:不对,两个对象的hashcode相同,euqals()不一定为true。
(2)两个对象的equals()为true时,他们的hashcode一定相同,对吗?
答:是的,两个对象的equals为true,它们的hashcode一定相同。
8.3 为什么重写equals()方法时必须要重写hashCode方法呢?
因为如果两个对象相等,则hashcode一定也是相同的,hashCode方法默认行为是对堆上的对象产生独特值。如果没有重写hashCode()方法,则该class的两个对象无论如何都不会相等,而我们使用equals()方法去比较两个对象是否相等时,会对两个对象分别调用equals方法,如果重写了equals方法的话,hashCode方法也必须被重写。
字符串
9.1 字符型常量和字符串常量的区别
(1)形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符;
(2)含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置);
(3)占内存大小 字符常量只占1字节 字符串常量占若干个字节(至少一个字符结束标志);
9.2 字符串常量池
字符串常量池位于堆内存中,专门用来存储字符串常量,这样可以提高内存的使用率,避免开辟多块内存空间存储相同的字符串,在创建字符串时JVM就会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在就实例化一个字符串放到常量池中,并且返回其引用。
9.3 String的简介以及特性
String不是基本是数据类型,它是引用数据类型,String是一个对象,它的底层就是一个char类型的数组,只是使用的时候不需要直接操作底层数组。
关于它的特性,有以下几点:
(1)不变性:String是只读字符串,保证了数据的一致性;
(2)常量池优化:String对象创建之后,会在字符串常量池中进行缓存,如果下次再创建同样的对象时,会直接返回缓存中的引用;
(3)安全性:String的底层使用了final关键字进行了定义,表示String类不能被继承,提高了系统的安全性。
9.4 关于一些String的题目
(1)String类被继承吗?String的值不能被改变吗?
答:String的底层由于使用了final关键字进行了定义,因此被final修饰的类不能被继承,其值不能在初始化之后不可被改变。
(2)String str="i"与 String str=new String(“i”)一样吗?
答:不一样,因为内存的分配方式不一样,String str = "i"的方式,Java虚拟机会将其分配到常量池中;
而String str = new String(“i”) 则会被分配到堆内存中。
(3)String s = new String(“abc”)创建了几个对象呢?
答:两个对象,一个是静态区的"abc",另外一个是用new创建在堆上的对象。
(4)HashMap中使用String做key有什么好处?
HashMap内部实现是通过key的hashcode来确定value的存储位置,因为字符串是不可变的,并且使用字符串时会在字符串常量池中进行缓存,所以当创建字符串的时候,hashcode会被缓存下来,不需要再次计算,所以相比于其他对象来说快一些。
9.5 String类有哪些常用的方法?
(1)indexOf():返回指定字符的索引。
(2)charAt():返回指定索引处的字符。
(3)replace():字符串替换。
(4)trim():去除字符串两端空白。
(5)split():分割字符串,返回一个分割后的字符串数组。
(6)getBytes():返回字符串的 byte 类型数组。
(7)length():返回字符串长度。
(8)toLowerCase():将字符串转成小写字母。
(9)toUpperCase():将字符串转成大写字符。
(10)substring():截取字符串。
(11)equals():字符串比较。
9.6 java 中操作字符串都有哪些类?它们之间有什么区别?应用场景
Java中操作字符串的类有String、StringBuffer、StringBuilder三个类。
三者的相同点:
(1)都可以存储和操作字符串;
(2)底层都使用了final修饰,不能被继承。
(3)提供了API相似。
三者的区别:
(1)String是不可变字符序列,String内容是不能被改变的;
(2)StringBuffer和StringBuilder是可变字符序列,他们都可以对字符串内容进行修改,
并且修改之后的内存地址不会发生改变;
(3)StringBuilder是JDK1.5的,效率高,但是它的线程不安全,
StringBuffer是JDK1.0的,效率低,但是它是线程安全的(方法加了Synchronized)。
应用场景:
如果是要操作少量的数据,就是用String就行了;
如果是单线程操作字符串缓存区下的大量数据,就使用StringBuilder;
如果是多线程操作字符串缓存区下的大量数据,就使用StringBuffer;
9.7 如何将字符串进行反转?
使用StringBuffer或者StringBuilder中的reverse()方法。
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
9.8 switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
在Java5以前,switch(expr)中的expr只能是byte、char、short、int四种数据类型。从Java5以后引入了枚举,expr可以是enum类型,Java7开始,expr就可以支持String字符串了,但是目前还不能使用long类型的。
9.9 代码题
请书写一段代码将字符串"10,5,9,20,33,4,6"进行排序
String str = "10,50,2,1,77,39,40";
String[] arr = str.split(",");
int[] arrInt = new int[arr.length];
for(int i=0;i<arrInt.length;i++){
arrInt[i]=Integer.parseInt(arr[i]);
}
//进行排序
Arrays.sort(arrInt);
System.out.println(Arrays.toString(arrInt));
String newStr = "";
for(int i = 0;i<arrInt.length;i++){
newStr = newStr+arrInt[i];
if(i<arrInt.length-1){
newStr = newStr+",";
}
}
System.out.println(newStr);
数组
数组的定义:
数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中每一个数据称作一个元素,每个元素都可以通过一个索引(下标)访问他们。
数组的四个基本特点:
- 数组的长度是确定的。数组一旦被创建,它的大小就是不可改变的;
- 数组元素的类型必须是相同类型,不允许出现混合类型;
- 数组类型可以是任何数据类型,包括基本类型和引用类型;
- 数组变量属于引用类型,数组也是对象,数组的元素相当于对象的属性。
数组本身就是对象,数组中的每个元素都相当于该对象内的成员变量,Java中对象是在堆中的,因此数组无论是保存原始类型还是其他类型,数组对象本省就是在堆中存储的。
一维数组的声明方式
type[] arr_name;//方式一(推荐使用这种方式)
type arr_name[];//方式二
注意:
- 声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才会分配空间,这时才与长度有关。
- 声明一个数组的时候,数组并没有真正被创建;
- 构造一个数组,必须指定长度。
数组的初始化方式
(1)默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组已经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
如下代码所示:
(2)数组的静态初始化
除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并且赋值。
(3)数组的动态初始化
数组定义和为数组元素分配空间并且赋值的操作分开进行。
数组的拷贝、Arrays、排序、查找、填充
拷贝
数组的拷贝指的是将某个数组的内容拷贝到另一个数组。实质上后面的容器扩容就是使用了“数组的拷贝”
System类中包含了一个static void arraycopy(object src, int srcpos, object dest, int destops, int length)方法,该方法可以将src数组里的元素值赋给dest数组的元素,其中srcpos指定从src数组的第几个元素开始拷贝,length参数指定将src数组的多少个元素赋给dest数组的元素。
String[] str = {"111","333","666","222","000","555"};
String[] newStr = new String[str.length+(int)(str.length*0.5)];
System.arraycopy(str,0,newStr,0,str.length);
for(String s:newStr){
System.out.print(s+" ");
}
输出:
Arrays类
JDK提供的java.util.Arrays类,包含了常用的数组操作,方便我们日常开发,Arrays类包含了:排序、查找、填充、打印内容等常见的操作。
- 打印内容
String[] str1 = {"111","333","666","222","000","555"};
System.out.println(Arrays.toString(str1));
- 排序
int[] arrInt = {10,9,55,33,44,37,66};
Arrays.sort(arrInt);
System.out.println(Arrays.toString(arrInt));
输出:
二分法查找
int[] a = {10,55,66,7,88,99,5,12,44};
System.out.println("原数组为:"+Arrays.toString(a));
Arrays.sort(a);//使用二分法查找要先先对原数组进行排序
System.out.println("排序后的数组:"+Arrays.toString(a));
//返回排序后新的索引位置,若未找到返回负数
System.out.println("该元素索引位置为:"+Arrays.binarySearch(a,12));
- 填充
int[] a = {1,44,666,22,33,555,14};
System.out.println(Arrays.toString(a));
//将数组索引为2到索引为5的元素全部替换为100
Arrays.fill(a,2,5,100);
System.out.println(Arrays.toString(a));
多维数组
多维数组可以看成以数组为元素的数组。可以有二维、三维、甚至更多维数组,但是实际开发中用得非常少。最多用到二维数组。
Comparable接口
相对某个嘞的对象之间做比较,就需要实现Comparable接口。接口中只有一个方法compareTo,这个方法定义了对象之间的比较规则。一句这个“比较规则”,我们就能对所有对象实现排序。
事实上,Java中排序的算法底层也是以来与Comparable接口。
Comparable接口中只有一个方法:
public int compareTo(Object obj);obj就是要比较的对象。
方法中将当前对象和obj这个对象进行比较,如果大于返回1,等于返回0,小于返回-1,(此处的1也可以是正整数,-1也可以是负整数)。compareTo方法的代码也比较固定。
冒泡排序法
该算法的运作如下:
- 第一步:比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 第二步:对每一对相邻元素作相同的工作,从开始第一对代结尾的最后一对。在这一点,最后的元素应该会是最大的元素;
- 第三步:针对所有的元素重复以上的步骤,出了最后一个;
- 第四步:持续每次对越来越少的元素重复上面的操作,之后没有任何一对数字需要比较。
代码如下所示:
int[] arrs = new int[]{1, 55, 77, 22, 33, 51, 22, 4};
System.out.println("原数组:"+Arrays.toString(arrs));
int[] ints = bubbleSort(arrs);
System.out.println("排序后:"+Arrays.toString(ints));
}
public static int[] bubbleSort(int[] arrs){
int temp;
for(int i=0;i<arrs.length;i++){
for(int j=0;j<arrs.length-1-i;j++){
if(arrs[j]>arrs[j+1]){
temp = arrs[j];
arrs[j] = arrs[j+1];
arrs[j+1] = temp;
}
}
}
return arrs;
}
冒泡排序法优化:
public static int[] bubbleSort1(int[] arrs){
int temp;
//外循环,n个元素排序,最多需要n-1次循环
for(int i=0;i<arrs.length;i++){
boolean flag = true;//定义一个标记
//内循环,每一次循环都会将数列中的前两个元素进行比较,将最大的值放到数列最后
for(int j=0;j<arrs.length-1-i;j++){
//前一个元素大于后一个元素就进行交换
if(arrs[j]>arrs[j+1]){
temp = arrs[j+1];
arrs[j+1] = arrs[j];
arrs[j] = temp;
//发生了交换,就说明数组还是无序的,需要继续进行比较
flag = false;
}
}
//如果没有发生比较,就说明数组是有序的,不需要继续进行比较了
if(flag){
break;
}
}
return arrs;
}
二分法查找
二分法检索(binary search)又称为折半检索,二分法检索的基本思想就是设数组中的元素从小到大有序地存放在数组(array)中,首相将给定值key与数组中间位置上元素的关键码(key)比较,如果相等,则检索成功;
否则,若key小,则在数组前半部分中继续进行二分法检索。
如下实例:
代码:
public static void main(String[] args) {
int[] arrs = {11,2,44,65,33,14,54,24};
//使用二分法进行查找时一定要对数组进行排序
Arrays.sort(arrs);
int searchNum = 2;
int index = binarySearch(arrs, searchNum);
System.out.println("查询数组:"+Arrays.toString(arrs)+"中的元素"+searchNum+"索引下标为:"+index);
}
public static int binarySearch(int[] array,int value){
int low=0;
int high=array.length-1;
while(low<=high){
int middle = (low+high)/2;
if(value == array[middle]){
return middle; //找到了返回索引
}
if(value > array[middle]){
low = middle+1;
}
if(value < array[middle]){
high = middle-1;
}
}
return -1;//表示未找到
}
后接 Java基础知识(二)