前言
一、包及访问权限
1.什么是包?
要理解什么是包,我们需要先设想一个情境——当我们在开发一个大型程序时,不同的开发人员都可能会写一个Test类,那么在这种情况下,就会发生冲突导致代码不能编译通过。这个时候就需要通过package来帮忙,而package就是包。
package时在使用多个类或接口时,为了避免名称重复而采用的一种措施。 使用包的主要目的是保证类的唯一性
package声明如下:
package package 名称;
经过package的声明之后,同一文件内的接口或类就都会被纳入相同的package中。
【注意】 1.创建包的时候,包名一定要小写。
2.创建的包,一定要在前面加上package包的路径。 3.包名要和代码路径相匹配. 例如创建 com.mrzhu.demo 的包, 那么会存在一个对应的路径 com/mrzhu/demo来存 储代码
4.如果一个类没有 package 语句, 则该类被放到一个默认包中
2.如何导入包?
通过import命令,可将某个package内的整个类导入。
例如: 在这里,大家想一个问题,如果我们每次都需要导入指定的类的话,那么代码是不是就需要写得很多了呀?
那么,我们有什么办法可以解决呢? 很简单,把名称改成*就可以了。如下: 这样的话,就导入了这个包里面的所有类。
【注意】 Java中包里面的类,是用到谁就调用谁,而不存在一下子把包里所有类都加载的情况。
3.JDK中常见的包
在JDK中,有各种实用的类,这些类按功能不同分别被放入了不同的包中,供开发者使用,常见的包如下:
- java.lang:系统常用基础类(String、Object、Math、System),此包从JDK1.1后自动导入,不需要用户手动导入。在java.lang包中还有一个子包:java.lang.reflect,用于实现java类的反射机制。
- java.net:进行网络编程开发包
- java.sql:进行数据库开发的支持包
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包
4.包的访问控制权限
在Java中有4种访问控制权限,分别是private、default、protected、public。
1.private访问控制符
如果一个成员方法或成员变量名的前面使用了private访问控制符,那么这个成员只能在这个类的内部使用。
2.default默认访问控制符
如果一个成员方法或成员变量名的前面没有使用任何访问控制符,就称这个成员所拥有的是默认的(default)访问控制符。默认访问控制符成员可以被这个包中的其他类访问。如果一个子类与其父类位于不同包中,子类也不能访问父类种的默认访问控制成员。
3.protected访问控制符
如果一个成员方法或成员变量名的前面使用了protected访问控制符,那么这个成员既可以被包中的其他类访问,也可以被不同包中的子类访问。
4.public访问控制符
如果一个成员方法或成员变量名的前面使用了public访问控制符,那么这个成员可以被所有的类访问,不管访问类与被访问类是否在同一包中。
二、继承
1.继承的基本概念
按常理说,继承一般我们会想到继承家产,继承了家产,这个家产就属于你的啦。
在Java中继承也大概是这种意思:
通过继承简化类的定义,扩展类的功能,但是只能直接继承父类中的公有属性和公有方法(继承你父亲的财产),而隐含地(不可见地)继承了私有属性(继承你父亲的基因)。
实现继承的格式:
class 子类名 extends 父类
上面看完你可能会有点懵,不急,我们通过下面例子来讲解一下:
class Animal{
public String name;
public void eat(){
System.out.println("Animal::eat()");
}
public void sleep(){
System.out.println("Animal::sleep()");
}
}
class Dog{
public String name;
public void eat(){
System.out.println("Animal::eat()");
}
public void sleep(){
System.out.println("Animal::sleep()");
}
public void run(){
System.out.println("Dog::run()");
}
}
class Bird{
public String name;
public void eat(){
System.out.println("Animal::eat()");
}
public void sleep(){
System.out.println("Animal::sleep()");
}
public void fly(){
System.out.println("Bird::fly()");
}
}
这个代码我们发现其中存在了大量的冗余代码。 仔细分析, 我们发现 Animal 和 Dog以及 Bird 这几个类中存在一定的关联关系:
- 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
- 这三个类都具备一个相同的 name 属性, 而且意义是完全一样的
- 从逻辑上讲, Dog和 Bird 都是一种 Animal
此时我们就可以让 Dog和 Bird 分别继承 Animal 类, 来达到代码重用的效果。
此时, Animal 这样被继承的类, 我们称为 父类 、基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为子类、派生类。
和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果
那么继承之后,我们的代码就变成了下面这样了:
class Animal{
public String name;
public void eat(){
System.out.println(this.name+"Animal::eat()");
}
public void sleep(){
System.out.println("Animal::sleep()");
}
}
class Dog extends Animal{
public void run(){
System.out.println("Dog::run()");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("Bird::fly()");
}
}
这样一来,代码就简单多了。
Java继承时需要注意以下几点:
- Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承),也就是说只能单继承,但是Java能多层继承,就是说A能继承B,B继承C,依次类推,但不宜超过3层继承。
- 子类会继承父类的所有 public 的字段和方法
- 对于父类的 private 的字段和方法, 子类中是无法访问的。也就是说,如果我们把上面例子中父类的name用private修饰,则无法访问,报错。
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
2.继承时方法调用顺序
class Animal{
public Animal(){
System.out.println("1.先输出");
}
}
class Dog extends Animal{
public Dog(){
System.out.println("2.后输出");
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog=new Dog();
}
}
通过上面例子我们可以看出:
子类对象在实例化时会默认调用父类中的无参构造方法,之后再调用本类中的相应构造方法。
3.super和this关键字
super关键字出现在子类中,调用父类中的构造方法。
换句话来说,super主要的功能是完成子类调用父类中的内容,也就是调用父类中的属性和方法。
子类进行构造的时候,需要先帮助父类完成构造。
class Animal{
public Animal(String name){
this.name=name;
}
}
class Dog extends Animal{
public Dog(String name){
super(name);
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog=new Dog("haha");
}
}
上述代码在子类的构造方法中,明确地指明了调用的是父类中有两个参数的构造方法。
super的用法: super——代表父类对象的引用
- super.data(); //访问父类中的属性
- super.func();// 访问父类中的成员方法
- super(); //调用父类的构造方法
this的用法之前文章提到过,this关键字详解,有兴趣可以去看看。
this——当前对象的引用
- this(); //调用本类其他构造方法
- this.data; //访问当前类中属性
- this.func(); //调用本类中的其他成员方法
4.final关键字
在Java中声明类、属性和方法时,可使用关键字final来修饰
- final标记的类不能被继承
- final标记的方法不能被子类覆写(密封方法)
- final标记的标量(成员或局部变量)即为常量,只能赋值一次。
1.final标记的变量只能赋值一次 2.final标记的类不能被继承实例 3.final标记的方法不能被子类覆写
三、多态
1.向上转型
先把下面这句话读两三遍理解一下:
对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用。而这种把对某个对象的引用视为对其基类型的引用的做法被称作为向上转型。
观察下面例子:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ");
}
}
public class TestDemo {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
bird.fly();
}
}
什么时候会发生向上转型?(向上转型发生的时机)
- 直接赋值
- 方法传参
- 方法返回
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ");
}
}
public class TestDemo {
public static Animal func() {
return new Bird("圆圆");
}
public static void main(String[] args) {
Animal animal = func();
animal.eat("虫子");
}
public static void func(Animal bird) {
bird.eat("虫子");
}
public static void main2(String[] args) {
Bird bird = new Bird("圆圆");
func(bird);
}
public static void main1(String[] args) {
Animal bird = new Bird("圆圆");
bird.eat("虫子");
}
}
【注意】
向上转型只能访问父类的方法和属性
2.动态绑定
当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
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);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal1 = new Animal("圆圆");
animal1.eat("谷子");
Animal animal2 = new Bird("扁扁");
animal2.eat("谷子");
}
}
【总结】
在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定。
3.方法重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)。
上面动态绑定的eat();方法就是重写。
我们以前学过重载,那么重写与重载有什么区别?
重写(override):
- 方法名称相同
- 返回值相同
- 参数列表相同
- 不同的类体现在继承关系上
重载(overload):
- 方法名相同
- 参数列表不同(参数的个数+类型)
- 返回值不作要求
- 在同一个类中
关于重写的注意事项:
- 需要重写的方法是不能被final修饰的。被final修饰之后,他是密封方法,不可以修改。
- 被重写的方法,访问修饰限定符一定不能是私有的
- 被重写的方法,子类当中的访问修饰限定要大于或等于父类的访问修饰符。(private<default<protected<public)
- 被static修饰的方法是不可以被重写的。
动态绑定和方法重写:
事实上, 方法重写是 Java 语法层次上的规则, 而动态绑定是方法重写这个语法规则的底层实现. 两者本质上描述的是相同的事情, 只是侧重点不同。
4.向下转型
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象
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);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
public class TestDemo{
public static void main(String[] args) {
Animal animal = new Bird("圆圆");
bird.fly();
}
}
对于 Animal animal = new Bird(“圆圆”) 这样的代码
- 编译器检查有哪些方法存在, 看的是 Animal 这个类型
- 执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型
如果想实现fly()方法的调用,则需要向下转型。
【注意】 向下转型不安全,我们可以通过instanceof 判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了。
5.理解多态
多态是一种思想
多态发生的前提:
- 父类引用,引用子类对象
- 父类和子类有同名的覆盖方法
- 通过父类引用调用这个重写的方法
使用多态的形式设计代码,我们可以写一些只关注父类的代码, 就能够同时兼容各种子类的情况。
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("这是一个?");
}
}
class React extends Shape{
@Override
public void draw() {
System.out.println("这是一个□");
}
}
public class TestDemo2 {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1=new Cycle();
Shape shape2=new React();
drawMap(shape1);
drawMap(shape2);
}
}
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和对应的实例相关), 这种行为就称为多态。
使用多态的好处:
- 类调用者对类的使用成本进一步降低
(1)封装是让类的调用者不需要知道类的实现细节. (2)多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可 - 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
(1) 圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很 多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. - 可扩展能力更强
(1)如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
四、抽象类
如果一个方法没有具体实现,那么它就可以是一个抽象方法。
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("这是一个?");
}
}
class React extends Shape{
@Override
public void draw() {
System.out.println("这是一个□");
}
}
public class TestDemo2 {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1=new Cycle();
Shape shape2=new React();
drawMap(shape1);
drawMap(shape2);
}
}
父类可修改为:
abstract class Shape {
abstract public void draw();
}
- 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体
代码 - 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类
【注意事项】
1.抽象类不能直接实例化
Shape shape = new Shape();
2.抽象方法不能是 private 的
abstract class Shape {
abstract private void draw();
}
- 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
抽象类的作用:
- 抽象类存在的最大意义就是为了被继承
- 抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
五、接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
上面抽象类的代码,我们可以做一下对比: 【注意事项】
- 使用 interface 定义一个接口
- 接口中的方法一定是抽象方法, 因此可以省略 abstract
- 接口中的方法一定是 public, 因此可以省略 public
- Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
- 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例
- 接口不能单独被实例化
- 接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static)
interface IShape {
void draw();
public static final int num = 10;
}
六、抽象类与接口的区别
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法,子类必须重写所有的抽象方法。
相同点:
- 都不能被实例化
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点:
- 接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
- 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承
- 接口强调特定功能的实现,而抽象类强调所属关系。
最后
生命不息,奋斗不止。勤勤恳恳做事,堂堂正正做人!
|