1.面向对象与面向过程
1.1 面向过程
- 以过程为中心的编程思想,分析出解决问题所需要的步骤,用函数将这些步骤实现,使用时依次调用即可。
- 强调的是功能行为,以函数为最小单位,考虑怎么做。
1.2 面向对象
- 面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为。
- 强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
2.类和对象
2.1 定义
类:对一类事物的描述,是抽象的、概念上的定义;(人类)
对象:类的一个实例;(具体某个人)
举例:我们跟控制台交互需要使用java提供的Scanner类,使用时我们要创建一个Scanner对象,通过这个对象来操作它的功能方法,完成我们和控制台的交互。
2.2 类的成员
- Filed = 属性 = 成员变量
- Method = 行为 = 成员方法
- 构造器
- 代码块
- 内部类
2.3 类和对象的使用
- 创建类,设计属性和行为;
- 创建类的对象(实例化对象);
- 通过对象调用类的属性和方法;
2.4 内存结构
对于引用数据类型来说,变量值不是null就是地址值(包含变量类型);
2.5 成员变量和局部变量对比
成员变量定义在类中方法外,除了成员变量其余变量都是局部变量;
2.5.1 相同点
- 定义格式相同:数据类型 变量名 = 变量值;
- 都是先定义后使用;
- 都有其对应的定义域;
2.5.2 不同点
- 在内存中加载的位置不同:成员变量在堆中(非static);局部变量在栈中。
- 权限修饰符的不同:成员变量可以添加权限修饰符(public private 缺省 protected);局部变量不可以。
- 在类中声明的位置不同:成员变量直接声明在类中,方法体外;局部变量声明在方法、形参、构造器形参、构造器内部。
- 默认初始值不同:成员变量有默认初始值,与一维数组相同;局部变量除形参外,都需要显示初始化。
2.5.3 定义域
成员变量的定义域是整个类中,局部变量定义域是在其最近一层的结构中(方法、代码块);
成员变量可以定义在类的任意位置,对于非静态的成员变量,它是随着对象的创建而产生的,而非静态的结构都是通过对象来调用,所以成员变量的定义位置与其被调用的结构的位置无关;
局部变量初始化必须在被调用的结构之前,因为无论是方法中还是代码块中,都是自上向下执行的,必须执行完局部变量的初始化语句,才会给局部变量分配内存空间;
两者的定义位置归根到底还是与其生命周期有关;
2.6 练习–内存分配
画出如下代码在执行时的内存分配情况
class Car{
String color = "red";
int num = 4;
void show(){
int a = 10;
System.out.println("color="+color+",num="+num);
}
}
class CarTest {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car();
c1.color = "blue";
c1.show();
c2.show();
} }
- 给成员变量显示赋值,初始值也是默认的;
- 方法中的局部变量都在栈中;
- main函数执行完成后,c1和c2都会被回收;
2.7 匿名对象
- 理解:我们创建的对象,没有显示的赋值给一个变量名。即为匿名对象。
- 特征:匿名对象只能调用一次。
- 使用:充当方法的实参。
3.方法
3.1 定义
权限修饰符 返回值类型 方法名(形参列表){
方法体;
}
3.1.1 权限修饰符
- public
- private
- 缺省
- protected
3.1.2 返回值类型
- 可以是基本数据类型,也可以是引用数据类型。
- 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量“return 数据”。如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。
3.1.3 方法名
标识符,和变量的命名规范一样,首单词的首字母小写,其余单词首字母大写。
3.1.4 参数列表
格式:数据类型1 形参1,数据类型2 形参2,…
3.1.5 return
- 使用范围:方法体中
- 作用:结束方法;对于有返回值类型的方法,使用“return 数据”返回所需数据。
- 注意:return关键字后不可声明执行语句。
3.1.6 注意
- 成员方法可以调用类中的属性和其它成员方法。
- 方法中不可以定义方法。
3.2 方法重载
3.2.1 意义
重载的最直接作用是方便了开发人员可以根据不同的参数个数,顺序,类型,自动匹配方法,减少了方法的命名和记忆。
3.2.2 定义
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
“两同一不同”:同一个类,同一个方法名;形参列表不同。
3.2.3 特点(两同一不同,四无关)
- 同一个类,同一个方法名;
- 形参列表不同:形参个数不同,对应位置的形参数据类型不同;
- 四个无关:与返回值类型无关,与权限修饰符无关,与方法体无关;与形参名无关;
- 如果没有实参对应的数据类型,会进行自动类型转换;(int -> double)
- 方法可以是同一个类中声明的,也可以是继承于同一个父类的,也可以是一个自己声明,一个继承的;
3.3 可变个数的形参
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
3.3.1 格式
数据类型 … 形参名称
定义:
public void max(String... str) {
// 与数组的遍历方式相同
for (int i = 0; i < str.length; i++) {
System.out.println(str[i]);
}
}
调用:(两种方式)
对象.max("a","b","c");
对象.max(new String[]{"a","b"});
3.3.2 细节
- 可以与同一个类中方法名相同,形参列表不同的方法构成重载;
- 不能与同一个类中方法名相同,形参为相同数据类型的数组同时存在;
- 用可变个数形参的方法时,实参的个数可以是0个,1个…;
- 可变个数参数在方法的形参中,必须声明在末尾;
- 一个形参列表中只能由一个可变个数形参;
3.3.3 与数组的区别
- 调用方法时,可以不用传入数组类型实参(相当于少创建一个数组对象);
- 必须声明在形参列表末尾且只能由一个。
3.4 方法的值传递机制
3.4.1 变量的赋值复习
- 对于基本数据类型,变量间的赋值是进行值传递,赋值之后两个变量间互相独立;
- 对于引用数据类型,变量间的赋值是进行地址传递,复制之后两个变量共享一块内存空间;
3.4.2 形参和实参
- 形式参数:方法定义时小括号中的参数;
- 实际参数:方法调用时传递的参数;
3.4.3 形参的传递机制:值传递
- 不管是数据值还是地址值,归根到底参数传递只有一种类型,就是值传递;
- 对于基本数据类型,实参传递给形参的是实参中存储的数据值。
- 对于引用数据类型,实参传递给形参的是实参所指向内存空间的地址值(包含数据类型);
3.4.4 练习1
public class TransferTest3{
public static void main(String args[]){
TransferTest3 test=new TransferTest3();
test.first();
}
public void first(){
int i=5;
Value v=new Value();
v.i=25;
second(v,i);
System.out.println(v.i+" "+i);
}
public void second(Value v,int i){
i=0;
v.i=20;
Value val=new Value();
v=val;
System.out.println(v.i+" "+i);
}
}
class Value {
int i= 15;
}
15 0
20 5
3.4.5 练习2
方法1:在method方法中输出a、b,并终止程序;
private static void method(int a, int b) {
System.out.println("a = " + 100);
System.out.println("b = " + 200);
System.exit(0); // 终止程序
}
方法2:重新println方法
private static void method(int a, int b) {
PrintStream ps = new PrintStream(System.out){
public void println(String x){
if("a=10".equals(x)){
x = "a=100";
}else if("b=10".equals(x)){
x = "b=200";
}
super.println(x);
}
};
System.setOut(ps);
}
3.4.6 练习3
微软: 定义一个int型的数组:int[] arr = new int[]{12,3,3,34,56,77,432};
让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。
//错误写法:如果从arr[0]开始,那么arr[0]就变为1了,其余值都是除以1。
for(int i= 0;i < arr.length;i++){
arr[i] = arr[i] / arr[0];
}
//正确写法1
for(int i = arr.length –1;i >= 0;i--){
arr[i] = arr[i] / arr[0];
}
//正确写法2
int temp = arr[0];
for(int i= 0;i < arr.length;i++){
arr[i] = arr[i] / temp;
}
3.4.7 练习4
println(char[] arr)方法体是对char[]数组进行遍历
* int[] arr = new int[10];
* System.out.println(arr);//地址值
*
* char[] arr1 = {'a','b','c'};
* System.out.println(arr1);//abc
4.递归(后面补)
5.封装
5.1 定义
封装指的是将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过类中提供的公共方法来实现对隐藏信息的操作和访问。
5.2 目的
追求程序设计的“高内聚,低耦合”特性,提高系统的可拓展性和可维护性。
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。但除此之外,没有其他制约条件。但是,实际问题中,我们往往需要给属性赋值加入额外限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行条件的添加。比如说,setLegs。同时,我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private)此时,针对于属性就体现了封装性。
5.3 封装性的体现
- 将类的属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)属性;
- 方法私有化;
- 单例模式:构造器私有化;
- 将类的权限修饰符设置为default,其它包就不可以调用该类;
5.4 权限修饰符
Java 权限修饰符public、protected、default(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
- 4 种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类;
- 修饰类的话,只能使用:缺省、public
5.5 权限修饰符之于封装
封装性的实现需要权限修饰符的配合–Java 提供了 4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性大小。
6.构造器
6.1 格式
权限修饰符 类名(形参列表) {
方法体;
}
6.2 作用
- 创建对象;
- 初始化对象信息;
6.3 细节
- 如果没有显式定义构造器,那么系统会默认提供一个无参构造器;
- 一个类中可以定义多个构造器,它们之间构成重载;
- 构造器与方法不同,没有返回值类型,且构造器名称就是类名;
- 如果显式定义了构造器,那么系统就不提供默认无参构造器;
- 一个类中至少有一个构造器;
7.属性赋值的过程
- 默认初始化
- 显式初始化
- 构造器初始化
- 通过“对象.属性”或“对象. 方法”给属性赋值
正确顺序:1 -> 2 -> 3 -> 4
8.JavaBean
有一个无参的公共的构造器
/*
* JavaBean 是一种 Java 语言写成的可重用组件。
* 所谓 javaBean,是指符合如下标准的 Java 类:
* > 类是公共的
* > 有一个无参的公共的构造器
* > 有属性,且有对应的 get、set 方法
*
*/
public class Customer {
private int id;
private String name;
public Customer(){
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
}
9.UML类图
介绍类以及类内部结构
10.this关键字
10.1 作用
this表示当前对象或者当前正在创建的对象(相对于构造器来说),可以用来修饰、调用属性,方法,构造器。
10.2 使用方法
this.属性(用来区分形参与成员变量)
this.方法()
this(形参列表) //在构造器中调用本类中其余构造器
1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性和方法。
通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性同名,我们必须显式地使用"this.变量"的方式,表明此变量是属性,而非形参。
2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用正在创建的对象属性和方法。
但是,通常情况下,我们都选择省略“this.”。特殊情况下,如果构造器的形参和类的属性同名,我们必须显式地使用"this.变量"的方式,表明此变量是属性,而非形参。
3.this 调用构造器(减少代码冗余)
① 我们可以在类的构造器中,显式的使用"this(形参列表)“的方式,调用本类中重载的其他的构造器!
② 构造器中不能通过"this(形参列表)“的方式调用自己。
③ 如果一个类中声明了n个构造器,则最多有n -1个构造器中使用了"this(形参列表)”。
④ "this(形参列表)"必须声明在类的构造器的首行!
⑤ 在类的一个构造器中,最多只能声明一个"this(形参列表)”。
4.this也可以单独使用,表示当前对象;super不可以单独使用。
10.3 getter和setter
idea快捷键alt+insert
;
getter方法中不用加this,因为没有重名的形参;
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
11.package和import关键字
11.1 package包
- 作用:便于项目中类或接口的管理;
- 同一个包中不能有同名的接口和类,不同的包中可以;
- 包名全部是小写字母;
- 包实质上就是一个文件夹;
11.2 import导入
- 作用:导入指定包下的类或接口;
- import的落脚点是类或接口,不是包;
- 导入xxx包下所有的类和接口,使用
import xxx.*
; - 如果同时使用位于两个包下相同类名的类,那么至少有一个类要使用全类名的方式来显示;
test1.Account account1 = new test1.Account();
- 如果使用A包的子包B中的类或接口,只写
import A.*
不行,要导入子包B中的类或接口; - 如果类或接口是java.lang包中定义的,那么不需要导入,直接使用即可;
- import static组合的使用:调用指定类或接口下的静态的属性或方法.
12.项目2–客户信息管理系统
12.1 CMUtility
12.1.1 sc.next()和sc.nextLine()
- sc.next()不会读入空格,在有效字符之后输入空格再输入字符,空格之后的字符不会被记录;
Scanner sc = new Scanner(System.in);
System.out.print("输入:");
String s = sc.next(); //不会读入空格
System.out.print("输出:");
System.out.println(s);
输入: 123 123 2312
输出:123
- sc.nextLine()会读入空格
Scanner sc = new Scanner(System.in);
System.out.print("输入:");
String s = sc.nextLine(); //不会读入空格
System.out.print("输出:");
System.out.println(s);
输入: 12321 2312 213
输出: 12321 2312 213
12.1.2 try-catch-finally
- 异常捕获成功后会继续执行后续代码;
- 发生异常后,try代码块中位于异常语句后的代码不会执行,所以如果有必须要执行的语句,一般放在finally中;
12.2 CustomerList增删改查操作
12.2.1 删除一个用户
数组中删除一个元素后,后边元素都要前移。
public boolean delCustomer(int index) {
if (index >= total || index < 0)
return false;
for (int i = index; i < total - 1; i++) {
customers[i] = customers[i + 1];
}
customers[total - 1] = null;
total--;
return true;
}
12.3 CustomerView
12.3.1 modifyCustomer
- 修改用户信息时,如果不想修改某一项,可以直接回车,此时后台判断为空白输入,会将原值返回;
- 修改时可以不用new一个新对象,直接调用原对象的setXxx方法进行修改即可;
12.4 内存解析
13.继承
13.1 定义
当多个类中拥有相同的属性和方法时,将这些内容抽取到一个单独的类中,别的类就不需要定义这些类或方法,直接继承哪一个类即可。
格式:class A extends B{}
13.2 目的
- 减少代码冗余,提高代码的复用性;
- 便于功能拓展;
- 为多态的实现提供前提;
13.3 细节
- 单继承:一个类只能有一个直接父类;
- 一个类可以被多个类继承;
- 子类继承父类后,就获取了直接父类和所有间接父类中的属性和方法,构造器不能被继承;
- 关于父类中私有(private)的属性和方法,子类也会获取。只是由于封装性,子类不能直接调用父类的私有结构,但是可以通过父类中的getter或者公共的方法来间接调用父类中的私有结构;
- 子类可以声明自己特有的属性和方法,实现功能的拓展;
- java.lang.Object类是所有没有显示继承的类的父类;
- 所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类;
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系(张三是个人,不能说旺财(狗)是个人);
13.4 向上查找原则(每一个父类都是一个子类)
13.4.1 规则
因为方法可以重写,而属性不能重写,所以方法满足动态绑定机制,而属性是就近原则。
多态是在继承的基础上实现的,多态主要的作用是提高代码的通用性,所以继承和多态的方法和属性调用逻辑相同。
方法:编译看左边,运行看右边。(普通继承等号左右是同一个类)
属性:编译看左边,运行看左边。
- 继承中的方法也满足动态绑定机制(运行看右边),调用方法时优先看等号右边,如果没有再向上查找,在方法中掉用方法也满足这个原则;
- 通过对象访问属性时,无论是不是多态,都看等号左边;
- 通过成员方法访问属性时,方法属于哪个类,就优先使用这个类中的属性,没有则向上找;
13.4.2 例题(重要)
- 上面的规则,注意在类A中的this是
new B()
, super代表A。- try-catch-finally中,在try中return执行之前始终会执行finally里面的代码,如果finally里面有return,则数据跟随finally改变并返回,不会再执行try中的return。如果没有return,则原数据不跟随finally里改变的数据改变!
class Test {
public static void main(String[] args) {
System.out.println(new B().getValue()); //
}
static class A {
protected int value;
public A (int v) {
setValue(v);
}
public void setValue(int value) {
this.value= value;
}
public int getValue() {
try {
value ++;
return value;
} finally {
this.setValue(value);
System.out.println(value);
}
}
}
static class B extends A {
public B () {
super(5);
setValue(getValue()- 3);
}
public void setValue(int value) {
super.setValue(2 * value);
}
}
}
22 34 17
13.5 实例化对象时构造器的执行顺序
先执行父类的构造器,在执行子类构造器中的语句。
14. 方法的重写
14.1 定义
方法的重写是指子类继承父类后,可以对父类同名同参数的方法进行覆盖操作;
14.2 细节(两小一大)
子类:重写方法;
父类:被重写方法;
如果子类的方法与父类的方法同名同参数,那么子类一定要满足重写的规则,不然编译报错
14.2.1 权限修饰符(大)
- 重写方法的权限修饰符要大于等于被重写方法的;
- 子类不能重写父类中声明为private权限的方法;
14.2.2 返回值类型(小)
- 被重写方法的返回值类型是void,重写方法的返回值类型也必须是void;
- 重写方法的返回值类型是被重写方法返回值类型的自身或者其子类;
- 如果被重写方法返回值是基本数据类型,那么重写方法必须与其保持一致,因为基本数据类型没有子类;
14.2.3 异常(小)
- 重写方法抛出的异常必须小于等于被重写方法抛出的异常
14.2.4 static
- 重写方法和被重写方法必须同时声明为static或者非static;
- 同时声明为非static时,可能是重写;
- 同时声明为static时,不是重写,因为static方法属于类的,子类无法覆盖父类的方法;
- 静态方法可以被继承,但是不可以被重写,所以通过子类或者子类对象可以访问父类的静态方法;
14.3 调用
- 重写之后,子类对象调用的是本类中重写的方法,而父类对象调用的仍是被重写的方法;
14.4 重写与重载的区别
14.5 意义
父类方法中的逻辑不适用于子类,所以子类需要对父类的方法进行重写。
15.super关键字(表示父类的xxx)
15.1 作用
在子类中使用,可以调用父类的属性、方法或者构造器;
15.2 使用
super.属性/方法/构造器
15.2.1 属性和方法
- 在子类的方法或者构造器中,当我们要使用父类的属性或方法时,需要
super.属性
或super.方法
显式调用,但是通常情况下,可以省略super; - 特殊情况:子类的属性与父类的属性同名(与数据类型无关)时,在子类中调用父类的属性,需要使用
super.属性
; - 特殊情况:在子类中重写了父类的方法,调用父类中被重写的方法时,需要使用
super.方法
; - 如果父子类中没有重名成员,访问父类的成员时,使用super,this和直接访问是一样的效果,但是执行过程不同(super是直接访问父类成员;this和直接访问先要在本类中查找,本类中没有再去父类中找);
- 使用super不能直接访问父类中的私有方法;
15.2.2 构造器
- 在子类构造器中可以使用
super(形参)
来显式调用父类的构造器; super(形参)
和this(形参)
一样,只能放在构造器的首行,所以在同一个构造器中两者只能出现一个且必须出现一个;- 如果在子类构造器中没有显示声明
super(形参)
和this(形参)
,那么默认会有一个super()
调用父类的空参构造器,但是如果父类没有空参构造器,那么子类构造器中必须显示调用父类的其余构造器; - 由
this(形参)
调用本类构造器不能形成循环可知,子类中至少有一个构造器会显式或隐式调用父类的构造器;
15.3 为什么super(…)或this(…)调用语句只能作为构造函数中的第一句出现?
无论通过哪个构造器创建对象,都要保证先初始化父类;
目的:当子类继承父类后,继承了父类中所有的属性和方法,因此子类有必要知道父类是如何进行初始化的。
15.4 this和super的区别
super不可以单独使用
16.子类对象实例化的过程
- 通过子类的构造器创建子类对象时,一定会直接或间接调用父类的构造器,进而调用父类的父类的构造器,直到调用到java.lang.Object的空参构造器为止,正是因为加载过所有的父类成员,所有子类对象才可以调用父类成员。
- 虽然创建子类对象时调用了父类构造器,但是自始至终就创建了一个对象,就是new的那个对象;
17.多态(向上转型)
17.1 对多态的理解
一种事物具有多种状态。
- 多态的意义在于提高了代码的通用性,主要体现在参数传递的过程中。比如说Object类中有一个equal()方法
public boolean equals(Object o)
,由于多态性的存在,我们传入的实参可以可以是任意类;但是如果没有多态性,我们就需要写多个不同形参类型的equals方法,造成代码冗余。 - 抽象类和接口的使用可定体现了多态性,因为它们无法实例化,作为形参时,只能传入子类/实现类对象。
- JDBC
17.2 使用
17.2.1 使用前提
- 存在继承关系;
- 子类对父类方法进行了重写;
17.2.2 格式
父类引用指向子类对象(向上转型):父类名称 父类引用 = new 子类名称();
// 1,2两种写法相同,都是向上转型
子类 子类对象 = new 子类();
父类 父类引用 = 子类对象;// 1
父类 父类引用 = (父类名称)子类对象; //2
17.2.3 多态的使用:虚拟方法调用
虚拟方法:子类重写了父类的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的,只能在运行期间确定。(多态情况下,父类中被重写的方法称为虚拟方法)
编译看左边,运行看右边:有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法。
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。==若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。==多态情况下,
“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
17.2.4 注意事项
- 对象的多态性只适用于非静态方法,不适用于属性和静态方法(编译和运行都看左边);
- 父类引用无法直接调用子类中特有的成员方法,编译时类型由声明该变量时使用的类型决定,父类中没有这些方法,编译会报错;
- 父类引用调用的是子类中重写的方法,不是子类中其它方法;
- “编译看左边,运行看右边”和“编译和运行都看左边”指的是通过父类引用直接调用方法或属性;如果是在方法中使用this或直接调用属性,那么方法属于谁,就是用谁的属性。
17.3 作用
提高了代码的通用性,常称作接口重用
17.4 向下转型
17.4.1 作用
多态情况下,内存中实际上加载了子类中特有的成员方法和属性,但是由于变量声明为父类类型,导致编译时只能调用父类的成员,无法直接调用子类中特有的成员,所以要通过向下转型将父类引用转为子类对象。(类似于强制转换)
17.4.2 格式
子类名称 对象名 = (子类名称)父类引用
17.4.3 instanceOf关键字
格式:x instanceof A:检验x的运行类型是否为类A或A子类的对象,返回值为boolean型。
作用:避免向下转型时,出现java.lang.ClassCastException
异常。
17.4.4 向下转型的常见问题
- 父类引用可以强转为子类对象,但是父类对象不能强转为子类对象,可以先将子类转为父类的父类,再将其向下转型为父类(问题二)
- 父类引用指的是使用多态,而父类对象是普通定义;
//问题一:编译时通过,运行时不通过
//举例一
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二
Person p4 = new Person();
Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
Object obj = new Woman();
Person p = (Person)obj;
//问题三:编译不通过
// Man m5 = new woman();
// String str = new Date();
// Object o = new Date();
// String str1 = (String)o;
17.5 练习
/*
* 练习:子类继承父类
*
* 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,
* 系统将不可能把父类里的方法转移到子类中。
*
* 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
* 这个实例变量依然不可能覆盖父类中定义的实例变量
*
*/
public class FieldMethodTest {
public static void main(String[] args){
Sub s= new Sub();
System.out.println(s.count); //20
s.display();//20
Base b = s;
//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否一样。
System.out.println(b == s); //true
System.out.println(b.count); //10 编译看左边,运行也看左边
b.display(); // 20 多态:编译看左边,运行看右边
}
}
class Base {
int count= 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count= 20;
public void display() {
System.out.println(this.count);
}
}
17.6 多态是编译时行为还是运行时行为?
多态时运行时行为。
* 证明见如下:
import java.util.Random;
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
// 通过使用随机数,可以知道多态时运行时的行为;
Animal animal = getInstance(key);
animal.eat();
}
}
17.7 练习–多态调用的是子类中重写的方法
- int[] arr和 int… arr是一样的,所以Sub类中的第一个add方法是对Base类中add方法的重写,第二个add方法不是重写,多态调用的是重写后的方法。
- 向下转型后调用的是第二个add方法,因为第二个方法的形参更加准确。
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3); //sub_1
Sub s = (Sub)base;
s.add(1,2,3); // sub_2
}
}
class Base {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
17.8 动态绑定机制(继承也适用)
17.8.1 为什么只有非静态的成员方法有动态绑定机制
因为多态是在重写的基础上实现的,只有非静态方法可以重写,而静态方法和属性都不可以重写,所以只有非静态的成员方法有动态绑定机制。
17.8.2 定义
17.8.3 使用
对于多态或继承来说,调用成员方法时,执行的是运行时类型的方法(就是子类中重写的方法),如果子类中没有进行重写,就会去调用父类中的方法;如果在调用的方法中还有方法调用,那么还是先执行运行时类型的方法,没有再向上找;
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();//向上转型
System.out.println(a.sum());// 30
System.out.println(a.sum1());// 20
}
}
class A {
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
// public int sum() {
// return i + 20;
// }
// public int sum1() {
// return i + 10;
// }
public int getI() {
return i;
}
}
18.Object类
18.1 Object类的基本介绍
- Object类是所有java类的根父类;
- 如果一个类没有显式声明继承其它类,那么这个类直接继承于Object类;
- Object类只有一个空参构造器;
- Object类中没有属性,只有方法;
- 数组可以看作一个特殊的类,它的父类也是Object;
18.2 "=="运算符
- 用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错;
- 可以在基本数据类型变量之间、引用数据类型变量之间比较;
- 对于基本数据类型变量,比较的是变量存放的数值是否相等。”==“也是运算符,比较时会有自动类型转换,所以不同的基本数据类型也可以比较(boolean除外);
- 对于引用数据类型变量,比较的是对象的内存地址是否相等;
18.3 equals()方法
18.3.1 Object类中源码
public boolean equals(Object obj) {
return (this == obj);
}
18.3.2 细节
- 所有的类都继承了equals()方法,如果没有进行重写,那么equals()方法和”==“是一样的;
- 基本数据类型没有方法/属性,因为类是引用数据类型;
- String、Date、File、包装类对equals()方法进行了重写,比较的是对象的实体内容(属性);
18.3.3 重写equals()方法
idea中可以使用alt+insert自动生成;
重写equals()方法时,如果属性也是自定义类,那么属性所属类中的equals()方法也要重写;
public boolean equals(Object o) {
// 1.比较两个对象的内存地址
if (this == o) return true;
// 2.判断o是否为空,不为空再比较两者的类型是否一致
if (o == null || getClass() != o.getClass()) return false;
// 3.向下转型
Test1 test1 = (Test1) o;
// 4.比较两者的属性是否相等
// 对于引用数据类型的属性,要先判断对象是否为空,不为空才能调用equals方法
return age == test1.age && Objects.equals(name, test1.name);
}
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
18.4 "=="和equals()的区别
- "=="既可以用于基本数据类型的比较,也可以用于引用数据类型的比较;而equals()只能用于引用数据类型的比较。
- 对于基本数据类型变量,"==“比较的是变量存放的数值是否相等;对于引用数据类型变量,”=="比较的是对象的内存地址是否相等;equals()如果没有被重写,那么和”==“作用相同,比较的也是对象的内存地址,如果被重写,一般用于比较对象的实体内容(例如:String,包装类,Date)
18.5 equals()练习
int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl)); //true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println(“12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//false
System.out.println("str1是否equals str2?"+(str1.equals(str2)));//true
System.out.println(“hello” == new java.util.Date()); //编译不通过
18.6 toString()方法
18.6.1 Object类中toString的定义方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
18.6.2 细节
1.System.out.println(对象);
和System.out.println(对象.toString());
当对象不是null时,两者效果相同;当对象为null时,直接会输出null,调用方法会报空指针异常;
2.像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用toString()时,返回"实体内容"信息;
3.char[]数组也重写了同toString方法;
4.自定义类如果重写toString()方法,当调用此方法时,返回对象的"实体内容".
5.String与其它类型数据进行连接操作时,自动调用toString()方法,对于基本数据类型则是调用包装类的toString()方法;
18.6.3 举例
public class ToStringTest {
public static void main(String[] args) {
Customer cust1 = new Customer("Tom" ,21);
System.out.println(cust1.toString()); //github4.Customer@15db9742
System.out.println(cust1); //github4.Customer@15db9742 ---> Customer[name = Tom,age = 21]
String str = new String("MM");
System.out.println(str);
Date date = new Date(45362348664663L);
System.out.println(date.toString()); //Wed Jun 24 12:24:24 CST 3407
}
}
public void test() {
char[] arr = new char[] { 'a', 'b', 'c' };
System.out.println(arr);//abc
int[] arr1 = new int[] { 1, 2, 3 };
System.out.println(arr1);//[I@722c41f4
double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
System.out.println(arr2);//[D@5b80350b
}
19.单元测试
19.1 要求
- 类的权限修饰符是public,且只能有一个public的空参构造器;
- 测试方法的权限修饰符是public,返回值为void,参数为空;
19.2 idea中添加模板
在live-template中添加了代码模板,快捷键是test;
19.3 使用
import org.junit.Test;
/**
* @Description:
* @author:zhou
* @create: 2022-01-22 16:56
*/
public class JUnitTest {
@Test
public void testEquals(){
System.out.println("123");
}
@Test
public void test123(){
System.out.println("456");
// System.out.println(1/0);
}
}
20.包装类
包装类是引用数据类型,变量的默认值是null
20.1 8种基本数据类型对应的包装类
基本数据类型 | 包装类 |
---|---|
int | Integer |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
20.2 基本数据类型转为包装类
使基本数据类型拥有类的特性(方法,属性),常用自动装箱
int i = 123;
// 方法1:将基本数据类型当作参数传入Integer构造器
Integer n = new Integer(i);
System.out.println(n); // 通过println输出时,n和n.toString()的效果相同
System.out.println(n.toString());
// 方法2:通过字符串参数构造包装类对象,字符串中的类型一定要与包装类兼容
Integer n1 = new Integer("123");
System.out.println(n);
Float f = new Float(23.1);
System.out.println(f);
Double d = new Double("23.1");
System.out.println(d);
// 方法3:自动装箱
Integer n2 = 5;
System.out.println(n2.toString());
20.3 包装类转为基本数据类型
包装类转为基本数据类型,可以进行运算;常用自动拆箱
// 方式1:通过包装类的xxxValue()方法
Integer i = new Integer(123);
int i1 = i.intValue();
System.out.println(i1);
// 方式2:自动拆箱
int i2 = i;
System.out.println(i2);
20.4 基本数据类型、包装类转为String
- 因为包装类会自动拆箱转为基本数据类型,所以基本数据类型转为String的方法包装类也适用;
- 除此之外,包装类还可以直接调用toString()方法;
// 方式1:直接与""连接
Integer i = new Integer(123);
String s = i + "";
System.out.println(s);
// 方式2:调用String的valueOf()方法
int i1 = 234;
String s1 = String.valueOf(i1);
System.out.println(s1);
20.5 String转为基本数据类型
使用包装类的parseXxx()方法,返回值类型是基本数据类型;
使用包装类的valueOf()方法;
Character没有parseChar()方法,使用String的charAt()方法就行;
int i2 = Integer.parseInt(s1);
System.out.println(i2+1);
20.6 面试题
20.6.1 习题1
Integer类中内置了一个Integer数组,里面存放的是[-128,127]的所有整数,如果使用自动装箱的方式赋值并且数值在[-128,127],那么就可以直接使用数组中的元素,不用new新的对象,提高了效率;如果超过这个范围,就需要new新对象;
public void method1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);// true
Integer x = 128;
Integer y = 128;
System.out.println(x == y);// false
}
20.6.2 习题2
通过IntegerCache方法可以得知,缓存在第一次使用时初始化,所以说如果已经创建了一个缓存中的整数,使用valueOf创建第二次时,不会使用new关键字,而用已经缓存的对象。所以使用valueOf方法创建两次对象,若对应的数值相同,且数值在-128~127之间时,两个对象都指向同一个地址。Integer i = 400这样的方式来创建Integer对象,与valueOf方法的效果是一样的。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
20.7 int和Integer进行比较
int1 == integer1
,Integer是int的封装类,当Integer与int进行==比较时,Integer就会拆箱成一个int类型,所以还是相当于两个int类型进行比较,这里的Integer,不管是直接赋值,还是new创建的对象,只要跟int比较就会拆箱为int类型,所以就是相等的。
21.static关键字
21.1 意义
我们有时候希望无论是否产生对象,无论产生了多少个对象,某些特定的内容在内存中只有一份,这样可以节省内存空间。
21.2 使用
static可以用来修饰属性、方法、代码块、内部类,表示这些内容都是属于类的,所有对象共享一份,任何对象去访问修改都是同一个。
21.2.1 static修饰属性
- 格式:
权限修饰符 static 数据类型 变量名;
- static修饰的属性称为类变量或静态变量,没有static修饰的属性称为实例变量;
- 静态变量随类的加载而加载,只加载一次,位于方法区的静态域中;
- 静态变量可以使用类名或对象名调用,实例变量只能使用对象名调用;
21.2.2 static修饰方法
- 格式:
权限修饰符 static 返回值类型 方法名(参数){方法体}
- 如果方法与调用者无关,则这样的方法通常被称为类方法或静态方法,由于不需要创建对象就可以调用类方法,简化了方法的调用。
- 静态方法随类的加载而加载,只加载一次;
- 静态方法可以使用类名或对象名调用,其中使用对象名调用时底层会转化为 "类名."调用的方式,所以如果对象值为null,通过此对象调用静态方法依然可以正常编译运行;
Test test = null ;但是test.hello();------>Test.hello();
- 静态方法中不能使用this和super,因为它不属于任何一个对象;
21.3 细节
- static只能用于修饰成员变量,不能用于修饰局部变量;
- 静态方法只能访问静态成员,不能直接访问非静态,因为静态是在非静态之前加载的,如果想要访问,必须创建对象来访问;
- 非静态方法可以访问静态成员和非静态成员;
- 使用类来调用静态成员时,也需要考虑权限修饰符;
- 静态方法可以被继承,但是不能被重写;
21.4 内存解析
21.5 什么时候需要静态变量和静态方法
21.5.1 静态变量
如果需要让一个类中的所有对象都共享一个变量,那么就可以考虑静态变量。例如:定义了一个Student类,需要统计所有学生个数,可以使用静态变量来做计数器。
21.5.2 静态方法
- 操作静态属性的方法,通常声明为静态方法。例如静态变量的getter和setter方法;
- 方法中不涉及到任何与对象相关的成员,可以考虑静态方法。例如:Arrays,Math,Collection集合类等;
21.6 单例模式
21.6.1 设计模式定义
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。可以理解为套路。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。不同的英雄拥有不同的连招。
21.6.2 单例模式
定义:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
三大要素:私有构造器,存放当前类对象的静态变量(因为静态方法不能访问非静态成员),提供给外界获取对象的静态方法。
21.6.3 饿汉型
优点:线程安全;
缺点:随着类的加载而创建对象,所以对象的生命周期过长;
**注意事项:**如果不提供静态方法,而是直接将属性改为public的,在外部也可以通过调用属性获取对象,但是违背了封装性的原则,因为此时在外部可以随意修改属性的值。如果仍然想使用属性获取对象,那么可以将属性变为static final
,这样外部就不能修改属性值了。
public class SingleTon {
private SingleTon() {
}
private static SingleTon singleTon = new SingleTon();
public static SingleTon getSingleTon() {
return singleTon;
}
}
public class SingleTon {
private SingleTon() {
}
public static final SingleTon singleTon = new SingleTon();
}
21.6.4 懒汉型
优点:第一次调用静态方法时创建对象,延迟对象创建,减少系统开销;
当前写法线程不安全
class SingleTon2{
private SingleTon2(){
}
private static SingleTon2 singleTon2;
public static SingleTon2 getSingleTon2(){
if(singleTon2==null){
singleTon2 = new SingleTon2();
}
return singleTon2;
}
}
21.6.5 单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
21.6.6 单例模式的应用
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application也是单例的典型应用
- Windows 的 Task Manager (任务管理器)就是很典型的单例模式
- Windows 的 Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
22.理解 main 方法的语法
22.1 main()方法格式解析
由于 Java 虚拟机需要调用类的 main()方法,所以该方法的访问权限必须是 public,又因为 Java 虚拟机在执行 main()方法时不必创建对象,所以该方法必须是 static 的,该方法接收一个 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。
22.2 细节
- main()方法主要的作用是充当程序的入口;
- main()方法也可以看作普通的静态方法,可以通过类名或者对象来调用;
- main()方法也可以作为我们与控制台交互的方式;
- 因为 main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员;
23.代码块
- 代码块位于类中方法外;
- 代码块只能用static修饰,不能使用权限修饰符;
- 代码块无需调用,随着类或对象的加载而自动执行;
23.1 静态代码块
23.1.1 格式
static{代码块内容;}
23.1.2 细节
- 静态代码块一般用于初始化类的信息;
- 静态代码块随着类的加载而执行,且只执行一次,所以优先于main方法和构造器的执行;
- 一个类中定义多个静态代码块时,按声明的先后顺序执行,但是一般一个类中只定义一个静态代码块;
- 静态代码块中只能调用静态成员,不能调用非静态成员;
23.2 非静态代码块
23.2.1 格式
{代码块内容;}
23.2.2 细节
- 非静态代码块一般用于初始化对象的信息;
- 非静态代码块随着对象的创建而执行,每创建一个对象都会执行一次,优先于构造器的执行;
- 一个类中定义多个非静态代码块时,按声明的先后顺序执行,但是一般一个类中只定义一个非静态代码块;
- 非静态代码块可以调用静态成员和非静态成员;
23.3 程序中加载顺序
总原则:由父及子,静态先行。
23.3.1 练习1
过程:
1.想要创建对象,先要加载类的信息,因为“由父及子”,所以先加载父类的信息(Object类 -> Root类 -> Mid类 -> Leaf类),静态代码块随类的加载而执行,所以就先执行三个类中的静态代码块和静态成员变量(按照在程序中出现的顺序初始化);
2.类的信息加载完毕,然后创建对象,非静态代码块和普通成员变量随对象的创建而执行,且先于构造器的执行,所以先执行父类中的非静态代码块、普通成员变量(按照在程序中出现的顺序初始化),接着执行父类中的构造器,然后执行子类的;
3.第二次创建对象时,静态代码块不再执行。
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("123");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
new Leaf();
}
}
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
--------------------------------------------------------
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
23.3.2 练习2
静态代码块优于main()方法的执行
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
23.4 属性赋值的执行顺序
1.默认初始化
2.显式初始化或者非静态代码块初始化(看两者声明的先后顺序)
3.构造器初始化
4.调用对象.属性
或对象.方法
进行赋值
24.final关键字
- static和final同时使用时(两者没有先后顺序),可以修饰属性和方法;
24.1 练习1
public class Something {
public int addOne(final int x) {
return ++x; 编译错误,x不能重新赋值
// return x + 1; 正确,没有赋值操作
} }
24.2 练习2
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other(); 错误,不能重新赋值
o.i++; 正确,引用数据类型变量中的内容可以改变,因为其属性不是final的
} }
class Other {
public int i; }
25. abstract关键字
25.1 意义
如果一个方法不确定方法体的内容,可以将该方法声明为抽象的,到子类中去重写该方法,该方法所在的类是抽象类。
25.2 抽象类
25.2.1 格式
权限修饰符 abstract class 类名{}
25.3 抽象方法
25.3.1 格式
权限修饰符 abstract 返回值类型 方法名(形参列表);
25.4 细节
- 抽象类不能实例化,但是有构造器,因为抽象类的子类实例化需要调用父类的构造器;
- 有抽象方法的类一定是抽象类,但是抽象类中可以没有抽象方法;
- 抽象类的子类要么重写父类(直接或间接)所有的抽象方法,要么也声明为抽象类;
- 抽象类的子类如果想要实例化,必须重写父类中所有的抽象方法;
- abstract只能用于修饰方法和类;
- 抽象类可以实现接口,因为实现类中如果不重写接口中的所有抽象方法,就是抽象类;
- 抽象类可以继承非抽象的类,因为除了Object类外,其余类都有父类;
25.5 抽象类与普通类的区别
抽象类只比普通类多了定义抽象方法的功能,而且不能被实例化,其它没有什么不同(都可以有任意的成员)。
25.6 abstract不能修饰静态方法、私有方法、final方法、final类
- 静态方法:静态方法不能被重写,abstract修饰的方法必须被重写;抽象的方法不允许直接调用,如果是静态的,就可以通过类名进行调用,存在冲突;
- 私有方法:由于封装性,私有方法不能被重写,abstract修饰的方法必须被重写;
- final方法:final修饰方法表示不能被重写,abstract修饰的方法必须被重写;
- final类:final修饰的类不能被继承,但是抽象类必须要被继承才有意义;
25.7 抽象类匿名子类的匿名对象
- 一次性且需要重写所有的抽象方法;
- 非抽象类也有匿名子类的匿名对象,和抽象类的格式相同;
25.7.1 抽象类匿名子类的对象
class EmployeeTest {
public static void main(String[] args) {
Employee是抽象类,匿名子类是不创建抽象类的子类
直接使用下面的形式来创建对象,需要重写所有的抽象方法
Employee e = new Employee() {
@Override
public void work() {
System.out.println();
}
};
}
}
public abstract class Employee {
public Employee() {
}
public abstract void work();
}
25.7.2 抽象类匿名子类的匿名对象
class EmployeeTest {
public static void main(String[] args) {
需要重写所有的抽象方法
new Employee() {
@Override
public void work() {
System.out.println();
}
}.work();
}
}
public abstract class Employee {
public Employee() {
}
public abstract void work();
}
25.8 模板方法设计模式
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式;
- 当功能内部一部分实现是确定的,一部分实现是不确定的,将不确定的抽象化,让子类去实现;
25.8.1 模板方法的应用
- 数据库访问的封装
- Junit单元测试
- Spring中JDBCTemlate、HibernateTemplate等
25.8.2 例子
public class TemplateTest {
public static void main(String[] args) {
SubTemplate subTemplate = new SubTemplate();
subTemplate.sendTime();
}
}
abstract class Template {
public final void sendTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println(end = start);
}
// 将code抽象化,让子类重写
protected abstract void code();
}
class SubTemplate extends Template {
@Override
protected void code() {
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
}
}
}
}
25.8 抽象方法应用实例
- 圆形、矩形、三角形有共同的父类几何图形,在几何图形中有一个求面积的方法,但是在几何图形中不知道如何实现该方法,所以只能将该方法声明为抽象的,在子类中对其重写。
- IO中的InputStream,OutputStream,Reader,Writer,在其内部定义了read()和write()抽象方法。
26.接口
26.1 概述
26.1.1 定义
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。==继承是一个"是不是"(is a)的关系,而接口实现则是"能不能"的关系。==接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
26.1.2 格式
- 接口的权限修饰符和类一样,只能是public和缺省;
权限修饰符 interface 接口名1,接口名2,....{}
26.1.3 意义
- 类与接口的区别:一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就可以得到多重继承的效果(一个类实现多个接口)。
- 实现与继承的区别:另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接;鸟和飞机都能费。
26.1.4 细节
- 接口不能实例化;
- 接口中的静态成员只能通过接口名调用,不能通过实现类对象调用;
- 接口中只能声明全局常量和方法,没有代码块和构造器;
26.1.5 实现类
格式:权限修饰符 class 类名 extends 父类名 implements 接口名{}
26.1.6 接口和类之间的关系
26.2 接口中的成员
26.2.1 JDK1.8之前
接口中只能有全局常量和抽象方法;
26.2.2 JDK1.8之后
接口中增加了静态方法(1.8)、默认方法(1.8)、私有方法(1.9)、私有静态方法(1.9);
26.2.3 默认方法
格式:[public] default 返回值类型 方法名(参数){}
- 接口中的默认方法相当于类中的普通方法,由于JDK1.8之前接口中的方法都是抽象的,为了兼容,所以增加了default关键字;
- public 可以省略;
- 接口中的默认方法有方法体,可以被实现类继承,也可以被实现类重写(重写不用加default);
- 接口中的默认方法在实现类中被重写后,如果想要在实现类中调用接口中的默认方法,使用
接口.super.默认方法()
; - 一个类实现多个接口,并且多个接口中的默认方法同名同参,那么实现类必须重写一个默认方法;
- 接口中的默认方法和父类中的非抽象方法同名同参时,不会冲突,优先使用父类中的方法;
26.2.4 静态方法
格式:[public] static 返回值类型 方法名(参数){}
- 接口中的静态方法不能被实现类继承,只能通过接口名进行调用;
26.2.5 抽象方法
格式:[public] [abstract] 返回值类型 方法名();
- 接口中的抽象方法的权限修饰符和abstract关键字都可以省略;
- 实现类中必须重写接口中的所有抽象方法,不然实现类也是抽象的;
26.2.6 私有方法和私有静态方法
格式:private [static] 返回值类型 方法名(参数){}
- 只能在接口内部被调用,主要用于解决多个默认/静态方法代码冗余的问题;
26.2.7 全局常量
格式:[public static final] 类型 变量名 = 变量值;
- 只能显式赋值,因为接口中没有代码块和构造器;
- 只能通过接口名进行调用;
26.3 抽象类与接口的异同
- 抽象类和接口都不能实例化,但是抽象类中有构造器,接口中没有;
- 抽象类和接口的权限修饰符一样,只能是public和缺省;
26.4 应用实例
- JDBC:操作数据库时,我们调用的都是JDBC中定义的接口,而不是某个数据库厂商提供的api
26.5 父类的变量和接口中的变量相同
interface A{
int x = 0;
}
class B{
int x =1;
}
class C extends B implements A {
public void pX(){
System.out.println(x); //super.x A.x
}
public static void main(String[] args) {
new C().pX();
}
}
答案:错误。在编译时会发生错误(错误描述不同的JVM有不同的信息,意思就是未明确的x调用,
两个x都匹配(就象在同时import java.util和java.sql两个包时直接声明Date一样)。对于父类的变量,可以用super.x来明确,
而接口的属性默认隐含为 public static final.所以可以通过A.x来明确。
27.内部类
- 成员内部类和局部内部类都会生成对应的字节码文件;
27.1 概述
27.1.1 定义
允许一个类A声明在另一个类B的内部,那么类A就是内部类,类B是外部类;根据类声明的位置,分为成员内部类(静态和非静态)和局部内部类(方法内、代码块内、构造器内);
27.1.2 不同类的权限修饰符
- 外部类:public和缺省
- 成员内部类:public/protected/确省/private
- 局部内部类:什么都不写
27.1.3 意义
只有当前类使用此结构,所以定义为内部类。
27.2 成员内部类
27.2.1 作为外部类的成员
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
27.2.2 作为一个类
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以abstract修饰
27.2.3 实例化内部类
静态成员内部类:InnerClass.A a = new InnerClass.A();
非静态成员内部类:InnerClass innerClass = new InnerClass(); InnerClass.B b = innerClass.new B();
public class InnerClass {
public static class A{
// 成员内部类
}
class B{
}
}
27.2.4 细节
- 静态内部类只能访问外部类的静态成员;
- 非静态内部类可以访问外部类的所有成员,包括私有成员;
- 静态内部类可以有静态成员,非静态内部类不可以有静态成员;
27.2.5 内部类中调用外部类成员
- 如果没有重名,直接调用即可;
- 如果重名,
this.成员
表示调用内部类对象,外部类.this.成员
表示调用外部类成员;
27.2.6 例题
public class OuterClass {
private double d1 = 1.0;
//insert code here
}
You need to insert an inner class declaration at line 3. Which two inner class declarations are
valid?(Choose two.)
A. class InnerOne{
public static double methoda() {return d1;}
}
B. public class InnerOne{
static double methoda() {return d1;}
}
C. private class InnerOne{
double methoda() {return d1;}
}
D. static class InnerOne{
protected double methoda() {return d1;}
}
E. abstract class InnerOne{
public abstract double methoda();
}
说明如下:
一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 A、B 错
二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。
故 D 错
三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确
四.答案为C、E
27.3 局部内部类
使用较少,一般使用匿名内部类
局部内部类只能在方法内部使用,出了方法就不能使用
27.3.1 匿名内部类
- 匿名内部类必须继承父类或实现接口。
- 匿名内部类对象只能使用多态形式引用,父类引用指向子类对象。
- 重写接口中所有的抽象方法是因为只有非抽象的类才能创建对象,除了抽象方法,匿名内部类中可以重写父类或接口中的任意方法来实现自己的需求。
接口名称 变量名 = new 父类构造器(实参列表)|实现接口(){重写接口中所有的抽象方法};
27.3.2 练习
public class Test {
public static void main(String[] args) {
// 创建了一个Object类的子类对象重写了Object中的equals方法,并赋值给父类引用,
// 所以使用对象o调用的是子类重写的方法
Object o = new Object() {
public boolean equals(Object obj) {
return true;
}
};
System.out.println(o.equals("Fred"));
}
}
27.3.3 匿名内部类的匿名对象
new 接口名称(){重写接口中所有的抽象方法}.方法();
27.3.4 注意事项
局部内部类中访问所在方法的局部变量时,这个局部变量必须是final的(java8之后可以省略)。因为从生命周期角度来说,new一个内部类对象是在堆中,而局部变量在栈中,方法结束后出栈,局部变量消失,而内部类的对象还存在堆中,等待垃圾回收; 后边在说