面向对象思想
面向对象的简称OOP(Object Oriented Programming) ,也称之为面向对象思想
Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。
它是一种编程思维,也是一种思考问题的方式
其中基本思想是使用面向对象中的三大特征继承、封装、多态进行程序设计
那么如何建立面向对象的思维?
1.先整体,在局部
先想好一个大概,然后在局部的实现
2.先抽象,再具体
先想象怎么做,然后去实现它
3.能做什么,再怎么做
知道它能做什么,然后再用它做什么,这就是我们面向对象的思维
现在想象一下,你现在提出要买辆车,接下来会发生什么?
1.先在家庭内部讨论要不要买车?(抽象)
2.买什么价位的车(具体)
3.买什么类型的车(具体)
4.钱从哪里拿?(具体)
5.签订合同,开始还贷款,怎么还?(具体)
6.什么时候提车?谁去开回来?(具体)
7.你高高兴兴地回家(具体)
在上面的每一个具体步骤中我们都是参与者,并且需要面对具体的每一个步骤和过程,这就是面向过程最直接的体现。这就是一个先整体在局部的一个过程,面向对象的思维也和我们实际生活有关
类和对象
面向对象编程是将现实生活中的概念通过程序进行模拟,其中有两个最重要的概念:对象,类。
所有现实世界存在的具体事物都是对象,如一个人,一台计算机,一个房间等。
对象具有属性和行为,例如,人的属性包括年龄,体重等,行为包括吃饭,走路,说话等
对象是指某个具体事物,而同一类事物有其共性,前面说到的属性和行为就是其共性。例如所有人的身高,体重等基本特征,也都具有吃,喝,玩,乐等这些共性。更为好地描述一类事物,我们对这些共性进行了归纳,就形成了类,这个过程我们称为"抽象"。
类的概念:
类是对具有相同特征或属性,具有相同行为能力的一类事物的描述或称呼
对象的概念:
对象是这一类事物带有属性值,具有具体行为的个体或实例
用一句话概括类与对象的关系
类是对象的抽象 对象是类的具体
我们对类和对象已经有了一些模糊的概念,但这和编程有什么关系呢?与以前的编程方式有什么不同呢?以前的编程方式是面向过程,每个具体功能都需要我们去进行具体的实现,而面向对象的思路则不同,我们需要完成某个功能,只需要向对象发送一个“命令”而具体怎么完成功能,是由该对象所属的类去定义的。
类的定义
面向对象编程的基本元素是类,类确定了对象的形式和行为,类是一个模板,用于创建同一类型的对象,那代码中是如何表示类呢?也就是类是如何声明的
类的声明:
在Java中定义一个类使用关键字class,一个合法的标识符和一对表示程序体的大括号,实例如下:
class <classname>{ //class关键字 classname表示类的名称,类名是一个名词,采用大小写混合的方式
//括号里包含属性和方法的声明
}
类的声明:
下面以人类为例,看看如何写一个"人"的class,代码如下
public class Person{ //声明了一个Person类
}
这就是一个完整的类的声明,当然Person类并没有定义属性和方法,它是"面目全非"的,既没有身高,性别,年龄等属性,也没有吃,喝,玩,乐等行为,当然目前也不能做任何是。确切的说,其实"类"不具备做事的功能,事情有类来定义,但是事情是由对象来做的就好像我们听到的"张三去跳舞了",但是没有人说"人类去跳舞了",这就是类和对象的区别。类是虚拟的,对象才是实实在在的,但对象是由类这个模板产生的。
那么如何给类添加对应的属性和行为。
类的属性:
下面来看看如何用变量来表示这些"属性",我们对"人类"进行完善,添加一些属性
Publicclass Person{
String name; //姓名
int age; //年龄
String sex //行为
String address; //地址
}
这样我们就已经学会了如何在class中声明属性
类的行为:
学完了属性,我们知道了如何反映物体的特性,现在想让对象来帮我们做一下具体的事情,以人类为例,想想人都能做些什么是事情呢?人都会吃饭,睡觉,行走.....显然这些是人都会做的,现在来学习如何声明这些"行为","行为"是完成某个具体的动作,我们用方法表示行为,如一下代码
Publicclass Person{
String name; //姓名
int age; //年龄
String sex //行为
String address; //地址
void eat(){ //方法:吃饭
System.out.prinltn("我会吃饭");
}
void run(){ //方法:跑步
System.out.prinltn("我会跑步");
}
}
声明"行为"其实就是在类中声明方法。现在一个带有属性和行为的具体类就被声明出来了,属性用变量来表示,行为用方法来表示
人类{
姓名; //属性
年龄; //属性
性别; //属性
地址; //属性
跑步{....} //方法
吃饭{....} //方法
}
要注意,到此为止,虽然类已经比较完善了,但自始至终并未产生一个能做事的对象来,我们只是把所有人的公共特点、行为通过类的形式归纳抽象出来。那么如何使用这个类,该如何通过这个类,产生一个具有姓名、年龄等属性,可以吃饭跑步的对象呢?
对象的创建
类的声明是对某一类事物的描述,是一种抽象概念,要想使用类就需要生成该类的对象,下面我们来学习如何创造类的对象。
使用关键字new创建对象:
创造对象可以用new关键字,语法如下:
Person p ;
p = new Person();
这句简单的代码就是使用关键字new来创建一个类的对象,对象的"名字"就叫做"p",是对该对象的一个引用,也就是在学习数据类型的时候提到的引入数据类型。注意new关键字的作用,new世界和三个是再分配内存空间,用于存放p这个对象
第一句代码实际上只是表示“我们要生一个 baby,名字已经起好了,叫 p”,第二句才是
真正的 baby 降生(内存分配)。那要是这个 baby 正在出生的时候起名字呢?我们通常会来
取下面这种声明方式:
Person p = new Person();
与我们前面学习的基本变量声明类似,只不过是声明的变量类型是自定义的类型而
西且需要采取 new 关键字,对比一下
Person p = new Person();
int i = 0;
通过对象访问属性和行为:
创建对象后,该如何使用它?
Publicclass Person{
String name; //姓名
int age; //年龄
String sex //行为
String address; //地址
void eat(){ //方法:吃饭
System.out.prinltn("我会吃饭");
}
void run(){ //方法:跑步
System.out.prinltn("我会跑步");
}
/*main方法*/
public static void main(String[]args){
//定义对象Person对象p
Person p ;
p = new Person();
//给p这个人起名字,定义年龄等属性值
p.name = "张三";
p.age = 18;
p.sex ="女";
p.address = "武汉徐东";
//调用对象p的属性及方法
System.out.println(p.name+"说:");
System.out.println("我叫"+p.name);
System.out.println("性别: " +p.sex);
System.out.println("我今年"+p.age+"岁");
System.out.println("我住在"+ p.address);
p.eat(); //调用方法
p.run();
}
}
运行的结果如下图:
其实上面的属性和方法是在main()方法内才被调用的,我们可以更灵活一点,从而完成更强大的功能,比如以下代码
public class Person {
String name; // 姓名
int age; // 年龄
String sex; // 行为
String address; // 地址
void eat() { // 方法:吃饭
System.out.println("我会吃饭");
}
void run() { // 方法:跑步
System.out.println("我会跑步");
}
// 自我介绍
void introduce() {
System.out.println(name + "说:");
System.out.println("我叫" + name);
System.out.println("性别: " + sex);
System.out.println("我今年" + age + "岁");
if (age <= 0) {
address = "北京,有事你等我";
} else if (age < 100) {
address = "武汉,有事你call我";
} else {
address = "上海,有事你来找我";
}
System.out.println("我在" + address);
eat();
run();
}
/* main方法 */
public static void main(String[] args) {
// 定义Person对象p
Person p = new Person();
// 给p这个人起个名字,定义年龄等属性值
p.name = "张三";
p.age = 18;
p.sex = "女";
// 让p做自我介绍
p.introduce();
}
}
在introduce()方法中,使用了name、address等属性,注意在该方法内是直接使用,因为定义时并不存在恩和对象,所以不能写为如p.name、p.eat()等。而在main()方法运行时,我们创建了p对象,并给姓名、年龄、性别三个属性赋了值,调用introduce方法p.introduce(), introduce()方法内的姓名、年龄等属性会自动指定为p对象的对应属性。
属性的默认值:
在定义类的属性后,在使用过程中,如果没有给这些属性赋值,JVM系统将自动为这些属性赋默认值。例如:
public class DefaultValue {
int i;
float f;
double d;
char c;
boolean b;
String s;
/*输出这些属性的值*/
void test(){
System.out.println("i = "+i);
System.out.println("f = "+f);
System.out.println("d = "+d);
System.out.println("c = "+c);
System.out.println("b = "+b);
System.out.println("s = "+s);
}
public static void main(String[] args) {
/*创建对象*/
DefaultValue d = new DefaultValue();
/*调用方法*/
d.test();
}
}
运行程序,输出结果如图:
可以看到,数值类型的初始值为 0,对于 char 类型,其值为“\u0000”,显示为空字符,boolean 类型的初始值为 false,而引用类型(如 String)的初始值为 null。通常情况下,为这些对于基本数据类型成员变量,JVM 会根据相应的数据类型初始化默认值,如 int 数据类型的默认值是0,即使不再初始化它们也能正常使用,只是初始数值可能不是我们所期望的数值而已。但引用数据类型(如 String)的初始默认值是 null,当试图操作该引用数据类型所指向的对象时,会造成运行时错误。
在编程过程中,虽然说每一个类的属性均有默认值,但是为了避免错误,我们应当在使用这些属性之前,对每个属性进行手工赋值。
构造方法
Person p;
p = new Person();
其中,“Perwon p:”指的是声明一个类型为“Person”的对象 p,注意仅仅是声明,并未真正让这个p诞生,第二句话才是生产p的过程,new 关键字用于分配内存空间来安置p对象。但是,new 后面的“Personl)”是什么?我们可以知道它是一个方法,但它是一个什么样的方法呢?而且,观察程序,我们发现并未定义过这么一个方法,且奇怪的是,方法名和类名是相同的,那它是从何而来的?
无参数的构造方法
对于一个类,如果我们不自定义构造方法,那么程序会自动构建无参数构造方法
public class Dog{
String name; //属性
void shout() { //方法
System.out.println(name + ":汪汪....");
}
public static void main(String[] args) {
DogTest dog = new DogTest();
dog.name = "旺财";
dog.shout();
}
}
程序中并未定义 Dog()方法,但在 main()方法内依然可以调用,从而创建 Dog 对象。
这其中的原因就是编译器完成了默认构造方法的创建,即:
public class Dog{
String name; //属性
Dog(){} //默认构造方法,有系统自动添加
void shout() { //方法
System.out.println(name + ":汪汪....");
}
public static void main(String[] args) {
DogTest dog = new DogTest();
dog.name = "旺财";
dog.shout();
}
}
可以看到,构造方法的名称与类名一样,而且构造方法没有返回值;另外,当类中已经创建了构造方法时,编译器就不再为类自动创建构造方法。编译器自动创建的构造方法为空方法,当然,我们自定义构造方法时,可以更灵活地运用。例如,我们经常使用构造方法来完成属性的初始化,以避免在生成对象后出现大量的赋值语句。
如下列代码:
public class Dog {
String name;
Dog() {
System.out.println("构造方法被调用");
name = "旺财";
}
void shout() {
System.out.println(name + ":汪汪....");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "旺财";
dog.shout();
}
}
运行程序,结果如下图:
本程序使用自定义无参数构造方法来完成属性的初始化,使得一只狗一出生就有了"旺财"的名字,另外,仔细观察结果发现,构造方法的确实在创建对象时被调用。注意构造方法和普通方法的区别
那么有没有办法能够实现在创建对象的同时,自由指定属性的值呢?下面就要用到带参数的构造方法。
带参数的构造方法
无参数的构造方法,既然叫方法,那么就代表构造方法可以和一般方法那样带参数,带参数的构造方法可以更灵活的给我们的属性赋值。还用Dog来表示有参构造方法如下
public class Dog {
String name;
//构造方法,接收一个字符串参数
Dog(String dogName) {
System.out.println("构造方法被调用");
name = dogName; //把参数值给name属性
}
void shout() {
System.out.println(name + ":汪汪....");
}
public static void main(String[] args) {
// 调用带参数的构造方法生成小狗旺财
Dog dog = new Dog("旺财");
dog.shout();
// 调用带参数的构造方法生成小狗来福
Dog dog1 = new Dog("来福");
dog1.shout();
}
运行结果如下图:
在程序中,我们定义了一个带字符串参数的构造方法,构造方法内把字符串参数的值赋给类属性name,mian()方法中,创建了两个对象,分别传递不同的参数值,由此得到两个不同名的小狗对象,显然,这比无参数构造方法更加灵活
对于上例,在类中已经声明了一个带参数的构造方法下,编译器是不会自动生成无参数的默认构造方法。
那么也就是 说构造方法用于初始化成员属性,通过new调用构造方法来初始化对象。当没有创建无参构造方法时,系统会自动创建一个无参的构造方法;当创建了构造方法,系统就不再帮我们创建构造方法了,此时调用构造方法,就需要使用自己创建的构造方法。
继承
顾名思义,就像是儿子继承爸爸的财产或者说某些行为身体动作一样,那么对于Java来说,继承是一种由已有的类创建新类的机制。利用继承,我们可以先创建一个共有属性的一般类那么也就是父类,再根据该一般类创建具有特殊属性的新类也就是子类,新类继承一般类的状态和行为,并根据需要增加自己新的状态和行为。由继承得到的类称为子类,被继承的类称为父类。Java 不支持多重继承,子类只能有一个父类。
先看以下代码:
public class Saloon_car {
String engine; //引擎
String Wheel; //车轮
String airbag; //货舱
public void run(){ //方法
//定义车跑动的行为
}
}
public class Truck {
String engine; //引擎
String Wheel; //车轮
String carport; //安全气囊
public void run(){ //方法
//定义车跑动的行为
}
}
为什么要用继承?
上面写了两个类的定义,我们会发现,这两个类有很多重复的属性和行为,这不仅带来了代码的繁杂,产生大量冗余代码,而且更重要的是,程序变得难以驾驭,我们很难对程序进行更新。如果现在我们要在创建一个或多个类属性和行为相似的类,就要单独写多个重复的属性和行为如果这样的话会造成代码冗余。那么如何解决这样的问题,就是要使用继承
继承的声明
继承使用的关键字是extends
格式如下:
修饰符 class 类名(子类名) extends 父类名
首先可以写一个父类——Car,在定义将车,卡车类,它们拥有父类Car的所有特征,再加上自己独有的特性,形成Saloon_car,Truck.代码如下
//汽车类
public class Car {
String engine; //引擎
String Wheel; //车轮
//....其他属性
public void run(){ //方法
//定义车跑动的行为
System.out.println("汽车在奔跑!");
}
}
//轿车类
public class Saloon_car extends Car{
String airbag; //安全气囊
}
//卡车类
public class Saloon_car extends Car{
String carport; //货舱
}
在上面代码,轿车类和卡车类继承后,拥有了汽车类中定义的所有属性和方法。我们可以测试一下
运行如下
方法的覆盖(方法的重写)
观察上图可以发现,不管是普通汽车,还是卡车和轿车,它们的运转都是一样的,结果均输出“汽车在奔跑!”,这是不恰当的。按道理来说,卡车和轿车的运转应该有自己的独立方式,不应当和普通汽车保持一致。
也就是说,子类需要对父类的方法加以改进,变成子类自己的方法,这就需要在子类中重新编写 方法,覆盖父类的 run()方法,这种做法在 Java 中叫作方法的覆盖(overide),又称方法重写。
实例如下
//父类汽车类
public class Car {
String engine; // 引擎
String Wheel; // 车轮
// ....其他属性
public void run() { // 方法
// 定义车跑动的行为
System.out.println("汽车在奔跑!");
}
}
//子类轿车类
public class Saloon_car extends Car {
String airbag; //安全气囊
public void run() { //不用父类方法,重新编写的run方法
//定义轿车
System.out.println("轿车在高速路上奔驰!");
}
}
//子类卡车类
public class Truck extends Car {
String carport; // 货舱
public void run() { // 不用父类方法,重新编写的run方法
System.out.println("卡车在工地上忙碌!");
}
}
再次运行程序,这时结果就正常了,汽车、轿车、卡车分别以自己的方式在奔跑。
方法覆盖要求:子类方法和父类方法同名,且参数相同。
要注意重载和重写的区别,重载既可以发生于一个类,也可以发生于子类与父类之间(子类继承父类方法,同时完成方法重载),而重写,只能是子类重写父类方法。
super关键字
super用法格式:
super.父类方法(或属性)
super就比较简单了,就是用来调用父类的属性或方法的
super一般用于子类的方法中
例如
public class A {
public void can(){
System.out.println("我是父类的方法");
}
public void A(){
System.out.println("我是A的构造方法");
}
}
public class B extends A{
public static void main(String[] args) {
B b = new B();
b.b();
}
public void b(){
super.A();
super.can();
}
}
this关键字
this 关键字指当前对象,也就是说,哪个对象调用了方法,这个方法内的 this 指的就是哪个对象。(指定当前对象)
用法:this.属性名(方法名)
public class A {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
上面的方法可以看到方法参数的名字如果和属性名一样,那么编译器如何识别name是参数还是属性呢,这就需要this关键字来辨别参数和属性了,前面有this关键字的后面跟的是当前类中的属性,这样this就能帮编译器分辨出来。
抽象类
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
没有抽象方法的类不一定是抽象类,但是有抽象方法的类一定是抽象类。
抽象方法
使用 abstract关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
例如:
public abstract void run():
用法:
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
接口
接口,是java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法 DK 7及以前),默认方法和静态方法 JDK 8),私有方法(JDK 9)
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引引用数据类型
接口的使用
它不能创建对象,但是可以被实现 ( implements,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
定义格式:
修饰符 interface 类名{}
例如:
public interface USB {}
final及访问修饰符权限
final用于修饰固定不变的变量、类、方法
修饰类:被修饰的类,不能被继承。
修饰方法:被修饰的方法,不能被重写。
修饰变量:被修饰的变量,不能被重新赋值,也叫做常量。
修饰类:final class 类名
修饰变量:final 数据类型 变量名
修饰方法:final 返回值类型 方法名
在Java中提供了四种权限分别为public、protected 、default 、private
权限如下
当方法、变量、类没有使用修饰符定义时,则被编译器默认使用default修饰
内部类
概念:在同一个Java文件中定义多个类,这种类又叫做内部类
public class User {
String name;
class Student{
int id;
int age;
}
}
在上面代码中Student为内部类,区别谁是内部类的关键就要看类前面的修饰符正常情况下我们一个类中通常使用public来修饰类,所以在这里没有用public修饰的类就叫做内部类
那么如何使用内部类,就拿上面的代码来举例
User.Student student = new User.Student();
类名.内部类名 对象名 = new 类名.内部类名构造方法
匿名内部类
匿名内部类是什么
首先, 如果在一个A类里面定义一个B类, 那么B类就是内部类, A类是外部类. 内部类就相当于外部类的一个成员, 你可以把内部类看成一个整体. 它又分为静态内部类和非静态内部类
匿名内部类是非静态内部类的一种特殊情况, 匿名即没有类名, 因此就不可能有构造函数, 不能创建对象.
但请注意实质上匿名内部类是有类名和构造函数的. 由编译器创建.
为什么要使用匿名内部类
为了程序员的方便以及代码的简洁.本来程序员应该先创建一个类继承抽象类或实现接口再创建对象, 但很多情况下, 这个类只会被使用一次, 似乎单独存在的必要性不大.. 为了简便, 匿名内部类允许我们在主方法当中进行抽象类/接口的实例化, 同时也可以进行对象的创建
匿名内部类的定义
new 实现接口()
{
// 匿名内部类类体部分
}
new 父类构造器(实参列表)
{
// 匿名内部类类体部分
}
这两种方式的定义分别对应两种方式,一种是接口,另一种是父类构造器
注意:对于实现接口, 由于接口是没有构造函数的, 注意这里一定是空参数
第二种是调用父类的构造器, 注意此处可以是空参数, 也可以传入参数
匿名内部类除了代码简洁以外, 主要有以下几个用途:
- 覆盖父类的方法
- 实现接口的方法
- 使用匿名内部类传入代码块进行初始化