转载请保留原文链接: http://dashidan.com/article/java/basic/13.html
Java是`OOP`(Object Oriented Programming)面向对象编程语言. java面向对象的三大特点: 封装, 继承和多态.
① Java中万事万物皆对象?
Java中万事万物皆对象?
这个说法的由来, 是因为java中所有的类默认都继承自Object
类. 但这个说法是不准确
的. 很多资料中这样说, 曾经给我带来过很大的困扰.Java属于强类型语言
, 方法
作为属性存在于对象中, 不作为独立的对象存在. 所以说在Java中万事万物皆对象的说法是不准确的, 方法和基本类型变量都不是对象的概念.在一些弱类型语言比如javascript
, 方法也可以作为对象
的概念来使用, 作为参数传递, 这一点和java是有区别的.
② 面向对象编程思想
提到面向对象, 不得不提到面向过程编程. 以C语言为例, 每个方法作为一个执行过程
存在, 面向过程编程是一种面向计算机思维, 从计算机执行的角度思考编写代码的编程方式.
从面向过程编程的基础上, 发展出来了一种新的编程思想, 面向对象编程, 来应对更复杂的应用场景.面向对象编程是一种面向人
的逻辑思维的编程方式, 将代码中的通用性抽象出来, 模拟现实世界的归类, 建立程序模型, 编写代码的过程. 对于程序员更加友好, 对于复杂度更高的程序更易于控制.
在生物分类学中, 分为界门纲目科属种
, 按照生物的种群和等级划分.
猫:
动物界->脊索动物门->哺乳纲->哺乳纲->食肉目->猫科->猫属->猫种
虎:
动物界->脊索动物门->哺乳纲->哺乳纲->食肉目->猫科->豹属->虎种
猫
和虎
同处于猫科
, 是猫科子类中的一种, 具有猫科的全部属性. 用Java面向对象的思想来编程可以这样表达:
猫科
作为父类
, 猫
和虎
作为子类
, 继承自猫科
拥有父类猫科
的全部属性, 独有的属性写在子类
(猫类或虎类)中.
面向对象编程思想与人类的逻辑思维很接近.
③ 构造函数
构造函数是一种特殊的函数. 构造函数与类名相同, 可以有参数也可以无参数, 在对象被创建时自动执行.
1.构造函数的作用:
- 在创建对象时完成完成对象数据成员的初始化
- 为对象数据成员开辟内存空间
- 为对象创建标识符
2.默认构造函数
当用户没有显式的定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为默认构造函数
. 默认构造函数名与类名相同, 没有参数.例:
public class Demo1 {
// 默认构造函数
public Demo1(){
}
}
默认构造函数在代码中看不到, 在编译时自动生成.
3.构造函数的特点
无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:
- 在对象被创建时自动执行.
- 构造函数的函数名与类名相同.
- 没有返回值类型、也没有返回值.
- 构造函数不能被显式调用.
4.构造函数的执行
通常是通过new
关键字创建对象时执行.
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 构造函数的执行
*/
public class Demo1 {
public static void main(String args[]) {
System.out.println("Demo1运行开始");
TestStructure testStructure = new TestStructure(1, "大屎蛋教程网", "dashidan.com");
int id = testStructure.getId();
String url = testStructure.getUrl();
String name = testStructure.getName();
System.out.println("id " + id + " url " + url + " name " + name);
System.out.println("运行完毕");
}
}
/**
* 测试构造方法类
*/
class TestStructure {
private int id;
private String name;
private String url;
/**
* 构造函数
*/
public TestStructure(int id, String name, String url) {
/** 构造函数初始化成员变量*/
System.out.println("执行TestStructure构造函数");
this.id = id;
this.name = name;
this.url = url;
System.out.println("id " + id + " name " + name + " url " + url);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getUrl() {
return url;
}
}
输出:
Demo1运行开始
执行TestStructure构造函数
id 1 name 大屎蛋教程网 url dashidan.com
id 1 url dashidan.com name 大屎蛋教程网
运行完毕
c++除了构造函数
,还有析构函数
的概念. 析构函数也是一种特殊的成员函数. 它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,如释放分配给对象的内存空间等. Java中只有构造函数,而没有析构函数.
④ 参数的值传递与引用传递
Java中方法中给变量赋值有2种方式: 值传递和引用传递.传递基本类型,String参数时是值传递. 传递引用类型参数时, 是引用传递.
1.值传递
传递的是值的拷贝. 也就是说传递参数时, 将值拷贝了一份, 与原有变量不再有关系. 基本类型
和字符串
传递采用的是值传递.
示例代码:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 值传递
*/
public class Demo2 {
public static void main(String[] args) {
int a = 1;
String name = "大屎蛋";
changeValue(a, name);
/** 原值不变*/
System.out.println("原值 a : " + a);
/** 原字符串不变*/
System.out.println("原值 name : " + name);
}
public static void changeValue(int num, String str) {
num++;
/** 输出改变的值, 改变了参数的值, 原值不变*/
System.out.println("changeValue num : " + num);
/** 添加字符串, 改变了作为参数传进来的字符串的值, 原字符串不变*/
str += "dashidan.com";
System.out.println("changeValue str : " + str);
}
}
输出:
changeValue num : 2
changeValue str : 大屎蛋dashidan.com
原值 a : 1
原值 name : 大屎蛋
2.引用传递
引用传递是指给索引对象赋值时, 是将对象内存的地址作为参数传递. 新的对象与原有指向同一份数据. 当修改对象的变量时, 原有指向改数据的对象都发生改变
.
代码示例:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 引用传递
*/
public class Demo3 {
public static void main(String[] args) {
TestObject testObject1 = new TestObject();
testObject1.setName("大屎蛋教程网");
/** 对象赋值,这里是引用传递,testObject1 将对象地址赋值给 testObject2, 他们指向同一个对象*/
TestObject testObject2 = testObject1;
System.out.println("修改前 testObject1 name " + testObject1.getName());
System.out.println("修改前 testObject2 name " + testObject2.getName());
/** 改变 testObject2 的name,testObject1的name也发生变化,因为他们指向同一个数据*/
testObject2.setName("dashidan.com");
System.out.println("修改后 testObject1 name " + testObject1.getName());
System.out.println("修改后 testObject2 name " + testObject2.getName());
}
}
/**
* 引用传递测试对象
*/
class TestObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
输出:
修改前 testObject1 name 大屎蛋教程网
修改前 testObject2 name 大屎蛋教程网
修改后 testObject1 name dashidan.com
修改后 testObject2 name dashidan.com
⑤ 对象的封装
1.隐藏实现过程
封装是把过程和数据隐藏起来,对数据的访问只能通过已定义的接口. 面向对象计算始于这个基本概念. 通过private
关键字可以将变量或者方法设置为只有本类可以访问, 其他类无法访问该数据, 这样便实现了数据的隐藏.
你有一只宠物, 宠物的名字你不希望别人随便改, 可以将宠物的名字设置为私有private
.
package com.dashidan.lesson5;
/**
* 大屎蛋教程网-dashidan.com
*
* Java教程进阶篇: 5.Java对象(4):封装
*/
public class Pet {
/** 私有属性名字*/
private String name;
}
2.公开数据访问接口
你这只宠物的名字编程私有之后, 你希望给他改个名字, 可以将改名的方法设置为公开方法. 这样就能通过统一的开放接口来设置宠物的名字, 并且可以开放一个获取名字的接口, 来获得这个私有的属性值.
示例代码:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 引用传递
*/
public class Demo3 {
public static void main(String[] args) {
TestObject testObject1 = new TestObject();
testObject1.setName("大屎蛋教程网");
/** 对象赋值,这里是引用传递,testObject1 将对象地址赋值给 testObject2, 他们指向同一个对象*/
TestObject testObject2 = testObject1;
System.out.println("修改前 testObject1 name " + testObject1.getName());
System.out.println("修改前 testObject2 name " + testObject2.getName());
/** 改变 testObject2 的name,testObject1的name也发生变化,因为他们指向同一个数据*/
testObject2.setName("dashidan.com");
System.out.println("修改后 testObject1 name " + testObject1.getName());
System.out.println("修改后 testObject2 name " + testObject2.getName());
}
}
/**
* 引用传递测试对象
*/
class TestObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
输出:
哈拆迁
⑥ 对象的继承
Java继承的本质是子类继承父类, 拥有父类的属性和方法, 是代码复用基础. 子类可以有自己的方法和属性.
1.继承方式
通过关键字extends.我们以猫和狗继承是宠物, 宠物都有走
的行为. 猫有吃鱼
的行为, 狗有吃骨头
的行为为例实现这个继承关系.
父类Pet
:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* Pet类 作为 父类
*/
public class Pet {
public void walk() {
System.out.println("Pet walk");
}
public void eat() {
System.out.println("Pet eat");
}
}
子类`Cat`:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* Cat 继承自 Pet类
*/
public class Cat extends Pet {
public void eatFish() {
System.out.println("Cat eatFish.");
}
@Override
public void eat() {
System.out.println("Cat eat");
}
}
子类`Dog`:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* Dog 继承自 Pet类
*/
public class Dog extends Pet {
public void eatBone() {
System.out.println("Dog eatBone.");
}
@Override
public void eat() {
System.out.println("Dog eat");
}
}
执行类
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
*/
public class Demo5 {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
/** 调用父类的方法*/
cat.walk();
dog.walk();
/** 调用子类的方法*/
cat.eatFish();
dog.eatBone();
}
}
输出:
Pet walk
Pet walk
Cat eatFish.
Dog eatBone.
java单根继承是指单个类只能继承一个父类, 在c++中是多根继承, 一个类可以同时继承多个父类.
3.对象静态绑定与动态绑定
绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来. java绑定分为静态绑定和动态绑定.
- 静态绑定
静态绑定也称作前期绑定. java中的方法只有final
, static
, private
和构造方法
是静态绑定
. 静态绑定发生在编译
时, 静态绑定用类
信息来完成.
- 动态绑定
动态绑定(dynamic binding)也称作后期绑定. java程序运行时根据具体对象的类型进行绑定. 动态绑定发生在运行
时. 动态绑定则需要用对象
信息来完成.
⑦ 对象的多态
1.多态的前提
当子类和父类存在同一个方法, 子类覆写了父类的方法, 程序在运行时调用父类的方法还是子类覆写的方法呢?
Java的多态解决了整个问题. 运行时根据对象类型来判断执行对应对象的方法.
多态的前提条件
1. 有继承关系.
2. 子类重写父类的方法.
3. 父类引用指向子类.
猫和狗都继承自宠物类, 宠物有吃的行为. 猫吃鱼狗吃骨头, 他们吃的行为不一样. 我们可以采用多态的方式来实现.
代码示例:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程进阶篇: 7.Java对象(6):静态绑定动态绑定与多态
*/
public class Demo6 {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
/**
* cat 和 dog 覆写了 父类的方法 eat 调用eat方法 时动态判断对象类型
* cat 对象 执行了 Cat中的 eat方法
* dog 对象 执行了 Dog中的 eat方法
*/
cat.eat();
dog.eat();
}
}
输出:
Cat eat
Dog eat
⑧ 引用当前对象与父对象
java中使用用this来引用当前对象, super引用当前对象的父对象.
1.this
使用this
获取当前对象的索引.如果省略this. 系统会默认自动添加加上this.
来指向当前对象.
以下代码中的setName
方法, 该方法中的传入的参数name
与成员变量name
重名, 这时需要用this.name
来表明引用成员变量.
代码示例:
package com.dashidan.lesson8;
/**
* 大屎蛋教程网-dashidan.com
*
* Java教程进阶篇: 8.Java对象(7):引用当前对象与父对象:this, super
* Pet类
*/
public class Pet {
/**
* 私有成员变量name
*/
private String name;
public String getName() {
/** 默认可以省略this. 系统会自动加上this.*/
return name;
}
public void setName(String name) {
/** this.name 通过this来表明引用当前对象成员变量name*/
this.name = name;
}
}
2.super
super
引用当前对象的父对象
. 需要访问父类方法或者变量的时候, 可以使用super.
来访问.示例代码:
cat
类:
在调用Cat
类的eat()
方法时, 方法体中通过super.eat()
调用了父类的eat()
方法. 故父类的eat()方法被执行, Pet eat.
输出到控制台.
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* Cat 继承自 Pet类
*/
public class Cat extends Pet {
public void eatFish() {
System.out.println("Cat eatFish.");
}
@Override
public void eat() {
System.out.println("Cat eat");
}
/**
*调用父类方法
*/
public void petEat() {
super.eat();
}
}
运行程序:
package com.dashidan.lesson8;
/**
* 大屎蛋教程网-dashidan.com
*
* Java教程进阶篇: 8.Java对象(7):引用当前对象与父对象:this, super
*/
public class Demo1 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
输出:
Pet eat.
3.this与super在构造函数中的使用
super
和this
均需放在构造方法内第一行. 否则编译不通过.
每个子类构造方法的第一条语句, 都是隐式调用super()
, 如果父类没有这种形式的构造函数, 那么在编译的时候就会报错.
this
和super
不能同时出现在一个构造函数里面.
因为this
必然会调用其它的构造函数, 其它的构造函数必然也会有super
语句的存在. 所以在同一个构造函数里面有相同的语句, 就失去了语句的意义, 编译器也不通过.
static
环境不能使用this
, super
。this和super都指的是对象, 是动态绑定, 所以都不能在static
环境中使用(静态绑定)包括: static变量, static方法, static语句块.
⑨ 抽象方法与抽象类
1.抽象方法
抽象方法是一种特殊的方法, 只有声明, 而没有具体的实现. 抽象方法必须用abstract
关键字进行修饰. 抽象方法的声明格式为:
public abstract void eat();
2.抽象类
如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract
关键字修饰.只要包含抽象方法, 必须命名为抽象类. 抽象类也可以包含普通方法.
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 抽象方法与抽象类
* AbstractPet类 作为 父类 抽象类
*/
public abstract class AbstractPet {
public abstract void sleep();
}
`Fish`类作为子类:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 抽象方法与抽象类
* Fish 类 继承自 AbstractPet类
*/
public class Fish extends AbstractPet {
/**
*实现抽象方法 sleep
*/
@Override
public void sleep() {
System.out.println("Never sleep.");
}
}
运行类
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 抽象方法与抽象类
*/
public class Demo9 {
public static void main(String[] args) {
Fish fish = new Fish();
fish.sleep();
}
}
输出:
Never sleep.
抽象类与普通类的区别: 1. 抽象方法必须为`public`或`protected`(`private`不能被子类继承,子类无法实现该方法), 缺省情况下默认为`public`. 2. 如果一个类继承于一个抽象类, 则子类必须实现父类的抽象方法. 如果子类没有实现父类的抽象方法, 则必须将子类也定义为为`abstract`类.
3.抽象类创建对象
抽象类不能用来创建对象吗?
答案是:抽象类可以被用new
的方式创建. 通过匿名内部类
方式.
附带真相的代码:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 抽象类创建对象
*/
public class Demo10 {
public static void main(String[] args) {
AbstractPet abstractPet = new AbstractPet() {
@Override
public void sleep() {
System.out.println("AbstractPet sleep");
}
};
abstractPet.sleep();
}
}
输出:
AbstractPet sleep.
虽然可以用new
的方式创建抽象类, 但不建议这么用, 这样做失去了抽象的意义.
示例代码中出现了@Override
标签.
Java中@Override标签作用
⑩ 接口
接口interface
在软件工程中, 泛指供其他人调用的方法或者函数. 从这里可以体会到Java语言设计者的初衷-对行为的抽象.
1.接口的声明
用interface
关键字声明接口.接口中可以含有变量和方法. 但是要注意, 接口中的变量会被隐式地指定为public static final
变量. 接口中的方法必须都是抽象方法
.
public interface Pet{
...
}
2.接口的实现
一个类实现特定的接口需要使用implements
关键字. 需要实现该接口中定义的方法, 如果没有全部实现, 需要将该类声明为abstract
.
public class Bird implements IPet {
...
}
示例代码: `IPet`接口:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* 接口 IPet
*/
public interface IPet {
void eat();
}
`Bird`类:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* 接口
*/
public class Bird implements IPet {
@Override
public void eat() {
System.out.println("Bird eat.");
}
}
运行类:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象的继承
* 接口
*/
public class Demo11 {
public static void main(String[] args) {
Bird bird = new Bird();
bird.eat();
}
}
输出: Bird eat.
3.抽象类和接口的区别
- 抽象类可以提供成员方法的实现细节, 而接口中只能存在
public abstract
方法. - 抽象类中的成员变量可以是各种类型, 而接口中的成员变量只能是
public static final
类型. - 接口中不能含有静态代码块以及静态方法, 而抽象类可以有静态代码块和静态方法.
- 一个类只能继承一个抽象类, 而一个类却可以实现多个接口.
- 抽象类作为很多子类的父类, 它是一种模板式设计, 而接口是一种行为规范.
- 抽象类和接口所反映的设计理念是不同的, 抽象类所代表的是
is-a
的关系, 而接口所代表的是like-a
的关系.
⑪ 对象实例类型判断
Java中的instanceof运算符是用来在运行时判断对象是否是指定类的实例.子类实例instanceof
父类, 返回ture. 因为继承关系, 子类的实例也是父类的实例.instanceof
是二目运算符, 返回一个布尔值, 表示该对象是否是指定类的实例.用法:
boolean result = object instanceof class
示例代码:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象实例类型判断
*/
public class Demo12 {
public static void main(String[] args) {
Pet pet = new Pet();
boolean isInstance = pet instanceof Pet;
System.out.println("pet instanceof Pet: " + isInstance);
Cat cat = new Cat();
/** 子类也属于父类的一个实例*/
isInstance = cat instanceof Pet;
System.out.println("cat instanceof Pet: " + isInstance);
}
}
输出: pet instanceof Pet: true cat instanceof Pet: true
⑫ 对象向上转型和向下转型
java中子类对象转为父类对象称为向上转型, 反之称为向下转型.
1.向上转型
向上转型时, 子类独有的方法会遗失, 只保留父类拥有的方法. 如果子类覆写了父类的方法则调用子类的这个方法. 向上转型不用强制转型.
2.向下转型
父类引用的对象转换为子类类型称为向下转型. 向下转型需要强制转型。示例类型与转型类型需要一致, 不一致会报错.
示例代码:
package com.dashidan.lesson12;
/**
* 大屎蛋教程网-dashidan.com
* <p>
* Java教程基础篇: 12.Java对象
* 对象向上转型和向下转型
*/
public class Demo13 {
public static void main(String[] args) {
Cat cat = new Cat();
/**
* testUpcasting 需要传入父类Pet实例,这里传入的是子类cat实例的引用
* 自动向上转型
* 由于子类Cat覆写了父类的cat()方法,执行子类的eat()方法
*/
testUpCasting(cat);
/** 父类对象的引用,指向子类实例*/
Pet pet = new Cat();
/**
* testUpcasting 需要传入父类Pet实例,这里传入的是父类Pet的引用
* 强制向下转型
* 由于子类Cat覆写了父类的cat()方法,执行子类的eat()方法
*/
testDownCasting((Cat) pet);
}
/**
* 测试向上转型
*/
public static void testUpCasting(Pet pet) {
pet.eat();
}
/**
* 测试向下转型
*/
public static void testDownCasting(Cat pet) {
pet.eat();
}
}
输出:
Cat eat.
Cat eat.