5. 面向对象
Java面向对象的三条主线:
- Java类及类的成员:属性、方法、构造器、代码块、内部类
- 面向对象的三大特征:封装性、继承性、多态性
- 其他关键字:this、super、static、final、abstract、interface、packpage、import等
5.1 面向过程与面向对象
-
面向对象(OOP-object oriented programming)与面向过程(POP-procedure oriented progranmming)
- 两者都是一种思想,面向对象是相对于面向过程而言的
- 面向过程,强调的是功能行为,以函数为最小单位,考虑怎么去做
- 面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
-
举例:
//面向过程 1. 打开冰箱 2. 把大象装进冰箱 3. 把冰箱门关上 //面向对象(强调具备了功能的对象,考虑谁来做) 人{ 打开(冰箱){ 冰箱.打开(); } 操作(大象){ 大象.进入冰箱(); } 关闭(冰箱){ 冰箱.关闭(); } } 冰箱{ 打开(){}; 关闭(){}; } 大象{ 进入冰箱(){}; }
-
思想概述
- 根据问题需要,选择问题所针对的现实世界中的实体
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类
- 将类实例化成计算机中的对象,对象是计算机中解决问题的最终工具
5.2 类和对象
-
类(class)和对象(object)是面向对象的核心概念
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的个体,也称作实例
-
类=抽象概念的人;对象=实实在在的某个人
-
面向对象程序设计的重点是类的设计;类的设计,其实就是类的成员的设计
-
Java类及类的成员
-
常见的类的成员有:
- 属性(Field):对应类的成员变量 = 域、字段
- 行为(Method):对应类的成员方法 = 函数
-
类的成员构成:
public class Person { String name; int age = 1; boolean isMale; public void eat(){ System.out.println("人可以吃饭"); } public void sleep(){ System.out.println("人可以睡觉"); } public void talk(String language){ System.out.println("人可以说"+language); } }
-
创建类的对象:
public class testPerson { public static void main(String[] args) { Person p1 = new Person(); //调用对象的属性和方法 p1.name = "Tom"; p1.age = 20; p1.isMale = true; //使用对象调用类的成员 p1.eat(); p1.sleep(); String lang = "English"; p1.talk(lang); } }
-
如果创建了一个类的多个对象,则每个对象都独立地拥有一套类的属性(非static的),意味着如果我们修改一个对象的属性a,则不影响另一个对象的属性a
Person p2 = new Person(); System.out.println(p2.name);//null
-
类的访问机制
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量(注意:static方法访问非static属性,编译不通过)
- 在不同类中的访问机制:先要创建访问类的对象,再用对象访问类中定义的成员
-
-
对象的内存解析:
-
JVM规定的内存结构
-
堆(Heap):此内存区的唯一目的就是存放对象的实例,所有的对象实例及数组都要在堆上分配
-
栈(Stack):指虚拟机栈(VM Stack),虚拟机栈用于存储局部变量等,方法执行完自动释放
-
方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
-
-
内存解析:
-
Person p1 = new Person(); p1.name = "Tom"; p1.isMale = true; Person p2 = new Person(); Person p3 = p1;//p1的地址值赋给p3,没有声明一个p3对象 p3.age = 10;
-
-
-
匿名对象:
-
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象
new Person().eat();
-
使用情况
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象
- 我们经常将匿名对象作为实参传递给一个方法调用
-
5.3 属性
-
语法格式:
修饰符 数据类型 属性名 = 初始化值;
-
说明;
-
修饰符:
- 常用的修饰符有:private、缺省、protected、public(被调用的可见性)
- 其他修饰符:static、final
-
数据类型:
任何基本数据类型或引用型数据类型
-
属性名:
属于标识符,符合命名规则即可
-
-
变量的分类:
-
**成员变量:**声明在——方法体之外,类体{ } 内
- 实例变量(不以static修饰)
- 类变量(以static修饰)
-
**局部变量:**声明在——方法体内部、方法形参、代码块内、构造器形参、构造器内部
-
形参(方法、构造器中定义的变量)
public void talk(String language){ System.out.println("人可以说"+language); }
-
方法局部变量(在方法内定义)
public void eat(){ String food = "烙饼"; System.out.println("人喜欢吃"+food); }
-
代码块局部变量(在代码块中定义)
-
-
区别比较:
成员变量 局部变量 声明的位置 直接声明在类中 方法形参或内部、代码块内部、构造器内等 修饰符 private、public、static、final等 不能用权限修饰符修饰,可以使用final 初始化值 有默认初始化值 没有默认的初始化值,必须显式赋值。(形参在调用时赋值即可) 内存加载的位置 堆空间 或 静态域内 栈空间
-
-
给属性赋值的方法:
- 默认初始化(只执行一次)
- 显式初始化(只执行一次)
- 构造器初始化(只执行一次)
- 通过
对象.方法
或对象.属性
的方式赋值
5.4 方法
-
什么是方法?
- 方法是类或对象行为特征的抽象,用来完成某个功能操作
- 将功能封装成方法的目的:可以实现代码重用,简化代码
- Java中的方法不能独立存在,所有的方法必须定义在类里
-
方法声明格式:
-
修饰符 返回值类型 方法名 (参数类型 形参1,参数类型 形参2,...){ 方法体程序代码; return 返回值; }
-
说明:
- 修饰符:public、缺省、private、protected等
- 返回值类型:void 没有返回值
- 形参列表:可以包含零个、一个或多个;用“,”隔开
-
-
return关键字的使用
- 使用范围:方法体内
- 作用:
- 结束方法
- 针对于有返回值类型的方法,使用
return 数据
方法返回所要的数据
- return关键字后边不可以声明执行语句
-
方法在使用中,可以调用当前类的属性或方法
方法中不可以定义方法
public class Person { String name; int age ; boolean isMale; public void eat(){ System.out.println("人可以吃饭"); } public void sleep(){ eat(); //调用当前类方法 if(age>0){ //调用当前类属性 System.out.println("人可以睡觉"); } } }
-
练习1:
利用面向对象的编程方法,设计类Circle计算圆的面积
public class Person { double radius; public double findArea(){ //不适用形参,设计时保留类的属性 return radius*radius*Math.PI; } } public class testPerson { public static void main(String[] args) { Person p1 = new Person(); p1.radius=2.4; System.out.println(p1.findArea()); } }
-
练习2:对象数组
定义类Student,包含三个属性:学号number(int),年级state(int),成绩
score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息public class Student { int number; int state; int score; @Override public String toString() { return "Student{" + "number=" + number + ", state=" + state + ", score=" + score + '}'; } } public class TestStudent { public static void main(String[] args) { //声明一个Student的数组 Student[] stus = new Student[20]; for(int i=0;i< stus.length;++i){ stus[i] = new Student(); stus[i].number = i+1; stus[i].state = (int)(Math.random()*(6-1+1))+1; stus[i].score = (int)(Math.random()*(100+1)); } TestStudent testStudent = new TestStudent(); testStudent.findStudent(stus); testStudent.popSort(stus); } //遍历对象数组找3年级 public void findStudent(Student[] stus){ for(int i=0;i< stus.length;++i){ if(stus[i].state == 3){ System.out.println(stus[i].toString()); } } } //按成绩冒泡排序 public void popSort(Student[] stus){ for(int i=0;i< stus.length;++i){ for(int j=0;j< stus.length-1-i;++j){ if(stus[j].score>stus[j+1].score){ Student temp = stus[j]; stus[j]=stus[j+1]; stus[j+1]=temp; } } } } }
5.5 再谈方法
-
方法的重载:
-
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可(为不同参数,提供不同的功能)
-
特点:与方法的返回值、权限修饰符 无关,只看参数列表
-
示例:
public class PrintStream { public static void print(int i) {……} public static void print(float f) {……} public static void print(String s) {……} public static void main(String[] args) { print(3); print(1.2f); print("hello!"); } }
-
通过对象调用方法时,通过(方法名 → \to →参数列表)确定用哪一个方法
-
-
可变个数形参:
-
jdk5.0新增的方法
-
示例:
public class TestStudent { public static void main(String[] args) { TestStudent testStudent = new TestStudent(); testStudent.show(); //可以是0个 testStudent.show("String1"); testStudent.show("String1","String2");//动态改变参数个数 } //public void show(String s){ // System.out.println("show(String s)"); //} //不注释的话testStudent.show("String1")会优先调用此方法 public void show(String ... strs){ //只需要写一个方法 System.out.println("show(String ... strs)"); for(int i=0;i< strs.length;++i){ //内部取每个参数时与数组一样 System.out.println(strs[i]); } } //public void show(String[] strs){ // System.out.println("show(String[] strs)"); //} //可变参数方法与形参类型相同的数组不构成重载,不能共存 }
-
注意:
String ... strs
只能放在形参列表的最后一个//Vararg parameter must be the last in the list public void show(String ... strs,int a){ }
-
-
⭐方法参数的值传递机制:
-
关于变量的赋值
-
基本数据类型变量:
int m = 10,n = 20; System.out.println(m+","+n); m=n;//此时赋值的是变量所保存的数据值 System.out.println(m+","+n); //10,20 //20,20
-
引用数据类型变量:(实例、数组 )
Student student1 = new Student(); student1.number = 1; System.out.println(student1.number); Student student2 = student1;//此时赋值的是变量保存的指向堆中实例的数据地址值 student2.number = 2; System.out.println(student1.number); //1 //2
-
-
方法的形参的传递机制:
-
形参:方法声明是的参数
-
实参:方法调用时,实际传给形参的值
-
Java中方法的参数传递只有一种:值传递
-
参数是基本数据类型:实参赋值给形参的只是实参实际存储的数据值,方法内对形参的操作,并不会影响到实参的值
public class TestStudent { public static void main(String[] args) { int m = 10; int n = 20; TestStudent testStudent = new TestStudent(); testStudent.swap(m,n); System.out.println(m+","+n);//输出:10,20 //调用了swap(),但m、n并没有交换 //类似问题:见下方附加题 } public void swap(int a,int b){ int temp = a; a=b; b=temp; } }
-
参数是引用数据类型:实参赋值给形参的是实参存储数据的地址值
public class Student { int m; int n; } public class TestStudent { public static void main(String[] args) { TestStudent testStudent = new TestStudent(); Student student = new Student(); student.m=10; student.n=20; testStudent.swap(student); System.out.println(student.m+","+student.n); //输出结果20,10(实现了交换) } public void swap(Student stu){ int temp = stu.m; stu.m= stu.n; stu.n=temp; } }
-
-
附加题:
public class Test { public static void main(String[] args) { int a = 10; int b = 20; method(a,b); //需要在调用method方法后,打印出a=100,b=200,写出method() System.out.println("a="+a); System.out.println("b="+b); } }
//方法一: public void method(int m,int n){ a=a*10; b=b*10; System.out.println("a="+a); System.out.println("a="+a); System.exit(0); //直接在此退出程序,不让执行main()的两个打印语句,只能在输出语句上做文章 }//方法二:对println()方法重写
-
-
-
递归方法(recursion):
-
一个方法体内调用它自身
-
示例:
//实现1-100所有自然数的和 public class Recursion { public static void main(String[] args) { Recursion recursion = new Recursion(); int sum = recursion.getSum(100); System.out.println(sum); } public int getSum(int n){ if(n == 1){ return 1; }else { return n + getSum(n-1); } } }
//实现递归数列求值:f(0)=1;f(1)=4;f(n+2)=2*f(n+1)+f(n) public class Recursion { public static void main(String[] args) { Recursion recursion = new Recursion(); int key = recursion.feibo(10); System.out.println(key); } public int feibo(int n){ if(n == 0){ return 1; }else if(n == 1){ return 4; }else{ //return feibo(n+2)-2*feibo(n+1);错误:栈溢出 return 2*feibo(n-1) + feibo(n-2); } } }
-
5.6 封装与隐藏
-
程序设计追求高内聚低耦合:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅对外暴露少量的方法用于使用
-
封装性设计思想:
隐藏对象内部的复杂性,之对外公开简单的API,便于外界调用,从而提高系统的可扩展性、可维护性(把该隐藏的隐藏起来,该暴露的暴露出来)
-
示例1:
当创建一个类的对象时,可以通过
对象.属性
来对对象进行赋值操作,但有时赋值操作要遵循属性的数据类型和存储范围的约束,同时实际问题中,需要给属性赋值添加额外的限制条件,这个条件不能在属性声明时体现,是能通过方法进行限制条件的添加public class Student { String name; private int legs; //legs改动时有特定限制条件,故legs设为private public void show(){ System.out.println("name="+name+",legs="+legs); } public void setLegs(int l){ if(l>=0&&l%2==0){ legs=l; }else{ legs=0; } } } public class TestStudent { public static void main(String[] args) { Student student = new Student(); student.name="abc"; //student.legs=-3; //'legs' has private access in 'Student' student.setLegs(-6); student.show(); } }
-
示例2:
private int legs;
:legs的赋值限制了,但怎么获取到legs的值呢?public void getLegs(){return legs;}
-
封装性的体现:
- 将类的属性
***
私有化(private),同时,提供公共的(publlic)方法来获取get***()
和设置set***()
私有属性 - 将方法设置为private,只能提供给类内部调用
- 单例模式
- …
- 将类的属性
-
四种访问权限修饰符:
修饰符 类内部 同一个包 不同包的子类 同一个工程 private √ 缺省 √ √ protected √ √ √ public √ √ √ √ 注意:
- 四种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
- public、protected、缺省、private,置于类的成员定义前
- 对于类class的权限修饰只能使用public、缺省
5.7 构造器(构造方法)
-
构造器的==特点:==
- 如果没有显示定义的构造器,则系统默认提供一个空参的构造器
- 定义构造器的格式:
权限修饰符 类名(形参列表){}
- 不声明返回值的类型
- 一个类中定义的多个构造器,构成重载
- 一旦显式地定义了构造器(任何形参列表的),系统将不再提供默认的空参构造器,故一个类中至少有一个构造器
-
构造器的==作用:==
-
创建对象:
对象类型 对象名 = new 构造器
public class Student { public Student(){ System.out.println("Student Constructor..."); } } public class TestStudent { public static void main(String[] args) { Student student = new Student(); } }
-
初始化对象的属性:
对象类型 对象名 = new 构造器(形参)
public class Student { String name; public Student(){ System.out.println("Student Constructor..."); } public Student(String s){ name=s; System.out.println("Student Constructor(String s)..."); } } public class TestStudent { public static void main(String[] args) { //创建对象时直接初始化属性 Student student = new Student("abc"); System.out.println(student.name); } }
-
5.8 JavaBean
-
是一种Java语言写成的可重用组件
-
所谓JavaBean,是指符合如下标准的Java类
- 类是公共的
- 有一个无参的公共构造器
- 有属性,且有对于的get、set方法
-
示例:
public class JavaBean { private String name; private int age; public JavaBean() { //空参构造器 } public String getName() { //get(),set()方法 return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
5.9 this关键字
-
引入原因:
在写set()方法时,形参往往需要取符合实际意义的名字,则就造成了与类属性名的重名,只有重名时才显示的写出this,通常都会省略this
类似地,构造器中也可能出现这种重名情况
public void setName(String name) { name = name; //区分不出哪个是形参,哪个是类属性 }
-
引入
this
关键字①修饰调用属性:public void setName(String name) { this.name = name; //this.name就表示当前类的属性 }
-
this
表示当前对象,即在之后调用时,所创建、使用的那个对象Student student = new Student(); student.setName("abc"); //this提前代指了这个名叫student的对象
-
this②修饰调用方法:
public class Student { public void eat(){ this.study(); //谁调用eat(),就调用它的study() System.out.println("吃"); } public void study(){ System.out.println("学"); } } public class TestStudent { public static void main(String[] args) { Student student = new Student(); student.eat(); } } //学 //吃
-
this③修饰调用构造器:
-
引入原因:若对象初始化时又必要的操作,就需要在每一个构造器里都添加这些代码,这样就造成了代码的冗余
public class Student { private String name; private int age; public Student() { //此处有Student初始化时必要的操作如吃eat()... this.eat(); } public Student(String name) { this.eat(); this.name=name; } public Student(int age) { this.eat(); this.age = age; } public Student(int age,String name) { this.eat(); this.age = age; this.name=name; } public void eat(){ System.out.println("吃"); } }
-
使用
this
关键字:public class Student { private String name; private int age; public Student() { this.eat(); } public Student(String name) { this(); //使用this调用Student()构造器 this.name=name; } public Student(int age,String name) { this(name); //使用this调用Student(String name)构造器 this.age = age; } public void eat(){ System.out.println("吃"); } } public class TestStudent { public static void main(String[] args) { Student student = new Student(20,"abc"); //此时只使用Student(String name)构造器,也会执行前驱操作eat() } }
-
注意:
- 如果一个类中有n个构造器,那么最多有n-1个构造器中使用了
this(形参列表)
,不能形成调用环 this(形参列表)
必须声明在当前构造器的首行- 构造器内部最多只能声明一个
this(形参列表)
,用来调用其他构造器
- 如果一个类中有n个构造器,那么最多有n-1个构造器中使用了
-
5.9 package、import关键字
-
package
- 为了更好的实现项目中的管理,提供包的概念
- 使用package声明类或接口所属的包,声明在源文件的首行
- 包属于标识符,遵循标识符的命名规则
- 每
.
一次就代表一层文件目录,package 顶层包名.子包名 ;
-
import
-
在源文件中显式地使用import导入指定包下的类、接口
-
声明在包的声明和类的声明之间
-
如果使用的类或者接口是属于
java.lang
包下定义的,则可以省略import导入 -
本包下定义的类或接口,也可以省略import导入
-
如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的
是哪个类import com.demo.Student;public class TestStudent { public static void main(String[] args) { //写全类名,用于区分不同包下的同名类 com.company.Student student = new com.company.Student(20,"abc"); Student student1 = new Student(); } }
-
如果已经导入
java.a
包下的类。那么如果需要使用a包的子包下的类
的话,仍然需要导入 -
import static
:导入指定类或接口中的静态结构:属性或方法import static java.lang.System.*; public class TestStudent { public static void main(String[] args) { out.println("可以省略Syste"); } }
-
5.10 继承性
-
继承性的好处
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的扩展
- 为之后的多态性的使用提供前提
-
继承性的格式:
class A extends B
- A:子类、派生类
- B:父类、超类、基类
- 一旦子类继承了父类后,子类中就获得了父类中声明的属性和方法
-
规则:
-
子类不能直接访问父类中私有的
private
的成员变量和方法。能获取到,只是因为封装性,使得子类不能直接调用 -
子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的扩展
-
-
示例:
public class Person { String name; private int age = 20; String major; public void eat(){ System.out.println("吃"); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class Student extends Person { public void study(){ System.out.println("学"); } public void showAge(){ //System.out.println(age); //int age不报错,但private int age会报错 System.out.println(getAge()); //子类继承得到了父类的getAge()方法,便可访问父类私有属性 } }
public class TestStudent { public static void main(String[] args) { Student student = new Student(); student.name="abc"; student.eat(); student.study(); student.showAge(); //即使private int age,但使用getAge()也可以获取到父类的age值 } }
-
Java只支持单继承和多层继承,不允许多重继承
-
Obeject类
-
如图:父类中并没有声明这些方法,但是父类对象可以调用
-
如果没有显式地声明一个类的父类的话,则此类继承于
java.lang.Object
类,意味着所有的Java类具有java.lang.Object类声明的功能
-
5.11 方法的重写
-
定义:在子类继承父类后,可以对父类中同名、同参数的方法,进行覆盖操作
-
应用:重写后,当创建子类对象后,通过子类对象调用父类中的同名同参数的方法时,实际执行的时子类重写后的方法
-
示例:
public class Person { public void eat(){ System.out.println("吃"); } } public class Student extends Person { public void eat(){ System.out.println("学生要吃健康的食物"); } }
-
重写的规定:
-
子类重写的方法的方法名和形参列表与父类被重写的方法名和形参列表相同
-
子类重写的方法的权限修饰符不小于弗雷被重写的方法的权限修饰符
特殊的:子类不能重写父类中声明为private权限的方法
-
返回值类型:
-
父类中被重写的方法的返回值为void,则子类中重写的方法的返回值只能是void
-
父类中被重写的方法的返回值为基本数据类型,则子类中重写的方法的返回值只能是相同的基本数据类型
-
父类中被重写的方法的返回值为A类,则子类中重写的方法的返回值只能是A类或A的子类
public class Person { public Object eat(){ System.out.println("吃"); } } public class Student extends Person { public String eat(){ System.out.println("学生要吃健康的食物"); } }
-
-
子类重写的方法抛出的异常类型大小不小于父类被重写的方法抛出的异常类型
-
子类和父类中同名同参数的方法要么都声明成非static的(考虑重写),要么都声明成static的(一定不是重写)
-
-
区分方法的重载与重写:
- 重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了。 Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
- ⭐对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定” ;
- ⭐而对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”
5.12 super关键字
-
引入原因:
- 当子类重写了父类的方法后,在子类中需要调用父类中那个没有被重写的方法时,(如果只使用方法名会被人认为是子类的重写后的新方法),就需要使用
super
关键字 - 当父类和子类中有同名属性时,在调用时如何区分
- 当子类重写了父类的方法后,在子类中需要调用父类中那个没有被重写的方法时,(如果只使用方法名会被人认为是子类的重写后的新方法),就需要使用
-
super的作用
-
我们可以在子类的方法或构造器当中,使用
super.属性
或this.属性
的方式,显式的调用父类中声明的属性或方法,但是,通常情况下会将关键字省略特殊情况下:当子类和父类定义了相同名的属性时,就需要使用关键字区分
示例:
public class Person { String name; int age = 20; int id = 10001; } public class Student extends Person { int id = 10001; public void show(){ //通常会将this与super关键字省略不写 System.out.println("age="+age+","+"name="+name); //当子类和父类的属性名相同时,就需要加上关键字加以区分 System.out.println("父类id="+super.id+"子类id="+","+this.id); } }
-
调用父类中未被重写的方法
示例:
public class Person { public void eat(){ System.out.println("吃"); } } public class Student extends Person { public void study(){ eat(); //子类中重写的 super.eat(); //父类中未被重写的 System.out.println("学"); } public void eat(){ System.out.println("学生要吃健康的食物"); } } public class TestStudent { public static void main(String[] args) { Student student = new Student(); student.study(); } } //学生要吃健康的食物 //吃 //学
-
super.
会直接在父类中找,如果找不到就一直向上找对应的方法或属性,this.
会直接在本类中找
-
-
super调用构造器
-
之前提到当父类的属性设置为private后,在子类的构造其中并不能使用this关键字获取到属性,只能使用父类中提供的get()方法
public class Person { String name; private int age = 20; public Person(){ } public Person(int age) { this.age = age; } } public class Student extends Person { public Student() { } public Student(int age,String name) { this.name = name; //this.age = age; //'age' has private access in 'com.company.Person' } }
-
使用
super()
调用父类的构造器public class Student extends Person { public Student() { } public Student(int age,String name) { //这里直接调用了父类的构造器:public Person(int age) super(age); this.name = name; } }
-
在调用构造器时,
super(形参列表)
的使用,必须声明在子类构造器的首行,这就与this(形参列表)
调用构造器也必须声明在首行,形成矛盾,故在类的构造器中,对于super与this关键的使用只能二选一 -
故在任意的子类构造器中都会首先调用父类的空参构造器,存在一个看不见的
super()
,故在写父类时习惯性的添加空参构造器,否则子类的构造器会报错public class Person { String name; int age = 20; public Person(){ System.out.println("我被调用了"); } } public class Student extends Person { public Student() { } public Student(int age,String name) { //如果构造器中没有显式地使用过super()或this() //则这里有一个默认的super(无参) this.age = age; this.name = name; } } public class TestStudent { public static void main(String[] args) { Student student = new Student(28,"abd"); } } //我被调用了
-
在类的构造器中,至少有一个使用了
super(形参列表)
,调用父类的构造器,具体见子类对象实例化的过程
-
5.13 子类对象实例化的过程
-
从结果上来看:(继承性)
- 子类继承父类之后,就获取了父类中声明的方法或属性
- 创建子类对象,在堆空间中,就回家子啊所有父类中声明的属性或方法
-
从过程上来看:
-
当我们通过子类构造器创建子类对象时,我们一定会直接或间接的调用父类的构造器,进而调用父类的父类的构造器,直到调用了
java.lang.Object
类中的空参的构造器为止(图中一个小矩形为一个构造器)
-
正因为加载过所有父类的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑进行调用
-
-
明确:
创建子类实例的过程中,虽然调用了父类的构造器,但至始至终就创建了一个对象
5.14 多态性
-
多态性:
可以理解为一个事物的多种形态
-
对象的多态性:
父类的引用指向子类的对象(子类的对象赋给父类的引用)
-
多态的使用:
有了对象的多态性以后,我们在编译期,只能调用父类声明的方法,但在运行期间,实际执行的是子类重写后的方法(编译看左边,运行看右边)
//对象的多态性:父类的引用指向子类的对象 Person person1 = new man();//⭐ Person person2 = new woman(); //虚拟方法调用 //多态的使用:当调用子父类同名同参的方法时,实际执行的是子类重写过的方法 person1.eat(); person2.eat(); //person1.earnMoney(); //⭐编译期间,认为person1是父类对象,不能调用子类特有的方法 // Cannot resolve method 'earnMoney' in 'Person'
-
多态性使用的前提:
- 有类的继承关系
- 有方法的重写
-
为什么要有多态性?
-
示例1:
public class Person { public void eat(){ System.out.println("吃"); } public void walk(){ System.out.println("走路"); } }
package com.company; public class man extends Person{ public void eat(){ System.out.println("男人吃"); } }
package com.company; public class woman extends Person{ public void eat(){ System.out.println("女人吃"); } }
public class PersonTest { public static void main(String[] args) { PersonTest personTest = new PersonTest(); personTest.func(new man());//⭐ personTest.func(new woman()); } public void func(Person person){//Person person = new man() person.eat(); //传入哪个子类实例,就执行相应的子类重写后的方法 person.walk(); } }
-
如果没有多态性:
public class PersonTest { public static void main(String[] args) { PersonTest personTest = new PersonTest(); personTest.func(new man()); personTest.func(new woman()); } //如果没有多态性,就需要写很多重载的func() public void func(man man){ man.eat(); man.walk(); } public void func(woman woman){ woman.eat(); woman.walk(); } }
-
示例2:
class Order{ public void method(Object obj){ } }
-
示例3:
class Driver{ //Connection conn = new MySQLConnection //Connection conn = new OracleConnection //不同的数据库new不同的子类实例 public void doData(Connection conn){ conn.method1();//从MySQL切换到Oracl方法体内的代码不需要改 conn.method2(); } }
-
-
对象的多态性只适用于方法,不适用于属性
public class PersonTest { public static void main(String[] args) { PersonTest personTest = new PersonTest(); Person person = new man(); System.out.println(man.id);//输出父类中的id } }
-
instanceof操作符
-
有了对象的多态性以后,内存中实际是加载了子类特有的属性和方法的
new man()
,但是由于变量声明为父类类型Person person
,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法并不能调用 -
如何才能调用子类特有的属性和方法
-
使用强制类型转换符向下转换
public class PersonTest { public static void main(String[] args) { Person person = new man();//多态 man man1 = (man)person; //使用强转后就可以调子类特有的的属性和方法 man1.earnMoney(); man1.isSmoking = true; } }
-
使用强转时可能出现
ClassCastException
异常woman woman1 = (woman) person; woman1.goShopping(); //Person person = new man():personnew的时候是一个man
-
为了避免出现上述异常,引入
instanceof
关键字
-
-
instanceof关键字的使用
-
使用情形:为了避免在向下转型时出现ClassCastException异常,我们在向下转型之前,先进性instanceof的判断,一旦返回为ture,就向下转型
public static void main(String[] args) { Person person = new man(); man man1 = (man)person; if(person instanceof woman){//判断为false不会执行 woman woman1 = (woman)person; woman1.goShopping(); } }
-
如果
a instanceof A
返回true,则b instanceof B
也返回true,其中B是A的父类
-
-
开发中使用很少,但也可以用
public void func(Person person){//Person person = new man() person.eat(); person.walk(); if(person instanceof man){ ((man) person).earnMoney(); } }
-
-
面试题:多态是编译时行为还是运行时行为?
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); //只有随机数key运行是确定后,才能确定到底执行哪个子类的方法 animal.eat(); } }
5.15 Object类的使用
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类, 则默认父类为java.lang.Object类
public class PersonTest { public static void main(String[] args) { Person person = new Person(); System.out.println(person.getClass().getSuperclass()); } } //class java.lang.Object
-
Object类中的功能(属性、方法)具有通用性
clone()
:复制一个对象equals()
:比较两个对象是否相等finalize()
:发现堆空间实体没有任何引用指向它,则将其回收(注:回收之前调用此方法)注:final、finally、finalize的区别getClass()
反射hashCode()
集合notify()
多线程notifyAll()
toString()
wait()
多线程wait(long timeout)
wait(long timeout,int nanos)
-
Object类中只有一个空参构造器
-
equals( ) 方法
-
==
运算符的回顾- 可以使用在基本数据类型和引用数据类型中
- 基本数据类型:比较两个变量存储的数据是否相等
- 引用数据类型:比骄两个对象的地址值是否相同
public class equalsTest { public static void main(String[] args) { //基本数据类型 int i = 10; int j = 10; System.out.println(i == j);//true double d = 10.0; System.out.println(i == d);//true boolean b = true; //System.out.println(i == b); char c = 10; System.out.println(i == c);//true char c1 = 'A'; char c2 = 65; System.out.println(c1 == c2);//true //引用类型 Person person1 = new Person("abc",22); Person person2 = new Person("abc",22); System.out.println(person1 == person2);//false } }
-
equals( )的使用:
-
是一个方法而不是运算符
-
只能适用于引用数据类型
-
Obejct类中equals( )的定义:和 == 作用是相同的(比较地址值)
public boolean equals(Object obj) { return (this == obj); }
Person person1 = new Person("abc",22); Person person2 = new Person("abc",22); System.out.println(person1.equals(person2)); //false:调用的是Object中的equals()方法
-
像String、Date、File、包装类等都重写了Object类中的equals( )方法,重写后不是比较地址值,而是比较两个对象的内容是否相同
String str1 = new String("ABCD"); String str2 = new String("ABCD"); System.out.println(str1.equals(str2)); //true:调用的是String中重写的equals()方法 Date date1 = new Date(312312312L); Date date2 = new Date(312312312L); System.out.println(date1.equals(date2)); //true:调用的是Date中重写的equals()方法
-
-
自定义类该如何重写equals( )方法:
-
需要重写Objcet类的equals()方法
public class Person { String name; int age = 20; public boolean equals(Object obj) { if (this == obj) { return true; } if(obj instanceof Person){ Person person = (Person)obj;//向下强转 if(this.age == person.age && this.name.equals(person.name)){ //这里age是基本数据类型比较时使用 == 比较 //name是String类型(引用型)比较需要使用equals()比较 return true; }else { return false; } } public class equalsTest { public static void main(String[] args) { Person person1 = new Person("abc",22); Person person2 = new Person("abc",22); System.out.println(person1.equals(person2)); //true:调用的是Person类中重写的equals()方法 } }
-
也可以自动生成:
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return age == person.age && name.equals(person.name); }
-
-
-
toString( )方法
-
未重写:
public class equalsTest { public static void main(String[] args) { System.out.println(person1.toString()); } } //输出:com.company.Person@2d97b5(地址值) //其实直接输出一个引用型实例就是调用了toString()
-
Object类中的toString( )方法:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
像String、Date、File、包装类等都重写了Object类中的toString( )方法,使得在调用对象的toString( )方法时,返回实体内容信息
-
自定义类也可以重写toString( )方法
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public class PersonTest { public static void main(String[] args) { Person person = new Person(); System.out.println(person.toString()); } } //Person{name='null', age=20}
-
5.16 单元测试方法的使用
-
idea中的JUnit单元测试步骤:
-
选中要测试的方法
-
CTRL + SHIFT + T
-
勾选要测试的方法和属性
-
-
示例1:
@org.junit.Test public void testEquals() { String str1 = "aa"; String str2 = "bb"; System.out.println(str1.equals(str2)); }
-
示例2:
@org.junit.Test public void testEquals() { Object obj = new Person(); Date date = (Date) obj; }
5.17 包装类的使用
-
针对八种基本数据类型定义相应的引用类型——包装类(封装类)
-
基本数据结构有了类的特点,就可以调用类中的方法;且有的方法形参是引用型,基本数据类型放不进去
-
基本类型、包装类与String类间的转换 :
-
基本类型 → \to →包装类(装箱)
package com.company; public class shiftTest { public static void main(String[] args) { int num1 = 10; Integer in1 = new Integer(num1); System.out.println(in1.toString());//10 Integer in2 = new Integer("123"); System.out.println(in2.toString());//123 Float f1 = new Float(12.3f); Float f2 = new Float("12.3"); System.out.println(f1.equals(f2));//true Boolean b1 = new Boolean(true); Boolean b2 = new Boolean("TrUe"); //只要不是这四个字母,就全认为是false System.out.println(b1);//true System.out.println(b2);//true Person person = new Person(); System.out.println(person.isMale);//false System.out.println(person.isFemale);//null } } public class Person { boolean isMale; Boolean isFemale;//这里是Boolean引用类型,默认值为null }
-
包装类 → \to →基本数据类型(拆箱)
public class shiftTest { public static void main(String[] args) { Integer in1 = new Integer(12); int i1 = in1.intValue();//⭐ System.out.println(i1);//12 Float f1 = new Float(12.3f); System.out.println(f1.floatValue());//12.3 } }
自动装箱、自动拆箱——jdk5.0之后
public class shiftTest { public static void main(String[] args) { int in1 = 10; Integer integer1 = in1;//自动装箱 System.out.println(integer1.toString()); int in2 = integer1.intValue();//自动拆箱 System.out.println(in2); } }
-
基本数据类型 → \to →String类型
public class shiftTest { public static void main(String[] args) { //方式一:连接运算 int in1 = 10; String str1 = in1 + ""; System.out.println(str1); //方式二:调用String的vlaueOf()方法 float f1 = 12.3f; String str2 = String.valueOf(f1); System.out.println(str2); Double d1 = new Double(12.4); String str3 = String.valueOf(d1); System.out.println(str3); } }
-
String类型 → \to →基本数据类型
public class shiftTest { public static void main(String[] args) { String str1 = "123"; int in1 = Integer.parseInt(str1); System.out.println(in1+1); //124 String str2 = "true1"; boolean b1 = Boolean.parseBoolean(str2); System.out.println(b1); //false原因同上 } }
-
5.18 static关键字
-
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
-
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下, 某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量
-
static静态的
-
static可以用来修饰:属性、方法、代码块、内部类
-
static修饰属性:
-
属性按是否使用static修饰分为静态属性 和 非静态属性(实例变量)
-
实例变量: 我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改一个对象的非静态属性时,不会改变其他对象的属性值
-
静态变量: 我们创建了类的多个对象,每一个对象共享有同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用静态变量时也会变为修改过的值
public class Person { static String nation; } public class PersonTest { public static void main(String[] args) { Person person1 = new Person(); person1.nation = "CHN"; Person person2 = new Person(); person2.nation = "China"; System.out.println(person1.nation);//China } }
-
-
static修饰属性的其他说明
-
静态变量随着类的加载而加载,可以没有对象的情况下,就使用类去调用
Person.nation = "US";
-
静态变量加载早于对象的创建
-
由于类只会加载一次,所以静态变量只会在内存中保存一份,存在方法区的静态域中
-
总结:
能否调用: 类变量 实例变量 类 yes no 对象 yes yes
-
-
静态属性举例:
System.out
Math.PI
-
静态变量的内存解析:
-
-
static修饰方法:
-
随着类的加载而加载,可以通过
类.静态方法
的方式调用静态方法 -
能否调用:
能否调用: 静态方法 非静态方法 类 yes no 对象 yes yes -
静态方法中,只能调用静态的方法和属性
-
非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
-
在静态方法中不能使用
this
或super
关键字 -
关于静态属性和静态方法的使用,要从生命周期的角度考虑
-
-
开发中:
- 如何确定一个属性是否要声明称static?
- 属性时可以被多个对象共享的,不会随着对象不同而不同
- 类中的常量一般也声明为static
- 如何确定一个方法是否要声明称static?
- 操作静态属性的方法通常设为静态方法,无需造对象调用
- 工具类中的方法,习惯上声明为static,比如:
Math.Arrays
- 如何确定一个属性是否要声明称static?
-
-
static应用
public class Circle { private double radius; private int id; public Circle() { id = init++; total++; } public Circle(double radius) { //id = init++; //total++; this();//调用Circle() this.radius = radius; } private static int total;//创建圆的个数 private static int init = 1001;//id初始值,被多个对象共享 public double findArea(){ return Math.PI * radius * radius; } public double getRadius() { return radius; } public int getId() { return id; } public static int getTotal() { return total; } } public class CircleTest { public static void main(String[] args) { Circle circle1 = new Circle(); Circle circle2 = new Circle(); System.out.println(circle1.getId());//1001 System.out.println(circle2.getId());//1002 System.out.println(Circle.getTotal());//2 } }
5.19 单例设计模式
-
什么是单例设计模式
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对 某个类只能存在一个对象实例,并且该类 只提供一个取得其对象实例的方法
- 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象
- 因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
-
饿汉式单例:(直接创建对象)
public class Bank { //提供私有化类的构造器 private Bank() { } //内部创建类的对象 //要求对象也必须声明成静态 private static Bank bank = new Bank(); //提供公共的静态的方法,返回类的对象 public static Bank getInstance(){ return bank; } } public class SingleTest { public static void main(String[] args) { //使用类调用获取对象的方法 Bank.getInstance(); } }
-
懒汉式单例:(用对象的时候才创建,不用的时候只声明)
public class Bank { //提供私有化类的构造器 private Bank() { } //内部声明类的对象 private static Bank bank = null;//⭐ //提供公共的静态的方法,返回类的对象 public static Bank getInstance(){ if(bank == null){ //不判断的话,会造成每调用一次就创建一个实例 return bank = new Bank(); } return bank; } } public class SingleTest { public static void main(String[] args) { //使用类调用获取对象的方法 Bank.getInstance(); } }
-
区别:
饿汉式 懒汉式 缺点: 对象加载时间过长 目前写法:线程安全问题 优点: 饿汉是线程安全的 (同时抢票) 延迟对象的创建 -
单例模式的优点:
由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
举例:
java.lang.Runtime
public class Runtime { private static final Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } /** ⭐Don't let anyone else instantiate this class */ private Runtime() {}
-
单例模式的应用场景:
- 网站的计数器,一般也是单例模式实现,否则难以同步
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
- 项目中, 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站) 也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
5.20 main方法
main( )
方法的使用:
-
main( )方法作为程序的入喉
-
main( )方法也是一个普通的静态方法
-
main( )方法也可以作为与控制台获得交互的方式(之前使用Scanner),使用形参
String[] args
传入右击 → \to →moreRun → \to →Modify Run Configuration → \to →Build and run → \to →Program argument → \to →填入键入值,空格隔开
5.21 代码块
-
代码块的作用:初始化类、对象
-
只能使用
static
修饰,分类:- 静态代码块
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次
- 作用: 初始化类的信息
- 如果一个类中定义了多个静态代码块,则按声明顺序执行
- 静态代码块的执行要优先于非静态代码块
- 不能调用非静态结构
- 非静态代码块
- 内部可以有输出语句
- 随着对象的创建而执行
- 每创建一个对象,就执行一个非静态代码块
- 作用: 可以在创建对象的时候,对对象的属性进行初始化
- 如果一个类中定义了多个非静态代码块,则按声明顺序执行
- 可以调用非静态结构、也可以调用静态结构
- 静态代码块
-
示例:
import java.util.Objects; public class Person { static String nation; String name; int age; //构造器 public Person(){ } //代码块 static { nation = "CHN";//静态变量初始化 System.out.println("只要类加载就执行,且只执行一次"); //eat();//不能调用非静态结构 } static { //可以有多个,顺序执行 } { //可以调用非静态结构 age = 20;//属性初始化 System.out.println("只要创建一个对象就执行一次"); //也可以调用静态结构 nation = "US"; } { } //方法 public void eat(){ System.out.println("吃"); } } public class PersonTest { public static void main(String[] args) { System.out.println(Person.nation); Person person1 = new Person(); System.out.println(person1.age); Person person2 = new Person(); } } //只要类加载就执行,且只执行一次 //CHN //只要创建一个对象就执行一次 //20 //只要创建一个对象就执行一次
-
总结:对属性可以赋值的位置:执行优先级:1、2/3(看顺序)、4、5
- 默认初始化
- 显式初始化
- 在代码块中赋值
- 构造器初始化
- 使用对象.属性或对象.方法的方式赋值
5.22 final关键字
-
final修饰一个类:
此类不能被其他类所继承,比如String类、System类
public final class Bank {} public class CNBank extends Bank {} //Cannot inherit from final 'com.company.Bank'
-
final修饰一个方法:
此方法不能被子类重写
//'eat()' cannot override 'eat()' in 'com.company.Person'; overridden method is fina
-
final修饰一个变量:
此时的“变量”就称为一个常量
//Cannot assign a value to final variable 'age'
-
final修饰属性:赋值方法
public class finalTest { final int A = 1;//显式赋值 final int B; final int C; { C = 2;//方式二:代码块中 } public finalTest() {//方式三:构造器中 B = 1; } public finalTest(int b) { B = 3;//所有构造器都要写,可以赋不同的值 } }
-
final修饰局部变量
public class finalTest { //修饰局部变量 public void test1(){ final int D = 1; } //修饰形参 public void test2(final int E){ System.out.println(E); } }
尤其: 当使用final修饰形参时,表面形参是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能进行重写赋值
-
5.23 抽象类与抽象方法
-
思想:
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
-
使用:可以用来修饰类、方法
-
abstract修饰类:(抽象类)
-
此类不能实例化
//'Person' is abstract; cannot be instantiated
-
抽象类中也需要构造器:子类实例化的时候一定也需要调用父类的构造器
-
开发中,都会提供抽象类的子类,让子类对象实例化
-
-
abstract修饰方法:(抽象方法)
-
抽象方法只有方法的声明,没有方法体
public abstract class Person { public abstract void eat(); }
-
包含抽象方法的类一定是个抽象类
public class Person { public abstract void eat();//Abstract method in non-abstract class }
-
若子类中重写了父类(所有父类)的所有的抽象方法,此类可实例化;若子类中没有重写了父类的所有的抽象方法,则此类也是一个抽象类,需要用abstract修饰
-
-
abstract不能修饰:
- 私有方法(抽象方法子类必须要重写,私有方法子类看不到,没法重写)
- 静态方法(子类和父类中同名同参数的方法要么都声明成非static的(考虑重写),要么都声明成static的(一定不是重写))
- final修饰的方法(final不能被重写)
- final修饰得类(不能被继承)
-
-
匿名子类:
public abstract class Person { public void eat(){ System.out.println("吃"); } } public class man extends Person { public void eat(){ System.out.println("男人吃"); } } public class testPerson { public static void main(String[] args) { //① methedPerson(new man());//匿名对象(且实参传子类对象,是多态的体现) //② man man = new man(); methedMan(man);//非匿名类-非匿名对象 //③ methedMan(new man());//非匿名类-匿名对象 //④ Person person = new Person() {//创建一个匿名子类的对象,但这个对象不匿名叫person @Override //自动重写 public void eat() { System.out.println("匿名子类中重写的吃"); } }; methedPerson(person); //⑥ methedPerson(new Person() {//创建一个匿名子类的对象,这个对象也匿名 @Override public void eat() { System.out.println("匿名子类-匿名对象"); } }); } public static void methedMan(man man){ man.eat(); } public static void methedPerson(Person person){ person.eat(); } }
5.24 接口
-
引入原因:
-
有时必须从几个类中派生出一个子类,继承他们所有的属性和方法。但是,Java不支持多重继承,有了接口就可以实现多重继承的效果
-
有时必须从几个类中抽取出一些共同的行为特征,而他们之间有没有is-a的关系,仅仅是具有相同的行为特征而已,如鼠标、键盘等都支持USB连接
-
-
接口使用
interface
来定义,与类是并列的两个结构 -
如何定义接口:定义接口中的成员
-
JDK7及以前:只能定义全局常量和抽象方法
-
全局常量:
public static final
(可以省略,变量一律认为是全局常量) -
抽象方法:
public abstract
public interface interface1 { public static final int MAX_SPEED = 7900; int MIN_SPEED = 1; public abstract void fly(); void stop(); }
-
-
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法和默认方法
-
-
接口中不能定义构造器,故接口不能实例化
-
Java开发中:接口通过类来实现(impletement)的方式使用
-
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
-
如果实现类没有覆盖了接口中的所有抽象方法,则此实现类仍为一个抽象类
public class Plane implements interface1{ @Override public void fly() { //将接口中的所有抽象方法重写 System.out.println("飞机飞"); } @Override public void stop() { System.out.println("飞机停"); } } public class interfaceTest { public static void main(String[] args) { Plane plane = new Plane();//重写后就可以创建实例了 plane.fly(); plane.stop(); } }
-
-
Java类可以实现多个接口 → \to →弥补了单继承的缺陷
格式:
class AA extends BB imlement CC,DD,EE
public class Bullet extends Object implements interface1 , interface2{ @Override public void fly() { } @Override public void stop() { } @Override public void attack() { } }
-
接口和接口之间可以继承,且可以多继承
public interface interface3 extends interface1,interface2{ }
一个类如果要实现interface3,那么次类中就要重写interface1和interface2中的所有抽象方法
-
接口的使用:
-
接口的具体使用,体现多态性
public interface USB { abstract void start(); abstract void stop(); } public class Flash implements USB{ @Override public void start() { System.out.println("U盘开始工作"); } @Override public void stop() { System.out.println("U盘停止工作"); } } public class Computer { public void tansferData(USB usb){ usb.start(); System.out.println("具体传输数据细节"); usb.stop(); } } public class USBtest { public static void main(String[] args) { Computer computer = new Computer(); Flash flash = new Flash(); computer.tansferData(flash);//形参声明是一个接口类,但实参是它的实现类 } } //U盘开始工作 //具体传输数据细节 //U盘停止工作
-
接口实际上可以看做是一种规范
-
开发中体会面向接口编程
-
-
创建接口匿名实现类的对象
package com.company; public class USBtest { public static void main(String[] args) { Computer computer = new Computer(); //创建了接口的非匿名实现类的非匿名对象 Flash flash = new Flash(); computer.tansferData(flash); //创建了接口的非匿名实现类的匿名对象 computer.tansferData(new Print()); //创建了接口的匿名实现类的非匿名对象 usbInterface usbInterface = new usbInterface() { @Override public void start() { System.out.println("重写开始方法"); } @Override public void stop() { System.out.println("重写结束方法"); } }; computer.tansferData(usbInterface); //创建了接口的匿名实现类的匿名对象 computer.tansferData(new usbInterface() { @Override public void start() { System.out.println("重写开始方法"); } @Override public void stop() { System.out.println("重写结束方法"); } }); } }
-
Java8的接口新特性
-
可以在接口中提供静态方法和默认方法
-
静态方法:接口中定义的静态方法,只能使用接口自己去调用(实现类的对象调用不了)
-
默认方法:如果实现类中重写了接口中的默认方法,调用时,仍然调用的是重写后的方法
public interface JDK8new { public static void method1(){ System.out.println("接口中可以写静态方法"); } public default void method2(){ System.out.println("接口中可以写默认方法"); } public default void method3(){ System.out.println("接口中的method3"); } } public class JDK8newImp implements JDK8new{ public void method2(){ System.out.println("重写的默认方法method2"); } } public class JSK8test implements JDK8new{ public static void main(String[] args) { JDK8newImp j = new JDK8newImp(); //j.method1(); JDK8new.method1(); //接口中定义的静态方法,只能使用接口去调用 j.method2(); //通过实现类的对象可以调用接口的默认方法 } }
问题1:当实现类的父类中与接口中有一个同名同参数的方法,那么使用该实现类的对象调用这个名字方法时,会调用谁的?
答:在实现类没有重写这个方法的前提下,默认调用的时父类中同名同参数的默认方法 (类优先原则)
public class superJDKnewImp { public void method3(){ System.out.println("实现类的父类中的method3"); } } public class JDK8newImp extends superJDKnewImp implements JDK8new{ } public class JSK8test implements JDK8new{ public static void main(String[] args) { JDK8newImp j = new JDK8newImp(); j.method3();//结果:实现类的父类中的method3 } }
问题2: 当实现类实现了两个接口时,但两个接口中有一个同名同参数的默认方法,那么调用时会调用哪个?
public interface JDK8new2 { public default void method3(){ System.out.println("接口2中的method3"); } } public class JDK8newImp implements JDK8new,JDK8new2{ } //报错:com.company.JDK8newImp inherits unrelated defaults for method3() from types com.company.JDK8new and com.company.JDK8new2
那么要怎么实现呢? → \to →必须在实现类中重写
public class JDK8newImp implements JDK8new,JDK8new2{ @Override public void method3() { System.out.println("必须重写才能解决两个接口中出现同名同参方法的使用"); } }
问题3: 之前可以在子类的方法中使用super关键字调用父类的方法,那我们想在实现类的方法中实现调用接口中的默认方法怎么办?
public class JDK8newImp extends superJDKnewImp implements JDK8new,JDK8new2{ @Override public void method3() { System.out.println("必须重写才能解决两个接口中出现同名同参方法的使用"); } public void method4(){ //⭐ //调用自己定义的重写方法 method3(); //调用父类中声明的方法 super.method3(); //调用接口中定义的方法 JDK8new.super.method3(); } }
-
-
5.25 内部类
-
举例:人-大脑,大脑离不开人,但将大脑作为人的一个变量,又不足以刻画大脑的功能,故将大脑看作人的一个内部类
-
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类
-
内部类的分类:
-
成员内部类:
public class innerClassTest { class AA{ } static class A1{ } }
-
局部内部类:方法内、代码块内、构造器内
public class innerClassTest { public void method(){ class BB{ } } { class CC{ } } public innerClassTest() { class DD{ } } }
-
-
内部类的使用:
-
成员内部类:
-
作为外部类的成员
-
调用外部类的结构
public class innerClassTest { class AA{ public void method1(){ eat(); //是innerClassTest.this.eat();的省略写法 } } public void eat(){ } }
-
可以被static修饰
-
可以被四种不同权限符修饰(此时是成员)
-
-
作为一个类:
-
可以定义属性、方法、构造器
public class innerClassTest { class AA{ String name; public AA() { } public void method1(){ System.out.println(" "); } } }
-
可以被final修饰:不能被继承
-
可以被abstract修饰:不能不被实例化
-
-
-
局部内部类(见下)
-
-
关注如下三个问题:
-
开发中局部内部类的使用
使用场景:
public class innerClassTest { //此方法的作用:返回一个实现了Comparable接口的实现类的对象实例 public Comparable getComparable(){ class MyComparables implements Comparable{ @Override public int compareTo(Object o) { return 0; } } return new MyComparables();//返回实现类的对象 } }
-
如何实例化成员内部类的对象
public class innerTest { public static void main(String[] args) { //创建静态的成员内部类的实例 //对象类型:外部类.内部类 innerClassTest.A1 a = new innerClassTest.A1(); //创建非静态的成员内部类的实例 //非静态不能使用类名直接去调用,先创建外部类的实例 innerClassTest innerClassTest = new innerClassTest(); innerClassTest.AA b = innerClassTest.new AA(); } }
-
如何在成员内部类中调用外部类的结构
当外部类和内部类中有同名的结构时:
public class innerClassTest { String name; class AA{ String name; public void method1(String name){ System.out.println(name);//传入的形参 System.out.println(this.name);//内部类AA中的name System.out.println(innerClassTest.this.name);//外部类中的neme } } }
-