类与对象
面向过程与面向对象:
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向对象(OOP)的三大特征:
l 封装——把数据隐藏在对象里面,不让外部对其随意操作。
l 继承——扩展类的功能。
l 多态——方法的重载、对象的多态性
类:
类是组成Java程序的基本元素,它封装了一系列的变量(即数据成员,也称为“域(field)”)和方法(即成员方法 method),是一类对象的原型。创建一个新的类,就是创建一个新的数据类型。实例化一个类,就得到一个对象。因此,对象就是一组变量和相关方法的集合,其中变量表明对象的状态、属性,方法表明对象所具有的行为。
类的定义:
[修饰符]class 类名 [extends 父类名] [implements 接口名表] { // 类体 成员变量声明 方法成员声明 } |
例、定义一个简单的Person类:
public class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } |
注意:该类中有两个属性name、age和一个方法tell().类以及定义出来了,但只有类是不能直接使用的,要实例化一个对象才可以使用.
例、实例化一个对象:
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo01{ public static void main(String args[]){ Person per = new Person() ; // 产生实例化对象per } } |
对象产生之后,就可以调用类中的一系列操作了。
例、为Person对象中的name和age赋值,并调用tell()方法
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo02{ public static void main(String args[]){ Person per = new Person() ; //*产生实例化对象;如果对象只声明的话,则无法 *直接使用,必须实例化后才可以正确使用。若该语句改为 *Person per则将出现空指向异常(NullPointerException)*/ per.name = "张三" ; // 为名字赋值 per.age = 30 ; // 为年龄赋值 per.tell() ; // 调用方法 } } |
一个类可以产生多个对象。
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo03{ public static void main(String args[]){ Person per1 = null ; // 声明对象 Person per2 = null ; // 声明对象 per1 = new Person() ; // 实例化对象 per2 = new Person(); per1.name = "张三" ; // 为名字赋值 per1.age = 30 ; // 为年龄赋值 per2.name="李四"; per2.age = 33 ; per1.tell() ; // 调用方法 per2.tell() ; } } |
其内存划分如下图-1:
图-1
因为声明了两个对象,所以在栈内存空间中开辟了两个空间,保存二个对象,之后些两个对象分别实例化,只要一出现关键字new就表示开辟新的内存空间。那么这二个对象之间不会互相影响
将上例改为:
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo04{ public static void main(String args[]){ Person per1 = null ; // 声明对象 Person per2 = null ; // 声明对象 per1 = new Person() ; // 实例化对象 per2 = per1 ; // 引用传递 per1.name = "张三" ; // 为名字赋值 per1.age = 30 ; // 为年龄赋值 per2.name="李四"; per2.age = 33 ; per1.tell() ; // 输出:姓名:李四,年龄:33 per2.tell() ; //同样输出:姓名:李四,年龄:33 } } |
其过程如下图-2:
图-2
图-3
构造方法:
在每个类中都存在一个构造方法,构造方法的主要目的是为类中的属性初始化。如果在一个类中没有明确声明一个构造方法的话,则自动生成一个无参的,什么也不做的构造方法。
构造方法的名称必须与类名一致
构造方法定义时没有返回类型说明
不能在构造方法使用return语句
构造方法本身是可以进行重载操作的,重载原则与普通方法一样
如果一个类中已声明了一个构造方法,则不会生成无参什么都不做的构造方法。
例:
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 Person(String name,int age) //声明构造方法 {this.name=name; this.age=age; } public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo05{ public static void main(String args[]){ Person per1 = new Person("张三",30); Person per2 = new Person("李四",33); per1.tell() ; // 调用方法 per2.tell() ; } } |
类的方法:
方法是一段可重复调用的代码,其定义格式:
[修饰符] [static] 返回值类型 方法名(类型 参数1,类型 参数2……)
{方法体
}
方法名的命名规范:第一个单词的首字母小写,之后每个单词的首字母大写。
方法的重载:方法名相同,参数类型或个数不同。例:
public class Demo06 {public static int add(int a,int b) {return a+b; } public static int add(int a,int b,int c) {return a+b+c; } public static double add(double a,double b) {return a+b; } public static void main(String[] args) {int a=1,b=2,c=3; double d=1.5,e=2.5; System.out.println(add(a,b)); //输出3 System.out.println(add(a,b,c)); //输出6 System.out.println(add(d,e)); //输出4.0 } } |
使用重载时要注意以下情况:
public class Demo07 {public static int add(int a,int b) {return a+b; } public static float add(int a,int b) {return a+b; } public static void main(String[] args) { } } 运行后将出现以下错误: 该操作不是方法的重载,因为重载的时候,看的不是方法的返回类型,而是参数的类型或个数。 |
在方法的使用中,可以用return来结束一个方法的操作。
输出0~9的数字 public class Demo08 { public static void f(int begin, int end) { if(begin>end) return; //结束该方法 System.out.println(begin); f(begin+1, end); } public static void main(String[] args) { f(0,9); } } |
方法的递归(方法自己调用自己)
有5个人坐在一起, 问第5 个人几岁,他说比第4个人大2岁。问第4个人岁数,他说比第3 个人大2岁。问第3个人,又说比第2个人大2岁问第2个人,说比第1个人大2岁。最后再问第 1个人,他说是10岁请问第5个人几岁? public class Demo09 {static int age(int n) { int c; if(n==1) c=10; else c = age(n-1)+2; return c ; } public static void main(String[] args) { System.out.println(age(5)); } } |
类的封装:
“封装”是面向对象思想的第一大要素,封装性是类的重要特性,如果没有能体现"封装性”的类,那么面向对象的另外两大要素”实现类的继承”以及”类的多态”将成为无源之水、无本之木。
public 声明的数据成员和成员函数可从类外部的任何地方访问。
而private 数据将被隐藏,在类外不可见,这就实现了数据封装的思想。
要从类外操纵private 成员,只能通过类的public或protected成员来实现。
看以下例子。
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo10{ public static void main(String args[]){ Person per = new Person() ; // 实例化对象 per.name = "张三" ; // 为name属性赋值 per.age = 30; per.tell() ; } } |
因为现在类中的所有操作都是对外部可见的,可以直接访问。可通过封装性来解决此问题,在JAVA中封装有很多的体现,但最简单的体现就是加入“private”关键字。上例改为:
class Person{ private String name ; // 表示人的姓名 private int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class Demo11{ public static void main(String args[]){ Person per = new Person() ; // 实例化对象 per.name = "张三" ; // 为name属性赋值 per.age = 30; per.tell() ; } } 运行后 |
name和age属性石使用private关键字声明的,所以无法在外部直接访问。证明现在的属性是安全的,可以直接保护了。但是现在的代码是安全了,知识安全过头了,都无法进行操作了。那该怎么办呢?被封装的属性可通过setter和getter方法设置和取得。如下例:
class Person{ private String name ; // 表示人的姓名 private int age ; // 表示人的年龄 public void tell(){ // 定义说话的方法 System.out.println("姓名:" + this.getName() + ",年龄:" + this.getAge()) ; } public void setName(String n){ name = n ; } public void setAge(int a){ if(a>=0&&a<=200) age = a ; } public String getName() {return name ; } public int getAge() {return age ;} } public class Demo12{ public static void main(String args[]){ Person per = new Person() ; // 实例化对象 per.setName("张三") ; // 为name属性赋值 per.setAge(-30); per.tell() ; } } |
类的继承:
通过继承可以简化类的定义,扩展类的功能。
实现继承的方式:class 子类名称 extends 父类{}
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 Person(String name,int age) {this.name=name; this.age=age; } public void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age); } } class Student extends Person {String id; //学号 Student(String id,String name,int age) {super(name,age); this.id=id; } void printId() {System.out.println("学号:"+id); } } public class Demo13{ public static void main(String args[]){ Student s=new Student("001","张三",20); s.printId(); s.tell(); } } |
JAVA只支持单继承,不允许多继承,虽然一个父类可以有多个子类,但一个子类只能有一个父类。就如,一个父亲可以有多个孩子,但一个孩子只能有一个亲身父亲。JAVA虽然不允许多继承,但可以多层继承,即:父类—子类—孙类。
继承的规定:子类只继承父类所有的公有成员和公有方法,无法继承私有的属性或方法。
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 Person(String name,int age) {this.name=name; this.age=age; } private void tell(){ // 定义说话的方法 System.out.println("姓名:" + name + ",年龄:" + age); } } class Student extends Person {String id; //学号 Student(String id,String name,int age) {super(name,age); this.id=id; } void printId() {System.out.println("学号:"+id); } } public class Demo14{ public static void main(String args[]){ Student s=new Student("001","张三",20); s.printId(); s.tell(); } }运行如下: |
子类对象在实例化时,默认调用父类中无参构造方法。
class Person{ String name ; // 表示人的姓名 int age ; // 表示人的年龄 Person() {System.out.println("***父类***");} public void tell() // 定义说话的方法 {System.out.println("姓名:" + name + ",年龄:" + age); } } class Student extends Person {String id; //学号 Student() { System.out.println("***子类***"); } void printId() {System.out.println("学号:"+id); } } public class Demo15{ public static void main(String args[]) {new Student();} } 运行后输出: ***父类*** ***子类*** |
方法的覆写:
方法名称与参数要完全一样,在覆写之后子类调用的方法永远是覆写之后的方法。覆写时子类方法的权限不能比父类拥有更严格的访问权限,覆写之后的权限最好与父类中的保持一致。
class Person{ public void tell() {System.out.println("***覆写前***");} } class Student extends Person { public void tell() {System.out.println("***覆写后***");} } public class Demo16{ public static void main(String args[]) {Student s=new Student(); s.tell();} } //运行后输出:***覆写后*** |
多态性:
多态从字面去解释就是多种形态...
具体到语言来说,
就是一个父类派生出来的多个子类,
这些子类都继承了父类的共性,
但各自拥有自己的特性,
也就是说子类是父类的多种形态(多态)你可以通过实例化某个子类
来将对象传给父类,
让父类(共性)去完成你所实例化的字类的属性或方法(特性)。
方法的重载与覆写实际上就是属于多态的一种体现。
比如,一个父类:车;有一些子类:奔驰、宝马、欧迪……。定义一个方法fun指明车的品牌,所有子类也都有这个方法fun。
车 a=new 奔驰();
车 b=new 宝马();
车 c=new 欧迪();
a. fun()则可得到奔驰,b.fun()则可得到宝马,c.fun()则可得到欧迪。
对象的多态性主要指的是,子类与父类对象的相互转换关系。
向上转型:父类名 父类对象 =子类实例
向下转型:子类名 子类对象 =(子类名)父类实例
class Car {public void fun1() {System.out.println("***汽车品牌***");} public void fun2() {System.out.println("***桥车型***");} } class Benchi extends Car {public void fun1() //将父类的方法覆写 {System.out.println("***奔驰***");} public void fun3() //此方法为子类自己定义的,父类中不存在 {System.out.println("车牌:粤B11223");} } public class Demo17 {public static void main(String[] args) {Car a=new Benchi(); /*发生向上转型:子类——>父类, *父类中有的方法才可调用。如a.fun3()则会报错,父类中无fun3()方法*/ a.fun1(); //输出:***奔驰*** a.fun2(); //输出:***桥车型*** } } |
如果想调用fun3()的方法则应发生向下转型的关系
class Car {public void fun1() {System.out.println("汽车品牌");} public void fun2() {System.out.println("桥车型");} } class Benchi extends Car {public void fun1() //将父类的方法覆写 {System.out.println("***奔驰***");} public void fun3() //此方法为子类自己定义的,父类中不存在 {System.out.println("车牌:粤B11223");} } public class Demo18 {public static void main(String[] args) {Car a=new Benchi(); /*发生向上转型:子类——>父类,此句若改为: *Car a=new Benchi()则会出现以下异常: *Exception in thread "main" java.lang.ClassCastException: *A cannot be cast to B*/ Benchi b=(Benchi)a; //发生向下转型(强制) b.fun1(); //输出:***奔驰*** b.fun2(); //输出:***桥车型*** b.fun3(); //输出:车牌:粤B11223 } } |
设计一个方法,可以接收Car类的所有子类的实例。
class Car {public void fun1() {System.out.println("汽车品牌");} public void fun2() {System.out.println("桥车型");} } class Benchi extends Car {public void fun1() //将父类的方法覆写 {System.out.println("***奔驰***");} public void fun3() //此方法为子类自己定义的,父类中不存在 {System.out.println("车牌:粤B11223");} } class Baoma extends Car {public void fun1() {System.out.println("***宝马***");} public void fun4() {System.out.println("车牌:粤B44556");} } public class Demo19 {public static void main(String[] args) {fun(new Benchi()); fun(new Baoma()); } public static void fun(Benchi bc) { bc.fun1(); bc.fun3(); } public static void fun(Baoma bm) { bm.fun1(); bm.fun4(); } } |
以上方式是通过方法的重载完成的,存在以下缺点。若Car类有10000个子类则须重载10000次。而且在每次增加子类时都须修改代码本身。所以此时用对象的多态性就可以很好的解决此类问题。因为所有的对象都会发生自动的向上转型。
class Car {public void fun1() {System.out.println("汽车品牌");} public void fun2() {System.out.println("桥车型");} } class Benchi extends Car {public void fun1() //将父类的方法覆写 {System.out.println("***奔驰***");} public void fun3() //此方法为子类自己定义的,父类中不存在 {System.out.println("车牌:粤B11223");} } class Baoma extends Car {public void fun1() {System.out.println("***宝马***");} public void fun4() {System.out.println("车牌:粤B44556");} } public class Demo20 {public static void main(String[] args) {fun(new Benchi()); fun(new Baoma()); } public static void fun(Car c) {c.fun1();} } |
但以上操作依然 存在一些问题,无法调用子类中自己定义的方法(父类不存在的方法)。且各个子类的方法又不尽相同,若要调用谋个子类的方法,则先判断所属那个子类。JAVA中提供了instanceof关键字完成这样的功能。
格式:对象 instanceof 类 // 返回boolean类型的数据,true或false。
class Car {public void fun1() {System.out.println("汽车品牌");} public void fun2() {System.out.println("桥车型");} } class Benchi extends Car {public void fun1() //将父类的方法覆写 {System.out.println("***奔驰***");} public void fun3() //此方法为子类自己定义的,父类中不存在 {System.out.println("车牌:粤B11223");} } class Baoma extends Car {public void fun1() {System.out.println("***宝马***");} public void fun4() {System.out.println("车牌:粤B44556");} } public class Demo21 {public static void main(String[] args) {fun(new Benchi()); fun(new Baoma()); } public static void fun(Car c) {c.fun1(); if(c instanceof Benchi) {Benchi bc=(Benchi)c; bc.fun3();} if(c instanceof Baoma) {Baoma bm=(Baoma)c; bm.fun4();} } } 运行后输出: ***奔驰*** 车牌:粤B11223 ***宝马*** 车牌:粤B44556 |