面向对象编程
面向对象编程基本可分为包,集成,组合,多台,抽象类,接口。
1.包是组织类的一种方式,使用包的主要目的是保证类的唯一性。
(1)导入包中的类
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
在Java中不仅可以使用Java.util.Data这种方式来引入这个包当中的data类,也可以使用import语句来导入包。如果需要使用Java.util中的其他类,可以使用import java.util.,其中号代表这个包中的所有类。
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
这个运行的结果代表的是格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数.
如果两个包中都存在某一个相同的类,如果两个包同时引用,则会出现编译出错的情况,会冲突,此时必须使用完整的类名。
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
(2) 静态导入
使用import static可以导入包中的静态方法和字段。
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
此时可以将System直接省去,可以很好地简化代码。
(3) 将类放到包中
规则:在文件的最上方加上一个package语句指定该代码在哪个包中。包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式,例如com.baidu.demo.
包名要和代码路径相匹配。如果一个类没有package语句,则该类被放到一个默认的包中。
(4) 包的访问权限控制
如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用.
demo1和demo2是同一个包中,test是其他包中.
package com.bit.demo;
public class Demo1 {
int value = 0;
}
package com.bit.demo;
public class Demo2 {
public static void Main(String[] args) {
Demo1 demo = new Demo1();
System.out.println(demo.value);
}
}
// 执行结果, 能够访问到 value 变量
10
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中不是公共的; 无法从外部程序包中对其进行访问
常见的系统包分为以下几类:
1).Java.lang:系统常用的基础类(string,Obejcet).
2).Java.lang.reflect:Java反射编程包;
3).Java.net:进行网络编程开发包。
4).Java.sql:进行数据库开发的支持包。
5).java.util:Java提供的工具程序包。
6).Java.io:I/O编程开发包。
2.继承
(1)代码中创建的类,主要是为了抽闲现实中的一些事物,有的时候客观事物之间就存在一些关联关系,那么在表示成类和对象的时候也会存在一定的关联。
基本语法:
class 子类 extends 父类 {
}
使用extends指定父类,Java中一个子类智能集成一个父类(C++,Python等语言支持多继承),子类会集成父类的所有public的字段和方法,对于父类的private的字段和方法,子类中是无法访问的,子类的实例中,也包含着父类的实例,此时可用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);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("猫粮");
Bird bird = new Bird("圆圆");
bird.fly();
}
}
类的继承,也可以理解成基于父类进行代码上的扩展,上述缩写的bird类,就是在animal的基础上扩展除了fly方法。
(2) protected关键字
如果把字段设为private,子类则不能访问,设置为public,不能体现封装的意义,此时可以用protected关键字。对于类的调用者来说,protected修饰的字段和方法是不能访问的,对于类的子类和同一个包中的其他类来说,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
}
}
四种关键字能访问的4种不同方式的访问权限如下:
上述的例子只是简单的一种继承情况,猫的种类有很多种,这就涉及到了多层继承,子类还可以进一步的再派生出新的子类。
(3) final 关键字
final关键字修饰一个变量或者字段的时候,表示常量不能被修改,final关键字也能修饰类,此时表示被修饰的类不能被继承。final 关键字的功能是 限制 类被继承
“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的.
3. 组合
组合和继承类似,也是一种表达类之间关系的方式,能够达到代码重用的效果。
例如一个学校由老师和学生组成。
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法,利于extend这样的关键字,仅仅是将一个类的实例作为另一个类的字段。
4.多态
(1) 向上转型
向上转型发生的时机:直接赋值,方法传参,方法返回。
直接赋值代码示例:
Bird bird = new Bird("圆圆");
Animal bird2=bird;
此时bird2是一个父类的引用,指向一个子类bird的实例,这种写法成为向上转型。
方法传参代码示例:
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
feed(bird);
}
public static void feed(Animal animal) {
animal.eat("谷子");
}
}
// 执行结果
圆圆正在吃谷子
此时形参animal的类型是Animal基类,实际对应到Bird(父类)的实例。
方法返回代码示例:
public class Test {
public static void main(String[] args) {
Animal animal = findMyAnimal();
}
public static Animal findMyAnimal() {
Bird bird = new Bird("圆圆");
return bird;
}
}
此时方法 findMyAnimal 返回的是一个 Animal 类型的引用, 但是实际上对应到 Bird 的实例.
(2) 动态绑定
当给bird类也加上同名的eat方法,并且在两个eat中分别加上不同的日志。
// 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 和 animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, animal2 指向Bird 类型的实例.针对 animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而animal2.eat() 实际调用了子类的方法.
因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定.
(3)方法重写
子类实现父类的同名方法,并且参数的类型和个数完全相同,这种情况称为重写。重写和重载的区别如下表。普通的方法可以重写,static修饰的静态方法不能重写。重写中子类方法的访问权限不能低于父类的方法访问权限。例如子类中的方法由private修饰,而父类中由public修饰,此时子类无法访问父类。针对重写的方法,可以使用@override注解来显示指定。
(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 对应的实例相关),这种行为成为多态.
多态的好处可以让类调用者对类的使用成本进一步降低.能够降低代码的圈复杂度,避免使用大量的if-else,可扩展能力更强。
(5)向下转型
向上转型是子类对象转成父类对象,向下转型就是父类对象转成子类对象,相比于向上转型来说,向下转型不常见,但也有一定用途。
(6) 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关键字,才是调用父类的方法。
this和super关键字的区别如下。
3.抽象类
(1) 语法
abstract class Shape {
abstract public void draw();
}
在draw方法前加上abstract关键字,表示这是一个抽象方法,同时抽象方法没有方法体,不能执行代码。对于包含抽象方法的类,必须加上abstract关键字表示这是一个抽象类。抽象方法不能直接实例化,并且抽象方法不能是private,抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,
也可以被子类直接调用。
abstract class Shape {
abstract public void draw();
void func() {
System.out.println("func");
}
}
class Rect extends Shape {
...
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.func();
}
}
// 执行结果
func
4.接口
(1)接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量. 代码示例如下
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();
}
}
使用interface定义一个结构,并且接口中的方法一定是抽象法法且一定是public,可以同时省略public abstract。cycle使用imple继承接口,代表实现的意思,在调用的时候同样可以创建一个接口的引用,对应到一个子类的实例,接口不能单独被实例化,接口智能包含抽象方法,对于字段来说,接口中智能包含静态常量。
interface IShape {
void draw();
public static final int num = 10;
}
其中的public static final 关键字都可以省略,省略后的num仍然可以表示public的静态常量
(2) 实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.现在我们通过类来表示一组动物.
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
class Duck extends Animal implements IRunning, ISwimming, IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在漂在水上");
}
}
class Reboot implements IRunning{
@Override
public void run() {
System.out.println("机器人在跑");
}
}
public class TestDemo6 {
public static void walk(IRunning running) {
System.out.println("我带着伙伴去散步");
running.run();
}
public static void swing(ISwimming iSwimming) {
System.out.println("我带着伙伴去散步");
iSwimming.swim();
}
public static void main(String[] args) {
Duck duck = new Duck("唐老鸭");
walk(duck);
swing(duck);
Reboot reboot = new Reboot();
walk(reboot);//参数可以不是动物只要会跑。
}
}
(3) 接口间的继承
接口可以继承一个接口,达到复用的效果。使用extends关键字。
interface A {
void funcA();
}
interface B {
void funcB();
}
interface D extends A,B{//extends扩展
void funcD();
}
class F implements D{
@Override
public void funcA() {
}
@Override
public void funcB() {
}
@Override
public void funcD() {
}
}
//.........
class C implements A,B {
@Override
public void funcA() {
}
@Override
public void funcB() {
}
}
其中C 接口同时继承A和B接口,中间用,隔开。需要重写接口A和B 的方法。
下表示抽象类和接口的区别:
核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以直接被子类直接使用,不用重写。而接口中不能包含普通方法,子类必须重写所有的普通方法。