Java面向对象(上)
Java是面向对象的程序语言,所有Java也就是支持 封装、继承和多态当然具体是如何实现的我们就简单谈谈。
首先我们来看看它的封装
封装
为什么要有封装?
在刚开始学习时程我们可能会经常出现通过某个对象直接访问其内部成员的情形,这就肯能引起一些潜在的问题,比如将Student的age成员变量设置为1000,这个语法上没有问题,但是显然不符合实际情况,所以就有了封装。
定义:
封装值得是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过类所提供的方法来实现对内部信息的操作和访问的。
封装的目的
- 隐藏类的实现细节;
- 让使用者只能从事先预定的方法来访问数据,从而可以在方法里加入控制逻辑,限制队成员变量的不合理的访问;
- 可进行数据检查,从而有利于保护对象信息的完整性;
- 便于修改,提高代码的可维护性。
实现封装的良好性需要从两个方面去考虑。
1.将对象的成员变量和实现细节隐藏起来,不允许外部直接去访问。
2.把方法暴露出来,让方法来控制对这些成员变量进行访问或者操作。
总之一句话就是把该隐藏的隐藏起来,把暴露的暴露出来。
封装的实现
封装的具体实现是靠访问控制符来实现的,Java提供了三个访问控制符private、protect和public,分别代表了3个访问控制级别。另外还有一默认访问控制级别。他们的控制级别大小为private < default < protect < public.
其中default并没有对应的访问控制符去控制,当不加任何访问控制符时系统默认使用该访问控制级别
我们在详细看一下这些访问控制符的权限
private | default | protect | public | |
---|---|---|---|---|
同一个类中 | Y | Y | Y | Y |
同一个包中 | Y | Y | Y | |
子类中 | Y | Y | ||
全局范围内 | Y |
注意事项
1.访问控制符是用于控制一个类的成员是否可以被其他类访问,而对于局部变量而言,其作用域就是他所在的方法,不可能被其它类访问,因此不能用访问权限控制符修饰。
2.对于外部类而言也不能用private和protected,因为外部类不处于任何类的内部,这样做没有意义。
3.另外在一个Java的源文件中如果你定义了多个类(当然不是很推荐这么作),它可以是一切合法的文件名,但是如果有一个public修饰的类,则这个文件必须与public修饰的类名相同。
接下来我们来看看代码中是怎么实现的吧。
package com.company;
public class Person {
//隐藏成员变量
private int age;
private String name;
//提供以下方法来操作
public int getAge() {
return age;
}
public void setAge(int age) {
//设置合理性0 < age < 100
if(age < 0 || age > 100){
System.out.println("输入年龄不合法");
return;
}
else
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
//设置的合理性判断
if(name.length()> 8 || name.length() < 2){
System.out.println("输入名称不合理");
return;
}
else
this.name = name;
}
}
这样我们就实现了一个person类的封装。接下来我们再在程序中调用来看看具体效果
package com.company;
public class Test {
public static void main(String[] args) {
Person p = new Person();
//age已被隐藏所以下列语句将出现编译错误
//p.age = 1000;
p.setAge(101);
//上面的语句设置不会错误但是会提示“不合法”
p.setAge(15);
//上面的语句会将年龄设置为15
System.out.println("age = " + p.getAge());
}
}
从上面的简单的例子我们可以看出了Java中是如何具体实现封装的了,
接下来我们来谈谈继承。
继承
概述:当多个类中存在相同的属性和行为时,经这些内容抽取到单独的一个类中。期中实现继承的类被称为子类,被继承的类被称为父类。通俗点来讲继承是一种一般和特殊的关系,就好像人类和学生,动物和狗。
实现:
Java是通过extends关键字来实现继承的语法如下
修饰符 Class SubClass extends SuperClass{
//类的定义部分
}
其中SubClass是子类名SuperClass是父类类名。
其实extends在英文中其实是拓展的意思(这也就是我们所说的继承其实也是对父类功能的一个拓展)。
我们来通过一段代码来了解一下继承的基本语法。
首先我们定义一个父类
package com.company;
public class SuperClass {
public int num;
public void show(){
System.out.println("我是父类");
}
}
我们在定义一个子类
package com.company;
public class SubClass extends SuperClass{
}
定义测试类
package com.company;
public class Test1 {
public static void main(String[] args) {
//创建子类对象
SubClass s = new SubClass();
//设置继承下来的成员变量
s.num = 1;
System.out.println("SubClass num = " + s.num);
//调用继承下来的成员方法
s.show();
}
}
我们来看一下运行结果
从运行结果中我们可以看出虽然说子类没有num的成员变量和show方法但是他通过继承获得了该成员变量和方法。这就是继承的作用。
继承的好处
1.提高了代码的复用性
2.提高了代码的拓展性
继承的弊端
继承这一特点强调了类与类之间的关系这就违背了开发的一个原则——高内聚,低耦合。那什么是内聚呢?就是说自己完成事情的能力,什么又是耦合呢?就是类与类之间的关系。
继承的特点
在Java中摒弃了C++语言中难以理解的多继承的特征。每一个类只能有一个直接父类。但是,Java支持多曾继承。
class Fruit extends Plant{...}
class Apple extends Fruit{...}
当然继承也不是父类中的所有东西子类都可以继承的比如以下几点不能被继承:
1.父类中的私有成员变量和方法。
2.父类的构造方法。(可通过Super关键字调用)
另外:继承中成员变量的访问遵循(就近原则)访问顺序如下(以a为例):
1.查找该方法中是否有名为a的局部变量。
2.查找当前类中是否包含a的成员变量
3.查找a的直接父类中是否包含成员变量a,依次上溯a的所有父类。直到java.lang.Object类,如果没有则编译报错。
有时候我们会遇到一些情况比如鸟类中都包含飞翔我们可以通过继承来获得,但是鸵鸟是一种特殊的鸟类它只会奔跑,所以这个时候我们就要重写父类方法了
重写父类方法
//鸟类
package com.company;
public class Bird{
public void fly(){
System.out.println("我能飞!");
}
}
//鸵鸟类
package com.company;
public class Ostrich extends Bird{
@Override
public void fly(){
System.out.println("我只能跑!");
}
}
//测试类
package com.company;
public class Test2 {
public static void main(String[] args) {
Ostrich os = new Ostrich();
//执行重写的fly方法:输出我只能跑!
os.fly();
}
}
从上面的代码中我们可以看出子类不再执行父类原有的方法了,而是执行重写过后的方法,其中 的@Override标记代表的是该方法为重写过后的方法。
这就相当于父类方法被覆盖掉了,但是如果我们想在本类中继续调用父类被重写的方法,我们可以用super限定符来实现。
###super限定
我们给刚才的Ostrich中添加一个方法。
public void callOverrideMethod(){
//通过super调用父类被重写的方法
super.fly();
}
这样我们就可以通过super调用父类被重写的方法了构造器同样可以,当然成员变量同名也会覆盖掉父类中的成员变量我们通过super也可以来调用。
重写注意事项
1.父类中的私有方法不能被重写(子类也不能继承);
2.子类重写父类方法时访问权限必须大于或等于父类方法;
3.静态方法的重写必须通过静态方法来重写(算不上重写,只是表现一样)。
多态
表现:
Java的引用变量有两个类型一个是编译时类型,一个是运行时类型。如果编译和运行时类型不一致,就有可能表现出多态。
前提:
1.多态必须以继承作为前提。
2.要有方法重写。
3.要有父类对象指向子类对象的引用
多态举例
首先看下面的代码:
//父类
public class Animal {
int num=100;
public Animal() {
System.out.println("父类的空参构造执行了");
}
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
public void show() {
System.out.println("abc");
}
public static void jingMethod(){
System.out.println("父类的静态方法");
}
}
//子类猫继承父类Animal
public class Cat extends Animal{
int num = 50;
public Cat() {
System.out.println("子类的构造方法执行了");
}
@Override
public void eat() {
System.out.println("猫爱吃鱼");
}
@Override
public void sleep() {
System.out.println("猫爱白天睡觉");
}
@Override
public void show() {
System.out.println("zishow");
}
public static void jingMethod() {
System.out.println("子类的静态方法");
}
}
//测试类
public class MyDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
cat.sleep();
System.out.println("------------------------");
Animal an = new Cat();
an.show();
an.eat();
an.sleep();
System.out.println(an.num);
an.jingMethod();
Animal.jingMethod();
Cat.jingMethod();
}
}
在上述代码中猫既是一个猫类也是Animal类所以可以通过父类Animal引用去指向子类猫的对象。这时候在调用时出现的是子类方法的行为特质 而不是父类方法的这儿就是多态。其中的Animal an = new Cat();中编译时为Animal但是在运行时确是Cat类。所以当你在调用方法时它会执行子类重写过后的方法。但是静态方法不同在调用时它依旧会执行父类中的方法。
多态提高代码的可拓展性
//定义一个父类Animal
public class Animal {
public void eat(){
System.out.println("吃饭");
}
}
//定义一个子类猫
public class Cat extends Animal{
//重写父类方法
@Override
public void eat() {
System.out.println("猫爱吃鱼");
}
}
//定义一个子类狗
public class Dog extends Animal{
//重写父类方法
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
//定义一个工具类并私有其构造方法
public class MyUtils {
//私有构造方法,外界不能就创建该类对象
private MyUtils() {
}
public static void method(Animal an) {
an.eat();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
//输出够爱吃骨头
MyUtils.method(a);
System.out.println("----------------------------------");
a = new Cat();
//输出猫爱吃鱼
MyUtils.method(a);
}
}
从上述的例子中我们可以看出猫和狗既属于猫和狗类也属于Animal类这时我们用一个父类的对象来分别指向猫狗类的对象时,再调用工具类中的静态方法,这样救输出了子类中重写过后的方法。
多态的弊端
多态的弊端就是不能调用子类中特有的方法如果需要调用则需要向下转型,所以多态就相当于是向上转型。
比如在上一个代码中如过Catlei中包含有一个Sleep()方法,这是父类中所没有的,当你在取用如下方法调用时就会出现错误:
Animal an = new Cat();
//an.sleep(); //编译报错
//向下转型
Cat c = (Cat)an;
c.sleep();
多态类型转换异常
比如说你有一个父类Animal、两个子类Cat和Dog
当你用多态形式来用一个父类引用指向子类对象时就可能出现下列情况
Animal an = new Dog()
Dog dog = (Dog)an;
an = new Cat();//少了这行代码就会出现类型转换异常ClassCastException
Cat cat = (Cat)an;
为什么会出现这种情况呢?
通俗点来看你的第一、二行代码说狗属于是动物类没有什么问题但是如果没有第三行代码,第四行代码又说狗是猫类这当然是说不通的所以就出现了类型转换异常。
instanceof运算符
那么如何避免这种错误的出现呢?
Java给出了一个instanceof运算符语法格式如下
引用类型变量 instanceof 类(或者接口);
它用于判断前面的对象是否是后面的类或者其子类、实现类和实现例。如果是返回true,如果不是返回false。
Aniaml an = new Dog();
Dog dog = (Dog) an;
if(an instanceof Cat) {
Cat cat = (Cat) an;
}
else {
an=new Cat();
Cat cat = (Cat) an;
}