面向对象
目录
一、概念
1、 面向过程(POP) 与 与 象 面向对象(OOP)
-
面向对象:Object Oriented Programming
-
面向过程:Procedure Oriented Programming
-
二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
-
面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
2、面向对象的三大特性
- 封装 (Encapsulation)
- 继承 (Inheritance)
- 多态 (Polymorphism)
3、POP与OOP示例
4、思想概述
- 程序员从面向过程的执行者转化成了面向对象的指挥者
- 面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
- **类(Class)和对象(Object)**是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
- “万事万物皆对象”
- 可以理解为:类 = 抽象概念的人:对象 = 实实在在的某个人
- 面向对象程序设计的重点是类的设计
- 类的设计,其实就是类的成员的设计
5、理解main方法的语法
- 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是
public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。 - 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
//运行程序CommandPara.java
java CommandPara "Tom" "Jerry" "Shkstart"
//输出结果
args[0] = Tom
args[1] = Jerry
args[2] = Shkstart
- 赋值过程
二、Java类以及类的成员
1、类和对象
-
现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理,Java代码世界是由诸多个不同功能的类构成的。
-
现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、… 那么,Java中用类class来描述事物也是如此。常见的类的成员有:
- 属 性:对应类中的成员变量
- 行 为:对应类中的成员方法
-
结构示例:
-
语法格式
/*
修饰符 class 类名 {
属性声明;
方法声明;
}
说明: 修饰符public :类可以被任意访问, 类的正文要用{ }括起来
*/
//举例
public class Person{
private int age ; // 声明私有变量 age
public void showAge(int i) { // 声明方法showAge( )
age = i;
}
}
- 自定义类创建步骤
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
2、对象的创建及使用
- 创建对象语法:
类名 对象名 = new 类名()
; - 使用“对象名. 对象成员 ”的方式访问对象成员(包括属性和方法)
//定义类
public class Animal {
public int legs;
public void eat(){
System.out.println(“Eating.”);
}
public viod move(){
System.out.println(“Move.”);
}
}
//使用类
public class Zoo {
public static void main(String args[]) {
// 创建对象
Animal xb = new Animal();
Animal xh = new Animal();
xb.legs = 4; //访问属性
xh.legs = 0; //访问属性
System.out.println(xb.legs); // 4
System.out.println(xh.legs); // 0
xb.legs = 2;
System.out.println(xb.legs); // 2
System.out.println(xh.legs); // 0
xb.eat(); //访问方法
}
}
注:如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的内存空间,且互不干扰。
- 类的访问机制
- 在一个类中的访问机制: 类中的方法可以直接访问类中的成员变量 。(例外:static 方法访问非 static,编译不通过 )
- 在不同类中的访问机制: 先创建要访问类的对象 , 再用对象访问类中定义的成员 。
2.1、内存解析
//定义类(创建)
class Person{
int age;
void shout(){
System.out.println(“oh,my god! I am ” + age);
}
}
//使用类
class PersonTest{
public static void main(String[] args) {
Person p1 = new Person();
Person p2 =new Person();
p1.age = -30;
p1.shout();
p2.shout();
}
}
-
对象的创建
-
对象的使用
-
对象的生命周期
-
内存解析
- 堆(Heap):此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 通常所说的栈(Stack):是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、
double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。 - 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 创建过程内存解析
2.2、匿名对象
- 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
- 如:
new Person().shout()
;
- 如:
- 使用情况
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
3、属性
-
语法格式
修饰符 数据类型 属性名 = 初始化值
;- 修饰符
- 常用的权限修饰符有:private、缺省(default)、protected、public
- 其他修饰符:static、final (暂不考虑)
- 数据类型
- 任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
- 属性名
- 属于标识符,符合命名规则和规范即可。
-
例如:
public class Person{
private int age; // 声明private变量 age
public String name = "Lila"; //声明public变量 name
}
3.1、变量的分类
-
成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部声明的变量称为局部变量。
-
初始化方面的异同
- 同:
- 都有生命周期,作用域
- 都遵循变量声明的格式:
数据类型 变量名 = 初始化值
- 异:
- 局部变量除形参外,均需显式初始化。
- 同:
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内 | 栈空间 |
- 内存解析
4.2、属性赋值过程
- 赋值的位置:
- ①默认初始化
- ②显式初始化
- ③构造器中初始化
- ④通过“对象.属性“或“对象.方法”的方式赋值
- 赋值先后顺序:
- ① - ② - ③ - ④
4、方法
4.1、概念
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里。
4.2、格式
/*
修饰符 返回值类型 方法名 (参数类型 形参1, 参数类型 形参2, …. ){
方法体程序代码
return 返回值;
}
*/
- 修饰符:
public, 缺省(default),private, protected等
- 返回值类型:
- 没有返回值:void。
- 有返回值,声明出返回值的类型。与方法体中“return 返回值” 搭配使用
- 方法名 :属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
- 形参列表:可以包含零个,一个或多个参数。多个参数时,中间用“, ”隔开
- 返回值:方法在执行完毕后返还给调用它的程序的数据。
4.3、方法的分类
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名(){} | 返回值的类型 方法名(){} |
有形参 | void 方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
4.4、方法的调用
-
方法通过方法名被调用,且只有被调用才会执行。
-
过程分析
-
注:
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
4.5、方法的重载
-
重载的概念
- 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
-
重载的特点
- 与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
-
重载示例:
//返回两个整数的和 int add(int x,int y){return x+y;} //返回三个整数的和 int add(int x,int y,int z){return x+y+z;} //返回两个小数的和 double add(double x,double y){return x+y;}
-
使用重载方法,可以为编程带来方便。
- 例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下 :
public void println(byte x) public void println(short x) public void println(int x) public void println(long x) public void println(float x) public void println(double x) public void println(char x) public void println(double x) public void println()
- 例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下 :
4.6、方法的重写
-
定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
-
要求(两同两小一大):
- (同)子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
- (小)子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- (小)子类方法抛出的异常不能大于父类被重写方法的异常
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
-
注意:
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
-
示例:
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; } public static void main(String args[]){ Student s1=new Student(); s1.name="Bob"; s1.age=20; s1.school="school2"; System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2 Person p1=new Person(); //调用Person类的getInfo()方法 p1.getInfo(); Student s2=new Student(); //调用Student类的getInfo()方法 s2.getInfo(); /*这是一种“多态性”:同名的方法,用不同的对 象来区分调用的是哪一个方法。*/ } }
-
重写与重载的区别:
- 从编译和运行的角度看:
- 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
- 所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为**“早绑定”或“静态绑定”**
- 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为**“晚绑定”或“动态绑定”。**
- 引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
- 从编译和运行的角度看:
4.7、可变形参的方法
- JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
- 说明:
- 声明格式:方法名(参数的类型名 …参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参位置,最多只能声明一个可变个数形参
public void test(String[] msg){
System.out.println(“含字符串数组参数的test方法 ");
}
public void test1(String book){
System.out.println("****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... 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"});
}
4.8、方法参数的值传递机制
- 方法,必须由其所在类或对象调用才有意义。若方法含有参数:
- 形参:方法声明时的参数
- 实参:方法调用时实际传给形参的参数值
- Java的实参值如何传入方法呢?
- Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参,不会改变原有的数据。
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。
- String:也不会改变原有的数据类型,String为不可变类型,final修饰
- Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
4.9、递归方法
- 递归方法:一个方法体内调用它自身。
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执
行无须循环控制。 - 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
//计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
5、构造器
-
构造器的特征:
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
-
构造器的作用: 创建对象;给对象进行初始化
- 如:
Order o = new Order(); Person p = new Person(“Peter”,15);
- 如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
- 如:
-
语法格式:
/* 修饰符 类名 (参数列表) { 初始化语句; } */ //示例 public class Animal { private int legs; // 构造器 public Animal() { legs = 4; } public void setLegs(int i) { legs = i; } public int getLegs() { return legs; } } /* 创建Animal类的实例:Animal a = new Animal(); 调用构造器,将legs初始化为4。 */
- 根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统 默认提供)
- 显式定义一个或多个构造器(无参、有参)
- 注 意:
- Java 语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器, 则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父 类的构造器不可被子类继承
- 根据参数不同,构造器可以分为如下两类:
-
构造器的重载
-
构造器一般用来创建对象的同时初始化对象。
-
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
-
构造器重载,参数列表必须不同
class Person{ String name; int age; public Person(String n,int a){ name=n; age=a;} public Person(String name,int age, Date d) {this(name,age);…} public Person(String name,int age) {…} public Person(String name, Date d) {…} public Person(){…} }
-
6、四种访问权限修饰符
- Java权限修饰符public、protected、default(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
default(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
- 对于class的权限修饰只可以用public和default(缺省)。
- public类可以在任意地方被访问。
- default类只可以被同一个包内部的类访问。
7、子类对象的实例化过程
-
内存解析
-
示例:
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}
}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}
}
/*
Creature无参数的构造器
Animal带一个参数的构造器,该动物的name为灰太狼
Animal带两个参数的构造器,其age为3
Wolf无参数的构造器
*/
8、JavaBean
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
- 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
- 示例:
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
9、Object类
9.1、概念
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
public class Person { ... }
等价于:
public class Person extends Object { ... }
- 例:
method(Object obj){…} //可以接收任何类作为其参数 Person p=new Person(); method(p);
9.2、主要结构
方法名称 | 类型 | 描述 | |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
9.3、== 操作符与equals方法
- ==:
- 基本类型比较值:只要两个变量的值相等,即为true。
int a=5; if(a==6){…}
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。
Person p1=new Person(); Person p2=new Person(); if (p1==p2){…}
- 用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
- 基本类型比较值:只要两个变量的值相等,即为true。
- equals() :所有类都继承了Object,也就获得了equals() 方法 。还可以重写。
- 只能比较引用类型 , 其作用与 “==” 相同, 比较是否指向同一个对象。
- 格式:
obj1.equals(obj2)
- 特例:当用equals() 方法进行比较时,对类File 、String 、Date 及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象 ;
- 原因:在这些类中重写了Object类的equals()方法 。
- 当自定义使用equals()时,可以重写。用于比较两个对象的 “ 内容 ” 是否都相等
- 重写equals()方法的原则
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
- 自反性:x.equals(x)必须返回是“true”。
- 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
- == 和equals的区别
- == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals 的话,它是属于java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是 == ; 我们可以看到String等类的equals 方法是被重写过的,而且String 类在日常开发中用的比较多,久而久之,形成了equals 是比较值的错误观点。
- 具体要看自定义类里有没有 重写Object 的equals 方法来 判断。
- 通常情况下,重写equals 方法,会比较类中的相应属性是否都相等。
9.4、toString() 方法
- toString() 方法在Object 类中定义 ,其返回值是String 类型,返回类名和它的引用地址 。
- 在进行String 与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date(); System.out.println(“now=”+now); 相当于 System.out.println(“now=”+now.toString());
- 可以根据需要在用户自定义类型中重写toString() 方法
如String 类重写了toString() 方法 , 返回字符串的值 。
s1=“hello”; System.out.println(s1);// 相当于System.out.println(s1.toString());
- 基本类型数据转换为String 类型时,调用了对应包装类的toString() 方法
int a=10;
System.out.println(“a=”+a);
10、包装类的使用
-
针对八种基本数据类型定义相应的引用类型—包装类(封装类)
-
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
-
基本数据类型包装成包装类的实例 — 装箱
- 通过包装类的构造器实现:
int i = 500; Integer t = new Integer(i);
- 还可以通过字符串参数构造包装类对象:
Float f = new Float(“4.56”); Long l = new Long(“asdf”); //NumberFormatException
- 通过包装类的构造器实现:
-
获得包装类对象中包装的基本类型变量 — 拆箱
- 调用包装类的.xxxValue()方法:
boolean b = BObj.booleanValue();
- 调用包装类的.xxxValue()方法:
-
JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配。
-
字符串转换成基本数据类型
- 通过包装类的构造器实现:
int i = new Integer(“12”);
- 通过包装类的**parseXxx(String s)**静态方法:
Float f = Float.parseFloat(“12.1”);
- 通过包装类的构造器实现:
-
基本数据类型转换成字符串
- 调用字符串重载的**valueOf()**方法:
String fstr = String.valueOf(2.34f);
- 更直接的方式:
String intStr = 5 + ""
- 调用字符串重载的**valueOf()**方法:
11、代码块
- 代码块(或初始化块)的作用:
- 对Java 类或对象进行初始化
- 代码块(或初始化块)的分类:
- 一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block),没有使用static修饰的,为非静态代码块 。
- static 代码块化通常用于初始化static 的属性
class Person { public static int total; static { total = 100;// 为total 赋初值 } …… //其它属性或方法声明 }
- 静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
- 非静态代码块:没有static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
- 程序中变量赋值执行顺序
12、内部类
12.1、概念
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
- Inner class的名字不能与包含它的外部类类名相同;
- 分类: 成员内部类(static成员内部类和非static成员内部类)
局部内部类(不谈修饰符)、匿名内部类
12.2、成员内部类
- 成员内部类作为类的成员的角色:
- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
- 成员内部类作为类的角色:
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
- 注意
- 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
/*
输出:
333
222
111
*/
12.3、局部内部类
-
如何声明局部内部类
class 外部类{ 方法(){ class 局部内部类{ } } { //代码块 class 局部内部类{ } } }
-
如何使用局部内部类
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
- 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
-
局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
- 局部内部类不能使用static修饰,因此也不能包含静态成员
12.4、匿名内部类
- 匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在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();
}
}
三、面向对象的特性
1、封装和隐藏
1.1、作用
-
为什么需要封装?封装的作用和含义?
- 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
- 我要开车,…
-
我们程序设计追求“高内聚,低耦合”。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅对外暴露少量的方法用于使用。
-
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
-
使用者对类内部定义的属性( 对象的成员变量) 的直接操作会导致数据的错误、混乱或安全性问题。
1.2、实现方式
- Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx() 和**setXxx()**实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
2、继承性
2.1、继承的概念
-
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
-
此处的多个类称为子类( 派生类),单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”
-
类继承语法规则:
class Subclass extends SuperClass{ }
-
继承的作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
- 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展” 。
-
注意:
- 不要仅为了获取其他类中某个功能而去继承
-
继承的规则:
-
子类不能直接访问父类中私有的(private)
-
Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok class SubDemo extends Demo1,Demo2... //error
-
2.2、子类与父类存在同名变量
//父类
class Father{
int value = 10;
public void sub(){
this.value--;
}
}
//子类
class Son extends Father{
int value = 0;
public void add(){
value++;
}
public int getSuperValue(){
return super.value;
}
}
//测试类
public class Test {
public static void main(String[] args){
Son son = new Son();
son.sub();
System.out.println(son.value); //0
System.out.println(son.getSuperValue()); //9
son.add();
System.out.println(son.value); //1
System.out.println(son.getSuperValue()); //9
}
}
- 使用子类对象调用方法使用同名变量,是按照方法来判断使用哪一个变量,调用父类的方法,使用的是父类中的变量,调用子类的方法,使用的是子类中的变量
3、多态性
3.1、概念
-
多态性,是面向对象中最重要的概念,在Java中的体现:
对象的多态性:父类的引用指向子类的对象- 可以直接应用在抽象类和接口上
-
Java引用变量有两个类型: 编译时类型和 运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致 , 就出现了对象的多态性 (Polymorphism)
- 多态情况下 : “ 看左边 ” : 看的是父类的引用(父类中不具备子类特有的方法) “ 看右边 ” : 看的是子类的对象(实际运行的是子类重写父类的方法)
-
对象的多态——在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student(); Object o = new Person(); //Object类型的变量o,指向Person类型的对象 o = new Student(); //Object类型的变量o,指向Student类型的对象
-
子类可看做是特殊的父类 ,所以父类类型的引用可以指向子类的对象:向型上转型(upcasting)。
-
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
Student m = new Student(); m.school = “pku”; // 合法,Student 类有school成员变量 Person e = new Student(); e.school = “pku”; // 非法,Person 类没有school成员变量
属性是在编译时确定的,编译时e为Person 类型,没有school成员变量,因而编译错误。 -
示例:
- 方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test { public void method(Person e) { // …… e.getInfo(); } public static void main(Stirng args[]) { Test t = new Test(); Student m = new Student(); t.method(m); // 子类的对象m传送给父类类型的参数e } }
3.2、方法的调用
-
正常的方法调用
Person e = new Person(); e.getInfo(); Student e = new Student(); e.getInfo();
-
虚拟方法调用( 多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person e = new Student(); e.getInfo(); // 调用Student 类的getInfo() 方法
-
编译时类型和运行时类型
编译时e 为Person 类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo() 方法。——动态绑定 -
示例:
前提:
Person类中定义了welcome()方法,各个子类重写了welcome()。
执行:
多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。
3.3、instanceof 操作符
- x instanceof A :检验x 是否为类A 的对象,返回值为boolean 型。
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
-------------------------------------------------------------------
public void method1(Person e) {
if (e instanceof Person)
// 处理Person 类及其子类对象
if (e instanceof Student)
// 处理Student 类及其子类对象
if (e instanceof Graduate)
// 处理Graduate 类及其子类对象
}
3.4、对象类型转换 (Casting )
-
基本数据类型的Casting:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
- 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
如float f=(float)12.0; int a=(int)1200L
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
-
对Java 对象的强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型( 强制类型转换) 实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof 操作符测试一个对象的类型
-
举例:
//对象类型转换
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l);
int in = 5;
// boolean b = (boolean)in;
Object obj = "Hello";
String objStr = (String) obj;
System.out.println(objStr);
Object objPri = new Integer(5);
// 所以下面代码运行时引发ClassCastException异常
String str = (String) objPri;
}
}
public class Test {
public void method(Person e) { // 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(String[] args){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
3.5、子类继承父类
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
3.6、小结
- 多态作用:
- 提高了代码的通用性,常称作接口重用
- 前提 :
- 需要存在继承或者实现关系
- 有方法的重写
- 成员方法:
- 编译时:要查看引用变量所声明的类中是否有所调用的方法。
- 运行时:调用实际new的对象所属的类中的重写方法。
- 成员变量:
- 不具备多态性,只看引用变量所声明的类。
- 多态不是编译时行为而是运行时行为
四、关键字
1、this
1.1、概念
-
在Java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
-
this 可以调用类的属性、方法和构造器
-
什么时候使用this关键字呢?
- 当在方法内需要用到调用该方法的对象时,就用this。
具体的:我们可以用this来区分属性和局部变量。
比如:this.name = name;
- 当在方法内需要用到调用该方法的对象时,就用this。
-
使用this,调用属性、方法
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
- 当形参与成员变量
同名
时,如果在方法内或构造器内需要使用成员变量,必须
添加this来表明该变量是类的成员变量 - 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找
- this可以作为一个类中构造器相互调用的特殊格式
-
注意
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
- 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行!
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
//使用this 调用本类的构造器
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(){ // 无参构造器
System.out.println("新对象实例化") ;
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ; //若属性的name前面没有this.则赋值不成功
}
public Person(String name,int age){
this(name) ; // 调用有一个参数的构造器
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age ;
}
}
2、package
2.1、package的概念
- package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:
package 顶层包名.子包名;
- 包对应于文件系统的目录,package 语句中,用 “ . ” 来指明包( 目录) 的层次;
- 包通常用小写单词标识。通常使用所在公司域名的倒置:com.itlazy.xxx
2.2、package的作用
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 包可以包含类和子包,划分项目层次,便于管理
- 解决类命名冲突的问题
- 控制访问权限
2.3、MVC设计模式
-
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
-
模型层 model 主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库 model.db -
视图层 view 显示数据
相关工具类 view.utils
自定义view view.ui -
控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
2.4、JDK中常用的包
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text----包含了一些java格式化相关的类
- java.sql----包含了java进行JDBC数据库编程的相关类/接口
- java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S----C/S
3、import
-
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。
-
语法格式:
import 包名.类名;
//import pack1.pack2.*;表示引入pack1.pack2包中的所有结构 import pack1.pack2.Test; public class PackTest{ public static void main(String args[]){ Test t = new Test(); //Test类在pack1.pack2包 中定义 t.display(); } }
-
注意:
- 在源文件中使用import显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 举例:可以*使用java.util.的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
- import static组合的使用:调用指定类或接口下的静态的属性或方法
4、super
- 在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
- 注意:
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
- 调用父类的构造器:
- 子类中所有的构造器默认都会访问父类中空参数的构造器,
- 当父类中没有空参数的构造器时,子类的构造器必须通过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;
}
}
5、this与super的区别
区别点 | this | super | |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
6、static
6.1、类属性、类方法的设计思想
- 类属性作为该类各个对象之间共享的变量。在设计类时, 分析
哪些属性不因对象的不同而改变
,将这些属性设置为类属性。相应的方法设置为类方法。 - 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法 ,从而简化了方法的调用。
6.2、概念
-
使用范围:
- 在Java类中,可用static修饰属性、方法、代码块、内部类
-
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
-
举例
class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
public static void main(String args[]){
Person Tom=new Person();
Tom.id=0;
total=100; // 不用创建对象就可以访问静态成员
}
}
------------------------------------------------------
public class StaticDemo {
public static void main(String args[]) {
Person.total = 100; // 不用创建对象就可以访问静态成员
//访问方式:类名.类属性,类名.类方法
System.out.println(Person.total);
Person c = new Person();
System.out.println(Person.total); // 输出101
}
}
- static修饰的属性及方法既可以使用
类名.属性/方法
调用,也可以使用实例.属性/方法
调用,虽然可以,但不要这样调。
6.3、类变量及实例变量内存解析
6.4、类方法
- 没有对象的实例时,可以用
类名. 方法名()
的形式访问由static修饰的类方法。 - 在static方法内部只能访问类的static 修饰的属性或方法,不能访问类的非static 的结构。
- 例如:
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;
}
public Person() {
total++;
id = total;
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("total is " + Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println("total is "+ Person.getTotalPerson());
}
}
/*
输出:
total is 0
total is 1
*/
-
因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也不能有super)
-
static 修饰的方法不能被重写
7、final
-
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
- final 标记的类不能被继承 。提高安全性,提高程序的可读性。
- String类、System类、StringBuffer类
- final 标记的方法不能被子类重写。
- 比如:Object类中的getClass()。
- final 标记的变量( 成员变量或局部变量) 即称为常量 。名称大写,且只能被赋值一次。
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。
final double MY_PI = 3.14;
- 若final修饰的是引用类型对象,表示对象地址不能改变,即对象内的属性可以改变。
- final 标记的类不能被继承 。提高安全性,提高程序的可读性。
-
举例:
//1.final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
//2. final 修饰方法
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("B");
}
}
//3. final 修饰变量——常量 常量名要大写(命名规范),内容不可修改。
class A {
private final String INFO = "aaa"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = "bbb";
}
}
- static final:全局常量
8、abstract(抽象类与抽象方法)
8.1、概念
-
用abstract关键字来修饰一个类,这个类叫做抽象类。
-
用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
比如:public abstract void talk();
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
-
含有抽象方法的类必须被声明为抽象类。
-
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
-
不能用abstract修饰变量、代码块、构造器;
-
不能用abstract修饰私有方法、静态方法、final的方法、final的类。
-
抽象类中可以定义构造方法,但是抽象类不能被实例化。
-
抽象类可以有静态方法,用类名直接调用
-
举例:
abstract class A {
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();
}
}
9、interface(接口)
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
-
**接口的本质是契约,标准,规范,**就像我们的法律一样。制定好后大家都要遵守。
-
举例
9.1、概念
- 接口(interface)是抽象方法和常量值定义的集合。
- 接口的特点:
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制。
- 接口定义举例
public interface Runner {
int ID = 1;
void start();
public void run();
void stop();
}
//相当于
public interface Runner {
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
9.2、接口的使用
- 定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
- 一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
9.3、接口举例
-
图解
-
代码示例
//一个类可以实现多个无关的接口
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();}
}
9.4、接口的应用(代理模式)
-
概述:
- 代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
-
代码示例:
interface Network {
public void browse();
}
// 被代理类
class RealServer implements Network {
@Override
public void browse() {
System.out.println("真实服务器上网浏览信息");
}
}
// 代理类
class ProxyServer implements Network {
private Network network;
public ProxyServer(Network network) {
this.network = network;
}
public void check() {
System.out.println("检查网络连接等操作");
}
public void browse() {
check(); //代理添加的行为
network.browse(); //利用多态调用被代理类的行为
}
}
public class ProxyDemo {
public static void main(String[] args) {
Network net = new ProxyServer(new
RealServer());
net.browse();
}
}
/*
输出:
检查网络连接等操作
真实服务器上网浏览信息
*/
-
应用场景:
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
-
分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- JDK自带的动态代理,需要反射等知识
9.5、 Java 8 中关于接口的改进
- Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
- 静态方法:使用 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!");
}
}
9.6、接口中的默认方法
- 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现: 接口冲突。
- 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
- 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守: 类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
10、抽象类与接口的区别
10.1、区别
区别点 | 抽象类 | 接口 | |
---|---|---|---|
1 | 定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8.0:默认方法、静态方法) |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
6 | 对象 | 都通过对象的多态性产生实例化对象 | 都通过对象的多态性产生实例化对象 |
7 | 局限 | 抽象类有单继承的局限 | 接口没有此局限 |
8 | 实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
9 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
- 在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
10.2、父类及接口中同名变量和方法
-
父类与接口或接口之间出现同名变量(在接口中属于常量,只能用
接口名.常量名
调用)-
接口跟类同级,在实现接口和继承父类都存在相同变量时,导致子类无法抉择
-
解决方法
-
在子类的方法中显示表明要输出的变量是父类还是接口的变量。(
super/接口名.变量名
) -
interface A{ int i = 0; } class B{ int i=10; } interface A2{ int i = 100; } class Test extends B implements A,A2{ public int printI(){ //return super.i; //父类中的i //return A.i; //接口A中的i return A2.i; //接口A2中的i } } public class Test01 { public static void main(String[] args) { Test t = new Test(); System.out.println(t.printI()); } }
-
-
-
父类与接口中存在同名方法
-
若子类没有重写该方法,则默认会优先调用父类中的方法,不会报错。
-
interface A{ public void run(); } class B{ public void run(){ System.out.println("B中的run()方法"); } } class Test extends B implements A{ } public class Test01 { public static void main(String[] args) { Test t = new Test(); t.run(); } } /* 输出: B中的run()方法 */
-
若子类重写了该方法,则相当于同时重写了父类以及接口中的方法。调用的是子类重写后的方法
-
interface A{ public void run(); } class B{ public void run(){ System.out.println("B中的run()方法"); } } class Test extends B implements A{ @Override public void run() { System.out.println("Test中的run()方法"); } } public class Test01 { public static void main(String[] args) { Test t = new Test(); t.run(); } } /* 输出: Test中的run()方法 */
-
-
接口之间存在同名方法
-
参数列表+返回值 都相同:实现类只要实现一次此方法即可
-
参数列表相同+返回值不同:实现类无法直接实现两个方法(IDE报错),因为不满足方法重载原则
-
参数列表不相同:实现类可以分别实现两个方法,可以方法重载
-
//参数返回值都相同 interface A{ public void run(); } interface A2{ public void run(); } class Test implements A,A2{ @Override public void run() { System.out.println("Test中的run()方法"); } } public class Test01 { public static void main(String[] args) { Test t = new Test(); t.run(); //Test中的run()方法 } } //参数相同,返回值不相同 interface A{ public void run(); } interface A2{ public int run(); } class Test implements A,A2{ @Override public void run() { //无论重写哪一个接口中的方法都会报错,参数不同无法构成重载,定位不到哪个方法 } } public class Test01 { public static void main(String[] args) { Test t = new Test(); t.run(); } } //参数列表不相同,分别重写,构成重载 interface A{ public void run(int a); } interface A2{ public int run(); } class Test implements A,A2{ @Override public int run() { return 0; } @Override public void run(int a) { } } public class Test01 { public static void main(String[] args) { Test t = new Test(); t.run(); } }
-