Java 面向对象
# Java 面向对象
# 1. 内存结构
- 堆(Heap),此内存区域的唯一目的 就是存放对象实例,几乎所有的对象 实例都在这里分配内存。这一点在 Java虚拟机规范中的描述是:所有的 对象实例以及数组都要在堆上分配。
- 通常所说的栈(Stack),是指虚拟机 栈。虚拟机栈用于存储局部变量等。 局部变量表存放了编译期可知长度的 各种基本数据类型(boolean、byte、 char 、 short 、 int 、 float 、 long 、 double)、对象引用(reference类型, 它不等同于对象本身,是对象在堆内 存的首地址)。 方法执行完,自动释 放。
- 方法区(Method Area),用于存储已 被虚拟机加载的类信息、常量、静态 变量、即时编译器编译后的代码等数据。
# 2. 方法
# 2.1方法重载
重载的概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
重载的特点
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类 型)。调用时,根据方法参数列表的不同来区别。
重载示例
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!");
}
}
2
3
4
5
6
7
8
9
10
# 2.2 形参个数可变的方法
# 2.2.1 说明
- 声明格式:方法名(参数的类型名 ...参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参位置,最多只能声明一个可变个数形参
# 2.2.2 示例
public void test(String[] msg){
System.out.println(“含字符串数组参数的test方法 ");
}
public void test1(String book){
System.out.println(“****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... books){
// books 是一个数组的结构
System.out.println("****形参长度可变的test1方法****");
}
public static void main(String[] args){
TestOverload to = new TestOverload();
//下面两次调用将执行第二个test方法
to.test1();
to.test1("aa" , "bb");
//下面将执行第一个test方法
to.test(new String[]{"aa"});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.3 方法传值机制
- 形参:方法声明时的参数
- 实参:方法调用时实际传给形参的参数值
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本 (复制品)传入方法内,而参数本身不受影响。
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
基本数据类型示例
public static void main(String[] args) {
int x = 5; System.out.println("修改之前x = " + x);// 5
// x是实参
change(x);
System.out.println("修改之后x = " + x);// 5
}
public static void change(int x) {
System.out.println("change:修改之前x = " + x);
x = 3;
System.out.println("change:修改之后x = " + x);
}
2
3
4
5
6
7
8
9
10
11
12
内存结构
引用数据类型示例
public static void main(String[] args) {
Person obj = new Person();
obj.age = 5;
System.out.println("修改之前age = " + obj.age);// 5
// x是实参
change(obj);
System.out.println("修改之后age = " + obj.age);// 3
}
public static void change(Person obj) {
System.out.println("change:修改之前age = " + obj.age);
obj.age = 3;
System.out.println("change:修改之后age = " + obj.age);
}
//其中Person类定义为:
class Person{
int age;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
内存结构
# 3. 封装
# 3.1 概述
程序设计追求“高内聚,低耦合”。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提 高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露 的暴露出来。这就是封装性的设计思想。如果使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。
# 3.2 实现方式
# 3.2.1 四种访问权限修饰符
Java权限修饰符
public
、protected
、(缺省)、private
置于类的成员定义前, 用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | ✔️ | |||
缺省(default) | ✔️ | ✔️ | ||
protected | ✔️ | ✔️ | ✔️ | |
public | ✔️ | ✔️ | ✔️ | ✔️ |
对于class
的权限修饰只可以用public
和default
(缺省)。
public
类可以在任意地方被访问。default
类只可以被同一个包内部的类访问。
访问权限范围
# 4. 构造方法
# 4.1 概述
构造器特征
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有 return语句返回值
构造器的作用 : 创建对象;给对象进行初始化
- 如:
Order o = new Order(); Person p = new Person(“Peter”,15);
- 如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的 构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自 动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们 要“洗澡”了。
分类
根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
注意
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
# 4.2 构造器重载
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
构造器重载,参数列表必须不同
public class Person { 构造器重载举例
private String name;
private int age;
private Date birthDate;
public Person(String n, int a, Date d) {
name = n;
age = a;
birthDate = d;
}
public Person(String n, int a) {
name = n;
age = a;
}
public Person(String n, Date d) {
name = n;
birthDate = d;
}
public Person(String n) {
name = n;
age = 30;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 5. 属性赋值过程
赋值的位置
① 默认初始化 (属性的默认值)
② 显式初始化 (定义类时直接给属性赋值)
③ 构造器中初始化 (从构造器中传参赋值)
④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序: ① - ② - ③ - ④
# 6. UML 类图
- 表示
public
类型,-
表示private
类型,#
表示protected
类型 - 方法的写法: 方法的类型
(+、-)
方法名(参数名: 参数类型):返回值类型
# 7. this 的使用
- 在
Java
中,this
关键字比较难理解,它的作用和其词义很接近。- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
this
可以调用类的属性、方法和构造器- 什么时候使用
this
关键字呢?- 当在方法内需要用到调用该方法的对象时,就用
this
。 - 具体的:我们可以用
this
来区分属性和局部变量。 比如:this.name = name
;
- 当在方法内需要用到调用该方法的对象时,就用
使用this
,调用属性、方法
- 在任意方法或构造器内,如 果使用当前类的成员变量或成 员方法可以在其前面添加
this
, 增强程序的阅读性。不过,通 常我们都习惯省略this
。 - 当形参与成员变量同名时, 如果在方法内或构造器内需要 使用成员变量,必须添加
this
来表明该变量是类的成员变量 - 使用
this
访问属性和方法时, 如果在本类中未找到,会从父类中查找
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ; }
public void getInfo(){
System.out.println("姓名:" + name) ;
this.speak();
}
public void speak(){
System.out.println(“年龄:” + this.age);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
this 代表当前对象
当前正在操作本方法的对 象称为当前对象。
public static void main(String args[]){
Person per1 = new Person("张三") ;
Person per2 = new Person("李四") ;
per1.getInfo() ; // 当前调用getInfo()方法的对象是per1
per2.getInfo() ; // 当前调用getInfo()方法的对象是per2
boolean b = per1.compare(per2);
}
2
3
4
5
6
7
使用this调用本类的构造器
this可以作为一个类中 构造器相互调用的特殊 格式
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(){ // 无参构造器
System.out.println("新对象实例化") ;
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ;
}
public Person(String name,int age){
this(name) ; // 调用有一个参数的构造器
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age ;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注意
- 可以在类的构造器中使用"
this
(形参列表)"的方式,调用本类中重载的其 他的构造器! - 明确:构造器中不能通过"
this
(形参列表)"的方式调用自身构造器 - 如果一个类中声明了
n
个构造器,则最多有n - 1
个构造器中使用了 "this
(形参列表)" - "
this
(形参列表)"必须声明在类的构造器的首行! - 在类的一个构造器中,最多只能声明一个"
this
(形参列表)"
# 8 继承性
# 8.1 概述
为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。
此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”
类继承语法规则:
class Subclass extends SuperClass{ }
作用
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
注意
不要仅为了获取其他类中某个功能而去继承
继承后子类的特点
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和 方法。
- 在
Java
中,继承的关键字用的是“extends”
,即子类不是父类的子集, 而是对父类的“扩展”。
# 8.2 关于继承的规则
- 子类不能直接访问父类中私有的
(private)
的成员变量和方法。
- Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//error
没有多继承
# 9 方法重写
# 9.1 概述
定义
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为
private
权限的方法
- 子类不能重写父类中声明为
- 子类方法抛出的异常不能大于父类被重写方法的异常
注意
子类与父类中同名同参数的方法必须同时声明为非static的
(即为重写),或者同时声明为static
的(不是重写)。因为static
方法是属于类的,子类无法覆盖父类的方法。
# 9.2 实例
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 10 super
- 在Java类中使用super来调用父类中的指定操作:
super
可用于访问父类中定义的属性super
可用于调用父类中定义的成员方法super
可用于在子类构造器中调用父类的构造器
注意
- 尤其当子父类出现同名成员时,可以用
supe
r表明调用的是父类中的成员 super
的追溯不仅限于直接父类super
和this
的用法相像,this
代表本类对象的引用,super
代表父类的内存空间的标识
关键字super举例
class Person {
protected String name = "张三"; //
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool() {
return school;
}
public String getInfo() {
return super.getInfo() + "\nschool: " + school;
}}
public class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
调用父类的构造器
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过
this
(参 数列表)或者super
(参数列表)语句指定调用本类或者父类中相应的 构造器。同时,只能”二选一” ,且必须放在构造器的首行 - 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又 没有无参的构造器,则编译出错
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
// 编译出错: no super(),系统将调用父类无参数的构造器。
public Student(String s) {
school = s;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 11 多态性
# 11.1 概述
多态性,是面向对象中最重要的概念
在Java中的体现:对象的多态性:父类的引用指向子类的对象 可以直接应用在抽象类和接口上
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下, “看左边” :看的是父类的引用(父类中不具备子类特有的方法) “看右边” :看的是子类的对象(实际运行的是子类重写父类的方法)
对象的多态—在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
2
3
父类类型的引用可以指向子类的对象,子类可看做是特殊的父类:向上转型(upcasting
)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法 。
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
//属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
2
3
4
5
6
多态实现前提 需要存在继承或者实现关系 、有方法的重写
# 11.2 虚拟方法调用
正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
2
3
4
虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
2
编译时e
为Person
类型,而方法的调用是在运行时确定的,所以调用的是Student
类的getInfo()
方法。——动态绑定
# 12 包装类
# 12.1 包装类种类
针对八种基本数据类型定义相应的引用类型—包装类(封装类)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
注意
Byte、Short、Integer、Long、Float、Double 包装类的父类是 Number
# 12.2 包装类使用
基本数据类型包装成包装类的实例 ---> 装箱
通过包装类的构造器实现:
int i = 500;
Integer t = new Integer(i);
2
还可以通过字符串参数构造包装类对象:
Float f = new Float(“4.56”);
Long l = new Long(“asdf”); //NumberFormatException
2
获得包装类对象中包装的基本类型变量 ---> 拆箱
调用包装类的.xxxValue()
方法: boolean b = bObj.booleanValue();
JDK1.5
之后,支持自动装箱,自动拆箱。但类型必须匹配。
int num1 = 10;
/*
* 自动装箱
* int num1 = 10;
* Integer in1 = num1;
* */
m(num1);//自动装箱
public void m (Object obj){
//...
}
/****************************/
Integer in = 12;
//自动拆箱
int i = in;
//其他数据类型的操作相同
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 12.3 类型转换
字符串转换成基本数据类型
通过包装类的构造器实现:
int i = new Integer(“12”);
通过包装类的 parseXxx(String s)
静态方法:
Float f = Float.parseFloat(“12.1”);
String s1 = "123";
int i = Integer.parseInt(s1);
Integer in1 = Integer.valueOf(s1);
System.out.println(in1);
String s2 = "true";
boolean b1 = Boolean.parseBoolean(s2);
System.out.println(b1);
2
3
4
5
6
7
8
9
基本数据类型转换成字符串
调用字符串重载的valueOf()
方法:
String fstr = String.valueOf(2.34f);
更直接的方式: String intStr = 5 + “”
Integer inte1 = 10;//自动装箱
int i1 =inte1;//自动拆箱
String s = i1 + "";
String s1 = String.valueOf(i1);
String s2 = String.valueOf(inte1);
2
3
4
5
6
包装类转基本数据类型
调用包装类的 xxxVlaue()
方法获取对应的基本数据类型
Integer in1 = 12;
int i1 = in1.intValue();
Float f1= 12.4f;
float f = f1.floatValue();
2
3
4
5
问题
/*
* 三目运算符编译后,两边类型要统一、产生了类型提升 Integer --》 Double
* */
Object obj1 = true ? new Integer(1):new Double(2.0);
System.out.println(obj1 instanceof Double); // ture
2
3
4
5
# 13 static 关键字
# 13.1 概述
下图的中国人的实例
country
属性就应该设置为static
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
如果想让一个类的所有实例共享数据,就用类变量 即用static修饰
使用范围
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
注意
- 在
static
方法内部只能访问类的static
修饰的属性或方法,不能访问类的非static
的结构 - 没有对象的实例时,可以用类名.方法名()的形式访问由
static
修饰的类方法,所以此static
方法内部不能有this
、super
static
修饰的方法不能被重写
# 13.2 使用实例
class Circle {
private double radius;
public static String name = "这是一个圆";
public static String getName() {
return name;
}
public Circle(double radius) {
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
public void display() {
System.out.println("name:" + name + "radius:" + radius);
}
}
public class StaticTest {
public static void main(String[] args) {
Circle c1 = new Circle(2.0);
Circle c2 = new Circle(3.0);
c1.display();
c2.display();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 13.3内存解析
# 14. main 方法
# 14.1 概述
由于Java虚拟机需要调用类的main()
方法,所以该方法的访问权限必须是 public
,又因为Java虚拟机在执行main()
方法时不必创建对象,所以该方法必须 是static
的,该方法接收一个``String`类型的数组参数,该数组中保存执行Java命令 时传递给所运行的类的参数。
又因为main()
方法是静态的,我们不能直接访问该类中的非静态成员,必须创 建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情 况,我们在之前的例子中多次碰到。
# 14.2 应用举例
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
2
3
4
5
6
7
在命令行窗口中运行程序 CommandPara.java
文件
java CommandPara “Tom" “Jerry" “Shkstart"
输出结果: args[0] = Tom args[1] = Jerry args[2] = Shkstart
# 15 代码块
# 15.1 概述
代码块(或初始化块)的作用: 对Java类或对象进行初始化
代码块(或初始化块)的分类: 一个类中代码块若有修饰符,则只能被static
修饰,称为静态代码块 (static block
),没有使用static
修饰的,为非静态代码块。
static
代码块通常用于初始化static
的属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
2
3
4
5
6
7
# 15.2 静态代码块
用 static
修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
输出: in static block total=100 total=100
# 15.3 非静态代码块
没有 static
修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
代码块执行顺序
执行先于构造器,由父及子,静态先行
# 16. final 关键字
在Java中声明类、变量和方法时,可使用关键字final
来修饰,表示“最终的”。
final
标记的类不能被继承。提高安全性,提高程序的可读性。
如:String
类、System
类、StringBuffer
类
final class A{
}
class B extends A{ //错误,不能被继承。
}
2
3
4
final`标记的方法不能被子类重写。
如:Object
类中的getClass()
。
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("bbb");
}
}
2
3
4
5
6
7
8
9
10
final
标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
final
标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。如: final double MY_PI = 3.14;
class A {
private final String INFO = "ver"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
}
}
2
3
4
5
6
注意
常量名要大写,内容不可修改
# 17. 抽象类、抽象方法
注意
abstract
只能用来修饰 类、方法
不能用 abstract
修饰变量、代码块、构造器;
不能用 abstract
修饰私有方法、静态方法、final
的方法、final
的类。
# 17.1 抽象类
没有具体的实例的方法,这样的类叫做抽象类。
# 17.1.1 概述
用 abstract
关键字来修饰一个类,这个类叫做抽象类。
含有抽象方法的类必须被声明为抽象类。
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
abstract class A {
public abstract void m1();
public void m2() {
System.out.println("A类中定义的m2方法");
}
}
class B extends A {
void m1() {
System.out.println("B类中定义的m1方法");
}
}
public class Test {
public static void main(String args[]) {
A a = new B();
a.m1();
a.m2();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提 供具体实现的对象的类。
实现抽象类的匿名类的实例创建
public abstract class Person{
public String species;
//抽象方法
public abstract void eat();
}
class Worker extends Person{
@Override
public void eat() {
System.out.println("work 重写的方法");
}
}
public class AnonymousClassTest {
public static void method1(Person p){
p.eat();
}
public static void method(Student s){}
public static void main(String[] args) {
//传入匿名对象参数
method(new Student("tom",12));
Worker w = new Worker();
//传入非匿名类的非匿名对像
method1(w); // 子类对象赋值给父类引用 Person p = w;使用了多态性
//传入非匿名类匿名对象
method1(new Worker());//子类对象赋值给父类引用 Person p =new Worker();使用了多态性
//创建一个匿名类的非匿名对象 p (匿名类指的是继承Person的类)
//实现抽象类的匿名类的实例创建
Person p = new Person() {
@Override
public void eat() {
System.out.println("匿名类的对象 ");
}
};
method1(p);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 17.2 抽象方法
用 abstract
来修饰一个方法,该方法叫做抽象方法。
抽象方法:只有方法的声明,没有方法的实现。以分号结束: 比如:public abstract void talk();
# 18. 接口
# 18.1 概述
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方 法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又 没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打 印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都 支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则 必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口(interface
)是抽象方法和常量值定义的集合。
示例如下
接口的特点
用
interface
来定义。接口中的所有成员变量都默认是由
public static final
修饰的。接口中的所有抽象方法都默认是由
public abstract
修饰的。接口中没有构造器。
接口采用多继承机制。
定义Java类的语法格式:先写
extends
,后写implements
如:
class SubClass extends SuperClass implements InterfaceA{ }
一个类可以实现多个接口,接口也可以继承其它接口。
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
接口的主要用途就是被实现类实现。(面向接口编程)
**与继承关系类似,接口与实现类之间存在多态性 **
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲, 接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义 (JDK7.0及之前),而没有变量和方法的实现。
jdk7 及以前 只能定义全局常量、抽象方法
- 全局常量 public static final 书写时可以不写(默认存在)
- 抽象方法 public abstract 书写时可以不写(默认存在)
dk8 新增 可以定义静态方法、默认方法
静态方法:使用 static
关键字修饰。可以通过接口直接调用静态方法,并执行 其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中 找到像Collection/Collections
或者Path/Paths
这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。 我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8 API中对Collection
、List
、Comparator
等接口提供了丰富的默认 方法。
public interface AA {
double PI = 3.14;
//默认方法
public default void method() {
System.out.println("北京");
}
default String method1() {
return "上海";
}
//静态方法
public static void method2() {
System.out.println(“hello lambda!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 18.2 示例
# 示例一
# 示例二
interface Runner {
public static final dec = "描述"; // public static final 可以省略
public abstract void start(); // public abstract 可以省略
public abstract void run();// public abstract 可以省略
public abstract void stop();// public abstract 可以省略
}
class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼
// 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 示例三
与继承关系类似,接口与实现类之间存在多态性
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Creator{public int eat(){…}}
class Man extends Creator implements Runner ,Swimmer{
public void run() {……}
public double swim() {……}
public int eat() {……}
}
//与继承关系类似,接口与实现类之间存在多态性
public class Test{
public static void main(String args[]){
Test t = new Test();
Man m = new Man();
t.m1(m);
t.m2(m);
t.m3(m);
}
public String m1(Runner f) { f.run(); }
public void m2(Swimmer s) {s.swim();}
public void m3(Creator a) {a.eat();}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 接口的几种实现方式
interface USB{ // 相当于一种规范 谁要与电脑传输数据 谁就要把接口实现
void start();
void stop();
}
class Computer{
public void transferData(USB usb) {
usb.start();
System.out.println("传输数据...");
usb.stop();
}
}
class Flash implements USB{
@Override
public void start(){
System.out.println("Flash开启");
}
@Override
public void stop(){
System.out.println("Flash结束");
}
}
class Printer implements USB{
@Override
public void start(){
System.out.println("打印机开启");
}
@Override
public void stop(){
System.out.println("打印机结束");
}
}
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
// 创建了非匿名接口实现对象的实例
Flash flash = new Flash();
// 接口不能造对象 只能传入实现该接口的方法的实例 体现多态性 最终调用的也是实现类的方法
com.transferData(flash);
// 接口的 非匿名实现类的匿名实例对象
com.transferData(new Printer());
// 接口的匿名实现类的非匿名实例对象
USB U1 = new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
};
// 接口的匿名实现类的匿名实例对象
com.transferData(new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 接口冲突的解决方式
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Man implements Filial, Spoony {
@Override
public void help() {
System.out.println("我该怎么办呢?");
// 分别调用解决冲突
Filial.super.help();
Spoony.super.help();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 19. 内部类
在 Java 中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
Inner class
一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Inner class
的名字不能与包含它的外部类类名相同;
分类
成员内部类(static
成员内部类和非 static
成员内部类)
局部内部类(不谈修饰符)、匿名内部类
# 19.1 成员内部类
成员内部类作为类的成员的角色
- 和外部类不同,
Inner class
还可以声明为private
或protected
; - 可以调用外部类的结构
Inner class
可以声明为static
的,但此时就不能再使用外层类的非static
的成员变量;
成员内部类作为类的角色
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为
abstract
类 ,因此可以被其它的内部类继承 - 可以声明为
final
的 - 编译以后生成
OuterClass$InnerClass.class
字节码文件(也适用于局部内部类)
注意
非static
的成员内部类中的成员不能声明为 static
的,只有在外部类或 static
的成员内部类中才可声明 static
成员。
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
成员内部类可以直接使用外部类的所有成员,包括私有的数据
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
# 示例一 定义成员内部类
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类Inner中s=" + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
public class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 示例二 在外部实例化内部类
public class InnerClass {
public static void main(String[] args) {
//创建静态成员内部类 实例化对象
Person.Dog d = new Person.Dog();
d.show();
//非静态内部类 实例化对象
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
}
}
class Person{
String name;
int age;
public void eat(){
System.out.println("人吃饭");
}
//静态内部类
static class Dog{
String name;
int age;
public void show(){
// eat(); 无法访问
System.out.println("...");
}
}
// 非静态内部类
class Bird{
String name;
static int age;//报错 非静态类中不能使用外部类的静态属性
public void sing(){
Person.this.eat(); //调用外部类结构
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 19.2 局部内部类
# 19.2.1 概述
局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的
.class
文件,但是前面冠以外部类的类名和$
符号,以及数字编号。 - 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方 都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。
- 局部内部类可以使用外部方法的局部变量,但是必须是
final
的。由局部内部类和局 部变量的声明周期不同所致。 - 局部内部类和局部变量地位类似,不能使用
public,protected
,缺省,private
- 局部内部类不能使用
static
修饰,因此也不能包含静态成员
如何使用局部内部类
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方 都不能使用该类
但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
如何声明局部内部类
class 外部类{
方法(){
class 局部内部类{
}
}
{
class 局部内部类{
}
}
}
2
3
4
5
6
7
8
9
10
# 19.3 匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一 个实例。一个匿名内部类一定是在 new
的后面,用其隐含实现一个接口或实现一个类。
格式:new 父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 }
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
局部内部类使用方式之一
public class InnerClassTest1 {
public void method(){
class AA{
}
}
//返回一个实现了 Comparable接口的类的对象
public Comparable getComparable(){
// 创建一个 实现了Comparale 的类 局部内部类
// 方式一
// class Mycomparable implements Comparable{
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
// return new Mycomparable();
//
// 方式二 匿名类
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 20. 面向对象总结图
