前言:向对象编程(OO,ObjectOriented,面向对象)是以对象为中心,以类和继承为构造机制的软件开发系统方法,是20世纪90年代软件开发方法的主流。到了现在,他依然是我们学习Java路上的一个必经之路。
本篇我们来介绍包、继承、组合、多态、抽象类、接口。
每文一图:
这可能就是面向对象编程吧! 面向对象编程:- 一.包
- 1.导入包中的类
- 2.将类放到包中
- 3.包的访问权限控制
- 二.继承
- 1.语法规则
- ① 基本语法
- ② super关键字
- ③子类和父类重名
- 2. protected 关键字
- 3.final关键字
一.包
什么是包?豆沙包还是叉烧包?不不不,这是Java的包!
包 (package) 是组织类的一种方式,使用包的主要目的是保证类的唯一性。
例如, 你在代码中写了一个 Test 类,然后你的同事也可能写一个 Test 类,如果出现两个同名的类,就会冲突,导致代码不能编译通过。所以我们就把属于自己的类打个包,你包里装的Test是你的Test,我包里是我的Test。
1.导入包中的类
其实,Java 中已经提供了很多现成的类供我们使用,也就是Java中已经被写好了实现了某一功能的代码,例如:
可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是上面这种写法很复杂,每一次引入Java中的类都要调用相应的类名,这种写法比较麻烦一些, 所以我们可以使用 import 语句导入包:
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用 java.util 中的其他类,但是又不记得是哪个引用,可以使用 import java.util.*,这是调用util底下所有的类,当然我们也不用担心像C语言一样引头文件就加载到代码中,从而加载整个包的内容。对于Java中,它会智能的引我们需要的类,不会造成空间的浪费。
例子:
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是我们更建议显式的指定要导入的类名,否则还是容易出现冲突的情况,因为不同的包中看可能存在相同的类。比如:
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
注意事项: import 和 C++ 的 #include 差别很大。C++ 必须 #include 来引入其他文件内容,但是 Java不需要。import 只是为了写代码的时候更方便,import 更类似于 C++ 的 namespace 和 using。
然后我们还有一个概念叫做静态导入,也就是在使用 import static 可以导入包中的静态的方法和字段,这样子达到一个静态导入的效果,在使用的时候就相当于不需要写静态类的名称:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
//直接省略了System
}
}
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);
}
}
静态类调用的静态属性或者静态方法可以省略不写,但一般情况下比较少用,这一方面的知识了解即可。
2.将类放到包中
介绍完Java中自己的包自己的类,那么就要到我们自己去创建自己的包了,怎么创建自己的包呢,我们有下面的基本方法:
基本规则:
1.在文件的最上方加上一个 package 语句指定该代码在哪个包中。包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如com.bit.demo1 )。
2.包名要和代码路径相匹配。例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码。
3.如果一个类没有 package 语句,则该类被放到一个默认包中。
我们在IDEA中创建一次给大家演示一下:
1)在 IDEA 中先新建一个包: 打开IDEA -> 右键 src -> 新建 -> 包
2)在弹出的对话框中输入包名, 例如 com.bit.demo1
3)在包中创建类, 右键包名 -> 新建 -> 类,然后输入类名即可。
4)此时我们看见在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句。
3.包的访问权限控制
对于创建好自己的包,我们就要探讨一下访问权限的事情了,我们已经了解了类中的 public 和 private,private 中的成员只能被类的内部使用。
如果某个成员不包含 public 和 private 关键字,此时这个成员可以在包内部的其他类使用,但是不能在包外部的类使用。也就是默认 包访问权限。
比如我们在包demo中的一个类中定义一个val,在demo包中创建另一个类,他还是可以访问的,但是在另一个包中创建的类里面就不能访问。
package com.bit.demo;
public class Demo1 {
int value = 0;//创建val变量 默认包访问权限
}
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中不是公共的; 无法从外部程序包中对其进行访问
}
}
那么对于包我们就了解到这里,下面是我们一些常用的包,大家也可以自己去研究一下其他的包。
| 常见的系统包 | 用途 |
|---|---|
| java.lang | 系统常用基础类(String、Object),此包从JDK1.1后自动导入。 |
| java.lang.reflect | java 反射编程包; |
| java.net | 进行网络编程开发包。 |
| java.sql | 进行数据库开发的支持包。 |
| java.util | 是java提供的工具程序包。(集合类等) 非常重要 |
| java.io | I/O编程开发包。 |
二.继承
面向对象的基本特征中,有:
封装:不必要公开的数据成员和方法 使用private关键字进行修饰。
意义:安全性。
继承:对共性的抽取,使用extends关键字进行处理。
意义:可以对代码进行重复使用。
代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法),有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联。
什么意思呢,也就是说,我们的一些客观事物存在一定的联系。那么在代码的世界中,使用我们如果写代码的时候每一个都去描述,那就会有很多相同的代码,造成代码的冗余。
例如, 设计一个类表示动物
注意,我们可以给每个类创建一个单独的 java 文件。类名必须和 .java 文件名匹配(大小写敏感)。
// 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 + "正在飞 ︿( ̄︶ ̄)︿");
}
}
这样子的类中,明明动物都会吃东西,然后我们在写代码的时候就会变得十分冗余,而仔细发现,我们的这几个代码是有相似之处的,存在一定的关联关系。
这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的。
这三个类都具备一个相同的 name 属性, 而且意义是完全一样的。
从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义)。
这时候就用到我们的继承了,此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果。
此时, Animal 这样被继承的类,我们称为父类,基类或超类,对于像 Cat 和 Bird 这样的类,我们称为子类,派生类和现实中的儿子继承父亲的财产类似,子类也会继承父类的字段和方法,以达到代码重用的效果。
1.语法规则 ① 基本语法
class 子类 extends 父类 {
}
1.使用 extends 指定父类
对于继承父类,我们使用一个关键字extends,表示子类 继承 父类。
2.Java 中一个子类只能继承一个父类
在Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承),子类会继承父类的所有 public 的字段和方法。
3.对于父类的 private 的字段和方法,子类中是无法访问的。
对于父类的 private修饰的东西,子类无法访问,这是父类私有的,比如生活中,我们可以继承很多父亲的东西,但也有一些东西是无法继承的。
② super关键字
上面的规则还有第四点:
4.子类的实例中,也包含着父类的实例。可以使用 super 关键字得到父类实例的引用。
这个是什么意思呢?这个其实就是构造的事情,当子类构造的时候,要先帮父类构造,这个构造就是构造方法。
比如说:
//父类
public class Animal {
public String name;
public Animal (String name){
this.name = name;//构造方法
}
public void eat(String food){
System.out.println(name+"爱吃"+food);
}
}
//子类
class Dog extends Animal{
//报错
}
因为在子类中,并没有帮父类构造方法,所以会导致报错,当我们需要父类构造方法的时候,子类就需要帮助父类,使用super关键字。
而且super只能在当前类的构造方法中使用,在外面是不行的。且super不能出现在静态方法当中
正确代码:
public class Animal {
public String name;
public Animal (String name){
this.name = name;
}
public void eat(String food){
System.out.println(name+"爱吃"+food);
}
}
class Dog extends Animal{
public Dog(String name){
super(name);
}
}
那么问题来了,当我们没有写构造方法的时候为什么也没有报错呢,因为当我们没有写构造方法的时候,系统默认生成一个构造方法,而对于子类也一样,会生成一个有super关键字的方法。
super关键字中其实用法也和this差不多:
1.super();
2.super.func();
3.super.data;
③子类和父类重名
当我们的子类和父类有重名的成员属性或者方法的时候,优先使用的是子类自己的,除非我们在使用的时候加上super.的前缀。
比如这一段代码:
class Animal {
public String name = "hello";
public int age;
protected int count;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name+"eat()");
System.out.println(count);
}
}
class Bird extends Animal{
public String wing;
public String name;//null
public Bird(String name,int age,String wing) {
super(name,age);
this.wing = wing;
}
public void fly() {
System.out.println(super.name+"fly()"+age);
}
}
我们在内存中是这样子的:
2. protected 关键字
刚才我们发现,如果把字段设为 private,子类不能访问。但是设成 public,又违背了我们 “封装” 的初衷,两全其美的办法就是 protected 关键字。
对于类的调用者来说, protected 修饰的字段和方法是不能访问的
对于类的子类和同一个包的其他类来说, protected 修饰的字段和方法是可以访问的
比如:
// Animal.java
public class Animal {
protected String name;//protected修饰的成员变量
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
}
}
那么对于这些卡范围的关键字,这里有一张表,可以很直观的看出各个关键字所修饰的限制范围:
| No | 范围 | private | default | protected | public |
|---|---|---|---|---|---|
| 1 | 同一个包中的同一个类 | √ | √ | √ | √ |
| 2 | 同一个包中的不同类 | √ | √ | √ | |
| 3 | 不同包中的子类 | √ | √ | ||
| 4 | 不同包中的非子类 | √ |
1.private: 类内部能访问,类外部不能访问
2.default(也叫包访问权限): 类内部能访问,同一个包中的类可以访问,其他类不能访问。
3.protected: 类内部能访问,子类和同一个包中的类可以访问,其他类不能访问。
4.public : 类内部和类的调用者都能访问。
那为什么会有protected关键字呢,因为我们在继承当中,如果我们写private定义的变量,虽然安全,但什么都访问不了,如果我们用public,那谁都能来看看,又不安全,所以这两者都太激进了,最后我们有了protected关键字。
对于类内部能访问,子类和同一个包中的类可以访问,其他类不能访问。也就是说,除了是它的子类,否则只有在自己的包中的类才可以访问,这样子就即安全,又可以达到在任何地方都可以继承父类了。
问题:什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”,即隐藏内部实现细节,只暴露出 必要 的信息给类的调用者,因此我们在使用的时候应该尽可能的使用比较严格的访问权限. 例如如果一个方法能用 private,就尽量不要用public。另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private,将所有的方法设为 public。
3.final关键字
刚才我们的例子中, 只涉及到 Animal, Cat 和 Bird 三种类. 但是如果情况更复杂一些呢?针对 Cat 这种情况, 我们可能还需要表示更多种类的猫:
所以,我们的继承中可以无限叠下去,a继承b,b继承c,c继承d…但是我们有一个不成文的约定:一般我们不希望出现超过三层的继承关系.,如果继承层次太多, 就需要考虑对代码进行重构了。
如果想从语法上进行限制继承, 就可以使用final 关键字!
final 关键字:修饰一个变量或者字段的时候, 表示 常量 (不能修改),也能修饰类, 此时表示被修饰的类就不能被继承。
final 关键字的功能是 限制 类被继承,“限制” 这件事情意味着 “不灵活”. 在编程中,灵活往往不见得是一件好事,灵活可能意味着更容易出错。所以我们就可以使用final让他忍一忍。
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
//Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
平时我们定义的String也是final修饰的变量, 不能被继承。
final的用处:
1.一个类不想被继承,就用final去修饰
2.修饰常量 final int a = 10 ,不可以被修改的常量
3.修饰类 final class A ,代表整个类不可以被继承
4.修饰方法 final
所以,当我们想让这个类不再被继承,就可以用final去修饰它了。
这就是本篇Java中的面向对象编程1的全部内容啦,关于面向对象的学习,一篇肯定是讲不完滴,欢迎关注。一起学习,共同努力!也可以期待这个系列接下来的博客噢。
链接:都在这里! Java SE 带你从零到一系列
还有一件事:



