Java语言概述
Java基础知识图解
Java语言的特点
JDK、JRE、JVM的关系
关键字和保留字
标识符
数据类型
基本数据类型占用存储空间
自动类型转换
- byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型
- boolean类型不能与其它数据类型运算
- 当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型
- 整数默认为int,小数默认为double
-float虽然只有4个字节,但其容量大于int和long
强制类型转换
- 自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符:(),但可能造成精度降低或溢出,格外要注意
- 通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型
如: String a = “43”; int i = Integer.parseInt(a);
- boolean类型不可以转换为其它的数据类型
运算符
运算符分类
- 算术运算符
- 赋值运算符
- 比较运算符(关系运算符)
- 逻辑运算符
- 位运算符
- 三元运算符
运算符注意问题
- 如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。 但被模数是负数则不可忽略,取模运算符号由被模数决定。此外,取模运算的结果不一定总是整数
- 三元运算符两个表达式类型要一致
运算符优先级
只有单目运算符、三元运算符、赋值运算符是从右向左运算的
程序流程控制
顺序结构
分支结构
循环结构
特殊关键字break、continue、return的使用
- continue只能使用在循环语句中,break只能用于switch 语句和 循环语句中
- continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明跳过的是哪一层循环
- break和continue后不能有其他语句
- return并非专门用于结束循环的,它的功能是结束一个方法。当一个方法执行到一个return语句时,这个方法将被结束。
数组
数组概述
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
- 数组的长度一旦确定,就不能修改。
- 直接通过下标(或索引)的方式调用指定位置的元素
一维数组的声明
- int[] arr;
- int arr[];
一维数组的初始化
数组一旦初始化,其长度是不可变的
-
静态初始化
int[] arr = new int[]{1,3,4};
int[] arr = {1,3,4};
-
动态初始化
int[] arr = new int[3];(这里的3可以是变量)
arr[0] = 1;
arr[1] = 3;
arr[2] = 4;
数组的默认初始化值
二维数组的初始化
-
静态初始化
int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};
int[] arr[] = new int[][]{{3,8,2},{2,7},{9,0,1,6}};
-
动态初始化
int[][] arr = new int[3][2];
int[][] arr = new int[3][];
Arrays工具类的使用
import java.util.Arrays
数组使用中常见异常
编译时不报错,因为还没加载到内存
字符串角标也会出现越界异常
面向对象(OOP)
面向对象学习的三条主线:
1. Java类及类的成员
2. 面向对象的三大特征
3. 其他关键字
主线一:类及类的成员
常见的类成员有Field = 属性 = 成员变量,Method = ( 成员) 方法 = 函数
类的语法格式
修饰符 class 类名 {
属性声明;
方法声明;
}
类的权限修饰符有public、缺省
Java自定义类
- 类:修饰符 类名{}
- 属性:修饰符 数据类型 属性名;
- 方法:修饰符 返回值类型 方法名(参数列表){}
对象的创建和使用
类名 对象名 = new 类名(参数);
类的访问机制:
- 在一个类中的访问机制: 类中的方法可以直接访问类中的成员变量 。(例外:static 方法访问非 static, 编译不通过 )
- 在不同类中的访问机制: 先创建要访问类的对象 , 再用对象访问类中定义的成员 。
对象的生命周期
当实例化对象没有被引用时,就被当作垃圾自动回收
内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 栈(Stack),通常是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
- 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
匿名对象
new Person().shout();
经常用来作为实参传递给方法使用
类的成员之一:属性
修饰符 数据类型 属性名 = 初始化值 ;
- 常用权限修饰符:public、protected、缺省、private;
- 其他修饰符:static、final
变量的分类:成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量,有默认值(参考数组初始化值)。
- 在方法体内部声明的变量称为局部变量。
类的成员之二:方法
语法格式
权限修饰符
public、protected、缺省、private
注意点
- return后面不能有语句,一定要有确定的return,错误:if(…){return …}
- 返回类型为void则return可加可不加
- return关键字的使用范围:方法体中,作用:结束方法、结束方法并返回数据
- 方法中可以调用方法,但不能定义方法
方法的重载(overload)
方法的重写(override/overwirte)
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型,子类重写的方法返回值数据类型:基本数据类型必须一致,而引用类型则必须一致或者是父类返回类型的子类
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,子类不能重写父类中声明为private权限的方法
- 子类方法抛出的异常不能大于父类被重写方法的异常
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
重载和重写的区别
- 二者的定义细节:略
- 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
可变个数的形参
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
注意:
- 声明格式:方法名(参数的类型名 …参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参位置,最多只能声明一个可变个数形参
方法参数的值传递机制
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参,地址值暗含数据类型,即不同引用数据类型之间不能直接赋值
递归
1.递归方法:一个方法体内调用它自身。
2.方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
3.递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
理解main方法
- 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
- 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
类的成员之三:构造器
语法格式
修饰符 类名 (参数列表) {
初始化语句;
}
构造器的特征
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
构造器的作用:
创建对象,给对象初始化
构造器分类:
1.隐式无参构造器(系统默认提供)
2.显式定义一个或多个构造器(无参、有参)
注意点
- Java 语言中,每个类都至少有一个 构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器(不管有参无参),则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
属性赋值过程
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:
① - ② - ③ - ④
类的成员之四:代码块
代码块的作用
对Java 类或对象进行初始化,也称为初始化块
代码块的分类
代码块只能被static修饰,称为静态代码块(static block),static 代码块化通常用于初始化static的属性,没有使用static修饰的为非静态代码块
静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:没有static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
程序中成员变量的赋值顺序
类的成员之五:内部类
概述
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
- Inner class的名字不能与包含它的外部类类名相同;
分类
- 成员内部类(静态和非静态)
- 局部内部类(构造器内、代码块内、方法内)
成员内部类
作为类的成员
1.和外部类不同,Inner class还可以声明为private或protected;
2.可以调用外部类的结构
3.Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
作为类
1.可以在内部定义属性、方法、构造器等结构
2.可以声明为abstract类 ,因此可以被其它的内部类继承
3.可以声明为final的
4.编译以后生成OuterClass$InnerClass.class字节码文件,也适用于局部内部类
注意
- 非static的成员内部类中的成员不能声明为static的,只有在外部类为static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
创建静态成员内部类:
Person.Brain pb = new Person.Brain();
创建非静态成员内部类
Person p = new Person();
Person.Brain pb = p.new Brain();
局部内部类
声明局部内部类:
使用局部内部类:
1.只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
2.但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
局部内部类的特点:
1.内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
2.只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
3.局部内部类可以使用外部类的成员,包括私有的。
4.局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
5.局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
6.局部内部类不能使用static修饰,因此也不能包含静态成员
7.在局部内部类的方法中调用该类所在的方法中的成员变量,则该变量必须为final修饰(jdk7以前需手动添加,之后默认为final)
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表)| 实现接口(){
// 匿名内部类的类体部分
}
特点:
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
主线二:面向对象之封装性
四种访问权限修饰符
Java权限修饰符public、protected、(缺省)、private置于 类的成员定义前,用来限定对象对该类成员的访问权限
对于class的权限修饰只可以用public和default(缺省)。
主线二:面向对象之继承性
为什么要有继承?
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
- 此处的多个类称为子类( 派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”
- 类继承语法规则:
class Subclass extends SuperClass{ }
继承的作用
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
注意点
-
不要仅为了获取其他类中某个功能而去继承
-
子类继承了父类,就继承了父类的方法和属性。
-
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
-
在Java中,继承的关键字用的是“extends”,即子类不是父类的子集, 而是对父类的“扩展” 。
-
子类不能直接访问父类中私有的(private) 的成员变量和方法,父类的私有属性子类也获取到了,只是因为封装性不能直接获取
-
Java只支持单继承和多层继承,不允许多重继承
-
所有的类都直接或间接地继承于java.lang.Object类
主线二:面向对象之多态性
对象的多态性
父类的引用指向子类的对象(可以直接应用在抽象类和接口上)
Java引用变量有两个类型
编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译看左边;运行看右边(只适用于方法,属性还是调用父类)
对象的多态
在Java中,子类的对象可以替代父类的对象使用一个变量只能有一种确定的数据类型,一个引用类型变量可能指向(引用)多种不同类型的对象
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
虚拟方法调用(Virtual Method Invocation)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
动态绑定
Person e = new Student();
e.getInfo(); // 调用Student 类的getInfo() 方法
编译时类型和运行时类型编译时e为Person 类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo() 方法(动态绑定)
多态作用
提高了代码的通用性,常称作接口重用,子类做实参,父类做形参
前提 :需要存在继承或者实现关系,有方法的重写
成员方法:编译时:要查看引用变量所声明的类中是否有所调用的方法;运行时:调用实际new的对象所属的类中的重写方法。
成员变量:不具备多态性,只看引用变量所声明的类。
对象类型转换 (Casting )
-
从子类到父类的类型转换可以自动进行
-
从父类到子类的类型转换必须通过造型(向下转型)( 强制类型转换) 实现,强转可能会出现异常
-
无继承关系的引用类型间的转换是非法的
-
在造型前可以使用instanceof
子类继承父类
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
主线二:面向对象之抽象性
抽象类与抽象方法
- 用abstract关键字来修饰一个类,这个类叫做抽象类。
- 用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束: 比如:public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化,但一定有构造方法(提供给子类),抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类,仍需用abstract修饰。
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
- 了解:匿名子类 && 匿名对象 && 匿名子类的匿名对象 && 匿名实现类 && 匿名实现类的匿名对象
主线三:各种关键字
四种权限修饰符
Java权限修饰符public、protected、 (缺省)、 private置于 类的成员定义前,用来限定对象对该类成员的访问权限。
- 对于class的权限修饰只可以用public和default(缺省)。
- public类可以在任意地方被访问。
- default类只可以被同一个包内部的类访问。
this
this是什么
- 在Java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- this 可以调用类的属性、方法和构造器
- 什么时候使用this关键字呢?
当在方法内需要用到调用该方法的对象时,就用this。 具体的:我们可以用this来区分属性和局部变量。 比如:this.name = name;
注意点
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
- 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
super
在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
注意点
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类,super先找直接父类,再找间接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
调用父类的构造器
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super( 参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
- 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又 没有无参的构造器,则编译出错
- 子类不继承父类构造器,但是显示或隐式地调用了父类构造器,目的是初始化父类继承给子类的属性方法
- 虽然调用了父类构造器,但至始至终只创建了一个对象,即new 的这一个对象
this 和super的区别
package
语法格式
写在首行
package 顶层包名;
- 包对应于文件系统的目录,package 语句中,用 “ “.” ” 包 来指明包( 目录) 的层次;
- 包通常用小写单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx
import
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻类。
语法格式:
import 包名.类名;
注意点
- 在源文件中使用import显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
- import static组合的使用:调用指定类或接口下的静态的属性或方法
instanceof
x instanceof A :检验x 是否为类A 的对象,返回值为boolean 型
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true
static
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
让一个类的所有实例共享数据,就用类变量!
类属性、类方法的设计思想
- 类属性作为该类各个对象之间共享的变量,在设计类时, 分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
- 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法 ,从而简化了方法的调用。
- 使用范围:在Java类中,可用static修饰属性、方法、代码块、内部类
- 由于类只会加载一次,所以静态变量也只会存在一份,存在方法区的静态域中
- 由父到子,静态先行
- 静态方法不能使用this和super关键字
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
类变量
- 类变量(类属性)由该类的所有实例共享
- 类中的常量常设置为static
内存解析
类方法
- 没有对象的实例时,可以用 类名. 方法名()的形式访问由static修饰的类方法。
- 在static 方法内部只能访问static 修饰的属性或方法,不能访问类的非static 的结构。
- 因为不需要实例就可以访问static方法,因此static 方法内部不能使用this和super
- static 修饰的方法不能被重写
- 操作静态属性的方法,通常设置为静态方法
- 工具类中的方法,习惯上设置为静态方法
final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
- final标记的类不能被继承。提高安全性,提高程序的可读性。
如String类、System类、StringBuffer类
- final 标记的方法不能被子类重写。
如:Object类中的getClass()。
- final 标记的变量(成员变量或局部变量) 即称为常量 。名称大写,且只能被赋值一次。
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。
- final可以修饰的结构:类、方法、变量
final修饰类
final修饰的类不能被继承
final修饰变量
final修饰的变量即为常量,要大写
- final修饰属性,则可以赋值的位置有:显示初始化、代码块中初始化、构造器中初始化(即在构造对象完成前赋值)
- final修饰局部变量,尤其是修饰形参时,则该形参只能使用,不能更改
- static final:全局常量
接口(interface)
接口(interface)是抽象方法和常量值定义的集合
关于接口
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能" 的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口的特点:
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器,即接口不能被实例化。
- 接口采用多继承机制。
- 先写extends,后写implements
- 一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现;在JDK8之后,除了全局常量和抽象方法之外,还可以定义静态方法、默认(default修饰)方法
JDK8以后的接口
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
- 接口中的静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
- 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法
- 通过实现类的对象,可以调用接口中的默认方法,若实现类重写了默认方法,则调用的是重写之后的
- 类优先原则:如果子类(实现类)继承的父类和实现的接口中出现了同名同参数的默认方法,而子类没有重写该方法,则默认调用的是父类的该方法
- 接口冲突:如果实现类实现的多个接口中出现了同名同参数的默认方法,而实现类没有重写该方法,则报错,此时必须重写此方法
- 在子类(实现类)中调用父类、接口被重写的方法:
方法名(); // 调用自己重写的方法
super.方法名(); //调用父类被重写的方法
接口名.super.方法名(); //调用接口被重写的方法
接口 vs 抽象类
常用类
Object类
Object类是所有Java类的根父类,如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类,数组可以看成是特殊的类,它也继承了Object
Object类的主要结构
==操作符与equals方法
==:
- 基本类型比较值:只要两个变量的值相等,即为true。
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。
用==进行比较时,符号两边的 数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
equals():所有类都继承了Object,也就获得了equals() 方法,还可以重写
- 只能比较引用类型,其作用与 “==” 相同, 比较是否指向同一个对象 。
- 格式:obj1.equals(obj2)
- 特例:当用equals() 方法进行比较时,对类File、String 、Date 及包装类(Wrapper Class)来说 ,是比较类型及内容而不考虑引用的是否是同一个对象,因为在这些类中重写了Object 类的equals()
- 当自定义使用equals()时,可以重写。用于比较两个对象的 “ 内容 ” 是否都相等
重写equals()的原则
toString方法
- toString() 方法在Object 类中定义,其返回值是String 类型,返回类名和它的引用地址 。
- 在进行String 与其它类型数据的连接操作时自动调用toString() 方法
- 可以根据需要在用户自定义类型中重写toString() 方法,返回字符串的值
- 基本类型数据转换为String 类型时,调用了对应包装类的toString() 方法
- Date和String等类都重写了toString方法
包装类
- 针对八种基本数据类型定义相应的引用类型—包装类(封装类)
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
基本类型、包装类与String之间的转换
经典面试题
异常处理
异常概述
异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
对于这些错误,一般有两种 解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等
异常体系结构
- 运行时异常
- 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子 类都是运行时异常。
- 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- 编译时异常
- 是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。
- 对于这类异常,如果程序不处理,可能会带来意想不到的结果。
常见异常
抓抛模型
Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常,抛出对象后,其后的代码不再执行。
- 异常对象的生成:
- 由虚拟机 自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出
- 由开发人员手动创建:Exception exception =new ClassCastException();创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样
2. 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。
3. 如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
4. 程序员通常只能处理Exception,而对Error无能为力
异常处理机制一:try-catch-finally
异常处理是通过try-catch-finally语句实现的
语法
try{
...... //可能产生异常的代码
}
catch( ExceptionName1 e ){
...... //当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
...... //当产生ExceptionName2型异常时的处置措施
}
finally{
...... //无论是否发生异常, 都无条件执行的语句
}
说明
- finally和catch是可选的
- 用try包装可能出现异常的代码,若执行过程中出现异常,就会自动生成对应类型的异常类对象,根据该对象类型到catch中匹配,匹配成功即执行对应catch中的代码,再执行finally中的代码。
- 若catch中的异常类型有子父类关系,则子类必须声明在父类之上(对比if…else if),否则报错
- 常用的异常对象处理方式:
String getMessage() //获取异常信息,返回字符串
void printStackTrace() //取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
- try中的变量只在try结构中有效
- try-catch-finally结构可以嵌套
- 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行,若finally中有return,则最后return的数据是finally决定的
体会
- 使用try-catch-finally处理编译时异常,使得程序在编译时不报错,但运行时仍可能报错,相当于把编译时可能出现的异常,延迟到运行时出现
- 开发中通常不针对运行时异常编写try-catch-finally,对于编译时异常则必须考虑异常处理
异常处理机制二:throws+异常类型
语法
throws+异常类型写在方法的声明处
说明
- 一旦方法执行时出现异常,则生成异常类的对象,若该异常类符合声明的异常类型,就会抛出,异常后的代码不再执行(throws只是将异常抛给了方法的调用者,并没有真正地处理异常)
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类 。
重写方法声明抛出异常的原则
重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对methodA()方法的调用-异常的捕获按父类声明的异常处理。
选择try-catch还是throws?
- 若父类被重写的方法没有用throws处理异常,则子类重写的方法只能用try-catch处理异常
- 执行的方法a中先后调用了多个方法,且这多个方法是层层递进的关系,则可以使用throws抛出异常,在方法a中再用try-catch统一处理异常
手动抛出异常throw
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要 使用人工创建并抛出 。
- 首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
IOException e = new IOException();
throw e;
- 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String("want to throw");
用户自定义异常类
- 一般地,用户自定义异常类都是RuntimeException的子类。
- 自定义异常类通常需要编写几个重载的构造器。
- 自定义异常需要提供serialVersionUID
- 自定义的异常通过throw抛出。
- 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
单元测试
设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱,即”套路”
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
饿汉式
懒汉式
懒汉式(线程安全版)
饿汉式 vs 懒汉式
饿汉式坏处:对象加载时间过长;
饿汉式好处:饿汉式是线程安全的;
懒汉式坏处:上述代码线程不安全(可优化)
懒汉式好处:延迟对象的创建
单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
模板方法设计模式(Template Method)-抽象类的应用
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
代理模式(Proxy)-接口的应用
概述
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象,比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
工厂模式