面向对象语言中都有三大属性:封装、继承、多态。
此处记录一下我对三者的理解,以后会不断修正!
封装
封装是面向对象语言的基础属性。从包、类、方法的创建中:我们一直在做一件事,将某一个功能的具体实现尽量隐藏,只提供少量的外部接口来给用户使用。现实生活中,我们使用的很多东西都是如此,比如说手机。普通用户不需要知道手机内部电路板是如何走线,芯片是如何排布,软件是如何运行的;我们只需要使用它的打电话软件、上网用的浏览器就够了。实现过程交给工程师已然足够!
包(package)
我们在引入类时,有时会出现类名相同的情况,我们需要更精确地界定类的出处。因此,包的存在必不可少,它将向我们区分相同名称的类的区别!
包的使用
首先看如下代码:
public class Demo {
public static void main(String[] args) {
java.util.ArrayList list = new java.util.ArrayList();
java.util.ArrayList list1 = new java.util.ArrayList();
}
}
我们实例化了java.util包中的ArrayList类,如果我们要是创建了自己的ArrayList,包名的介入会将两者区分。但如果我们总是带上包名引用该类的话就显得太过繁杂;因此,我们可以使用导入命令(import
)来简化代码,当我们在导入包后就可以直接使用类名了。(如果真的存在同时调用不同包中的同名类,还是得加上包名,但这种情况比较少见):
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList list = new ArrayList();
}
}
当我们要导入包中多个类时,可以使用包名.*
:
import java.util.*;
public class Demo {
public static void main(String[] args) {
ArrayList list = new ArrayList();
}
}
这样我们就能够直接使用java.util
中的所有类了。
包的创建
程序员一般将自己创建的轮子保存在本公司的包中以待以后使用,避免重复工作。为了防止包名也重复,大家默认包名一般以公司域名倒写+项目名来命名,如果没有域名,那就起一个非常难见到的包名。比如说我要创建一个包,可以命名为online.suiyu.test
要注意包名一般都是小写的。
那我们如何在代码中实现呢?看下方代码:
package online.suiyu.test;
public class Demo {
public static void main(String[] args) {
System.out.println("hello");
}
}
这样,我们就将Demo.java
放入到了online.suiyu.test
包中。但这还没结束,我们需要使用如下方法编译源文件:
javac -d . Demo.java
这样,我们就编译了包与类文件。此时我们查看文件将会发现当前目录下存在如下目录:
D:.
├─online
│ └─suiyu
│ └─test
这是包的表现形式,Demo.java就存在于test文件夹中!当然,如果想保存到其他路径可以直接指定目录即可:
//将包保存到D:\java目录
javac -d D:\java DemoClass.java
如果使用eclipse的话,直接在创建类的时候也能指定包,不指定的话就是default包。
JAR包的创建与使用
jar包其实也是压缩包的一种,可以使用压缩软件打开。
在命令行中,输入jar
,可以看到以下提示
$ jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] fi les ...
选项:
-c 创建新档案
-t 列出档案目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
jar cvfm classes.jar mymanifest -C foo/ .
根据提示:
压缩指定文件(多个文件或文件夹):
jar -cf Demo.jar online
生成了名为Demo.jar
的jar包。
查看jar包中的文件列表
$ jar -tf Demo.jar
META-INF/
META-INF/MANIFEST.MF
online/
online/suiyu/
online/suiyu/test/
online/suiyu/test/Demo.class
可以看到已经压缩的文件夹与文件(其中添加了一些特殊文件及文件夹)
运行jar中的class文件
$ java online.suiyu.test.Demo
hello
和没压缩一样,只要指定包名+类名直接使用就行了。
类
类的存在隐藏了具体实现,防止别人更改代码,更加安全!
类中使用以下控制符控制访问权限:
private | default | protected | public | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中 | √ | √ | √ | |
子类中 | √ | √ | ||
全局范围内 | √ |
继承
简单介绍
当我们创建了类时,就已经完成继承了,因为所有类的标准根类都是Object
类,编译器会自动完成继承的过程。我们要实现继承的话一般使用extends
关键字。这里记录一般类的特点:
- 就像是人都有一个亲生父亲,而一个父亲可以有很多儿子一样,类可以有很多子类,但只能有一个父类
- 子类继承父类后,可以拥有父类的方法和属性,并且可以添加、覆盖、重写父类的方法
- 子父类的继承受类的控制符约束
- 父类有非默认构造器时,子类也必须写出构造器,并在子类构造器第一行调用
super
来加载父类构造器 - 子类可以在重写父类方法后依然调用父类方法,需要加入
super
区分;为了区分调用的是自己的方法可以加上this
(也可省略) - 父类与父类的父类等都是子类的基类型
举例介绍
水果的重量、颜色、种类不同。其中苹果就是其中一个种类。
代码实现
class Fruit {
public double weight;
String color ;
Fruit(double weight, String color) {
this.weight = weight;
this.color = color;
}
protected void info() {
System.out.println("这个水果是" + color + "的,有" + weight + "kg");
}
}
class Apple extends Fruit {
Apple(double weight, String color) {
super(weight, color);
}
//重写
public void info() {
System.out.println("这个苹果是" + color + "的,有" + weight + "kg");
}
// 重载
void info(String addInfo) {
System.out.println("这个苹果是" + color + "的,有" + weight + "kg," + addInfo);
}
void anotherInfo() {
this.info();
System.out.print("对于上面这句话我们也可以说成");
super.info();
}
}
public class Test {
public static void main(String[] args) {
new Fruit(10.5, "紫色").info();
new Apple(0.5, "红色").info();
new Apple(0.6, "红色").info("我最喜欢吃苹果!");
new Apple(0.3, "青色").anotherInfo();
}
}
运行结果:
这个水果是紫色的,有10.5kg
这个苹果是红色的,有0.5kg
这个苹果是红色的,有0.6kg,我最喜欢吃苹果!
这个苹果是青色的,有0.3kg
对于上面这句话我们也可以说成这个水果是青色的,有0.3kg
多态
简单说明
继承允许将对象视为它自己本身的类型或其基类型来加以处理。比如上方代码中苹果可以被当做苹果同时也可以被当做水果来处理!而多态的存在能够消除类型之间的耦合性,并且能够改善代码的组织结构和可读性,同时它还能使得后续的代码随着后续的需求不断"生长"!
多态的建立要满足三个条件:继承、重写、向上转型!
向上转型就是把某个对象的引用视为对其基类型的引用!
举例说明
我们还拿学生的例子来说。小明是一个光荣的小学生,他喜欢和他们班的同学一起去读书。他的邻居大明是一名大学生,很喜欢旅游。
代码实现
//Test.java
abstract class Student {
public abstract void learnMath();
}
class Pupil extends Student {
public void learnMath() {
System.out.println("我是一个小学生,我们学小学数学");
}
//休闲方式
void reading() {
System.out.println("我喜欢和同学一起去读书");
}
}
class Undergraduate extends Student {
public void learnMath() {
System.out.println("我是一个大学生,我们学高等数学");
}
//休闲方式
void tour() {
System.out.println("我喜欢和同学一起去旅游");
}
}
public class Test {
public static void main(String[] args) {
aboutMe(new Pupil());
System.out.println();
Undergraduate daHua = new Undergraduate();
aboutMe(daHua);
}
public static void aboutMe(Student stu) {
stu.learnMath();
if (stu instanceof Pupil) {
Pupil pu = (Pupil)stu;
pu.reading();
} else if (stu instanceof Undergraduate) {
((Undergraduate)stu).tour();
}
}
}
运行结果
我是一个小学生,我们学小学数学
我喜欢和同学一起去读书
我是一个大学生,我们学高等数学
我喜欢和同学一起去旅游
代码解析
使用了aboutMe
函数来对象的相关行为。形参Student
是两个实参Pupil
与Undergraduate
的基类。因此,当两个子类对象分别传入进来后就实现了向上转型,此时就相当于执行了如下代码:
Student stu = new Pupil();
Student stu = new Undergraduate();
Student stu = new Pupil();
中Student stu
会在栈中创建一个基类的基本变量,它指向右边子类new Pupil()
创建的对象的内容!
看如下图,子类和父类都有learnMath
方法,我们调用stu.learnMath
时,其实也是调用xiaoming.learnMath
①。但当我们调用stu.reading
时,由于父类中没有该方法,因此,他也不会有索引指向子类的reading
方法,就会出错。如果我们想要调用该方法,应该将父类强制向下转型成子类,此时就有了②③。这就是Test.java
中的aboutMe
方法实现原理。一个方可可以处理不同的类,这就是多态的有点。为什么说多态能够使代码不断"成长"呢?因为现在即使我们再加入其它类(但必须是Student的子类),比如说研究生也可以直接使用aboutMe
方法!
此处的代码解析可能有些欠妥,不过便于理解,以后会逐步修正!
小例题
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A {
public String show(B obj) {
return ("B and B");
}
public String show(A obj) {
return ("B and A");
}
}
class C extends B {
}
class D extends B {
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
以上会输出什么?
答案:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
此题引用于该博客,该博主讲的够全面了,可以跳过去看看。在这说一些我的解题思路:拿4来说吧,a2.show(b)
1、我们需要找到子父类(A,B)都已经存在的方法,要知道,多态中父类没有的方法,即使子类有,我们也不会调用,此处找到show(A obj)
。
2、此处子类的方法覆盖了父类的方法。我们传递的参数是b(class B),b的基类是A。多态中子类可以当做基类使用,所以会调用子类中的show(A obj)
方法!
总结起来就是:父子都有才可调用,优先调用子类方法,形参子类可当父类!
本人小白一个,欢迎访问我的个人博客,同时也欢迎来相互交流学习!