JavaSE学习笔记(3)---面向对象三大特性
JavaSE学习笔记(3)---面向对象三大特性
面向对象的三大特征:继承、封装、多态
1.封装
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。然后封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。其原则就是将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
1.封装的步骤
- 使用 private 关键字来修饰成员变量。
- 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。
2.封装的操作——private关键字
-
private的含义
-
private是一个权限修饰符,代表最小权限。
-
private可以修饰成员变量和成员方法。
-
被private修饰后的成员变量和成员方法,只在本类中才能访问。
-
-
private的使用格式
private 数据类型 变量名;
使用 private 修饰成员变量,代码如下:public class Student { private String name; private int age; }
-
提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下:
public class Student {
private String name;
private int age;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
}
3.封装优化1——this关键字
-
this的含义
this代表所在类的当前对象的引用(地址值),即对象自己的引用。记住 :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。 -
this使用格式
this.成员变量名;
使用 this 修饰方法中的变量,解决成员变量被隐藏的问题,代码如下:
public class Student {
private String name;
private int age;
public void setName(String name) {
//name = name;
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//age = age;
this.age = age;
}
public int getAge() {
return age;
}
}
小贴士:方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。
4.封装优化2——构造方法
当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。
构造方法的定义格式:
修饰符 构造方法名(参数列表){ // 方法体 }
构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。使用构造方法后,代码如下:
public class Student {
private String name;
private int age;
// 无参数构造方法
public Student() {}
// 有参数构造方法
public Student(String name,int age) {
this.name = name;
this.age = age;
}
}
注意事项
如果你不提供构造方法,系统会给出无参数构造方法。
如果你提供了构造方法,系统将不再提供无参数构造方法。
构造方法是可以重载的,既可以定义参数,也可以不定义参数。
-
标准代码——JavaBean
JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。public class ClassName{ //成员变量 //构造方法 //无参构造方法【必须】 //有参构造方法【建议】 //成员方法 //getXxx() //setXxx() }
编写符合 JavaBean 规范的类,以学生类为例,标准代码如下:
public class Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成员方法
publicvoid setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
publicvoid setAge(int age) {
this.age = age;
}
publicint getAge() {
return age;
}
}
测试类,代码如下:
public class TestStudent {
public static void main(String[] args) {
//无参构造使用
Student s= new Student();
s.setName("小米");
s.setAge(18);
System.out.println(s.getName()+"‐‐‐"+s.getAge());
//带参构造使用
Student s2= new Student("小明",18);
System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
}
}
封装是非常有必要的,因为:
- 封装提高了数据的安全性
别人不能够通过变量名.属性名的方式来修改某个私有的成员属性 - 操作简单封装后,多个调用者在使用的时候,只需调用方法即可,调用者不需要再进行判断
- 隐藏了实现
实现过程对调用者是不可见的,调用者只需调用方法即可,不知道具体实现过程
继承
现实生活中的继承,孩子继承父辈的财产,孩子可以直接拿父辈的财产来使用,Java中的继承是指在现有类的基础上定义一个新的类,现有类称为父类,新的类称为子类,子类会自动拥有父类的可继承的内容多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。内容
1. 继承的定义
继承:
就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
好处
- 提高代码的复用性。
- 类与类之间产生了关系,是多态的前提。
2.继承的格式
通过extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
举个例子,代码如下:
// 1.定义父类
class Person {
String name;
int age;
public void eat() {
System.out.println(name + " 吃饭");
}
public void sleep() {
System.out.println(name + " 睡觉");
}
}
// 2.再写子类
class Student extends Person {
}
class Teacher extends Person {
}
// 3.定义测试类
public class Demo01 {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "小王";
stu.age = 18;
stu.eat();
stu.sleep();
Teacher tea = new Teacher();
tea.name = "马老师";
tea.age = 30;
tea.eat();
tea.sleep();
Person p = new Person();
p.name = "张三";
p.age = 20;
p.eat();
p.sleep();
}
}
3.继承案例
请使用继承定义以下类:
1.程序员(Coder)
成员变量: 姓名,年龄
成员方法: 吃饭,睡觉,敲代码
2.老师(Teacher)
成员变量: 姓名,年龄
成员方法: 吃饭,睡觉,上课
1. 父类Person类
public class Person {
String name;
int age;
public void eat() {
System.out.println(name + "吃饭");
}
public void sleep() {
System.out.println(name + "睡觉");
}
}
2. 子类Coder类
public class Coder extends Person {
// 敲代码
public void coding() {
System.out.println(name + "敲代码");
}
}
3. 子类Teacher类
public class Teacher extends Person {
public void teach() {
System.out.println(name + "上课");
}
}
4. 测试类
public class Demo02 {
public static void main(String[] args) {
Coder c = new Coder();
c.name = "马化腾";
c.age = 45;
c.eat();
c.sleep();
c.coding();
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
Teacher t = new Teacher();
t.name = "马云";
t.age = 50;
t.eat();
t.sleep();
t.teach();
}
}
4.父类不可被继承的内容
并不是父类的所有内容都可以给子类继承的,以下2个内容不能被子类继承:
- 被private修饰的
- 构造方法不能继承
- 被finall关键字修饰的类不能被继承
通过getter/setter方法访问父类的private成员变量
public class Demo03 {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子类无法使用
// 通过getter/setter方法访问父类的private成员变量
System.out.println(z.getNum2());
z.show1();
// z.show2(); // 私有的子类无法使用
}
}
class Fu {
public Fu() {
}
public int num1 = 10;
private int num2 = 20;
public void show1() {
System.out.println("show1");
}
private void show2() {
System.out.println("show2");
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
class Zi extends Fu {
/* 2. 构造方法不能继承,因为构造方法和类名相同,父类和子类的名称肯定不相同,无法继承 */
// public Fu() {
// }
}
- 修饰类: 修饰的类不能被继承。比如:Math、String等。
final class A {}
5. 继承后的特点——成员变量
成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。
成员变量重名
如果子类父类中出现重名的成员变量,这时的访问是有影响的。子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于之前学过的 this 。
使用格式:super.父类成员变量名
注意: 父类中的成员变量是非私有的,子类中可以直接访问。若父类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢,可以在父类中提供公共的getXxx方法和setXxx方法。
6.继承后的特点——成员方法
成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
成员方法重名——重写(Override)
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做 方法重写 (Override) 。
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
注意事项
1.子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
2.子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
7.继承后的特点——构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响?
-
构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
-
继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造
多态
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
1. 多态的定义
多态: 是指同一行为,具有多个不同表现形式。
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。 比如:同样是调用人的“休息”方法,张三是睡觉,李四是旅游,程序员是敲代码,数学教授是做数学题; 同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。
多态的要点:
-
多态是方法的多态,不是属性的多态(多态与属性无关)。
-
多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
-
父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
class Animal {
public void shout() {
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("汪汪汪!");
}
public void seeDoor() {
System.out.println("看门中....");
}
}
class Cat extends Animal {
public void shout() {
System.out.println("喵喵喵喵!");
}
}
public class TestPolym {
public static void main(String[] args) {
Animal a1 = new Cat(); // 向上可以自动转型
//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
animalCry(a1);
Animal a2 = new Dog();
animalCry(a2);//a2为编译类型,Dog对象才是运行时类型。
//编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
// 否则通不过编译器的检查。
Dog dog = (Dog)a2;//向下需要强制类型转换
dog.seeDoor();
}
// 有了多态,只需要让增加的这个类继承Animal类就可以了。
static void animalCry(Animal a) {
a.shout();
}
/* 如果没有多态,我们这里需要写很多重载的方法。
* 每增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
static void animalCry(Dog d) {
d.shout();
}
static void animalCry(Cat c) {
c.shout();
}*/
}
2.对象的转型
父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型!
示例 对象的转型
public class TestCasting {
public static void main(String[] args) {
Object obj = new String("new"); // 向上可以自动转型
// obj.charAt(0) 无法调用。编译器认为obj是Object类型而不是String类型
/* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
* 不然通不过编译器的检查。 */
String str = (String) obj; // 向下转型
System.out.println(str.charAt(0)); // 位于0索引位置的字符
System.out.println(obj == str); // true.他们俩运行时是同一个对象
}
}
在向下转型过程中,必须将引用变量转成真实的子类类型(运行时类型)否则会出现类型转换异常ClassCastException。
类型转换异常
public class TestCasting2 {
public static void main(String[] args) {
Object obj = new String("news");
//真实的子类类型是String,但是此处向下转型为StringBuffer
StringBuffer str = (StringBuffer) obj;
System.out.println(str.charAt(0));
}
}
为了避免出现这种异常,我们可以使用学过的instanceof运算符进行判断,如下所示:
向下转型中使用instanceof
public class TestCasting3 {
public static void main(String[] args) {
Object obj = new String("news");
if(obj instanceof String){
String str = (String)obj;
System.out.println(str.charAt(0));
}else if(obj instanceof StringBuffer){
StringBuffer str = (StringBuffer) obj;
System.out.println(str.charAt(0));
}
}
}