Java的类演进过程
1、从面向过程到面向对象
在大家最熟悉的C语言中,如果要定义一个复杂的数据类型就用结构体(Struct)来实现,而为结构体的每个操作都定义一个函数,这个函数与结构体本身的定义没有任何关系。程序的重心集中在函数上。这样的项目结构较为松散且混乱,而且代码的重用性和维护性较差。所以就有了面向对象的概念。面向对象强调的是对象,然后由对象去调用功能。万物皆为对象;程序是对象的集合,他们通过发送消息来告知彼此所要做的;每个对象都有自己的由其他对象所构成的存储;每个对象都拥有其类型;某一特定类型的所有对象都可以接收同样的消息。面向对象特征:封装性(encapsulation),继承(inheritance),多态性(polymorphism)。
2、类与对象
类是某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的个体。对象具有状态、行为和标识。每一个对象都可以拥有内部数据(它们给出了该对象的状态)和方法(它们产生行为),并且每一个对象都可以唯一地与其他对象区分开来,具体地说,就是每一个对象在内存中都有一个唯一的地址。
类可以将数据和函数封装在一起,其中数据表示类的属性,我们一般称为成员变量,函数表示类的行为,我们一般称为成员方法。
class Person { String name; int age; public void eat(){ System.out.println("Person can eat."); } }
类是抽象的,不能直接对类来进行操作,需要将此类具体化,所以需要将类转换为对象,同过new关键字来创建一个该类的对象。
Person per = new Person();
通过per对象来进行操作,包括类属性的赋值和函数的执行。
public class ClassDemo { public static void main(String[] args){ Person per = new Person(); per.age = 30; per.name = "Wendy"; per.eat(); } }
这就是简单的类与对象的操作。就像人的身份一样,首先你定义一个人的模型,包括姓名、年龄、吃饭等等,然后在这个模型的各种属性上赋值,这就形成了不同的个体,也就是对象,通过对这些对象进行操作以达到最终目的。
3、封装
在上面简单的例子中我们可以发现,Person类产生的对象中,可以直接操作其中的成员变量,例如:
Person per1 = new Person(); per1.age = -30;
如果外面的程序可以随意修改一个类的成员变量,会造成不可预料的程序错误,比如数据的错误、混乱或安全性问题。所以我们需要将类的成员变量进行封装,使用private关键字来说明成员变量的访问权限,只有该类的其他成员方法可以调用,其他类中的方法不能调用,这个成员变量就成为了私有成员变量。
class Person{ private String name; private int age; public void eat(){ System.out.println("ClassDemo can eat."); } }
为了实现良好的封装性,通常将类的成员变量声明为private,再通过public方法来对这个变量进行访问。对一个变量的操作,一般都有读取和赋值操作,分别定义两个方法来实现这两种操作,一个是getXxx(),用来读取这个成员变量操作,另外一个是setXxx(),用来对这个成员变量赋值。
class Person{ private String name; private int age; //设置年龄 public void setAge(int age) { this.age = age; } //读取年龄 public int getAge() { return age; } //设置姓名 public void setName(String name) { this.name = name; } //读取姓名 public String getName() { return name; } public void eat(){ System.out.println("Person can eat."); } }
我们通过将类的成员变量声明为私有的(private),再提供一个或多个公有(public)方法实现对该成员变量的访问或修改,这种方式就称为封装。实现封装可以达到以下目的。
· 隐藏类的实现细节;
· 让使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
· 便于修改,增强代码的可维护性;
· 可进行数据检查。
4、重载
现在需要完成一个求和的功能,add()。但是当项目需求不断的发生改变,我们就需要对应的提供多个求和的方法。
public class FunctionDemo { public static void main(String[] args) { System.out.println(sum(10,20)); System.out.println(sum1(10,20,30)); System.out.println(sum2(10,20,30,40)); System.out.println(sum3(10.5f,20f)); } //需求1:求两个数的和 public static int sum(int a,int b) { System.out.println("int"); return a + b; } //需求2:求三数的和 public static int sum1(int a,int b,int c) { return a + b + c; } //需求3:求四个数的和 public static int sum2(int a,int b,int c,int d) { return a + b + c + d; } //需求4:求浮点数的和 public static float sum3(float a,float b) { System.out.println("float"); return a + b; } }
但是现在他们不同方法的名字是不一样的。而我们又要求方法命名做到:见名知意。很明显,现在没有做到。那么,肿么办呢?针对这种情况:方法的功能相同,参数列表不同的情况,为了见名知意,Java允许它们起一样的名字,这叫方法重载。
方法重载:
在同一个类中,方法名相同,参数列表不同。与返回值类型无关。
参数列表不同:
A:参数个数不同
B:参数类型不同
编译器会根据实际情况挑选出正确的方法,如果编译器找不到匹配的参数或者找出多个可能的匹配就会产生编译时错误,这个过程被称为重载的解析。
public class FunctionDemo { public static void main(String[] args) { //jvm会根据不同的参数去调用不同的功能 System.out.println(sum(10,20)); System.out.println(sum(10, 20, 30)); System.out.println(sum(10, 20, 30, 40)); System.out.println(sum(10.5f, 20f)); } //需求1:求两个数的和 public static int sum(int a,int b) { System.out.println("int"); return a + b; } //需求2:求三数的和 public static int sum(int a,int b,int c) { return a + b + c; } //需求3:求四个数的和 public static int sum(int a,int b,int c,int d) { return a + b + c + d; } //需求4:求浮点数的和 public static float sum(float a,float b) { System.out.println("float"); return a + b; } }
而方法重载我们会在一般方法上使用,但是更多的是使用在构造方法上。这里就不解释什么叫构造方法了,可以另行学习。当创建一个对象时,JVM会去调用对应类的构造方法,根据构造方法来为类的成员变量进行数据初始化赋值。默认的构造方法是无参构造,但是我们定义了多个构造方法,而且这些构造方法具有不同个数或不同类型的参数,编译器就会根据括号中传递的参数来选择相应的构造方法。
class Person{ private String name; private int age; //无参构造方法 public Person() { System.out.println("调用无参构造方法"); } //参数为name的构造方法 public Person(String name){ System.out.println("调用参数为name的构造方法"); this.name = name; } //参数为age的构造方法 public Person(int age){ System.out.println("调用参数为age的构造方法"); this.age = age; } //参数为name,age的构造方法 public Person(String name, int age) { System.out.println("调用参数为name,age的构造方法"); this.name = name; this.age = age; } //设置年龄 public void setAge(int age) { this.age = age; } //读取年龄 public int getAge() { return age; } //设置姓名 public void setName(String name) { this.name = name; } //读取姓名 public String getName() { return name; } } public class ClassDemo { public static void main(String[] args){ Person per1 = new Person(); System.out.println("Name:"+per1.getName()+"\tAge:"+per1.getAge()); Person per2 = new Person("Wendy"); System.out.println("Name:"+per2.getName()+"\tAge:"+per2.getAge()); Person per3 = new Person(25); System.out.println("Name:"+per3.getName()+"\tAge:"+per3.getAge()); Person per4 = new Person("Ann",30); System.out.println("Name:"+per4.getName()+"\tAge:"+per4.getAge()); } }
输出结果:
调用无参构造方法
Name:null Age:0
调用参数为name的构造方法
Name:Wendy Age:0
调用参数为age的构造方法
Name:null Age:25
调用参数为name,age的构造方法
Name:Ann Age:30
5、继承
首先我们按照需求编写两个类,Student类和Teacher类。
class Student{ private String name; private int age; Student() {} Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void eat(){ System.out.println("Person can eat."); } } class Teacher{ private String name; private int age; Teacher() {} Teacher(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void eat(){ System.out.println("Person can eat."); } }
通过观察上面代码可以发现name,age成员变量,以及getXxx()/setXxx(),eat()等都是相同的。如果我们后来继续定义类,举例:工人类,军人类。他们是不是也具备这些内容。那么,我们每一次定义这样的类的时候,都要把这些重复的内容都重新定义一遍。现在考虑能不能把这些相同的内容给定义到一个独立的类中。然后,让这多个类和这个独立的类产生一个关系,有了这个关系后,这多个类就可以具备这个独立的类的功能。为了实现这个效果,java就提供了一个技术:继承。使用extends关键字,class 子类名 extends 父类名 {} 。
继承的好处:
A:提高了代码的复用性,多个类相同的成员可以放到同一个类中。
B:提高了代码的维护性,如果功能的代码需要修改,修改一处即可。
C:让类与类之间产生了关系,是多态的前提。其实这也是继承的一个弊端:类的耦合性很强。
上面的代码使用继承改进为:
class Person{ private String name; private int age; Person() {} Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void eat(){ System.out.println("Person can eat."); } } class Student extends Person{ Student() {} } class Teacher extends Person{ Teacher() {} }
这样在Student和Teacher类中也可以使用Person的成员变量、构造方法和成员方法。以后如果需要添加与Person类差不多属性和函数的类的话,直接继承Person就可以了,从而减少代码重复率。
操作以上三个类:
public class ExtendsDemo { public static void main(String[] args) { Student stu = new Student(); stu.setName("Wendy"); stu.setAge(28); System.out.println("Name:" + stu.getName() + "\tAge:" + stu.getAge()); stu.eat(); Teacher teacher = new Teacher(); teacher.setName("Yan"); teacher.setAge(45); System.out.println("Name:"+teacher.getName()+"\tAge:"+teacher.getAge()); teacher.eat(); } }
输出结果:
Name:Wendy Age:28
Person can eat.
Name:Yan Age:45
Person can eat.
6、重写,复写
从上面的例子我们可以看出Student类和Teacher类都使用Person类的eat方法,但是如果我想在Student类打印的是“Student can eat”,Teacher类打印的是“Teacher can eat”呢?这就需要用到java提供的方法重写功能。
类中出现了和父类中一模一样的方法声明,也被称为方法覆盖,方法复写。
A:如果方法名不同,就调用对应的方法。
B:如果方法名相同,最终使用的是子类自己的。
方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样既沿袭了父类的功能,又定义了子类特有的内容。
class Student extends Person { Student() { } public void eat() { System.out.println("Student can eat."); } } class Teacher extends Person { Teacher() { } public void eat() { System.out.println("Teacher can eat."); } }
测试类:
public class ExtendsDemo { public static void main(String[] args) { Student stu = new Student(); stu.setName("Wendy"); stu.setAge(28); System.out.println("Name:" + stu.getName() + "\tAge:" + stu.getAge()); stu.eat(); Teacher teacher = new Teacher(); teacher.setName("Yan"); teacher.setAge(45); System.out.println("Name:"+teacher.getName()+"\tAge:"+teacher.getAge()); teacher.eat(); } }
输出结果:
Name:Wendy Age:28
Student can eat.
Name:Yan Age:45
Teacher can eat.
可以看出最终打印的是子类重写的方法。
注意:重载和重写的区别。
A:方法重写(Override):
在子类中,出现和父类中一模一样的方法声明的现象。
B:方法重载(Overload):
同一个类中,出现的方法名相同,参数列表不同的现象。
方法重载能改变返回值类型,因为它和返回值类型无关。
7、多态
1)多态表示某一个事物,在不同时刻表现出来的不同状态。
2)举例:
猫可以是猫的类型。猫 m = new 猫();
同时猫也是动物的一种,也可以把猫称为动物。动物 d = new 猫();
3)多态前提和体现:
A:要有继承关系;
B:要有方法重写,没有也可以,但是没有重写则没有继承的意义;
C:有父类引用指向子类对象 父类 对象名称 = new 子类();
4)多态中的成员访问特点:
A:成员变量
编译看左边,运行看左边。
B:构造方法
创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
C:成员方法
编译看左边,运行看右边。
D:静态方法
编译看左边,运行看左边。
(静态和类相关,算不上重写,所以,访问还是左边的)
由于成员方法存在方法重写,所以它运行看右边。
class Fu { public int num = 100; //成员方法 public void show() { System.out.println("show Fu"); } //成员静态方法 public static void function() { System.out.println("function Fu"); } } class Zi extends Fu { public int num = 1000; public int num2 = 200; //成员方法 public void show() { System.out.println("show Zi"); } //子类特有的方法 public void method() { System.out.println("method zi"); } //成员静态方法 public static void function() { System.out.println("function Zi"); } } public class DuoTaiDemo { public static void main(String[] args) { //要有父类引用指向子类对象。 //父类 f = new 子类(); Fu f = new Zi(); System.out.println(f.num); //100 //找不到符号 //System.out.println(f.num2); f.show(); //show zi //找不到符号 //f.method(); f.function(); //functin Fu } }
输出结果:
100
show Zi
function Fu
5)多态的好处:
A:提高了代码的维护性(继承保证);
B:提高了代码的扩展性(多态保证);
6)多态的弊端:
A:不能使用子类的特有功能,解决方法:父类 f = new 子类(); 子类 z = (子类)f; 向下转型。
7)对象间的转型问题:
A:向上转型:
Fu f = new Zi();
B:向下转型:
Zi z = (Zi)f;//要求该f必须是能够转换为Zi的,使用instanceof操作符判断某一个类是否实现了某个接口,或者是用来判断一个实例对象是否属于一个类。用法:对象 instanceof 类(或接口)。
public class DuoTaiDemo { public static void main(String[] args) { //父类 f = new 子类(); Fu f = new Zi(); System.out.println(f.num); //100 f.show(); //show zi f.function(); //functin Fu Zi zi = (Zi)f; zi.method(); //method zi } }
8、抽象类
类是抽象的,比如定义的Person类,它相当于一个模型,而继承于Person类的Student类和Teacher类才是真正的实现类,所以Person类中的eat方法不应该是具体的,具体的操作应该由继承于它的子类来实现。我们把一个不是具体的功能称为抽象的功能,而一个类中如果有抽象的功能,该类必须是抽象类。
抽象类的特点:
1)抽象类和抽象方法必须用abstract关键字修饰
2)抽象类中不一定有抽象方法,但是有抽象方法的类必须定义为抽象类
3)抽象类不能实例化,因为它不是具体的。
4)抽象的子类
a:如果不想重写抽象方法,该子类是一个抽象类。
b:重写所有的抽象方法,这个时候子类是一个具体的类。
5)抽象类的实例化其实是靠具体的子类实现的。是多态的方式。
Person per = new Student();
6)抽象类的成员特点:
成员变量:既可以是变量,也可以是常量。
构造方法:有。用于子类访问父类数据的初始化。
成员方法:既可以是抽象的,也可以是非抽象的。
抽象类的成员方法特性:
A:抽象方法 强制要求子类做的事情。
B:非抽象方法 子类继承的事情,提高代码复用性。
abstract class Person { private String name; private int age; protected Person() {} Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public abstract void eat(); public abstract void sleep(); } class Student extends Person { public Student(String name,int age) { super(name,age); } public void eat() { System.out.println("Student can eat."); } public void sleep(){ System.out.println("Student can sleep."); } } class Teacher extends Person { public Teacher(String name,int age) { super(name,age); } public void eat() { System.out.println("Teacher can eat."); } public void sleep(){ System.out.println("Teacher can sleep."); } }
测试类:
public class AbstractDemo { public static void main(String[] args){ Person per1 = new Student("Jone",16); per1.eat(); per1.sleep(); System.out.println("Name:"+per1.getName()+"\tAge:"+per1.getAge()); Person per2 = new Teacher("Sam",60); per2.eat(); per2.sleep(); System.out.println("Name:"+per2.getName()+"\tAge:"+per2.getAge()); } }
输出结果:
Student can eat.
Student can sleep.
Name:Jone Age:16
Teacher can eat.
Teacher can sleep.
Name:Sam Age:60
9、接口
如果一个抽象类中的所有方法都是抽象的,就可以将这个类用另外一种方式来定义,也就是接口定义。接口是抽象方法和常量值的定义的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义而没有变量和方法的实现。
接口的特点:
1)接口用关键字interface表示;
格式:interface 接口名 {}
2)类实现接口用implements表示
格式:class 类名 implements 接口名 {}
3)接口不能实例化,按照多态的方式来实例化,由具体的子类实例化。其实这也是多态的一种,接口多态。
4)接口的子类:
· 抽象类实现接口,如果是抽象类则可以不重写接口的抽象方法,但是没有意义;
· 具体类实现接口,需要重写接口中的所有抽象方法(推荐方案)。
5)多态的3种方式:
· 具体类多态(实际应用中几乎不用);
· 抽象类多态(常用);
· 接口多态(最常用)。
6)接口的成员特点:
· 成员变量:只能是常量,并且是静态的,默认修饰符 public static final;
· 构造方法:没有,因为接口主要是扩展功能的,而没有具体存在;
· 成员方法:只能是抽象方法, 默认修饰符 public abstract,建议自己手动给出。
//定义动物培训接口 interface AnimalTrain { public abstract void jump(); } //抽象类实现接口 abstract class Dog implements AnimalTrain { } //具体类实现接口 class Cat implements AnimalTrain { public void jump() { System.out.println("猫可以跳高了"); } } class InterfaceDemo { public static void main(String[] args) { //AnimalTrain是抽象的; 无法实例化 //AnimalTrain at = new AnimalTrain(); //at.jump(); AnimalTrain at = new Cat(); at.jump(); //猫可以跳高了 } }
10、类与类,类与接口以及接口与接口的关系
1)类与类
继承关系,只能单继承,但是可以多层继承;
2)类与接口
实现关系,可以单实现,也可以多实现,class son implements Father,Mother{},还可以在继承一个类的同时实现多个接口 class son extends object implements Father,Mother{};
3)接口与接口
继承关系,可以单继承,也可以多继承。
抽象类和接口的区别
1)成员区别:
A:抽象类
成员变量:可以是常量,也可以是变量
构造方法:有
成员方法: 可以是抽象,也可以是非抽象
B:接口
成员变量:只可以是常量
成员方法:只可以是抽象
2)关系区别: A:类与类:继承,单继承
B:类与接口:实现,单实现,多实现
C:接口与接口:继承,单继承,多继承
3)设计理念区别
A:抽象类:被继承体现的是:”is a”的关系。抽象类中定义的是该继承体系的共性功能。
B:接口:被实现体现的是:”like a”的关系。接口中定义的是该继承体系的扩展功能。
//定义抽烟接口 interface Smoking { //抽烟的抽象方法 public abstract void smoke(); } //定义抽象人类 abstract class Person { //姓名 private String name; //年龄 private int age; public Person() {} public Person(String name,int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //吃饭(); public abstract void eat(); //睡觉(){} public void sleep() { System.out.println("睡觉觉了"); } } //具体老师类 class Teacher extends Person { public Teacher() {} public Teacher(String name,int age) { super(name,age); } public void eat() { System.out.println("吃大白菜"); } } //具体学生类 class Student extends Person { public Student() {} public Student(String name,int age) { super(name,age); } public void eat() { System.out.println("吃红烧肉"); } } //抽烟的老师 class SmokingTeacher extends Teacher implements Smoking { public SmokingTeacher() {} public SmokingTeacher(String name,int age) { super(name,age); } public void smoke() { System.out.println("抽烟的老师"); } } //抽烟的学生 class SmokingStudent extends Student implements Smoking { public SmokingStudent() {} public SmokingStudent(String name,int age) { super(name,age); } public void smoke() { System.out.println("抽烟的学生"); } } public class InterfaceDemo { public static void main(String[] args) { //测试学生 SmokingStudent ss = new SmokingStudent(); ss.setName("杨小红"); ss.setAge(27); System.out.println(ss.getName()+"---"+ss.getAge()); ss.eat(); ss.sleep(); ss.smoke(); System.out.println("-------------------"); SmokingStudent ss2 = new SmokingStudent("陈小明",30); System.out.println(ss2.getName()+"---"+ss2.getAge()); ss2.eat(); ss2.sleep(); ss2.smoke(); //测试老师 SmokingTeacher st = new SmokingTeacher("陈老师",36); System.out.println(st.getName()+"---"+st.getAge()); st.eat(); st.sleep(); st.smoke(); } }
输出结果:
杨小红---27
吃红烧肉
睡觉觉了
抽烟的学生
-------------------
陈小明---30
吃红烧肉
睡觉觉了
抽烟的学生
陈老师---36
吃大白菜
睡觉觉了
抽烟的老师