Java的面向对象编程
Posted 木木林Violet
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java的面向对象编程相关的知识,希望对你有一定的参考价值。
Java的面向对象编程
1.包
包 (package) 是组织类的一种方式,通俗来讲则是文件夹。
使用包的主要目的是保证类的唯一性。因为在实际项目编写的过程中,难免会遇到和别人编写出同名的类,编译时则会报错,而合理地使用包则可以解决这个问题。
1.1.导入包中的类
在Java中,会提供很多现成的类给我们使用,我们则可以调用相应包中的类来实现我们的目的,例如:
public class Test
public static void main(String[] args)
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
ps:java.util.Date是Java中用来获取时间的类。
这样编写虽然没有任何问题,但在每次使用该的时候,都需要加上包的前缀,会显得很麻烦,则有了一种一劳永逸的方法:用import关键字导入类。
import java.util.Date;
public class Test
public static void main(String[] args)
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
这样在我们每次每次使用时,可以直接使用该类,而不再需要加上包的前缀。
1.2.静态导入
当我们想要导入包中的静态方法和字段时,可以使用import static来导入。
import static java.lang.Math.*;
public class Test
public static void main(String[] args)
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
1.2.包的访问权限设置
我们目前已经学习了public和private两个访问权限,一个表示公开,一个表示私密。
而在在访问权限中,还有一种叫default的访问权限,被default修饰的成员,只能在本包中使用,一旦离开了本包,则无法访问。
ps:default在书写时可以不写。
下面的代码则是一个例子,其中Demo1和Demo是在同一个包中,Test是在其他包中。
Demo1.java
package com.bit.demo;
public class Demo1
int value = 0;
Demo2.java
package com.bit.demo;
public class Demo2
public static void Main(String[] args)
Demo1 demo = new Demo1();
System.out.println(demo.value);
// 执行结果, 能够访问到 value 变量
10
Test.java
import com.bit.demo.Demo1;
public class Test
public static void main(String[] args)
Demo1 demo = new Demo1();
System.out.println(demo.value);
// 编译出错
Error:(6, 32) java: value在com.bit.demo.Demo1中不是公共的; 无法从外部程序包中对其进行访问
2.继承
在代码中创建类,主要是为了抽象现实中的一些事物(包括属性和方法)。
但有时这些类之间会存在一些联系,比如两者是包含的关系,或者是共有一部分代码。
例如,设计一个类表示动物:
// Animal.java
public class Animal
public String name;
public Animal(String name)
this.name = name;
public void eat(String food)
System.out.println(this.name + "正在吃" + food);
// Cat.java
class Cat
public String name;
public Cat(String name)
this.name = name;
public void eat(String food)
System.out.println(this.name + "正在吃" + food);
// Bird.java
class Bird
public String name;
public Bird(String name)
this.name = name;
public void eat(String food)
System.out.println(this.name + "正在吃" + food);
public void fly()
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
能很明显的看出,这段代码之间有很多冗余的代码。
而这及各类都有一定的关联关系:
1.这三个类都具备一个相同的 eat 方法,而且行为是完全一样的。
2.这三个类都具备一个相同的 name 属性,而且意义是完全一样的。
3.从逻辑上讲,Cat 和 Bird 都是一种 Animal (is - a 语义)。即表示Cat is an animal和Brid is an animal。
于是,我们可以使用继承,让Cat和Brid类分别继承Animal类,使得Cat和Brid类可以使用Animal类,从而达到了代码重用的效果。
此时,Animal 这样被继承的类,我们称为父类(或基类、超类),对于像 Cat 和 Bird 这样的类,我们称为子类(或派生类),和现实中的儿子继承父亲的财产类似,子类也会继承父类的字段和方法,以达到代码重用的效果。
2.1.语法规则
使用extends关键字来表示子类继承父类。
class 子类 extends 父类
//属性
//方法
注意事项:
1.Java中,一个子类只能继承一个父类。
2.子类会继承父类中所有public的属性和方法,而private的属性和方法因为出了父类后便无法访问,因此就算是继承,也同样无法访问。
3.对于子类的实例,也包含着父类的实例,因此可以使用super关键字来得到父类实例的引用。
于是,我们便可以对之前的代码,使用继承来进行改进:
class Animal
public String name;
public Animal(String name)
this.name = name;
public void eat(String food)
System.out.println(this.name + "正在吃" + food);
class Cat extends Animal
public Cat(String name)
// 使用 super 调用父类的构造方法.
super(name);
class Bird extends Animal
public Bird(String name)
super(name);
//编写Brid属于自己的fly方法
public void fly()
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
public class Test
public static void main(String[] args)
Cat cat = new Cat("小黑");
//cat调用继承来的eat方法
cat.eat("猫粮");
Bird bird = new Bird("圆圆");
//brid调用继承来的eat方法
bird.fly();
2.2.protect关键字
但若我们将属性设置为private,子类则无法访问,但若将属性设置为public,又违背了封装的初衷。
于是,我们可以使用protected关键字。
1.对于类的调用者来说,protected修饰的属性和方法时不可以访问的。
2.但对于子类和同一个包中的其他类来说,protected修饰的属性和方法还是可以正常访问。
例:
// Animal.java
public class Animal
protected String name;
public Animal(String name)
this.name = name;
public void eat(String food)
System.out.println(this.name + "正在吃" + food);
// Bird.java
public class Bird extends Animal
public Bird(String name)
super(name);
public void fly()
// 对于父类的 protected 字段, 子类可以正确访问
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
// Test.java 和 Animal.java 不在同一个 包 之中了.
public class Test
public static void main(String[] args)
Animal animal = new Animal("小动物");
System.out.println(animal.name); // 此时编译出错, 无法访问 name
2.3.访问权限总结
1.private: 类内部能访问,只要出了此类便无法访问。
2.默认(也叫包访问权限):类内部能访问,同一个包中的类可以访问,其他类不能访问。
3.protected:类内部能访问,子类和同一个包中的类可以访问,其他类不能访问。
4.public:类内部和类的调用者都能访问。
注意事项:
我们在编写有关于类的代码时,要时刻注意并尽量做到“封装”,只将必要的信息给类的使用者。于是,我们在使用时要使用尽可能严格的权限,例如若一个方法可以使用使用private时,就应该尽量不要使用public。
2.4.final关键字
当final关键字用于修饰属性时,表示常量(不可被修改),也因此常常和static一同使用。
而final关键字用于修饰类时,表示限制此类被继承,即表示此类不可被继承。
ps:Java中部分自带的类即被final修饰,于是不可被继承,例如常用的String类。
例:
final public class Animal
...
public class Bird extends Animal
...
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
3.多态
3.1.向上转型
在之前的例子中,我们已经写出了如下代码:
Bird bird = new Bird("圆圆");
但这个代码还有其他的写法:
Bird bird = new Bird("圆圆");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");
此时 bird2 是一个父类 (Animal) 的引用,指向一个子类 (Bird) 的实例。这种写法称为向上转型。
向上转型的写法可以结合is-a语义来理解,即Brid is an animal。
可以理解为我们在叫那只鸟的时候,可以直接叫它“鸟”,也可以直接叫它“动物”,因为结合语境,我们可以联想到此时叫的动物就是这只鸟,而不会理解位是其他动物。
至于为什么叫“向上转型”?
因为在程序设计时,对于复杂的情况,程序员会通过画图来梳理每个类之间的关系,而父类通常都会被画在上方,子类画在下方,于是创建子类的对象由父类应用接收就会表现为向上指向,因此被称为向上转型。
3.2.动态绑定
在Java中,调用某个类的方法时,要看这个引用指向的是父类还是子类,即new的是什么。这个过程是程序在运行的时候决定的(不是在编译期),被称为动态绑定。
// Animal.java
public class Animal
protected String name;
public Animal(String name)
this.name = name;
public void eat(String food)
System.out.println("我是一只小动物");
System.out.println(this.name + "正在吃" + food);
// Bird.java
public class Bird extends Animal
public Bird(String name)
super(name);
public void eat(String food)
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
// Test.java
public class Test
public static void main(String[] args)
Animal animal1 = new Animal("圆圆");
animal1.eat("谷子");
Animal animal2 = new Bird("扁扁");
animal2.eat("谷子");
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子
通过这段代码可以看出:因为animal1是指向Animal类的实例,所以animal.eat()实际上调用的是父类的方法;而animal2是指向Brid类的实例,所以在调用eat()方法的时候,调用的是Brid类的方法。
即:一个对象有是否有方法是由类型来决定(由在实例化对象时的类型决定),而实际调用相应方法的时候,是由指向相应类来决定(为new的类决定)。
3.3.方法重写
对于上述例子中的eat方法来说,子类实现父类的同名方法,并且参数的类型和个数完全相同,这种情况被称为是重写(也被称为覆写或覆盖)。
注意事项:
1.重写和重载并不相同。
2.普通方法可以重写,但static修饰的静态方法不能重写。
3.重写中子类的方法访问权限不可以低于父类的方方访问权限。
4.重写的方法返回值不一定和父类的方法相同(但最好是相同)。
例:
// Bird.java
public class Bird extends Animal
@Override
private void eat(String food)
...
在上述的代码中,@Override是用来注解此方式是重写的方法。有了这个注解,能帮我们进行一些合法性的检验。例如不小心将方法名字拼写错误(如写成了aet),那么此时编译器会发现父类中没有aet方法,会编译报错,提示无法构成重写。
3.4.理解多态
上述提到的动态绑定和重写就是多态的基础,于是我们可以来试着写一下有关于多态的代码了。
class Shape
public void draw()
// 啥都不用干
class Cycle extends Shape
@Override
public void draw()
System.out.println("○");
class Rect extends Shape
@Override
public void draw()
System.out.println("□");
class Flower extends Shape
@Override
public void draw()
System.out.println("♣");
/我是分割线//
// Test.java
public class Test
public static void main(String[] args)
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
// 打印单个图形
public static void drawShape(Shape shape)
shape.draw();
在这段代码中,分割线上方的代码是类的实现者编写的,而分割线下方的代码时类的调用者编写的。
当类的调用者在编写drawMap方法的时候,参数的类型是Shape(父类),此时在该方法内部并不知道,也不关注当前shape引用指向的是哪个类型(哪个子类)的实例。此时shape这个引用调用draw方法可能会有多种不同的表现(和shape对应的实例相关),这种行为被称为多态。
使用多态的好处:
1.类的调用者的使用成本进一步降低。因为多态可以让类的调用者连这个类的类型是什么都不用知道,只需要知道这个对象具体有某个方法即可。
2.能降低代码的“圈复杂度”,避免使用大量的if-else。
3.代码的可拓展能力更强。因为对于类的调用者来说,只要创建一个新类的实例就可以了,改动成本很小。
3.5.super关键字
前面的代码中由于使用了重写机制,调用到的是子类的方法。如果需要在子类内部调用父类方法怎么办?可以使用
super 关键字。
super关键字表示获取到父类实例的引用。有有两种常见的用法:
(1)使用super来调用父类的构造
public Bird(String name)
super(name);
(2)使用super来调用父类的普通方法
public class Bird extends Animal
public Bird(String name)
super(name);
@Override
public void eat(String food)
// 修改代码, 让子调用父类的接口.
super.eat(food);
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
在这个代码中,如果在子类的eat方法直接调用eat(不加super),那么此时会被认为是调用子类自己的eat(也就是递归了)。而加上super关键字后,才是调用父类的方法。
4.抽象类
4.1.语法规则
在编程中,将没有实际工作的方法,可以将它设计成一个抽象方法,包含抽象方法的类我们陈伟抽象类。
abstract class Shape
abstract public void draw();
1.在draw方法钱加上abstract关键字表示这是一个抽象方法。同时抽象方法没有方法体(即没有,不能执行具体的代码)。
2.对于包含抽象方法的类来说,必须假话是哪个abstract关键字,来表示这是一个抽象类。
注意事项:
1.抽象类不能直接实例化。
2.抽象类的权限不能是private。
3.抽象类中可以包含其他的非抽象方法,也可以包含字段。这个非抽象方法和普通方法的规则都是一样的,可以被重写,也可以被子类直接调用。
4.2.作用
抽象类的存在的最大意义就是为了被继承。
抽象类本身不能被实例化,想要使用,只能创建该抽象类的子类,然后让子类重写类中的抽象方法。(非抽象方法可以不重写)
使用抽象类的作用,主要是为了相当于多一重编译器的校验:使用抽象类的场景,实际的工作不应该由父类来完成,而应该由子类来完成,那此时若不小心误用成了父类,使用不同类编译时是不会报错的,但若父类是抽象类,则会在实例的时候提示错误,可以让我们尽早的发现问题。
5.接口
接口是抽象类的进一步体现。因为接口中的方法都是抽象方法,字段只能包含静态常量。
1.使用 interface 定义一个接口。
2.接口中的方法一定是抽象方法,因此可以省略 abstract。
3.接口中的方法一定是 public,因此可以省略 public。
4.Cycle 使用 implements 继承接口,此时表达的含义不再是 “扩展”,而是 “实现”。
5.在调用的时候同样可以创建一个接口的引用,对应到一个子类的实例。
6.接口不能单独被实例化。
例:
interface IShape
void draw();
class Cycle implements IShape
@Override
public void draw()
System.out.println("○");
public class Test
public static void main(String[] args)
IShape shape = new Rect();
shape.draw();
注意事项:
1.在编写接口的时候,方法和属性钱可以不带public,static,final和abstract,因
以上是关于Java的面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章