一、面向对象特征一:封装性
1.为什么要引入封装性?
我们程序设计追求“高内聚,低耦合”。
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 :仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2.问题引入:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private).
-->此时,针对于属性就体现了封装性。
3.封装性思想具体的代码体现:
体现一:将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取getXxx()和设置setXxx()此属性的值。
隐藏一个类中不需要对外提供的实现细节;
使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑, 限制对属性的不合理操作;
便于修改,增强代码的可维护性;
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}
体现二:不对外暴露的私有的方法
体现三:单例模式(将构造器私有化)
体现四:如果不希望类在包外被调用,可以将类设置为缺省的。
4.Java规定的四种权限修饰符
Java权限修饰符public、protected、(缺省)、private置于类的成员定义前, 用来限定对象对该类成员的访问权限。
4.1 权限从小到大顺序为:private < 缺省 < protected < public
4.2 具体的修饰范围:
package com.bcbd.java;
public class Order {
private int orderPrivate;
int orderDefault;
public int orderPublic;
private void methodPrivate(){
orderPrivate = 1;
orderDefault = 2;
orderPublic = 3;
}
void methodDefault(){
orderPrivate = 1;
orderDefault = 2;
orderPublic = 3;
}
public void methodPublic(){
orderPrivate = 1;
orderDefault = 2;
orderPublic = 3;
}
}
package com.bcbd.java;
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.orderDefault = 1;
order.orderPublic = 2;
//出了Order类之后,私有的结构就不可以调用了
// order.orderPrivate = 3;//The field Order.orderPrivate is not visible
order.methodDefault();
order.methodPublic();
//出了Order类之后,私有的结构就不可以调用了
// order.methodPrivate();//The method methodPrivate() from the type Order is not visible
}
}
package com.bcbd.java1;
import com.bcbd.java.Order;
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.orderPublic = 2;
// 出了Order类所属的包之后,私有的结构、缺省声明的结构就不可以调用了
// order.orderDefault = 1;
// order.orderPrivate = 3;//The field Order.orderPrivate is not visible
order.methodPublic();
// 出了Order类所属的包之后,私有的结构、缺省声明的结构就不可以调用了
// order.methodDefault();
// order.methodPrivate();//The method methodPrivate() from the type Order is not visible
}
}
4.3 权限修饰符可用来修饰的结构说明:
4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
对于class的权限修饰只可以用public和(default)缺省。public类可以在任意地方被访问。 default类只可以被同一个包内部的类访问。
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
二、面向对象特征二:继承性
1.继承性的作用:
继承的出现减少了代码冗余,提高了代码的复用性。
继承的出现,更有利于功能的扩展。
继承的出现让类与类之间产生了关系,提供了多态的前提。
2.继承性的格式: class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
2.1体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。
只有因为封装性的影响,使得子类不能直接调用父类中私有(private)的结构而已。
2.2 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
子类和父类的关系,不同于子集和集合的关系。
extends:延展、扩展
3.继承性的规定:
1.Java中类的多层继承:一个类可以被多个子类继承。
2.Java中类的单继承性:一个类只能有一个父类。
3.子父类是相对的概念。
4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
6. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。Object类是所有Java类的根父类
7. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。
8. 意味着,所有的java类具有java.lang.Object类声明的功能。
public class Creature {
public void breath(){
System.out.println("呼吸");
}
}
public class ExtendsTest {
Creature c = new Creature();
c.toString();
c.breath();
}
4.方法的重写:
4.1重写的定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
4.2重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ //方法体}
(1). 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
(2). 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限 。子类不能重写父类中声明为private权限的方法。
(3). 子类方法抛出的异常不能大于父类被重写方法的异常 。
(4). 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。
返回值类型:
>父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
>父类被重写的方法的返回值类型是A类型(引用数据类型),则子类重写的方法的返回值类型可以是A类或A类的子类
>父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
4.3重写的注意: 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为 static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
4.4区分方法的重载与重写
① 二者的概念。
(1)重载定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
(2)重写定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。
② 重载和重写的具体规则。
(1)重载"两同一不同":同一个类、相同方法名
参数列表不同:参数个数不同,参数类型不同
(2)子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
③ 重载:不表现为多态性。重写:表现为多态性。
答:方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。
4.5四种访问权限修饰符
public class OrderTest {//OrderTest类和Order类在同一个包下
public static void main(String[] args) {
Order order = new Order();
order.orderDefault = 1;
order.orderProtected = 2;
order.orderPublic = 3;
order.methodDefault();
order.methodProtected();
order.methodPublic();
//同一个包中的其他类(OrderTest类),不可以调用Order类中私有的属性、方法
// order.orderPrivate = 4;
// order.methodPrivate();
}
}
public class SubOrder extends Order {//SubOrder类与Order类在不同包
public void method(){
orderProtected = 1;
orderPublic = 2;
methodProtected();
methodPublic();
//在不同包的子类中,不能调用Order类中声明为private和缺省权限的属性、方法
// orderDefault = 3;
// orderPrivate = 4;
//
// methodDefault();
// methodPrivate();
}
}
public class OrderTest {//OrderTest类与Order类在不同包,且没有父子类关系
public static void main(String[] args) {
Order order = new Order();
order.orderPublic = 1;
order.methodPublic();
//不同包下的普通类(非子类)要使用Order类,不可以调用声明为private、缺省、protected权限的属性、方法
// order.orderPrivate = 2;
// order.orderDefault = 3;
// order.orderProtected = 4;
//
// order.methodPrivate();
// order.methodDefault();
// order.methodProtected();
}
}
5.super关键字
super 关键字可以理解为:父类的
5.1在Java类中使用super来调用父类中的指定操作:
super可用于访问父类中定义的属性 。
super可用于调用父类中定义的成员方法。
super可用于在子类构造器中调用父类的构造器。
5.2注意:
尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员。
super的追溯不仅限于直接父类,还要间接父类。
super和this的用法相像,this代表本类对象的引用,super代表父类的内存 空间的标识。
5.3super的使用:调用属性和方法
(1) 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
(2) 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的 使用"super.属性"的方式,表明调用的是父类中声明的属性。
(3)特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
5.4super调用构造器
(1) 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器。
(2) "super(形参列表)"的使用,必须声明在子类构造器的首行!
(3)我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现。
(4)在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()。
(5)在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器。
举例
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name); school = s;
}
// 编译出错: no super(),系统将调用父类无参数的构造器。
public Student(String s) {
school = s;
}
}
6.子类对象的实例化过程
6.1. 从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类(直接、间接父类)中声明的属性。
6.2. 从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,.....直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。
6.3.强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}}
三、面向对象特征三:多态性
1.理解多态性:可以理解为一个事物的多种形态。作用:提高了代码的通用性,常称作接口重用 。
2.何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)------向上转型。可以直接应用在抽象类和接口上。
3. 多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明 该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译,看左边;运行,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类
对象,那么该变量就不能再访问子类中特有的属性和方法。
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
//属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编
译错误。
正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父 类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法 确定的(运行时行为)。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类 的getInfo()方法。——动态绑定
4.多态性的使用前提: ① 存在类的继承关系或接口的实现关系 ② 有方法的重写
多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。
public class Person {
String name;
int age;
int id = 1001;
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
}
public class PersonTest {
public static void main(String[] args) {
Person p2 = new Man();
System.out.println(p2.id);//1001(调用属性时编译和运行都看左边)
}
}
6.多态性使用的举例:
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
class Order{
public void method(Object obj){//object类是所有类的父类
}
}
//举例三:
class Driver{
public void doData(Connection conn){//conn = new MySQlConnection(); / conn = new OracleConnection();
//规范的步骤去操作数据(抽象化)
// conn.method1();
// conn.method2();
// conn.method3();
}
}
//举例四:方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test {
public void method(Person e) {
// ……
e.getInfo();
}
public static void main(Stirng args[]) {
Test t = new Test();
Student m = new Student();
t.method(m); // 子类的对象m传送给父类类型的参数e
}
}
7.多态是编译时行为还是运行时行为?(证明如下代码)
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
8.instanceof关键字
8.1有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
8.2如何才能调用子类特有的属性和方法?
向下转型:使用强制类型转换符。
Person p2 = new Man();
Man m1=p2;//这是错误的,因为Java中赋值符号两边数据类型应该一样,或者是基本数据类型的自动类型提升。
Man m1 = (Man)p2;//强制类型转换。m1为Man类型,p2地址值赋值给了m1。
//使用强转时,可能出现ClassCastException的异常。
Woman w1 = (Woman)p2;//运行时出现ClassCastException的异常。
基本数据类型的对象类型转换(Casting):
(1)自动类型转换:小的数据类型可以自动转换成大的数据类型。 如long g=20; double d=12.0f
(2)强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型 。如 float f=(float)12.0; int a=(int)1200L
对Java对象的强制类型转换称为造型
(1)从子类到父类的类型转换可以自动进行。
(2)从父类到子类的类型转换必须通过造型(强制类型转换)实现。
(3)无继承关系的引用类型间的转换是非法的。
(4)在造型前可以使用instanceof操作符测试一个对象的类型。
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l);
int in = 5;
// boolean b = (boolean)in;
Object obj = "Hello";
String objStr = (String) obj;
System.out.println(objStr);
Object objPri = new Integer(5);
// 所以下面代码运行时引发ClassCastException异常
String str = (String) objPri;
}
}
public class Test {//对象类型转换举例
public void method(Person e) { // 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(String[] args){
Test t = new Test();
Student m = new Student();
t.method(m); }
}
8.3instanceof关键字的使用
a instanceof A:判断对象a是否是类A的实例,返回值为boolean类型。如果是,返回true;如果不是,返回false。
要求a所属的类与类A必须是子类和父类的关系,否则编译错误。 如果a属于类A的子类B,a instanceof A值也为true
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
Person p2 = new Man();
Man m1 = (Man)p2;
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");不输出System语句
}
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
System.out.println("******Man******");//输出System语句
}
//如果 a instanceof A返回true,则 a instanceof B也返回true。其中,类B是类A的父类。
if(p2 instanceof Person){
System.out.println("******Person******");//输出System语句
}
if(p2 instanceof Object){
System.out.println("******Object******");//输出System语句
}
向下转型常见的问题
//问题一:编译时通过,运行时不通过
//举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;
// Object o = new Date();
// String str1 = (String)o;//没有意义
//问题二:编译通过,运行时也通过
// Object obj = new Woman();//多态性
// Person p = (Person)obj;//Object类型向下转型成Person类型
//问题三:编译不通过
// Man m5 = new Woman();//没有子父类关系
// String str = new Date();//没有子父类关系