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,因为在接口中,会自动默认。
2.对于字段来说,接口中只能包含静态常量(final static)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值