文章目录
1.包
在Java中,包是组织类的一种方式。
使用包的主要目的是保证类的唯一性。
例如,你在代码中写了一个Test类,你的同事也写了一个Test类,当两个同名的类出现时,就会产生冲突。
1.1 import和package的区别
在Java中,当我们需要用到某一个具体的类时,就需要用到import关键字;有时我们会看到一个类的最上面会有package…字段,表示的是当前类存放在哪一个包中,即存放在哪一个文件夹中。
例如:
package demo2;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
代码中的package就表示当前类存放在文件夹demo2中,import引用的util包下的Date类,import还有一种引用方式,如下:
import java.util.*;
这个*代表util包下的所有类,但这样写不会把这个包中的所有类都引入进来,而是在你后续的代码中用到了其中的哪一个类,他就会自动引入对应的类。(这里与C语言中的include不一样,C语言中的include引入一个包会将整个包都引入进来)
但是*这种引用方式也有不好的地方,对于Date类,不止util包中有,如果我们同时引用两个包含Date类的包,这时候系统就不知道该调用哪一个,就会出现错误。
1.2 静态导入
使用import static
可以导入包中的静态的方法和字段,例如:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
一般情况下我们的输出是System.out,但是当我们引用了System包后,程序中就不用再写System了,这种静态导入不常用,并且也降低了代码的可读性。
2.继承
我们知道面向对象的三大属性是封装、继承和多态,封装是将不必要公开的成员和方法用private关键字进行修饰,这样这些成员和方法就只能在类内使用了,也保证了其安全性。
那么继承的意义是什么呢?我们来看一段代码:
//猫类
class Cat {
public String name;
public Cat(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
//鸟类
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方法,上述写法有大量重复的代码。这时候我们就可以让继承登场了,继承的意义就在于可以抽取不同类中的共性,从而减少冗余,来看下面通过继承来实现的代码:
//动物类
public 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(name);
}
}
//鸟类
class Bird extends Animal{
public Bird(String name){
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
我们称Animal类为父类、超类和基类,Cat和Bird这样的类称为子类、派生类。
语法
- 使用extends指定父类
- Java中一个子类只能继承一个父类
- 子类会继承父类的所有public的字段和方法
- 父类的private字段和方法,子类中无法访问
- 子类的实例中,包含着父类的实例,可以用super关键字得到父类实例的引用
2.1 访问权限
在Java中,访问权限修饰符有4个,其作用范围如上图所示,对于选择哪种修饰符需要结合具体情况。
2.2 final关键字
规则:
- final修饰变量时,表示常量,即不能修改,并且需要在创建的时候就赋初值
- final修饰类时,表示被修饰的类不能被继承
我们平时使用的String字符串类,就是用final修饰的,不能被继承。
3. 多态
3.1 向上转型
我们用上面的鸟类来创建一个鸟对象,代码应该是这样的:
Bird bird = new Bird("小鸟鸟");
这段代码也可以写成这样:
Animal bird = new Bird("小鸟鸟");
此时的bird是一个父类(Animal)的引用,指向一个子类(Bird)的实例,这种写法就称为向上转型。
向上转型发生的时机:
- 直接赋值
- 方法传参
- 方法返回
3.2 动态绑定
当子类和父类中出现同名方法时,我们调用的时候会调用哪一个呢?
我们对上面的代码稍加修改,如下:
// 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("谷子");
}
}
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子
我们发现,当用父类引用指向子类,即发生向上转型时,会调用子类的eat方法,这个过程是程序运行时决定的,不是编译时决定的,就被称为动态绑定。
3.3 方法重写
对于上面的eat方法,子类实现父类同名方法,并且参数个数和类型完全一样,就称为方法的重写、覆盖、覆写。
重写注意事项:
- 普通方法可以重写,静态方法不行
- 重写时,子类方法的访问权限不能低于父类
- 重写方法返回值类型需要一样(特殊情况,协变类型返回值类型可以不一样)
- 对于重写,可以在重写的方法上使用
@override
来注解
重写与重载的区别:
- 重载要求方法名称一样,参数的类型或个数不一样;重写要求方法名称、返回值类型、参数个数及类型完全一样
- 重载时在一个类中实现的;重写是在继承关系中实现的
- 重载对于权限没有要求;重写要求子类重写方法的访问权限必须大于等于父类
3.4 理解多态
有了向上转型、动态绑定和方法重写后,我们就可以使用多态了。来看代码:
class Animal{
public void func(){
System.out.println("hahaha"); //这个可以为空
}
}
class Dog extends Animal{
@Override
public void func() {
System.out.println("我是小狗");
}
}
class Cat extends Animal{
@Override
public void func() {
System.out.println("我是小猫");
}
}
public class Test {
public static void print(Animal animal){
animal.func();
}
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
print(dog);
print(cat);
}
}
输出为:
我是小狗
我是小猫
在这段代码中,Test上方的代码是类的实现者编写的,下方是类的调用者编写的。当类的调用者在编写print方法时,参数类型为Animal(父类),该方法内部此时不知道、也不需要知道Animal引用要指向哪个子类,这时指向不同的子类可能就会有不同的表现,这种行为就称为多态。
多态的好处:
- 封装可以让类的调用者不需要知道类的实现细节
- 多态可以让类的调用者连这个类的类型时什么都不需要知道,只需要知道这个对象有某个方法即可,可以理解为封装的更进一步,让类的调用者对类的使用成本进一步降低
3.5 super关键字
super表示获取到父类实例的引用,有两种常见的用法。
-
使用super来调用父类的构造器
public Bird(String name) { super(name); }
-
使用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方法,如果不用super就被视为子类自己调用自己的eat方法,即递归,当加上super关键字后,就是调用父类的方法了。super关键字还可以调用父类的成员变量。
3.6 super和this的区别
- this用于访问本类中的属性和方法;super是在子类中访问父类的属性和方法
- this会先查找本类,本类中没有就会调用父类;super会直接调用父类中的属性或方法
- this是谁去调用它就指向谁,super没有这一特点