双元教育
目录
面向对象概念复习
-
类和对象
-
构造器和静态工厂
-
对象的创建和引用
-
关键字
-
类间关系
-
Object类
-
抽象类与接口
面向对象概念复习
面向对象开发的终级目的:代码复用。
面向对象开发是大规模开发的必然产物。
现实世界中类与对象是个体与整体的关系;在计算机世界中类与对象抽象与具体的关系。 计算机世界中的类是抽象的,是描述性。而对象是类的具体的实现,是类所描述的功能的具体拥有者。 *抽象(动词)的定义:抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。 -共同特征是指那些能把一类事物与他类事物区分开来的特征,这些具有区分作用的特征又称本质特征。
在利用开发语言描述现实现世界的时候,用面向对象的思想更容易描述,更符合人类正常的思维方式。
类和对象
类是以文件的形式存放在硬盘中的,其扩展名为.class。类中只会包含两种东西:成员变量、成员方法。在Java中,大部分情况下,类外不能包含任何东西。
类只是蓝图或模板,不能真正加以利用。要想使用类中的成员变量和成员方法,必须首先实例化这个类,实例化的过程就是从类构造对象的过程。这过程包含以下几个步骤:
1,利用加载加器将类从硬盘加载到内存中,在内存中类以Class类的对象的形式存在,该对象会在内存一直存在。
2,利用new关键字和类的构造方法创建一个真正的、可以使用的类的实例,一般称其为对象。
对象存放在堆中,而以Class类的对象的形象出现的类在放在方法区中。
构造器与静态工厂
构造器
构造方法是通过类创建对象的最常用的方法。从设计上来说构造方法是将蓝图变成客观实在众多步骤中的一步。
需要注意的是:构造方法不是构造对象的方法。构造方法只是构造对象的语法中的一部分;同时当对象构造出来之后,如果需要对其中的成员变量进行初始化,则可以利用构造方法进行初始化。
构造方法的两个主要特征:方法名与类名一致(包括大小写);方法声明不带返回值类型,包括void。
的Java中,一个构造方法是不能单独使用的,必须和new关键字一同使用。
new Person();
构造方法可以重载,但不可以重写(覆盖)。 如果一个类没有定义构造方法,系统会默认添加一个无参的构造方法。如果有了有参的构造方法,则不会再添加无参的构造方法。
静态工厂
从JDK8开始,Java引入了很多静态工厂。如:
LocalData.of(...);
对象的创建和引用
创建对象时利用构造方法,创建一个构造对象的语法:
new 构造方法();
要想构造一个Date对象,需要在构造器前面加上new操作符,如下:
new Date(); //由于没有获得该对象的位置参数,所以该对象被创建后立即丢失掉了。
这个表达式在堆区中构造了一个新对象,这个对象被初始化为当前的日期和时间。
注意:创建的新对象没有名字;程序员也不知道对象在堆中的具体位置。换言之,当前对象被创建出来后就立即找不到了。
可以将对象传递给一个方法,如下:
System.out.println(new Date()); //这种方式是一次性的使用对象
也可以将一个方法应用于刚刚构造的对象之上。如下:
String s=new Date().toString(); //这种方法也是一次性的使用对象
在以上的两个例子中,构造的方法仅使用一次。通常构造的对象需要被多次使用,因此,需要将对象的位置参数存放在一个变量中。这个变量被称为引用变量,每个引用变量都属于一个类型,类型描述了这个变量所引用的以及能够引用的对象类型,如下:
Date date; //创建一个可以引用Date类的对象的引用变量
date=new Date(); //创建一个Date类的对象,并将该对象的引用(位置参数、首地址)赋值给date变量。
//以上代码可以缩写成如下形式:
Date date=new Date();
以上代码执行后,date变量中存放了Date类实例(对象)的位置参数(内存中的首地址)。对date变量的任何操作,其实就是操作其指向的对象。但是date并不是对象,对date的准确称谓是:引用了Date类的实例的引用变量date。
强调一点:变量无法存放对象。在Java中任何变量一旦生成,其长度也就固定了;而对象的长度是不定的。所以变量中永远无法存储对象,只能存储对象的引用。
NullPointerException异常指的是:程序员利用引用变量调用对象中的方法时,引用变量实际上没有存储任何对象的位置参数。此时就会出现该异常。
方法的重载
在类的内部,出现的方法名相同,但参数列表不同的方法,称为重载方法。重载方法出现的原因是编译环境更加聪明了。
所谓的参数列表不同,指的是:参数的类型,参数的个数,参数的顺序中有至少一个不同,就是参数列表不同。
*需要注意的是:方法的重载不考虑返回值和访问类型。
*方法的重载本质上是JVM对方法签名的解析。方法签名的概念:方法名+参数列表。
注意:构造方法也可以重载
this关键字与super关键字
需要提前说明的是:this关键字和super关键字的在实现机制上的差别是巨大的。这里将二者放在一起讲解,只是因为二者在应用的形式差不多而已。
this
this是关键字,同时也一个真实的变量,即所谓的隐式传值,代表当前对象。 this的三种用法:
this(); //在构造器中调用本类另一个构造器
this.method()/this.attribute //调用成员方法或成员变量
private String name;
public void setName(String name){
this.name=name;
}
..=this; //this作为赋值符号的右边元素出现,this不仅是关键字也是一个引用变量。
隐式传值,指的是在方法调用的时候,JVM会隐式把调用方法的对象,一起作为参数传到方法内部。也就是在方法内部的this实际上是一个方法参数。
注意:静态方法没有this,因为调用静态方法的一定是类,而不是某个对象。
add方法在Student类中时,如下:
public void add(int a,int b){...}
//编译器改写成
public void add(int a,int b,Student this){...}
调用:
Student s=new Student();
s.add(10,15);
//编译器改写成:
s.add(10,15,s);
super
super的两种用法:
super(); //在构造器中调用父类的构造器
super.method()/super.attribute //调用父类的被重写的方法或公共的成员变量。
//super不能在赋值运算符的右边出现。super仅仅是个关键字,不能把关键字赋值给一个变量。
static关键字
static可以修饰方法和属性,被修饰的方法称为类方法(静态方法);被修饰的属性称为类属性(静态变量)。
成员变量与静态变量的区别
静态变量是属于类的,成员变量是属于对象的。
静态变量属于类的含义:由同一个类实例化出的不同对象,共享该类的静态变量。
从内存的角度来看,静态变量存放在方法区中;而成员变量则存放在对象内部,也就是堆中。
*静态变量在第一个该类的对象初始化的时被声明和赋初值。之后,该类的其它对象创建时将不在涉及静态变量。
类方法: 调用方式:类名.属性名 or 类.方法名 对象.属性名 or 对象.方法名也可以,但不建议这样做。
大部分情况下静态方法出现在工具类中。
静态变量使用规则: 1,可以在非静态方法体中引用静态变量。 2,在静态方法体中不可以引用非静态变量(成员变量)。 原因:静态方法执行,不能保证一定存在对象。 解决:将非静态变量变成静态变量;或者实例化当前类,使用对象调用。 3,可以在静态方法体中创建非静态变量(局部变量)。
4,static关键字修饰的变量不能出现在方法中,也就是说静态变量不能是局部变量。
5,由于静态变量将被多对象共享,所以大部分时候,静态变量都应该被final修饰。
静态语句块:
static{
...
}
该语句块中的语句将在该类被第一次加载时运行,以后不会再运行了。
final关键字
不能继承、不能重写、不能修改
final修饰类,表明类不能被继承;final修饰方法,表明方法不能被重写;修饰成员变量或局部变量,表明为常量。
*常量名一般全部大写。
final类--不可继承 需要阻止其他人利用某个类定义子类时,需要使用final关键字。如下:
final class MyManager extends Manager{
...
}
*MyManager类将无法被任何类继承。
final方法--不能覆盖(重写) 类中的方法也可以被声明为final,这样做,子类就不能覆盖这个方法。 *需要注意的是,final类中的所有方法自动地成为final方法。 *实际上采用final关键字不但可以防止父类中方法被子类覆盖,而且还可以加快应用的运行速度。
final变量(成员变量)--不可修改 可以将变量(成员变量)定义为final,构建对象时必须初始化这样的变量。在后面的操作中,不能够再对它进行修改。 *对于final的成员变量,系统不会为其赋初始值,而是要求在构造器完成之前必须显示赋初始化。 *对于final的局部变量,可以只声明而不初始化,一旦初始化值就不能修改,初始化的时间只要在使用其值之前就可以了。 final修饰符大都应用于原始类型,或不可变类型。对于可变的类,使用final修饰符会造成理解上的混乱。
不可变类和可变类
不可变类指的是其核心数据结构被final修饰,即常量。比如:String类。
可变类指的是其核心数据结构可变,如Date类。(JDK1.8加入了新的日期类型LocalDate类,就是不可变的。)
类与类之间的关系
泛化关系
类与类之间的继承关系,即“is-a”关系。
泛化关系在Java语言中可以直接翻译为关键字extends和implements。
类的继承的最重要的法则:除非必要,不要继承。
关联关系
类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是单向的也可以是双向的,通常不鼓励双向的关联。
在Java语言中,关联关系是使用实例变量实现的。也就是说,在一个类中出现了另一个类的实例,我们说这两个类是关联关系。
-
聚合关系--关联关系的一种,是强的关联关系。聚合是整体与个体之间的关系。如汽车类与引擎类、轮胎类的关系。
public class Person { private int id; private User user; public Person() {} }
-
合成关系--关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。如人和四肢的关系。
public class Person { private int id; private User user; public Person(User user) { this.user=user; } }
聚合关系和合成关系的区别:合成关系的两个对象的生命周期要一致;聚合关系的两个对象的生命周期不一定一致。
依赖关系
依赖关系也表现为类与类这间的连接,依赖总是单向的。
在Java语言中,依赖关系表现为方法的参数。一个类中的方法参数是另一类时,就该依赖于另一类。
public class Person {
private int id;
public void setPerson(User user) {
}
}
公共父类Object
java.lang.Object是Java程序中所有类的父类。每个类都直接或间接继承自Object类。 Object中的方法,以下方法大部分是本地方法(native method),只有equals()和toString()方法不是。
Object方法
//1,对象创建方法
private static native void registerNatives();
//2,常用业务方法
public final native Class<?> getClass(); //本地方法,返回当前对象所对应的类对象
public native int hashCode(); //本地方法,生成当前对象的唯一标识
public boolean equals(Object o); //比较两个引用变量的地址(和==的返回值一样。)
public native Object clone(); //本地方法,克隆对象方法。
public String toString(); //返回对象的文字描述
//3,多线程相关方法,这些方法将在多线程课程中讲解
public native void notify()
public native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
//4,垃圾回收方法
protected void finalize() throws Throwable //空方法,该方法一般不需要程序员调用
Java实现的方法
toString():对对象的文字描述,它会返回String实例。返回:类名@16进制的哈希码 equals():比较对象的内存地址是否相同。的Object类中,equals()和==是一样的。
本地方法
本地方法是C/C++代码实现的方法,在Java中使用native关键字声明。
hashCode():返回哈希码。 clone():复制对象本身的方法。 getClass():返回对象于执行时期的Class实例。
注意:根据Java虚拟机规范,Object的hashCode方法要求对不同对象返回不同的int类型数据,但并没有规定具体实现的技术细节,最常见的实现是返回对象内部地址转换成的int类型数据。
空实现方法
finalized():析构方法,该方法在Object中是空实现。子类大部分时候不需要覆盖该方法。
多线程相关方法
多线程相关方法也是本地方法。
notify() notifyAll() wait()
说明
1,equals()和hashCode()方法是成对的,重写其中一个,必须重写另一个。因为这两个方法的结果逻辑上相关。
2,getClass()返回的静态区中Class类的实例。这个实例代表类本身。
继承
public SubClass extends SuperClass{
...
}
默认情况下,所有的类都继承于Object类,该类是所有Java类的共同的父类。 在Java中继承是单继承。即一个子类只能继承一个父类。
类的访问修饰符
protected和缺省修饰符都有一个特点:包内可以访问。
private | 完全私有,类内可以访问 |
---|---|
缺省 | 包内可以访问 |
protected | 子类及包内可以访问 |
public | 都可以访问 |
级联调用的构造器
任何一个构造方法的第一句一定是super(...);如果不加,系统会默认添加(系统默认添加的是调用父类的无参的构造器,即super();)。这就是所谓的“上遡造型”。
当调用某个类的构造器的时候,将首先调用这个类的父类的构造器,如果其父类还有父类的构造器则继续上溯。也就是说构造器的执行顺序是从最高父类开始,依次执行。即调用了子类构造器会自动调用父类构造器,直至Object构造器。 也就是说,每一个构造器在执行前,都会首先自动在构造器中添加super(参数列表)方法。如果开发人员自己编写super(参数列表)方法,系统就不会自动添加代码了。 *需要注意的是:手动添加的super(参数列表)方法,必须位于构造器的第一句,否则会出现编译错误。 构造器被设计为级联调用的原因是:根据替代性原理,子类对象必须能够替代父类对象,因此在构造子类对象的之前,要首先构造一个父类对象,然后在父类对象的基础上进一步构造子类对象。也就说,Java中的子类对象中都隐含着一个父类对象,子类对象是在父类对象的基础上进一步雕琢完成的。 *需要注意的是:构造器是不能继承的。 *需要注意的是:子类的构造器,不管是无参还有有参,都会默认调用父类的无参构造器。
方法重写(覆盖)
方法的重写:父类与子类之间发生的,子类的方法覆盖父类的同签名方法的现象。 重写的特性: 1,构造方法不能被重写。 2,方法签名相同的方法才可以重写;重写时访问权限不能低于父类。
*方法签名:方法名,参数列表,返回值类型或其子类型,外加访问修饰符。
从定义上讲:覆盖是更加合适的概念。父类的方法被盖住,但还是存在的。
置换法则与多态
置换法则:程序中出现超类对象的任何地方都可以用子类对象置换。 例如,一个Employee类型变量既可以引用一个Employee类型对象,也可以引用一个Employee类的任何一个子类的对象。
多态:(程序)父类的引用变量可以引用其子类的对象的现象,被称为多态。 多态的特征:父类对象出现的地方,子类对象一定可以出。
Person p=new Person();
Object o=new Person(); //多态
在jdk1.5之前,所有集合框架,都是采用多态设计的。直到现在,在集合不使用泛型的时候,也是利用多态来存储数据。
List list=new ArrayList();//声明一个集合类型对象,本身引用也是多态的。该集合中的每个元素都是Object。
list.add("ok"); //把一个String类型的对象,放入集合,由于多态支持,可以把String转变成Object。
String temp=(String)list.get(0); //get(0)取出是一个Object对象,要赋值给String类型必须强制类型转换。
---泛型---
List<String> list=new ArrayList<>();//从jdk1.7开始,jvm可以推测出类型,所以第二个菱型就不需要了。
list.add("ok");
String temp=list.get(0);
注意:jdk1.5引入的泛型其实是假泛型,其底层依然是多态。
另外,接口也支持多态。也就是说,可以用接口类型的引用变量,引用实现了该接口的类的实例。
动态绑定
父子类方法发生重写时的动态绑定技术。当父类的引用变量引用子类的实例,同时子类又重写了父类中的方法,这时调用该方法,调用的是父类的方法还是子类的方法? 动态绑定:实例方法与引用变量实际引用的对象的类型绑定。上面的问题的答案是:调用的是子类的方法。 静态绑定:实例变量与静态方法与引用变量的类型绑定。但是静态绑定技术不应该出现。
instanceof关键字
instanceof运算符:检测对象是否是类或接口直接或间接子类的对象。
对象 instanceof 类或接口
instanceof关键字的经典应用,String类中equals()方法的源代码例子:
public boolean equals(Object anObject) {
//比较两个引用变量是否指向一个对象
if (this == anObject) {
return true; //如果指向一个对象,直接返回true
}
if (anObject instanceof String) {//检测anObject所引用的对象是否是String类,或String类的子类。
...
}
return false;
}
抽象类(abstract关键字)
抽象类核心的作用:固定(父类)方法原型;放任(子类)方法实现。
1,类中有一个方法是抽象方法的类,也必须是抽象类。 2,类中没有抽象方法的类,也可以是抽象类,此时该类抽象的目的是阻止该类实例化。
*abstract关键字与final关键不能同时出现。
抽象方法 1,抽象方法没有方法体。 2,抽象方法所在的类一定是抽象类。 3,继承了有抽象方法的抽象类的类,必须实现全部的抽象方法,否该类依然是一个抽象类。
并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
是从技术层面上考虑,为什么要使用抽象类? 答:当父类需要某个方法,而这个方法在父类中无法确定时。有两种解决方案: 1,父类中去掉这个方法,方法完全由子类来完成。这样做无法实现继承层次上的多态,实际失去继承的基本意义。 2,父类变成抽象类,该方法变成抽象方法,即:规定方法的声明,同时开放方法的实现。子类直接继承父类的方法声明,采用自己的方式实现这个方法。 *第1种方法无法使用多态,第2种相当于(不是)重写了父类的方法,可以直接使用多态。 总结:限制方法声明,开放方法实现,有利于多态。
例子:
public abstract class 交通工具{
public void move();
}
public class 飞机 extends 交通工具{
public void move(){
System.out.println("飞");
}
}
public class 汽车 extends 交通工具{
public void move(){
System.out.println("跑");
}
}
交通工具 object0=new 飞机();
object0.move();
交通工具 object1=new 汽车();
object1.move();
接口
接口与类的继承是没有任何关系。
interface(接口)在软件开发中,有两个完全不同的含义。 1,指的是某个类,对外暴露出的某些方法,使用者可以不知道方法的实现,只需要知道方法如何调用,就可以很容易的使用方法。比如常见的API。 2*,特指面向对象开发技术中的一个特殊的技术,即接口。
接口是一种标准,实现了某个接口的类,将具备这种标准。
接口的解释: 1,接口是一个契约(合同),体现在接口的名称上。 2,接口是一种能力,体现在接口的方法上。 *接口本身和继承没有关系的。 *接口是一种约定。 实现了某个接口的类,也就应该具有这个接口所应该是具有某种能力。
java源代码中的例子
整体上接口是一种标准
package java.lang;
public interface Comparable<T> { //Comparable就是契约
public int compareTo(T o); //compareTo就是能力
}
*Comparable接口是一个使用具备排序能力的接口;compareTo()方法是一个比较大小的方法。
//没有方法声明的接口
package java.lang;
public interface Cloneable {
}
使用一个类的多个对象可以在集合中排序
public class Person implements Comparable<Person>{
private int id;
@Override
public int compareTo(Person o) {
if(this.id>o.id){
return 1;
}else if(this.id<o.id){
return -1;
}
return 0;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class Test {
public static void main(String[] args) {
Person p1=new Person();
p1.setId(10);
Person p2=new Person();
p2.setId(1);
Person[] person={p1,p2};
for(Person p:person){
System.out.println(p.getId());
}
Arrays.sort(person); //排序,注意:如果没有实现Comparable接口,这里会报异常。
for(Person p:person){
System.out.println(p.getId());
}
}
}
*注意:如果不实现comparable接口,会报错:
Exception in thread "main" java.lang.ClassCastException: edu.yuhf.test4.Person cannot be cast to java.lang.Comparable
Person==实现接口==>Comparable.compareTo()===>可以比大小了===>具备了排序的能力
接口,是面向对象设计中,最高层次抽象。
接口的特性: 1,接口是不能被实例化的。接口是完全抽象,没任何具体东西。JDK1.8引入的接口默认方法的概念。 2,接口的实现类必须实现接口中的所有非默认方法。 3,实现类可以实现多个接口。某个类所实现的所有接口,没有相互之间的联系。 4,接口中的变量默认都是公共的静态的常量;接口中的方法的默认都公共抽象方法。 在接口中声明的变量都是默认被public static final修饰的。 (接口中不能有成员变量) 在接口中声明的方法都是默认被public abstract修饰。 JDK1.8之后,在接口中可以出现方法实现,称为默认方法,这种方法必须使用default关键字修饰。 5,在很多情况下,接口都作为类型来使用的。比如说:接口与其实现类之间也是支持多态。
接口本身是一种标准。即在达到要求的前提下,赋予绝对行动自由。 某个类实现接口,就是承诺;或者说承诺具备某种能力。 当某个类实现了一接口,实际就相当于签订一个契约。
例子:小汽车要想在欧洲销售必需符合欧IV标准。
public interface e4{
public void paifan();
}
public class A3Car implements e4{
public void paifan(){
...
}
}
面向抽象编程:所有的类都应该继承或实现一个抽象类或一个接口。也就是说,应用程序的系统是建立抽象基础上的。
一个类可以实现多个接口,被实现的接口间不需要有任何关系。实现多个接口时,接口名用逗号隔开。
接口中的默认方法和静态方法
jdk8中在接口中加入了默认方法,可以为接口提供一个默认实现。必须用default修饰符标记这样的方法。 jdk8之前,Java API中有大量的接口伴随类。比如:Collection/AbstractCollection等,但是jdk8之后这种方法已经过时了。 默认方法冲突 1,超类优先,如果超类提供一个具体方法,同名且有相同参数类型的默认方法会被忽略。 2,接口冲突,如果一个接口提供了一个默认方法,另一个接口提供了一个同名且参数类型相同的方法(无论是不是默认方法),同时实现这两个接口的类必须覆盖这个方法来解决冲突。
jdk8中还可以在接口中定义静态方法。