文章目录
-
- Java标识符
- Java修饰符
- 运算符
- 强制类型转换
- JDK和JRE有什么区别
- 基本类型
- 例题
- Object类及其常用方法简介
- String的用法
- String、StringBuffer、StringBuilder的区别是什么?
- toString用法
- 逃逸字符
- 大小写转换
- 数组
- 数组变量
- 容器类
- 继承
- 对象与类
- 构造器
- 封装
- 方法重载(overload)
- 重写与重载的区别
- 线程
- JDBC
- Dao模式
- TCP/ip和UDP区别
- Junit单元测试
- finally和finalize的区别
- 关于变量声明
- "=="和equals方法究竟有什么区别?
- String到底变了没有?
- final关键字修饰
- instanceof
- JVM相关
- 基础知识点
- 异常框架
- Java IO
- 多线程并发
- 网络
- 时间日期处理
- XML解析/ JSON解析
- 泛型
- 注解
- JNI
- 练习题

Java标识符
Java 中标识符是为方法、变量或其他用户定义项所定义的名称。标识符可以有一个或多个字符。
(1) 定义:标识符就是用于给 Java 程序中变量、类、方法等命名的符号。
标识符:凡是自己可以起名字的地方都叫标识符
比喻 类名 变量名 方法名 接口名 包名等等
(2)标识符的命名规则
可以由字母(A~ Z 和 a~ z),数字(0~9),下划线(_),美元符($)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)。但不能包含@,%,空格等其他特殊字符,不能以数字开头。
不可以使用关键字和保留字 但能包含关键字和保留字
Java中严格区分大小写 长度无限制
标识符不能包含空格
合法举例:myName,My_name,Points,$points,_sys_ta,OK,_23b,_3 _,Myvoid、date、 $ 2011、 _ date、D _$date
非法举例: #name,25name,class,&time,if、123.com、2com、for、-salary、123abc
提示:标识符命名时,切记不能以数字开头,也不能使用任何 Java 关键字作为标识符,而且不能赋予标识符任何标准的方法名。
(3)Java中的命名规范
包名:多单词组成时所有字母都小写
类名、接口名:多单词组成时 所有的单词的首字母大写 :ZxxY
变量名 、方法名:多单词组成时,第一个单词首写字母小写 ,第二个单词开始每个单词首字母大写 xxxYyyZzx
常量名:所有字母都大写,多单词时每个单词用下划线连接 XXX_YYY_ZZZ
注意1.在起名字的时候 为了提高阅读性 要尽量有意义 “见名知意”
注意2.java采用unicode字符集 因此标识符也可以使用汉字声明 但是不建议使用
Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类。
数据类型:boolean、int、long、short、byte、float、double、char、class、interface
流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally
修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native
动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new
保留字:true、false、null、goto、const
Java修饰符
访问控制存在的原因:
a、让客户端程序员无法触及他们不应该触及的部分
b、允许库设计者可以改变类内部的工作方式而不用担心影响到客户端程序员。
Java语言提供了很多修饰符,主要分为以下两类:
访问修饰符
非访问修饰符
访问权限修饰符
在Java里面一共包含4种访问权限修饰符
1、private:私有的。只允许在本类范围中进行访问,不能被其它任何类 ( 包括子类 ) 访问,利用这个访问权限,表现出封装思想。
使用对象:变量、方法。 注意:不能修饰类(外部类)
2、缺省修饰符:默认default(不用把default写出来),允许在当前类,同包类/非子类都可调用,跨包子类/子类都不允许;
使用对象:类、接口、变量、方法。
3、protected:受保护的。允许在当前类,同包中的子类/非子类都可以以及跨包子类调用。 还可以在不同包中所继承的子类访问。跨包的非子类不允许调用。
使用对象:变量、方法。 注意:不能修饰类(外部类)。
4、public:用 public 修饰的域称为公共域。允许在任意位置访问。不仅可以是同一个类或子类,还是同一个包中的类或子类,又还是不同包中的类或子类,都可以访问。 public 修饰符会降低运行的安全性和数据的封装性,所以一般应减少 public 域的使用。
使用对象:类、接口、变量、方法
<访问权限范围越小,安全性越高>
按照前面的顺序,自上而下,访问范围越来越大;自下而上,限制能力越来越强:
访问控制和继承
请注意以下方法继承的规则:
父类中声明为public的方法在子类中也必须为public。
父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
父类中声明为private的方法,不能够被继承。
在面向对象设计和编程的过程中,归根结底就是类的设计。而要设计出不仅满足当前需求同时也能符合未来需求的类,就很有必要对类中属性和行为设置合适的访问权限。
都应该给予合理的规划和布局,一来保证程序的功能要求,二来要体现程序的扩展性能,三来要呈现程序友好性质。总之,程序应该更好地服务用户。而访问权限在这样宏伟目标里扮演着重要角色。
非访问权限修饰符
1、static 修饰符:用来创建类方法和类变量。用 static 修饰的方法称为静态方法。
静态方法是属于整个类的类方法;而不使用static 修饰、限定的方法是属于某个具体类对象的方法。 由于 static方法是属于整个类的,所以它不能操纵和处理属于某个对象的成员变量,而只能处理属于整个类的成员变量,即 static 方法只能处理 static的域。
2、final 修饰符:用来修饰类、方法和变量。
final 修饰的类不能够被继承
final修饰的方法可以被子类继承,但是不能被子类重写。声明 final 方法的主要目的是防止该方法的内容被修改。
final修饰的变量为常量,是不可修改的。
用修饰符 final修饰的方法称为最终方法。最终方法是功能和内部语句不能更改的方法,即最终方法不能重写覆盖。final固定了方法所具有的功能和操作,防止当前类的子类对父类关键方法的错误定义,保证了程序的安全性和正确性。所有被 private 修饰符限定为私有的方法,以及所有包含在 final 类 ( 最终类) 中的方法,都被认为是最终方法。
3、abstract 修饰符:用来创建抽象类和抽象方法,用 abstract 修饰符修饰的类,被称为抽象类。抽象方法仅有方法头,没有方法体和具体实现。 抽象类不能用来实例化对象,可以包含抽象方法,抽象方法没有被实现,无具体功能,只能衍生子类。声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
抽象方法
抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。抽象方法不能被声明成final和strict。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract sample();
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//实现抽象方法
void m(){
.........
}
}
4、synchronized修饰符 :主要用于多线程程序中的协调和同步。
5、volatile 修饰符:易失 ( 共享 ) 域修饰符 volatile是用来说明这个成员变量可能被几个线程所控制和修改。通常 volatile 用来修饰接受外部输入的域。
修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
6、transient修饰符:暂时性域修饰符 transient 用来定义一个暂时性变量。序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。其特点是:用修饰符transient 限定的暂时性变量,将指定 Java虚拟机认定该暂时性变量不属于永久状态,以实现不同对象的存档功能。否则,类中所有变量都是对象的永久状态的一部分,存储对象时必须同时保存这些变量。
7、本地方法控制符 native :用修饰符 native 修饰的方法称为本地方法。为了提高程序的运行速度,需要用其它的高级语言书写程序的方法体,该方法可定义为本地方法用修饰符 native 来修饰。
局部变量修饰符
局部变量只能由final 来修饰。
其实局部变量不能赋予权限修饰符的? 为什么这么说呢,那是因为局部变量的生命周期是一个方法的调用期,因此没必要为其设置权限访问字段。因为你既然能访问到这个方法,就没有必要再为其方法内的变量赋予访问权限了,这完全就是多此一举。再者该变量在方法调用期间已经被加载进了虚拟机栈,说白了就是它肯定能被当前线程访问到,所以你没必要设置它。
还有为什么局部变量不能用static修饰呢?因为静态变量是在方法之前先加载,所以如果在方法内设置了静态变量,可想而知,连方法都没加载,方法内的静态变量你能加载成功么?
接口
对于接口而言,其修饰符只能用public、default和abstract, 不能用final、static修饰。接口默认修饰为abstract。
接口中方法修饰符
只能使用public和abstract,默认也是public 、abstract。 需要说明的是:自Java1.8以后,接口允许定义静态方法,也就是说你可以用static来修饰接口中的方法了。
运算符
算术运算符
运算符按照操作数的数量可以分为单目运算符、双目运算符和三目运算符。
Java 中的算术运算符主要用来组织数值类型数据的算术运算,按照参加运算的操作数的不同可以分为一元运算符和二元运算符。
一元运算符
算术一元运算一共有 3 个,分别是 -、++ 和 --。
int a = 12;
System.out.println(-a);
int b = a++;
System.out.println(b);
b = ++a;
System.out.println(b);
//输出结果为 -12、12、14
二元运算符
Java 语言中算术运算符的功能是进行算术运算,除了经常使用的加(+)、减(-)、乘()和除(\)外,还有取模运算(%)。
算术运算符都是双目运算符,即连接两个操作数的运算符。优先级上,、/、% 具有相同运算级别,并高于 +、-(+、- 具有相同级别)
算术赋值运算符
算术赋值运算符只是一种简写,一般用于变量自身的变化
赋值运算符
赋值运算符是指为变量或常量指定数值的符号。赋值运算符的符号为“=”,它是双目运算符,左边的操作数必须是变量,不能是常量或表达式。
其语法格式如下所示:变量名称=表达式内容
在 Java 语言中,“变量名称”和“表达式”内容的类型必须匹配,如果类型不匹配则需要自动转化为对应的类型。
赋值运算符的优先级低于算术运算符,结合方向是自右向左;表示一个动作,即将其右侧的值送到左侧的变量中(左侧只允许是变量,不能是表达式或其他形式);不要将赋值运算符与相等运算符“==”混淆。
逻辑运算符(&&、||和!)
逻辑运算符把各个运算的关系表达式连接起来组成一个复杂的逻辑表达式,以判断程序中的表达式是否成立,判断的结果是 true 或 false。
&& 与 & 区别:如果 a 为 false,则不计算 b(因为不论 b 为何值,结果都为 false)
|| 与 | 区别:如果 a 为 true,则不计算 b(因为不论 b 为何值,结果都为 true)
结果为 boolean 型的变量或表达式可以通过逻辑运算符结合成为逻辑表达式。逻辑运算符 &&、|| 和 !进行逻辑运算。
逻辑运算符的优先级为:
!运算级别最高,&& 运算高于 || 运算。
!运算符的优先级高于算术运算符,而 && 和 || 运算则低于关系运算符。
结合方向是:逻辑非(单目运算符)具有右结合性,逻辑与和逻辑或(双目运算符)具有左结合性。
关系运算符
关系运算符(relational operators)也可以称为“比较运算符”,用于用来比较判断两个变量或常量的大小。关系运算符是二元运算符,运算结果是 boolean 型。当运算符对应的关系成立时,运算结果是 true,否则是 false。
关系表达式是由关系运算符连接起来的表达式。关系运算符中“关系”二字的含义是指一个数据与另一个数据之间的关系,这种关系只有成立与不成立两种可能情况,可以用逻辑值来表示,逻辑上的 true 与 false 用数字 1 与 0 来表示。关系成立时表达式的结果为 true(或 1),否则表达式的结果为 false(或 0)。
注意点如下所示:
(1)基本类型的变量、值不能和引用类型的变量、值使用 == 进行比较;
(2)boolean 类型的变量、值不能与其他任意类型的变量、值使用 == 进行比较;
(3)如果两个引用类型之间没有父子继承关系,那么它们的变量也不能使用 == 进行比较。
(4)== 和 != 可以应用于基本数据类型和引用类型。当用于引用类型比较时,比较的是两个引用是否指向同一个对象,但当时实际开发过程多数情况下,只是比较对象的内容是否相当,不需要比较是否为同一个对象。
关系运算符的优先级为:>、<、>=、<= 具有相同的优先级,并且高于具有相同优先级的 !=、==。
关系运算符的优先级高于赋值运算符而低于算术运算符,结合方向是自左向右。
自增和自减运算符(++和–)
在对一个变量做加 1 或减 1 处理时,可以使用自增运算符 ++ 或自减运算 --。++ 或 – 是单目运算符,放在操作数的前面或后面都是允许的。++ 与 – 的作用是使变量的值增 1 或减 1。操作数必须是一个整型或浮点型变量。
在使用自增/自减运算时应注意下面几个问题。
(1)自增/自减只能作用于变量,不允许对常量、表达式或其他类型的变量进行操作。常见的错误是试图将自增或自减运算符用于非简单变量表达式中。
(2)自增/自减运算可以用于整数类型 byte、short、int、long,浮点类型 float、double,以及字符串类型 char。
(3)在 Java 1.5 以上版本中,自增/自减运算可以用于基本类型对应的包装器类 Byte、Short、Integer、Long、Float、Double 和 Character。
(4)自增/自减运算结果的类型与被运算的变量类型相同。
位运算符
位逻辑运算符
位逻辑运算符包含 4 个:&(与)、|(或)、~(非)和 ^(异或)。除了 ~(即位取反)为单目运算符外,其余都为双目运算符。
位与运算符
位与运算符为 &,其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位同时为 1,那么计算结果才为 1,否则为 0。因此,任何数与 0 进行按位与运算,其结果都为 0。
举例:100&0
结果为0
int x = 5,y = 12; // 创建整型变量保存两个数
int z = x&y; // 对这两个数进行位与运算,结果保存到z
语句执行后变量 Z 的值是 4
位或运算符
位或运算符为 |,其运算规则是:参与运算的数字,低位对齐,高位不足的补零。如果对应的二进制位只要有一个为 1,那么结果就为 1;如果对应的二进制位都为 0,结果才为 0。
举例:11|7
运算结果为 15
位异或运算符
位异或运算符为^,其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位相同(同时为 0 或同时为 1)时,结果为 0;如果对应的二进制位不相同,结果则为 1。
举例:11^7
运算结果为 12
提示:在有的高级语言中,将运算符^作为求幂运算符,要注意区分。
位取反运算符
位取反运算符为~,其运算规则是:只对一个操作数进行运算,将操作数二进制中的 1 改为 0,0 改为 1。
举例:~10
运算结果为 65525
位移运算符
位移运算符用来将操作数向某个方向(向左或者右)移动指定的二进制位数。下表列出了 Java 语言中的两个位移运算符,它们都属于双目运算符。
左位移运算符
左移位运算符为 «,其运算规则是:按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
将整数 11 向左位移 1 位的过程如图所示
原来数的所有二进制位都向左移动 1 位。原来位于左边的最高位 0 被移出舍弃,再向尾部追加 0 补位。最终到的结果是 22,相当于原来数的 2 倍。
右位移运算符
右位移运算符为 »,其运算规则是:按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补零。
例如,将整数 11 向右位移 1 位的过程如图所示。
原来数的所有二进制位都向右移动 1 位。原来位于右边的最低位 1 被移出舍弃,再向最高位追加 0 补位。最终到的结果是 5,相当于原数整除 2 的结果。
复合位赋值运算符
所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。复合位赋值运算符由赋值运算符与位逻辑运算符和位移运算符组合而成。
int a = 1;
int b = 2;
int c = 3;
a &= 4;
a |= 4;
a ^= c;
a -= 6;
b >>= 1;
c <<= 1;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
a = 1
b = 1
c = 6
instanceof 运算符
instanceof运算符可以确定对象是否属于一个特定的类.该运算符是二目运算符,左边的操作元是一个对象,右边是一个类,当左边的对象是右边的类或子类创建的对象时,该运算符运算的结果是true,否则是false。
使用格式:( Object reference variable ) instanceof (class/interface type)
public static void main(String[] args) {
String a = "name";
System.out.println(a instanceof String);
System.out.println(a instanceof Object);
}
输出结果:true,true
三目运算符(条件运算符? :)
条件运算符的符号表示为“?:”,使用该运算符时需要有三个操作数,因此称其为三目运算符。
使用条件运算符的一般语法结构为:result = <expression> ? <statement1> : <statement3>;
其中,expression 是一个布尔表达式。当 expression 为真时,执行 statement1, 否则就执行 statement3。此三元运算符要求返回一个结果,因此要实现简单的二分支程序,即可使用该条件运算符。
运算符优先级
Java 语言中大部分运算符也是从左向右结合的,只有单目运算符、赋值运算符和三目运算符例外,其中,单目运算符、赋值运算符和三目运算符是从右向左结合的,也就是从右向左运算。
一般而言,单目运算符优先级较高,赋值运算符优先级较低。算术运算符优先级较高,关系和逻辑运算符优先级较低。多数运算符具有左结合性,单目运算符、三目运算符、赋值运算符具有右结合性。
使用优先级为 1 的小括号可以改变其他运算符的优先级
--y || ++x && ++z;
① 先计算 y 的自减运算符,即 --y。
② 再计算 x 的自增运算符,即 ++x。
③ 接着计算 z 的自增运算符,即 ++z。
④ 由于逻辑与比逻辑或的优先级高,这里将 ② 和 ③ 的结果进行逻辑与运算,即 ++x && ++z。
⑤ 最后将 ④ 的结果与 ① 进行逻辑或运算,即 --y||++x&&++z。
强制类型转换
又称造型,用于显式转换一个数值的类型。在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成的精度降低或溢出。
举例:
double x=3.14;
int nx=(int) x;
char c='a';
int d=c+1;
提示:不能在布尔类型和任何数值之间做强制类型转换。
操作比较大的数值交换时,要留意是否溢出,尤其是整数
优先级高于四则运算
形式:(类型) 值
若果想把一个浮点数的小数部分去掉,在值的前面加上int,例如:
int i=32/3.0;
int i=(int) (32/3.0);
提示:只是从哪个变来那个计算出了一个新的类型的值,它并不改变那个变量,无论是值还是类型都不改变
JDK和JRE有什么区别
JDK:开发工具包
JRE:运行环境
基本类型
基本类型分为基本数据类型和引用数据类型
类型 | 字节 |
---|---|
byte | 1 |
int | 4 |
short | 2 |
float | 4 |
double | 8 |
long | 8 |
在java中,一个小数会默认为double类型的值,因此在为一个float类型的变量赋值时需要注意一点,所赋值的后面一定要加上字符F(或者小写f),而为double变量赋值时,可以在赋值的后面加上字符D(或者小写d),也可以不加。
float f=123.4f;
double d1=100.1;
自动类型转换
指的是容量小的数据类型可以自动转换为容量大的数据类型
例题
(1)1-1/2 +1/3-1/4+1/5-1/6+…1/n
import java.io.*;
import java.util.Scanner;
class test
{
public static void main (String[] args) throws java.lang.Exception
{
Scanner in =new Scanner(System.in);
int n=in.nextInt();
int sign=1;
double result=0.0;
for(int i=1;i<=n;i++) {
result=result+sign*1.0/i;
sign=-sign;
}
System.out.println(result);
}
}
(2)逆序输出一个整数
Scanner in =new Scanner(System.in);
int x=in.nextInt();
int y=0;
do {
y=y*10+x%10;
x/=10;
}while(x>0);
System.out.println(y);
(3)求最大公约数
枚举法
Scanner in =new Scanner(System.in);
int a=in.nextInt();
int b=in.nextInt();
int ret=0;
int i;
for(i=2;i<=a&&i<=b;i=i+1) {
if(a%i==0) {
if(b%i==0) {
ret=i;
}
}
}
System.out.println(a+"和"+b+"的最大公约数是"+ret);
辗转相除法
Scanner in =new Scanner(System.in);
int t;
int a=in.nextInt();
int b=in.nextInt();
int origa=a;
int origb=b;
while(b!=0) {
t=a%b;
a=b;
b=t;
}
System.out.println(origa+"和"+origb+"的最大公约数是"+a);
1、如果b等于0,计算结束,a就是最大公约数
2、否则,计算a除以b的余数,让a的值为b,而b为余数的值
3、回到第一步
比如:
a | b | r(余数) |
---|---|---|
12 | 18 | 12 |
18 | 12 | 6 |
12 | 6 | 0 |
6 | 0 |
最大公约数为6
Object类及其常用方法简介
Object类是一个特殊的类,是所有类的父类,如果一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。这里主要总结Object类中的三个常用方法:toString()、equals()、hashCode()
取得对象信息的方法:toString()
该方法在打印对象时被调用,将对象信息变为字符串返回,默认输出对象地址。
class Student
{
String name = "Mary";
int age = 21;
}
public class Text{
public static void main(String[] args)
{
Student s = new Student();
System.out.println("姓名:"+s.name+",年龄:"+s.age);//输出对象属性
System.out.println(s);//直接输出对象信息
System.out.println(s.toString());//调用父类方法输出对象信息
}
}
输出结果:
姓名:Mary,年龄:21
ClassNotes.Student@15db9742
ClassNotes.Student@15db9742
上述结果看出编译器默认调用toString()方法输出对象,但输出的是对象的地址,我们并不能看懂它的意思。那么就要通过重写Object类的toString()方法来输出对象属性信息。
class Student
{
String name = "Mary";
int age = 21;
public String toString()
{
return "姓名:"+name+",年龄:"+age;
}
}
输出结果:姓名:Mary,年龄:21
对象相等判断方法:equals()
该方法用于比较对象是否相等,而且此方法必须被重写。
class Student
{
String name;
int age;
public Student(String name,int age)
{
this.name=name;
this.age=age;
}
}
public class Text{
public static void main(String[] args)
{
Student s1 = new Student("Mary",21);
Student s2 = new Student("Mary",21);
System.out.println(s1.equals(s2));//输出一个boolean值
System.out.println(s1.equals(s2)?"s1和s2是同一个人":"s1和s2不是同一个人");//?:条件运算符
}
}
输出结果:s1和s2不是同一个人。
很明显输出的结果是错误的,因为equals()方法比较的是两个对象的地址,所以必须重写方法才能到达目的。
//重写父类(Object类)中的equals方法
public boolean equals(Object o)
{
boolean temp = true;
Student s1 = this;
if(o instanceof Object)
{
Student s2 = (Student)o;
if(!(s1.name.equals(s2.name)&&s1.age==s2.age))
{
temp = false;
}
}
else
{
temp = false;
}
return temp;//返回一个布尔值
}
对象签名:hashCode()
该方法用来返回其所在对象的物理地址(哈希码值),常会和equals方法同时重写,确保相等的两个对象拥有相等的.hashCode。
class Student
{
String name;
int age;
//重写父类(Object类)中的equals方法
public boolean equals()
{
boolean temp;
Student s1 = new Student();
s1.name="张三";s1.age=12;
Student s2 = new Student();
s2.name="张三";s2.age=12;
System.out.println("s1的哈希码:"+s1.hashCode());
System.out.println("s2的哈希码:"+s2.hashCode());
if((s1.name.equals(s2.name))&&(s1.age==s2.age))
{
temp = true;
}
else
{
temp = false;
}
return temp;
}
//重写hashCode()方法
public int hashCode()
{
return age*(name.hashCode());
}
}
public class Text{
public static void main(String[] args)
{
Student s3 = new Student();
System.out.println(s3.equals()?"s1和s2是同一人":"s1和s2不是同一人");
}
}
输出结果:
s1的哈希码:9298668
s2的哈希码:9298668
s1和s2是同一人
String的用法
int length() | 获取长度,字符串中包含的字符数 |
---|---|
char charAt(int index) | 取字符串中的某一个字符,,其中的参数index指的是字符串中序数。字符串的序数从0开始到length()-1 |
int length() | 获取长度,字符串中包含的字符数,也就是字符串的长度 |
int indexOf(int ch) | 返回的是ch在字符串中第一次出现的位置。 |
int indexOf(int ch,int fromIndex) | 从fromIndex指定位置开始,获取ch在字符串中出现的位置。 |
int lastIndexOf(String str) | 反向索引 |
boolean contains(str) | 字符串中是否包含某一个子串 |
boolean isEmpty() | 原理就是判断长度是否为0 |
boolean startsWith(str) | 字符串是否以指定内容开头 |
boolean endsWith(str) | 是否以某个字符串结束 |
boolean equals(str) | 判断字符内容是否相同,复写了object类中的equals方法 |
boolean.equalsIgnorecase() | 判断内容是否相同,并忽略大小写 |
char[] tocharArray() | 将字符串转成char数组 |
String(byte[],offset,count) | 将字节数组中的一部分转成字符串 |
byte[] getBytes() | 将字符串转成字节数组 |
String replace(oldchar,newchar) | 将字符号串中第一个oldChar替换成newChar |
String[] split(regex) | 切割 |
String subString(begin,end) | 取从begiin位置开始到end位置的子字符串 |
String toUpperCsae() | 把字符串大写 |
String toLowerCsae() | 把字符串小写 |
String trim() | 将字符串两端的多个空格去除 |
int compareTo(string) | 当前String对象与anotherString比较。相等关系返回0;不相等时,从两个字符串第0个字符开始比较,返回第一个不相等的字符差,另一种情况,较长字符串的前面部分恰巧是较短的字符串,返回它们的长度差 |
String concat(String str) | 将该String对象与str连接在一起 |
boolean contentEquals(StringBuffer sb) | 将该String对象与StringBuffer对象sb进行比较,当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。 |
static String copyValueOf(char[] data, int offset, int count) | 这两个方法将char数组转换成String,与其中一个构造函数类似。返回指定数组中表示该字符序列的 String |
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) | 该方法将字符串拷贝到字符数组中。其中,srcBegin为拷贝的起始位置、srcEnd为拷贝的结束位置、字符串数值dst为目标字符数组、dstBegin为目标字符数组的拷贝起始位置。 |
int hashCode() | 返回当前字符的哈希表码 |
String substring(int beginIndex) | 取从beginIndex位置开始到结束的子字符串 |
boolean matches(String regex) | 告知此字符串是否匹配给定的正则表达式 |
boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) | 测试两个字符串区域是否相等。 |
String、StringBuffer、StringBuilder的区别是什么?
java中用于处理字符串常用的有三个类:
java.lang.String
java.lang.StringBuffer
java.lang.StrungBuilder
三者共同之处:都是final类,不允许被继承,主要是从性能和安全性上考虑的,因为这几个类都是经常被使用着,且考虑到防止其中的参数被参数修改影响到其他的应用。
StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。
String:不可变,线程安全。使用 final 关键字修饰字符数组来保存字符串,并且String也是Immutable类的典型实现,被声明为final class,除了hash这个属性其它属性都声明为final,因为它的不可变性,所以例如拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。
由于String是不可变的,每次对 String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象,性能是最低下的。
StringBuffer:可变,并且线程安全(对方法加了同步锁或者对调用的方法加了同步锁),但效率差。
为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上synchronized。但是保证了线程安全是需要性能的代价的。
StringBuilder:可变,线程不安全(并没有对方法进行加同步锁),效率高。 JDK1.5发布,它和StringBuffer本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。
StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder ,底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。
运行速度:
StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
举例:
String str="abc";
System.out.println(str);
str=str+"de";
System.out.println(str);
运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
String str="abc"+"de";
StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());
三者使用的总结:
String:适用于少量的字符串操作的情况
单线程操作字符串缓冲区下操作大量数据:适用StringBuilder
多线程操作字符串缓冲区下操作大量数据:适用StringBuffer
toString用法
toString()方法返回反映这个对象的字符串
因为toString方法是Object里面已经有了的方法,而所有类都是继承Object,所以“所有对象都有这个方法”。
它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法
总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法
详细用法
(1)undefined和null没有toString()方法
undefined.toString();//错误
null.toString();//错误
(2)布尔型数据true和false返回对应的’true’和’false’
true.toString();//'true'
false.toString();//'false'
Boolean.toString();//"function Boolean() { [native code] }"
(3)字符串类型原值返回
'1'.toString();//'1'
''.toString();//''
'abc'.toString();//'abc'
String.toString();//"function String() { [native code] }"
(4)数值类型的情况较复杂
Number.toString();//"function Number() { [native code] }"
1、正浮点数及NaN、Infinity加引号返回
1.23.toString();//'1.23'
NaN.toString();//'NaN'
Infinity.toString();//'Infinity'
2、负浮点数或加’+'号的正浮点数直接跟上.toString(),相当于先运行toString()方法,再添加正负号,转换为数字
+1.23.toString();//1.23
typeof +1.23.toString();//'number'
-1.23.toString();//-1.23
typeof -1.23.toString();//'number'
3、整数直接跟上.toString()形式,会报错,提示无效标记,因为整数后的点会被识别为小数点
0.toString();//Uncaught SyntaxError: Invalid or unexpected token
因此,为了避免以上无效及报错的情况,数字在使用toString()方法时,加括号可解决
(0).toString();//'0'
(-0).toString();//'0'
(+1.2).toString();//'1.2'
(-1.2).toString();//'-1.2'
(NaN).toString();//'NaN'
此外,数字类型的toString()方法可以接收表示转换基数(radix)的可选参数,如果不指定此参数,转换规则将是基于十进制。同样,也可以将数字转换为其他进制数(范围在2-36)
var n = 17;
n.toString();//'17'
n.toString(2);//'10001'
n.toString(8);//'21'
n.toString(10);//'17'
n.toString(12);//'15'
n.toString(16);//'11'
(5)对象Object类型及自定义对象类型加括号返回[object Object]
{
}.toString();//报错,Unexpected token .
({
}).toString();//[object Object]
({
a:123}).toString();//[object Object]
Object.toString();//"function Object() { [native code] }"
function Person(){
this.name = 'test';
}
var person1 = new Person();
person1.toString();//"[object Object]"
(6)toString()方法在此的作用是将StringBuffer类型转换为String类型.
public class Zhang
{
public static void main(String[] args)
{
String MyStr = new StringBuffer().append("hello").toString();
MyStr = new StringBuffer().append(MyStr).append(" Guys!").toString();
System.out.println(MyStr);
}
}
逃逸字符
字符 | 意义 |
---|---|
\b | 回退一格 |
\t | 到下一个表格位 |
\n | 换行 |
\r | 回车 |
" | 双引号 |
’ | 单引号 |
\ | 反斜杠本身 |
大小写转换
‘a’-'A’可以得到两段之间的距离
a+‘a’-‘A’ 可以把一个大写字母变成小写字母
a+‘A’-‘a’ 可以把一个小写字母变成大写字母
数组
定义数组的形式:<类型>[] <名字>=new <类型>[元素个数]
int[] grades=new int[100]; double[] grades=new double[20];
提示:
元素的个数必须是整数
元素个数必须给出
元素个数可以是变量
数组变量
数组变量是数组的管理者而非数组本身
数组必须创建出来然后交给数组变量管理
数组变量之间的赋值时管理权限的赋予
数组变量之间的比较是判断是否管理同一个数组
对象数组
对象数组中的每个元素都是对象的管理者而非对象本身
容器类
容器类的定义
容器是一种在一个单元里处理一组复杂元素的对象。使用集合框架理论上能够减少编程工作量,提高程序的速度和质量,毕竟类库帮我们实现的集合在一定程度上时最优的。在Java中通过java.util为用户实现了一个Collection Framework,这个集合框架用统一的架构来表示和操作所有的集合,具体包含以下内容:
interface:表示集合的抽象数据类型,它将容器的具体实现与提供的接口分离;
implement:表示集合接口的具体实现;
algorithms:对集合中的元素提供的一些泛型算法,例如查找,排序;
容器与数组的区别与联系
① 容器不是数组,不能通过下标的方式访问容器中的元素
② 数组的所有功能通过Arraylist容器都可以实现,只是实现的方式不同
③ 如果非要将容器当做一个数组来使用,通过toArraylist方法返回的就是一个数组
容器类关系图
虚线框表示接口
实线框表示实体类
粗线框表示最经常使用的实体类。
点线的箭头表示实现了这个接口。
实线箭头表示类能够制造箭头所指的那个类的对象。
Java集合工具包位于Java.util包下。包括了非常多经常使用的数据结构,如数组、链表、栈、队列、集合、哈希表等。
学习Java集合框架下大致能够分为例如以下五个部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)。
集合类主要分为两大类:Collection和Map
Collection是List、Set等集合高度抽象出来的接口,它包括了这些集合的基本操作,它主要又分为两大部分:List和Set。
List接口通常表示一个列表(数组、队列、链表、栈等),存放的元素有序且允许元素有重复。经常使用实现类为ArrayList和LinkedList,另外还有不经常使用的Vector。另外。
Set接口通常表示一个集合,当中的元素不允许反复(通过hashcode和equals函数保证),经常使用实现类有HashSet和TreeSet。HashSet是通过Map中的HashMap实现的,而TreeSet是通过Map中的TreeMap实现的。另外,TreeSet还实现了SortedSet接口,因此是有序的集合(集合中的元素要实现Comparable接口,并覆写Compartor函数才行)。
Map是一个映射接口,当中的每一个元素都是一个key-value键值对。相同抽象类AbstractMap通过适配器模式实现了Map接口中的大部分函数,TreeMap、HashMap、WeakHashMap等实现类都通过继承AbstractMap来实现。另外,不经常使用的HashTable直接实现了Map接口。它和Vector都是JDK1.0就引入的集合类。
Iterator是遍历集合的迭代器(不能遍历Map,仅仅用来遍历Collection)。Collection的实现类都实现了iterator()函数。它返回一个Iterator对象。用来遍历集合,ListIterator则专门用来遍历List。而Enumeration则是JDK1.0时引入的,作用与Iterator同样。但它的功能比Iterator要少。它仅仅能再Hashtable、Vector和Stack中使用。
抽象类AbstractCollection、AbstractList和AbstractSet分别实现了Collection、List和Set接口,这就是在Java集合框架中用的非常多的适配器设计模式,用这些抽象类去实现接口,在抽象类中实现接口中的若干或所有方法,这样以下的一些类仅仅需直接继承该抽象类,并实现自己须要的方法就可以,而不用实现接口中的所有抽象方法。
Arrays和Collections是用来操作数组、集合的两个工具类。比如在ArrayList和Vector中大量调用了Arrays.Copyof()方法。而Collections中有非常多静态方法能够返回各集合类的synchronized版本号,即线程安全的版本号。当然了。假设要用线程安全的结合类,首选Concurrent并发包下的相应的集合类。
容器类持有对象方式
1、 Collection:一个独立元素的序列,这些元素都服从一条或者多条规则。
List必须按照插入的顺序保存元素,而set不能有重复的元素。
Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
可以用add()方法向Collection对象中加元素。
boolean add(Object o):向容器中添加指定的元素
boolean remove(Object o)删除指定的对象
int size()返回当前集合中元素的数量
boolean contains(Object o)查找集合中是否有指定的对象
boolean isEmpty()判断集合是否为空
Iterator iterator():返回能够遍历当前集合中所有元素的迭代器
boolean containsAll(Collection c)查找集合中是否有集合c中的元素
boolean addAll(Collection c)将集合c中所有的元素添加给该集合
void clear()删除集合中所有元素
void removeAll(Collection c)从集合中删除c集合中也有的元素
void retainAll(Collection c)从集合中删除集合c中不包含的元素
Object[] toArray():返回包含此容器中所有元素的数组。
Object set(int index,Object element):将下标为index的那个元素置为element
Object add(int index,Object element):在下标为index的位置添加一个对象element
Object put(Object key,Object value):向容器中添加指定的元素
Object get(Object key):获取关键字为key的那个对象
package IT;
import java.util.ArrayList;
public class App
{
public static void main(String[] args)
{
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(12);
arrayList.add(10);
arrayList.add(35);
arrayList.add(100);
System.out.println("原容器中的元素为:");
System.out.println(arrayList);
System.out.println("\n");
/*******重置set(int index,Object element)*******/
System.out.println("将下标为1位置的元素置为20,将下标为2位置的元素置为70");
arrayList.set(1, 20);
arrayList.set(2, 70);
System.out.println("重置之后容器中的元素为:");
System.out.println(arrayList);
System.out.println("\n");
/*******中间插队add(int index,Object element)*******/
System.out.println("在下标为1的位置插入一个元素,-----插入元素:此时容器后面的元素整体向后移动");
arrayList.add(1, 80);//在下标为1的位置插入一个元素,此时容量加1,-----位置后面的元素整体向后移动
System.out.println("插入之后容器中的元素为:");
System.out.println(arrayList);
System.out.println("插入之后容器中的容量为:");
System.out.println(arrayList.size());
System.out.println("\n");
/*******中间删除元素remove(int index)*******/
System.out.println("将下标为3位置的元素70删除,-----删除元素:此时容器位置后面的元素整体向前移");
arrayList.remove(3);
System.out.println("删除之后容器中的元素为:");
System.out.println(arrayList);
System.out.println("删除之后容器中的容量为:");
System.out.println(arrayList.size());
}
}
2 、Map:一组以“键-值”(key-value)的形式出现的配对,Map也不接受重复的key值。
你能够用put()方法往Map里面加元素。
Collection 和 Collections的差别:
Collections是个java.util下的类,它包括有各种有关集合操作的静态方法,实现对各种集合的搜索、排序、线程安全化等操作。
Collection是个java.util下的接口。它是各种集合结构的父接口。继承自它的接口主要有Set 和List。
Iterator接口
上述全部的集合类。都实现了Iterator接口
Java的迭代器(Iterator)只能单向移动(ListIterator可以双向)
Iterator用来:
- 使用方法iterator()要求容器返回一个Iterator
- 使用next()方法来获得序列中的下一个元素
- 使用hasNext()来判断序列中是否还有元素
- 使用remove将迭代器元素删除
这是一个用于遍历集合中元素的接口,主要包括hashNext()、next()、remove()三种方法。它的一个子接口ListIterator在它的基础上又加入了三种方法,各自是add(),previous(),hasPrevious()。也就是说假设实现Iterator接口,那么在遍历集合中元素的时候,仅仅能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口。比方HashSet、HashMap;
而那些元素有序的集合,实现的一般都是ListIterator接口,实现这个接口的集合能够双向遍历,既能够通过next()訪问下一个元素,又能够通过previous()访问前一个元素,比方ArrayList。
Set接口
Set是一种不包括反复的元素的Collection,即随意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
Set的构造函数有一个约束条件,传入的Collection參数不能包括反复的元素。
Set推断两个对象同样不是使用==运算符,而是依据equals方法。
var set2 = mutable.Set.empty[Int]
set2 += 10
set2 ++= List(50,100,200)
set2 += 500
println("Set输出的结果:")
println(set2)
var map3 = mutable.Map.empty[String,Double]
map3 += "Spark"->90.0
map3 += "Hadoop"->80.0
map3 ++= List("Scala"->100.0,"Java"->60.0)
println("Map输出的结果:")
println(map3)
Set基本特性
无序性:不保证元素的存入顺序和取出顺序一致。
不可重复性:两个对象不能相同。
HashSet
HashSet:是Set接口的一个子类,根据哈希码进行存放。底层数据结构是哈希表。
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,不添加
是false:就直接添加到集合
否:就直接添加到集合
最终:
自动生成hashCode()和equals()即可
基本的特点是:存放元素无序、不重复(元素插入的顺序与输出的顺序不一致)。
HashSet有下面特点:
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素能够是null,但仅仅能放入一个null
HashSet<String> S= new HashSet<String>();
s.add("first");
s.add("second");
s.add("first");
for(String k:S){
System.out.println(S)
}
输出结果:
second first
public class TestHashSet
{
public static void main(String [] args)
{
HashSet h=new HashSet();
h.add("1st");
h.add("2nd");
h.add(new Integer(3));
h.add(new Double(4.0));
h.add("2nd"); //重复元素,未被添加
h.add(new Integer(3)); //重复元素,未被添加
h.add(new Date());
System.out.println("开始:size="+h.size());
Iterator it=h.iterator();
while(it.hasNext())
{
Object o=it.next();
System.out.println(o);
}
h.remove("2nd");
System.out.println("移除元素后:size="+h.size());
System.out.println(h);
}
}
当向HashSet结合中存入一个元素时。HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后依据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合推断两个元素相等的标准是两个对象通过equals方法比較相等。而且两个对象的hashCode()方法返回值相等
其规则是假设两个对象通过equals方法比較返回true时,其hashCode也应该同样。另外,对象中用作equals比較标准的属性,都应该用来计算 hashCode的值。
LinkedHashSet
底层数据结构由链表和哈希表组成。
由链表保证元素有序。
由哈希表保证元素唯一。
LinkedHashSet集合相同是依据元素的hashCode值来决定元素的存储位置。当遍历该集合时候,LinkedHashSet将会以元素的加入顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的所有元素时。性能比HashSet好,可是插入时性能略微逊色于HashSet。
TreeSet类
底层数据结构是红黑树。(是一种自平衡的二叉树)
如何保证元素唯一性呢?
根据比较的返回值是否是0来决定
如何保证元素的排序呢?
两种方式
自然排序(元素具备比较性)
让元素所属的类实现Comparable接口
比较器排序(集合具备比较性)
让集合接收一个Comparator的实现类对象
TreeSet是SortedSet接口的唯一实现类,采用树形链表存储。TreeSet能够确保集合元素处于排序状态。
TreeSet支持两种排序方式,自然排序和定制排序,当中自然排序为默认的排序方式。
向TreeSet中增加的应该是同一个类的对象。
TreeSet推断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比較没有返回0
public class TestTreeSet
{
public static void main(String [] args)
{
TreeSet ts=new TreeSet();
ts.add("orange");
ts.add("apple");
ts.add("banana");
ts.add("grape");
Iterator it=ts.iterator();
while(it.hasNext())
{
String fruit=(String)it.next();
System.out.println(fruit);
}
}
}
var treeSet = TreeSet(10,20,30,90,100,200,50)
println(treeSet)
/*键值对排序是根据key的值进行排序的,没有value的事情,让我联想到了MapReduce中排序的时候之所以根据k2
而不是v2的值进行排序,这是因为哈希映射内部决定的,而不是MapReduce决定的
呵呵!注意:排序区分大小写的哦!!!*/
var treeSet2 = TreeSet[String]("Spark","Anang","Baby","Hello")
println(treeSet2)
var treeMap = TreeMap[String,Integer]("Java"->100,"Scala"->88,"Python"->60,"Anglebaby"->500)
println(treeMap)
toString()方法
凡是把类对象放到容器中,相应的类都应该实现Object类中的toString()方法;凡是Java中自带的数据类型,都已经重写完了toString()方法
实例:
未重写toString()方法之前
package IT;
public class App
{
public static void main(String[] args)
{
//Java中自带的类
System.out.println("-----凡是Java中自带的数据类型都已经重写完了toString()方法!---");
System.out.println(new Integer(2).toString());
System.out.println(new String("zhang").toString());
//用户自定义的类Student
System.out.println(new Student("zhangsan",99.8).toString());
}
}
class Student
{
public String name;
public double score;
public Student(String name,double score)
{
this.name = name;
this.score = score;
}
}
重写toString()方法后:
package IT;
import java.util.ArrayList;
public class App
{
public static void main(String[] args)
{
ArrayList<Student> arr = new ArrayList<Student>();
arr.add(new Student("zhangsan",89.8));
arr.add(new Student("lisi",90));
arr.add(new Student("wangwu",60.6));
System.out.println(arr);
}
}
class Student
{
public String name;
public double score;
public Student(String name,double score)
{
this.name = name;
this.score = score;
}
public String toString()
{
return this.name+"\t"+this.score;
}
}
自然排序:
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素依照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就能够比較大小。
obj1.compareTo(obj2)方法假设返回0,则说明被比較的两个对象相等
假设返回一个正数。则表明obj1大于obj2
假设是负数,则表明obj1小于obj2。
假设我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是依据集合元素的大小。以升序排列,假设要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法
List接口
List是有序的Collection(单列集合)。使用此接口可以精确的控制每一个元素插入的位置。用户可以使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口。和标准的Iterator接口相比。ListIterator多了一些add()之类的方法,添加、删除、设定元素。 还能向前或向后遍历。
实现List接口的经常使用类有LinkedList,ArrayList,Vector和Stack。
LinkedList、ArrayList都实现了List接口,都是不同步的,线程不安全,元素是有序的、可重复
List基本特性
有序性:指的是元素的存入顺序和取出顺序一致,每个元素对应一个index。
重复性:指的是equals 方法比较后的结果相等。
ArrayList类
线程不安全
内部使用Object数组存储对象,在容量不满足时会进行扩容,所谓扩容,就是创建新的数组,并复制原来的数据。
因为是数组结构,所以复制时使用了native方法
每次扩容变为原来数组长度的1.5倍,但不会超过Integer.MAX_VALUE -8 。
通过索引检索数据,效率高。
ArrayList类:内部存储采用数组结构实现的List容器,实现了可变大小的数组,不同步,线程不安全,查询(get set)效率高。
优点:随机访问元素(get,set方法),查询快
缺点:中间插入和移除元素比较慢(add和remove方法),add()时判断是否数组越界,数组扩容为原来的1.5倍,线程不安全,效率高。
ArrayList没有同步。使用size,isEmpty,get,set方法执行时间为常数。add方法开销为分摊的常数。加入n个元素须要O(n)的时间。
这个容量可随着不断加入新元素而自己主动添加。可是增长算法并未定义。当须要插入大量元素时,在插入前能够调用ensureCapacity方法来添加ArrayList的容量以提高插入效率。
和LinkedList一样,ArrayList也是非同步的(unsynchronized),效率比较高。
泛型类举例:
定义ArrayList,用于存放String的类
notes为对象的管理者
private ArrayList<String> notes=new ArrayList<String>();
package IT;
import java.util.ArrayList;
import java.util.Iterator;
//数组的所有功能通过ArrayList容器都可以实现,只是实现的方式不同
public class App
{
public static void main(String[] args)
{
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(12);
arrayList.add(10);
arrayList.add(35);
arrayList.add(100);
Iterator<Integer> iterator = arrayList.iterator();//获取容器的迭代器
while(iterator.hasNext())
{
Integer value = iterator.next();//获取当前游标右边的元素,同时游标右移-->
System.out.println(value);
}
System.out.println("通过ArrayList容器获取一个数组arr:");
Object[] arr = arrayList.toArray();
for(int i=0;i<arr.length;i++)
{
System.out.println(arr[i]);
}
}
}
LinkedList类
LinkedList类:内存存储采用双向链表结构实现的List容器,允许null元素,增加、删除、修改元素方面效率比ArrayList高。
线程不安全,效率高
优点:中间插入和移除元素速度快(add和remove方法)
短处:随机访问数据(get和set方法),查询慢
此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意:LinkedList没有同步方法。假设多个线程同一时候访问一个List,则必须自己实现访问同步。
一种解决方法是在创建List时构造一个同步的List:
List list =Collections.synchronizedList(new LinkedList(...));
Vector类
底层数据结构是数组,查询快,增删慢,线程安全,效率低
所有读写的方法都添加了synchronized修饰,因此多了线程安全的特性,但牺牲了访问速度。
扩容方式默认是成倍增长,也可以自己指定
Collections.synchronizedList( List list ) 也提供了同样的功能。
Vector很类似ArrayList,可是Vector是同步的。
由Vector创建的Iterator,尽管和ArrayList创建的Iterator是同一接口。可是,由于Vector是同步的。当一个Iterator被创建并且正在被使用,还有一个线程改变了Vector的状态(比如: 加入或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException。因此必须捕获该异常。
Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。主要的push和pop方法。还有peek方法得到栈顶的元素,empty方法測试堆栈是否为空,search方法检測一个元素在堆栈中的位置。Stack刚创建后是空栈。
ArrayList和Vector的差别:
同步性: Vector是线程安全的,即同步的。而ArrayList是线程序不安全的,不是同步的。由Vector创建的Iterator,尽管和ArrayList创建的Iterator是同一接口。可是,由于Vector是同步的,当一个Iterator被创建并且正在被使用,还有一个线程改变了Vector的状态(比如,加入或删除了一些元素)。这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
数据增长: 当须要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
Array与ArrayList的区别及各自适用的场景:
(1)array可以保存基本类型和对象类型,arrayList只能保存对象类型
(2)array数组的大小是固定的不能更改,而ArrayList的大小可以改变
(3)Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。
(4)ArrayList有更加丰富的方法如addAll()、removeAll()、iterator()
适用场景:
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里,但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList。
Map接口
Map没有继承Collection接口,以键值对的方式来进行存储,即key到value的映射。
一个Map中不能包括同样的key。每一个key仅仅能映射一个 value。
Map接口提供3种集合的视图。
Map的内容能够被当作一组key集合,一组value集合,或者一组key-value映射。
boolean equals(Object o)比较对象
boolean remove(Object o)删除一个对象
put(Object key,Object value)添加key和value
Hashtable类
Hashtable类
底层数据结构是哈希表。线程安全,效率低
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,不添加
是false:就直接添加到集合
否:就直接添加到集合
最终:
自动生成hashCode()和equals()即可
Hashtable继承Map接口,实现一个key-value映射的哈希表。不论什么非空的对象都可作为key或者value。
Hashtable是同步的。使用key的hashCode计算数组索引。
加入数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
举例:
将1,2,3放到Hashtable中,他们的key各自 是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));</span>
要取出一个数,比方2,用对应的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
因为作为key的对象将通过计算其散列函数来确定与之相应的value的位置,因此作为key的对象都必须实现hashCode和equals方法。
实例:
重写前:
package IT;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
public class App
{
public static void main(String[] args)
{
//Java中自带的数据类型
System.out.println("先测试Java中自带的数据类型:");
HashMap<String, Double> hashMap1 = new HashMap<String,Double>();
hashMap1.put("zhangsan", 96.0);
hashMap1.put("lisi", 88.6);
hashMap1.put("wangwu", 98.6);
hashMap1.put("wangting", 87.5);
hashMap1.put("zhangsan", 96.0);
hashMap1.put("lisi", 88.6);
hashMap1.put("wangwu", 98.6);
hashMap1.put("wangting", 87.5);
Set<String> keySet = hashMap1.keySet();
Iterator<String> iterator = keySet.iterator();
while(iterator.hasNext())
{
String key = iterator.next();
System.out.println(key+"\t"+hashMap1.get(key));
}
System.out.println("Java中自带的数据类型:相同的对象会覆盖!");
System.out.println("\n");
//用户自定义的数据类型:为重写之前
System.out.println("测试用户自定义的数据类型--未重写两个方法之前:");
HashMap<Student, String> hashMap2 = new HashMap<Student,String>();
hashMap2.put(new Student("zhangsan",88.8), "beijing");
hashMap2.put(new Student("lisi",88.8), "beijing");
hashMap2.put(new Student("wangwu",66.9), "beijing");
hashMap2.put(new Student("zhangsan",88.8), "beijing");
hashMap2.put(new Student("lisi",88.8), "beijing");
hashMap2.put(new Student("wangwu",66.9), "beijing");
Set<Student> keySet2 = hashMap2.keySet();
Iterator<Student> iterator2 = keySet2.iterator();
while(iterator2.hasNext())
{
Student key = iterator2.next();
System.out.println(key+"\t"+hashMap2.get(key));
}
System.out.println("如果没有重写:导致相同的对象不会被覆盖!");
}
}
class Student implements Comparable<Student>
{
public String name;
public double score;
public Student(String name,double score)
{
this.name = name;
this.score = score;
}
public String toString()
{
return this.name+"\t"+this.score;
}
public int compareTo(Student obj)
{
if(this.score > obj.score)
return 1;
else
return -1;
}
}
结果:
先测试Java中自带的数据类型:
wangting 87.5
wangwu 98.6
lisi 88.6
zhangsan 96.0
Java中自带的数据类型:相同的对象会覆盖!
测试用户自定义的数据类型–为重写两个方法之前:
zhangsan 88.8 beijing
wangwu 66.9 beijing
lisi 88.8 beijing
wangwu 66.9
beijing zhangsan
88.8 beijing
lisi 88.8 beijing
如果没有重写:导致相同的对象不会被覆盖!
重写后:
package IT;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
public class App
{
public static void main(String[] args)
{
//用户自定义的数据类型:为重写之后
System.out.println("测试用户自定义的数据类型--重写两个方法之后:");
HashMap<Student, String> hashMap2 = new HashMap<Student,String>();
hashMap2.put(new Student("zhangsan",88.8), "beijing");
hashMap2.put(new Student("lisi",88.8), "beijing");
hashMap2.put(new Student("wangwu",66.9), "beijing");
hashMap2.put(new Student("zhangsan",88.8), "beijing");
hashMap2.put(new Student("lisi",88.8), "beijing");
hashMap2.put(new Student("wangwu",66.9), "beijing");
Set<Student> keySet2 = hashMap2.keySet();
Iterator<Student> iterator2 = keySet2.iterator();
while(iterator2.hasNext())
{
Student key = iterator2.next();
System.out.println(key+"\t"+hashMap2.get(key));
}
System.out.println("重写过后:相同的对象会被覆盖!");
}
}
class Student implements Comparable<Student>
{
public String name;
public double score;
public Student(String name,double score)
{
this.name = name;
this.score = score;
}
public String toString()
{
return this.name+"\t"+this.score;
}
public int compareTo(Student obj)
{
if(this.score > obj.score)
return 1;
else
return -1;
}
@Override
public int hashCode()
{
return (int) (this.name.hashCode()*score);//保证相同对象映射到同一个索引位置
}
@Override
public boolean equals(Object obj)
{
Student cc = (Student)obj;
return this.name==cc.name&&this.score==cc.score;
}
}
结果:
测试用户自定义的数据类型–重写两个方法之后:
wangwu 66.9 beijing
zhangsan 88.8 beijing lisi
88.8 beijing
重写过后:
相同的对象会被覆盖!
hashCode和equals方法继承根类Object,假设你用自己定义的类当作key的话。要相当小心,依照散列函数的定义,假设两个对象同样,即obj1.equals(obj2)=true,则它们的hashCode必须同样。
但假设两个对象不同。则它们的hashCode不一定不同,假设两个不同对象的hashCode同样,这样的现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
要牢记一条:要同一时候复写equals方法和hashCode方法,而不要仅仅写当中一个。
HashMap类
底层数据结构是哈希表。线程不安全,效率高
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,添加到value值链上
是false:就直接添加到集合
否:就直接添加
最终:
自动生成hashCode()和equals()即可
HashMap类:内部“键”从用Set存放,不能重复,键相同,值覆盖
HashMap和Hashtable类似,不同之处在于HashMap是非同步的。而且允许null。即null value和null key。
可是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,假设迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高或者load factor过低。
TreeMap类
用来存储键值的映射关系,其中不允许重复的键。
WeakHashMap类
WeakHashMap是一种改进的HashMap。它对key实行“弱引用”,假设一个key不再被外部所引用,那么该key能够被GC回收。
总结
假设涉及到堆栈,队列等操作。应该考虑用List
对于须要高速插入,删除元素,应该使用LinkedList
假设须要高速随机訪问元素。应该使用ArrayList。
假设程序在单线程环境中,或者訪问只在一个线程中进行,考虑非同步的类,其效率较高。
假设多个线程可能同一时候操作一个类,应该使用同步的类。
Queue
队列,先入先出,FIFO,由linkedList实现,LinkedList采用双向链表,本身就有addFirst addLast getFirst getLast等功能的需求,而队列是只是特定功能的LinkedList,二者实现的方法都是一样
public interface Queue<E> extends Collection<E> {
boolean add(E e); // 添加元素到队列中,相当于进入队尾排队。
boolean offer(E e); //添加元素到队列中,相当于进入队尾排队.
E remove(); //移除队头元素
E poll(); //移除队头元素
E element(); //获取但不移除队列头的元素
E peek(); //获取但不移除队列头的元素
}
remove()和poll()方法删除并返回队列的头。 从队列中删除哪个元素是队列排序策略的一个功能,它与实现不同。 remove()和poll()方法在队列为空时的行为不同: remove()方法抛出异常,而poll()方法返回null 。
ArrayList和Vector的区别
Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
集合中键值对null值的允许
List:可以有多个null,可以有重复值
HashSet:能插入一个null(因为内部是以 HashMap实现 ),忽略不插入重复元素。
TreeSet:不能插入null (因为内部是以 TreeMap 实现 ) ,元素不能重复,如果待插入的元素存在,则忽略不插入,对元素进行排序。
HashMap:允许一个null键与多个null值,若重复键,则覆盖以前值。
TreeMap:不允许null键(实际上可以插入一个null键,如果这个Map里只有一个元素是不会报错的,因为一个元素时没有进行排序操作,也就不会报空指针异常,但如果插入第二个时就会立即报错),但允许多个null值,覆盖已有键值。
HashTable:不允许null键与null值(否则运行进报空指针异常)。也会覆盖以重复值。基于线程同步。
Iterator和ListIterator的区别是什么
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引。
HashTable和HashMap区别
(1)继承不同
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
(2)Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
(3)HashTable不同意null值(key和value都不能够),HashMap同意null值(key和value都能够)。在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
(4)两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
(5)哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
(6) HashTable的应用很广泛。HashMap是Hashtable的轻量级实现(非线程安全的实现)。
(7)HashMap把Hashtable的contains方法去掉了。改成containsvalue和containsKey。
HashMap与TreeMap的区别
HashMap:键和值都能为空,键没有排序功能
TreeMap:键和值都不能为空,键有排序功能
Collections和Collection有什么区别
Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。
它有两个常用的子接口:
——List:对元素都有定义索引。有序的。可以重复元素。
——Set:不可以重复元素。无序。
Collections是集合框架中的一个工具类。该类中的方法都是静态的。
提供的方法中有可以对list集合进行排序,二分查找等方法。
通常常用的集合都是线程不安全的。因为要提高效率。
如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。
继承
继承关键字:extends/implements
implements关键字使用可以变向的使java具有多重继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口与接口之间采用逗号分隔)
继承中,子类会具有父类的一般特性也会具有自身的特性,需要注意的是java不支持多继承,但支持多重继承
继承的特性
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() {
//吃东西方法的具体实现 }
public void sleep() {
//睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。(父类元构造器不需要使用)
super与this关键字不能同时存在于同一个构造方法中
this关键字:指向自己的引用。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
输出结果为:
animal : eat
dog : eat
animal : eat
this在实例方法和构造方法种的作用
this是java多态的体现之一
this只可以在构造方法和实例方法种存在,不能出现在static修饰的方法或代码块中
this在构造方法中表示刚刚创建的对象
this在实例方法种表示调用改方法的对象
this可以在实例方法和构造方法中访问对象属性和实例方法
this有时可以省略
this可以在实例方法中作为返回值
this可以当作实参
this可调用重载的构造方法
final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;
用于修饰方法,该方法不能被子类重写
final修饰的变量(成员变量和局部变量)是常数,只能赋值一次
声明类:final class 类名 {//类体}
声明方法:修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final
对象与类
对象是实体,需要被创建,可以为我们做事情
类是规范,根据类的定义来创建对象
对象=属性+服务
数据:属性或状态
操作:函数
把数据和数据的操作放在一起就是封装
创建对象的四种方式
1、使用new关键字
2、对象反序列化
3、反射
4、clone
构造器
概述:构造方法存在于类中,给对象数据(属性)初始化;
构造方法也叫构造器或者构造函数
构造方法与类名相同,没有返回值,连void都不能写
构造方法可以重载(重载:方法名称相同,参数列表不同),不能重写
子类中不能定义一个方法无void无返回值的方法,编译错误,即子类无法继承构造方法,但是子类的构造器中可以调用父类的构造方法(默认自动调用无参构造)
如果一个类中没有构造方法,那么编译器会为类加上一个默认的构造方法。
默认构造方法格式如下:
public 类名() {
}
如果手动添加了构造器,那么默认构造器就会消失。
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
构造方法在创建对象时调用,具体调用哪一个由参数决定。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
// SubClass 类继承
class SubClass extends SuperClass{
private int n;
SubClass(){
// 自动调用父类的无参数构造器
System.out.println("SubClass");
}
public SubClass(int n){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
private int n;
SubClass2(){
super(300); // 调用父类中带无参数的构造器
System.out.println("SubClass2");
}
public SubClass2(int n){
// 自动调用父类的有参数构造器
System.out.println("SubClass2(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
System.out.println("------SubClass 类继承------");
SubClass sc1 = new SubClass();
SubClass sc2 = new SubClass(100);
System.out.println("------SubClass2 类继承------");
SubClass2 sc3 = new SubClass2();
SubClass2 sc4 = new SubClass2(200);
}
}
输出结果为:
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
构造方法负责对象成员的初始化工作,为实例变量赋予合适的初始值。
构造方法的语法规则:方法名与类名相同。没有返回类型。
使用new关键字实例化对象的过程实际上就是调用构造方法的过程。或者说实例化一个对象实际上就是去调用这个对象的构造方法。
Xin xin=new Xin();
在实例化对象的时候,已经实例变量赋予了初始值,完成了对象的初始化工作。
如果一个成员函数的名字和类的名字完全相同,则在创建这个类的每一个对象的时候会自动调用这个函数
在类中定义,构造函数不是开发人员调用的,而是交给jvm调用的
为什么需要构造方法?
为属性赋值时,很容易忘记对其中的一项或多项进行赋值。构造方法可以简化对象初始化,为实例变量赋值。
构造方法中this的使用
构造方法种可以使用this,表示刚刚创建的对象
构造方法种this可用于
this访问对象属性
this访问实例方法
this在构造方法中调用重载的其他构造方法(要避免陷入死循环)
只能位于第一行,不会触发新对象的创建
public class Student {
public String name;
public int age;
public void eat() {
System.out.println("eat....");
}
//构造器
//使用this()调用重载构造器不能同时相互调用,避免陷入死循环
public Student() {
//this()必须出现在构造器的第一行,不会创建新的对象
this(15);//调用了具有int类型参数的构造器
System.out.println("默认构造器");
}
public Student(int a) {
this.eat();
eat();//this.可以省略
}
//this在构造器中表示刚刚创建的对象
public Student(int a, String s) {
System.out.println("两个参数的构造器");
this.age = a;
this.name = s;
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student(15, "小明");
System.out.println(s1.name + ":" + s1.age);
Student s2 = new Student(12, "小红");
System.out.println(s2.name + ":" + s2.age);
Student s3 = new Student();
}
}
带参数的构造方法
可以显示地为实例变量赋予初始值。在不同的条件下创建不同的对象,这些对象的实例变量具有不同的值。
注意:在使用带参数的构造方法实例化对象时,传递的值和构造方法的参数应当在个数、次序和数据类型上相互配备。
通过调用带参数的构造方法,在创建对象时,一并完成了对象的初始化工作,简化了对象初始化的代码。
无参构造函数
public 类名(){
}
1、构造函数是由虚拟机调用的
2、构造函数函数名必须和类型保持一致
3、构造函数是返回值格式的
构造函数的作用:
1、构造函数式初始化,是创建对象用的
2、一个类在编译的时候虚拟机默认给该类添加一个无参的构造函数
3、当类中不存在构造函数的时候,默认添加一个无参的构造函数,但如果添加了构造函数,虚拟机就不再添加无参的构造函数
4、构造函数初始化并且给成员变量赋值
封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
什么是封装?
封装就是将属性私有化,提供公有的方法访问私有属性。
做法就是:修改属性的可见性来限制对属性的访问,并为每个属性创建一对取值(getter)方法和赋值(setter)方法,用于对这些属性的访问。
private String name;
public String getName(){
return;
}
public void setName(String name){
this.name=name;
}
为什么需要封装?
通过封装,可以实现对属性的数据访问限制,同时增加了程序的可维护性。
由于取值方法和赋值方法隐藏了实现的变更,因此并不会影响读取或修改该属性的类,避免了大规模的修改,程序的可维护性增强。
this关键字是什么意思?
有时一个方法需要引用调用它的对象。为此,Java定义了this这个关键字。简单地说,This是在对象内部指代自身的引用。可以直接引用对象,可以解决实例变量和局部变量之间发生的任何同名的冲突。
如何实现封装,实现封装的具体方法?
(1)修改属性的可见性来限制对属性的访问。
(2)为每个属性创建一对赋值方法和取值方法,用于对这些属性的访问。
(3)在赋值和取值方法中,加入对属性的存取的限制。
实现Java封装的步骤
- 修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person {
private String name;
private int age;
}
这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
2、 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。
实例:
/* 文件名: EncapTest.java */
public class EncapTest{
private String name;
private String idNum;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public String getIdNum(){
return idNum;
}
public void setAge( int newAge){
age = newAge;
}
public void setName(String newName){
name = newName;
}
public void setIdNum( String newId){
idNum = newId;
}
}
以上实例中public方法是外部类访问该类成员变量的入口。
通常情况下,这些方法被称为getter和setter方法。
因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。
/* F文件名 : RunEncap.java */
public class RunEncap{
public static void main(String args[]){
EncapTest encap = new EncapTest();
encap.setName("James");
encap.setAge(20);
encap.setIdNum("12343ms");
System.out.print("Name : " + encap.getName()+
" Age : "+ encap.getAge());
}
}
方法重载(overload)
重载体现了java的多态
方法名称相同,参数项(个数、类型、次序)不相同。认为一个方法是另一个方法的重载方法。
注意:重载只跟参数有关,与返回类型无关。方法名和参数相同而返回类型不相同,不能说是重载。
典型的方法重载:System.out.println(); Sysstem.out代表了一个java.io.PrintSteam对象,具有多个println(打印)方法,该方法可
以接收不同类型的数据作为参数根据类型不同,
调用不同的打印方法。
在java.lang包中的Math类中的max方法。
Public static int max(int a,intb);
Public static int max(long a,long b);
Public static int max(float a,float b);
Public static int max(double a,double b);
在调用方法之前,java虚拟机先判断给定的类型,然后决定到底调用执行那个max()方法。
构造方法重载
构造方法重载是方法重载的一个典型的特例。参数列表不同。
可以通过重载构造方法来表达对象的各种多种初始化行为。也就是说在通过new语句创建一个对象时,可以实现在不同的条件下,让不同的对象具有不同的初始化行为。
Private String name;
Private String sex;
Public Xin(String name){
this.name=name;
}
Public Xin(String name,String sex){
this.name=name;
this.sex=sex;
}
以下对重载描述错误的是(B)
A、方法重载只能发生制一个类的内部
B、构造方法不能重载
C、重载要求方法名相同,参数列表不同
D、犯法的返回值类型不是区分方法重载的条件
解析:
重载要求方法名相同、参数列表不同,构造方法可以重载,但不能重写。
重载:存在于一个类中,方法名相同,方法参数的类型或个数不同
重写:存在于子父类中,方法名、方法参数、返回值全部相同
重写与重载的区别
线程
线程的生命周期
线程的生命周期包括5个阶段,新建、就绪、运行、阻塞、销毁
• 新建:使用new关键字创建一个线程对象,这个阶段仅仅是在JVM中开辟一个内存空间;
• 就绪:调用线程对象的.start方法(),使线程处于runnable可运行状态;
• 运行:CPU分配时间片给线程,线程开始执行run方法里面定义的各种操作;
• 阻塞:线程在运行状态的时候,可能会遇到一些特殊情况,导致线程停止下来,如sleep(),wait(),处于阻塞状态的线程需要等待其他机制使得线程的阻塞状态被唤醒,比如调用notify(),notifyall()。被唤醒的线程不会立即进行执行run方法的操作,而是需要等待CPU重新分配时间片进行执行;
• 销毁:线程执行完毕,或者线程被终止、或者线程里面执行的方法出现异常,就会导致线程被销毁,释放资源;
线程调度
协作式调度模型
每个线程可以有自己的优先级,但优先级并不意味着高优先级的线程一定会被最先调度,而是由cpu时机选择的,所谓协作式的线程调度,就是说一个线程在执行自己的任务时,不允许被中途打断,一定等当前线程将任务执行完毕后才会释放对cpu的占有,其它线程才可以抢占该cpu。
抢占式调度模型
线程的切换不由线程本身控制,每个线程由系统来分配执行时间
但是可以"建议"操作系统多分配执行时间,java设置了10个级别的线程优先级,在两个线程同时处于ready状态时,优先级越高的线程越容易被系统选择
二者的比较:抢占式线程调度不易发生饥饿现象,不易因为一个线程的问题而影响整个进程的执行,但是其频繁阻塞与调度,会造成系统资源的浪费。协作式的线程调度很容易因为一个线程的问题导致整个进程中其它线程饥饿。
线程的优先级
1-10 其中10最高,1最低
垃圾回收
调用System.gc()方法通知java虚拟机立即垃圾回收
Sleep与wait()的区别
Sleep():表示当前线程休眠多少毫秒,不会释放对象的锁。
wiat():表示当前线程进锁池状态,放弃CPU的使用权利,立即释放对象的锁,直到notif()方法把该线程唤醒。
JDBC
名称 | 功能 |
---|---|
DriverManager | 驱动管理器获得数据库连接 |
Connection | 数据库连接接口 |
Statement | 语句接口,用来静态操作sql语句 |
PreparedStatement | 可以调用存储过程的预定义语句 |
ResultSet | 结果集,保存数据记录的结果集合 |
ResultSetMetaData | 结果集元数据,如:数据库名称、版本等 |
CallableStatement | 可以调用存储过程的预定义语句 |
使用Java JDBC操作数据库一般需要6步:
(1)建立JDBC桥接器,加载数据库驱动;
class.forname 加载驱动
(2)连接数据库,获得Connection对象(使用数据库连接地址,用户名,密码);
DriverManager(获得数据连接的一个工厂(实例化作用)) 获得连接
(3)获得数据库Statement对象;
(4)执行数据库操作,得到结果集ResultSet;
(5)读取结果;
(6)关闭数据库连接(statement关闭,Connection关闭);
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
public class MyTest {
public static void main(String args[]) {
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
// 获得MySQL驱动的实例
Class.forName("com.mysql.jdbc.Driver").newInstance();
// 获得连接对象(提供:地址,用户名,密码)
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1/store","root", "1234");
if (!con.isClosed())
System.out.println("Successfully connected ");
else
System.out.println("failed connected");
//建立一个Statement,数据库对象
st = con.createStatement();
// 运行SQL查询语句
rs = st.executeQuery("select * from Weather.question_type_1;");
// 读取结果集
while (rs.next()) {
System.out.println("column1:"+rs.getInt(1));
System.out.println("column2:"+rs.getString(2));
System.out.println("column3:"+rs.getString(3));
System.out.println("column4:"+rs.getString(4));
}
// 关闭链接
con.close();
} catch(Exception e) {
System.err.println("Exception: " + e.getMessage());
}
}
}
Statement与PreparedStatement的区别
Statement:适合执行批处理,不能接收参数,有SQL注入风险。
PreparedStatement:预编译的语句,能接收参数,效率高,防止SQL注入。
Dao模式
什么是DAO模式
DAO (Data Access objects) 是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来。
DAO(Data Access Object)是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。
在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。
用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。
DAO模式实际上包含了两个模式:
Data Accessor(数据访问器):解决如何访问数据的问题
Data Object(数据对象):解决的是如何用对象封装数据
对外提供相应的接口
在面向对象设计过程中,有一些"套路”用于解决特定问题称为模式。
DAO 模式提供了访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口。
从以上 DAO 模式使用可以看出,DAO 模式的优势就在于它实现了两次隔离。
1、隔离了数据访问代码和业务逻辑代码。业务逻辑代码直接调用DAO方法即可,完全感觉不到数据库表的存在。分工明确,数据访问层代码变化不影响业务逻辑代码,这符合单一职能原则,降低了藕合性,提高了可复用性。
2、隔离了不同数据库实现。采用面向接口编程,如果底层数据库变化,如由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQ 实现不用修改。这符合 “开-闭” 原则。该原则降低了代码的藕合性,提高了代码扩展性和系统的可移植性。
DAO(Data Access Object)介绍
DAO应用在数据层那块(对于数据库进行的原子操作,增加、删除等;)用于访问数据库,对数据库进行操作的类。
典型DAO层次分析
1、DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。
2、DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。
3、实体类:用于存放与传输对象数据。
4、数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改。
DAO的好处
DAO的好处就是提供给用户的接口只有DAO的接口,所以如果用户想添加数据,只需要调用create函数即可,不需要数据库的操作。
DAO模式举例
DAO 接口:
public interface PetDao {
/**
* 查询所有宠物
*/
List<Pet> findAllPets() throws Exception;
}
DAO 实现类:
public class PetDaoImpl extends BaseDao implements PetDao {
/**
* 查询所有宠物
*/
public List<Pet> findAllPets() throws Exception {
Connection conn=BaseDao.getConnection();
String sql="select * from pet";
PreparedStatement stmt= conn.prepareStatement(sql);
ResultSet rs= stmt.executeQuery();
List<Pet> petList=new ArrayList<Pet>();
while(rs.next()) {
Pet pet=new Pet(
rs.getInt("id"),
rs.getInt("owner_id"),
rs.getInt("store_id"),
rs.getString("name"),
rs.getString("type_name"),
rs.getInt("health"),
rs.getInt("love"),
rs.getDate("birthday")
);
petList.add(pet);
}
BaseDao.closeAll(conn, stmt, rs);
return petList;
}
}
Dao实体类:
public class Pet {
private Integer id;
private Integer ownerId; //主人ID
private Integer storeId; //商店ID
private String name; //姓名
private String typeName; //类型
private int health; //健康值
private int love; //爱心值
private Date birthday; //生日
}
DAO工具类:
public class BaseDao {
private static String driver="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/epet";
private static String user="root";
private static String password="root";
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
public static void closeAll(Connection conn,Statement stmt,ResultSet rs) throws SQLException {
if(rs!=null) {
rs.close();
}
if(stmt!=null) {
stmt.close();
}
if(conn!=null) {
conn.close();
}
}
public int executeSQL(String preparedSql, Object[] param) throws ClassNotFoundException {
Connection conn = null;
PreparedStatement pstmt = null;
/* 处理SQL,执行SQL */
try {
conn = getConnection(); // 得到数据库连接
pstmt = conn.prepareStatement(preparedSql); // 得到PreparedStatement对象
if (param != null) {
for (int i = 0; i < param.length; i++) {
pstmt.setObject(i + 1, param[i]); // 为预编译sql设置参数
}
}
ResultSet num = pstmt.executeQuery(); // 执行SQL语句
} catch (SQLException e) {
e.printStackTrace(); // 处理SQLException异常
} finally {
try {
BaseDao.closeAll(conn, pstmt, null);
} catch (SQLException e) {
e.printStackTrace();
}
}
return 0;
}
}
TCP/ip和UDP区别
TCP协议的特点:
1、长连接
2、三次握手
3、基于流的数据传输
4、数据传输中安全可靠
TCP/ip:
1、长连接
2、给予数据传输
3、大小没有限制
4、速度慢
5、数据不会发生丢失
UDP:
1、短连接
2、给予数据包
3、包大小有限制 64k
4、传输速快,但是数据容易丢失
Junit单元测试
使用单元测试,测试代码步骤如下:
1、定义一个类,TestXXX,里面定义方法,testxxx
2、添加junit的支持
右键工程—add Library—Junit–Junit4
3、在方法的上面加上注解,其实就是一个标记
@test
public void testQuery(){
....
}
4、光标选中方法名字,然后右键执行单元测试,或者是打开outline视图,然后选择方法右键执行。
finally和finalize的区别
finally:
try{
}
catch{
}
finally{
}
finally语句块里代码无论是否出现异常,一定被执行。
finalize:是Object类的一个方法,销毁一个对象前会默认调用对象的finalize()方法。
关于变量声明
String str = "Hello!";
通常认识为:
一个String,内容是“Hello!”,然而这样模糊的回答通常是概念不清的根源。
准确的回答:
一个指向对象的引用,名为“str”,可以指向类型为String的任何对象,目前指向"Hello!"这个String类型的对象。
我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,再加一句:
String string = str;
我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是和str指向同一个对象
本地变量
定义在函数内部的变量是本地变量
本地变量的生存期和作用域都是函数内部
成员变量的生存期是对象的生存期,作用域是类内部的成员函数
本地变量的规则
本地变量定义在块内
1、它可以是定义在函数的块内
2、也可以定义在语句的块内
3、甚至可以随便拉一大括号来定义变量
块外面定义的变量在里面仍然有效
不能在一个块内定义同名的变量,也不能定义块外面定义过的变量
本地变量不会默认初始化
参数在进入函数的时候被初始化了
"=="和equals方法究竟有什么区别?
"==" 操作符特定用来比较变量的值是否相等,比较是否同一个
int a=10;
int b=10;
则a==b将是true。
String a=new String("flag");
String b=new String("flag");
则a==b将返回false,为什么出现这样的结果?
对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为"flag"的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用"= =" 操作符,结果会是false。a和b所指的对象,它们的内容都是"flag",应该是“相等”,但是==操作符并不涉及到对象内容的比较。
比较对象内容是否相同,采用equals方法:
Object对象的equals方法实现:
boolean equals(Object o){
return this==o;
}
Object对象默认使用了= =操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把这个任务留给了类的创建者。
String到底变了没有?
答案:没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。
String str = "hello";
str = str + " world!";
在这段代码中,str原先指向一个String对象,内容是"hello",然后我们对str进行了"+"操作,那么str所指向的那个对象并没有改变。这时,str不指向原来那个对象了,而指向了另一个String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是str这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
而非s = new String("Initial Value");
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。
final关键字修饰
final关键的作用:让被修饰的变量"不变",但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:
1.引用本身的不变
2.引用指向的对象不变。
引用本身的不变:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译期错误
引用指向的对象不变:
final StringBuffer a=new StringBuffer("immutable");
a.append(" try!"); //编译通过
可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。
至于它所指向的对象的变化,final是不负责的。这很类似= =操作符:= =操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。
-final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实那是徒劳的。
instanceof
instanceof是Java的一个二元操作符,和==,> , <是同一类。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。
String str = "I AM an Object!";
boolean isObject = str instanceof Object;
我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,
显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:
public class Bill {
//省略细节}
public class PhoneBill extends Bill {
//省略细节}
public class GasBill extends Bill {
//省略细节}
在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:
public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//计算电话账单
}
if (bill instanceof GasBill) {
//计算燃气账单
}
...
}
这样就可以用一个方法处理两种子类。 然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法
public double calculate(PhoneBill bill) {
//计算电话账单
}
public double calculate(GasBill bill) {
//计算燃气账单
}
JVM相关
JVM作为java运行的基础, JVM几乎是面试必问的问题。JVM技术能力可以佐证java开发能力的高低。JVM是个类,需要掌握的知识有:
JVM内存模型和结构
GC原理,性能调优
调优:Thread Dump, 分析内存结构
class 二进制字节码结构, class loader 体系 , class加载过程 , 实例创建过程
方法执行过程
Java各个大版本更新提供的新特性(需要简单了解)
基础知识点
这里主要罗列一些散落的,没有系统归类的一些java知识点。在日常的开发中用到也不少。 这块内容其实还有很多,目前只是暂时归纳了这几个:
equals , hashcode , string/stringbuffer ,final , finally , finalize
异常框架
在企业级设计开发中, 异常的设计与处理的好坏,往往就关系着这个系统整体的健壮性。一个好的系统的异常对于开发者来说,处理应该统一,避免各处散落很多异常处理逻辑;对于系统来说,异常应该是可控的,并且是易于运维的,某些异常出现后,应该有应对的方法,知道如何运维处理,所以虽然异常框架很简单,但是对于整个企业级应用开发来说,异常处理是很重要的,处理好异常就需要了解Java中的异常体系。
这部分需要掌握的知识点不多,主要就是:
异常的体系:
Throwable
Exception
RuntimeException
Error
RuntimeException 和 一般 Exception 的区别, 具体处理方法等
Java IO
IO 在java中不仅仅是文件读写那么简单,也包括了 socket 网络的读写等等一切的输入输出操作。比如说 标准HTTP请求中Post的内容的读取也是一个输出的过程等等。
对于IO,Java不仅提供了基本Input、Output相关的api,也提供了一些简化操作的Reader、Writer等api,在某些开发(涉及大量IO操作的项目)中也很重要,一般日常的开发中也会涉及(日志,临时文件的读写等)。
知识点主要有:
基本IO的体系: 包括有InputStream , OutputStream, Reader/Writer, 文件读取,各种流读取等
NIO 的概念, 具体使用方式和使用场景
多线程并发
多线程是Java中普遍认为比较难的一块。多线程用好了可以有效提高cpu使用率, 提升整体系统效率, 特别是在有大量IO操作阻塞的情况下;但是它也是一柄双刃剑, 如果用不好,系统非但提升不大,或者没有提升,而且还会带来多线程之间的调试时等问题。
多线程的实现和启动
callable 与 runable 区别
syncrhoized ,reentrantLock 各自特点和比对
线程池
future 异步方式获取执行结果
concurrent 包
lock
…
网络
Java中也是提供了可以直接操作 TCP协议、UDP协议的API。在需要强调网络性能的情况下,可以直接使用TCP/UDP 进行通讯。在查看Tomcat等的源码中,就可以看到这些相关API的使用情况。不过一般也比较少会直接使用TCP,会使用诸如MINA、Netty这样的框架来进行处理,这个方面的开发涉及不多。
时间日期处理
对于Java开发者来说,需要熟练地使用API来对时间和日期做相关的处理。
XML解析/ JSON解析
其实这两块内容都不是JavaSE里面的内容,但是在日常开发中,和其他程序交互,和配置文件交互,越来越离不开这两种格式的解析。
不过对于一个开发者来说,能够了解一些XML/JSON具体解析的原理和方法,有助于你在各个具体的场景中更好的选择合适你的方式来使得你的程序更有效率和更加健壮。
XML: 需要了解 DOM解析和 SAX解析的基本原理和各自的适用场景
JSON: 需要了解一些常用JSON框架的用法, 如 Jackson, FastJson, Gson 等。。
泛型
这是JDK5开始引入的新概念,其实是个语法糖,在编写java代码时会有些许便利, 一般的应用或者是业务的开发,只需要简单使用,不一定会用到定义泛型这样的操作,但是开发一些基础公共组件会使用到,可以在需要的时候再细看这个部分,一般情况下只要会简单使用即可。
注解
也是jdk5 之后引入的。Spring是个优秀的框架,最开始就以xml作为标准的配置文件。不过到了Spring3 之后,尤其是 spring-boot 兴起之后,越来越推崇使用标注来简化xml配置文件了,对于开发者来说,可以节省不少xml配置的时间。但是劣势是在于标注散落在各个类中,不像xml,可以对所有配置有个全局性的理解和管理,所以还没有办法说完全就取代所有的xml。
JNI
Java Native Interface,可以允许Java中调用本地接口方法,一般用于C/C++代码的调用。需要注意的是在java中加载so/dll文件的路径问题,本身调用接口并不复杂,但是经常在是否加载了所需的本地接口库中花费较多时间。
练习题
1、一个Java源程序是由若干个类组成。如果源文件中有多个类时,则只能有一个类是public类,并且这个类必须与源文件名同名。
3、异常处理中finally块可以确保无论是否发生异常,该块中的代码总能被执行。finally块不执行的唯一情况是在异常处理代码中执行System.exit(0);语句退出java虚拟机。
4、使用Math.random();返回带正号的double值,该值大于等于0.0且小于1.0。使用该函数生成[30,60]之间的随机整数的语句是:(int)(Math.random()*31)+30
5、安装JDK后,为了告诉计算机javac.exe和java.exe等执行文件的位置,需要配置的环境变量是:path
6、char类型用来表示在Unicode编码表中的字符,长度是2个字节。
7、如果希望将自定义类Student的多个对象放入集合TreeSet,实现所有元素按照某个属性的自然顺序排列,则需要Student类实现Comparable接口。
8、迭代器Iterator为集合而生,专门实现集合遍历,该几口有三个方法:hasNext()、next()、remove()
9、序列化是指将java对象转换成字节序列,从而可以保存到磁盘上,也可以在网络上传输,使得不同的计算机可以共享对象。
10、Java反射技术中,每个Method对象对应一个方法,获得Method对象后,可以调用其invoke来调用对应方法。
12、^是异或位运算符,运算规则是如果两个操作数相同,结果是0,否则结果为1.
13、Collections类是专门用来操作集合的工具类,提供一系列静态方法实现对各种集合的操作。
14、InputStreamReader和OutputStreamWriter是转换流,采用了适配器设计模式,可以将字节流转换成字符流。而在各种节点流和包装流之间则采用了装饰模式,装饰模式是继承的一种有效替代方案,避免产生大量的子类。