一、包
1.概念
包(package)是组织类的一种方式,在Java中的"包"就是操作系统的文件夹,声明一个包使
用package关键字。
使用包的主要目的是保证类的唯一性。(解决类同名称问题)
例如,我在代码中写了一个Test类,然后你的同事也可能写一个Test类,如果出现两个同名
的类,就会冲突,导致代码不能编译通过.
若存在多个文件夹的嵌套,使用"."分隔,
例如:package www.java.rocketclass
类的全名称:包名.类名。
2.导入包中的类
java中已经提供了很多现成的类供我们使用,我们使用import语句导入相关包中某个具体的类(import导入的是类)
1. 在www.bit.java.rocketclass.Test类中导入www包下的Animal这个类
package www.bit.java.rocketclass;
import www.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
}
}
package www;
public class Animal {
public static void main(String[] args) {
Animal animal = new Animal();
}
}
2. 导入JDK类,导入java.util包中的Date类
如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
package www.bit.java.rocketclass;
import www.Animal;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
Date date = new Date();
}
}
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
LinkedList linkedList = new LinkedList();
}
}
注意:当程序用到了两个相同名称的类
(1)此时导入类要使用类的全名称
import java.util.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
}
}
(2)2.import明确指定导入的是哪个包下的哪个类:import java.util.Date;
3.静态导入
使用 import static 可以导入包中的静态变量(属性)和静态方法 (建议使用类名称访问类中的静态属性或方法)
import static java.lang.System.out;
import static java.lang.Math.max;
public class Test {
public static void main(String[] args) {
// 类名.属性名
out.println(123);
out.println(123);
// 类名称.方法名称
int ret = max(10,20);
int ret1 = max(10,20);
out.println(ret);
}
}
4.包的访问权限
如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用.
关于访问修饰符:
权限从小到大依次是:private < default) < protected < public
private:私有,当前类的内部可见
default:啥也不写就是包权限。当前包的内部可见,不包含子包,同级目录下可见
protected:继承,不同包的有继承关系的类之间可见
public:当前项目可见
5.常见的系统包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包。
二、封装
1.特点
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就了。使用private关键字修饰成员变量或成员方法,表示私有的
访问权限从小到大依次是:private < default) < protected < public
1. 当 public 修饰的成员变量或者成员方法时, 在当前程序(项目)中都是可见的,可以直接被类 的调用者使用. 当 private 修饰的成员变量或者成员方法时, 不能被类的调用者使用,只在该类的内部使用,出了 这个类的{},对外部就隐藏了,外部不知道由其存在。
2当一个属性被private封装后,外部要使用就必须通过该类自己提供的getter和setter方法使用,(getter和setter方法就是个普通的成员方法,只是在命名上有规则)
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name){
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("caocao");
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
caocao
name: caocao age: 0
注意事项
1. getName 即为 getter 方法, 表示获取这个成员的值. setName 即为 setter 方法, 表示设置这个成员的值 。
2. 当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例 的引用. 不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
3. alt + insert (或者 alt + F12) 快速生成 setter / getter 方法. 在 VSCode 中可以使用鼠标右键 菜单 -> 源代码操作中自动生成 setter / getter 方法.
三、继承
1.概念
代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法). 有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联. 例如, 设计一个类表示动物
例如, 设计一个类表示动物 注意, 我们可以给每个类创建一个单独的 java 文件. 类名必须和 .java 文件名匹配(大小写敏感).
//Animal.java
public class Animal {
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);
}
}
//Cat.java
public class Cat {
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);
}
}
//Dog.java
public class Dog {
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);}
}
在这段代码中可以看到三个类的内部代码都一样,存在了大量的冗余代码
关系:(1)这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
(2)这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
(3)从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义).
Dog is an Animal 狗狗是动物
Cat is an Animal 猫猫是动物
当类和类之间满足一个类 is a 另外一个类,一定是存在继承关系。天然继承例如 :Bird is an Animal , Duck is an Animal, Person is an Animal
此时Dog, Cat, Bird,Duck,Person都应该是Animal的子类
当一个类继承了另一个类,另一个类中所有的属性和方法子类就天然具备了
2.语法规则
1. Java中使用extends表示类的继承。extends 英文原意指 "扩展". 而我们所写的类的继承, 也可以理解成基于父类进行代码上的 "扩展"
语法规则:
class 子类 extends 父类 {
}
当一个类继承了另一个类,另一个类中所有的属性和方法子类就天然具备了
//Animal.java
public class Animal {
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);
}
}
//Cat.java
public class Cat extends Animal {
//天然继承了Animal类中所有的属性和方法
}
//Dog.java
public class Dog extends Animal {
//天然继承了Animal类中所有的属性和方法
}
2.继承规则:
(a)要能使用继承,前提必须满足类之间的is a关系
//Dog.java
//错误
public class taidi extends Animal,Dog {
//天然继承了Animal类中所有的属性和方法
}
(b)一个子类只能使用extends继承一个父类。 (单继承)。Java中不允许多重继承,extends 后面只能跟一个父类,允许多层继承,没法当儿子,可以当孙子。多层继承:满足继承关系的类之间一-定是逻辑上垂直的关系,例如:Taidi(泰迪)既是Dog的子类也是Animal的子类
(c)子类会继承父类的所有属性和方法,显示继承(public属性和方法可以直接使用)
急式继承(private属性和方法),子类其实也继承了这个属性和方法,但是无法直接使用,需要通过父类提供的方法来访问
静态的属性和方法是归于某个类所有,当一个类继承了另一个类,所有静态属性和方法也就继承了。到底能否直接使用,还是要看权限是不是public
3.protected关键字(理解)
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 "封装" 的初衷. 两全其美的办法就是 protected 关键字
1.protected 同包下的没有关系的类之间以及不同包的有继承关系的类之间可见的。
Test和父类Animal是同一个包,没有继承关系,此时Test可使用父类中name属性;
Test类与父类Person在不同包中,没有继承关系,name在父类中,此时per .name无法访问
SubType子类和Base父类在不同包中,存在继承关系,在SubType子类可以访问父类中protected属性。
注意:如果在子类中使用父类引用(父类引用.)直接访问父类中的protected属性,这是不可以的
4.super关键字
1. super修饰属性:表示从父类中寻找同名属性
当有继承关系时, this关键字默认先在当前类中寻找同名属性,若没找到,继续向.寻找父类中是否有同名属性直接使用name。编译器默认都是this.name,
如果想访问父类中的name属性时咋办,使用super 关键字
super先从直接父类中寻找同名属性,若不存在再向上寻找
注意:不能使用super直接调用父类private权限的属性。
this与super区别:
super先从直接父类中寻找同名属性,若不存在再向上寻找
this直接从当前类中找同名属性,若不存在再向上搜索。
2.super修饰方法
(1)修饰构造方法
super(父类构造方法的参数) // 调用父类的有参构造
super(); //直接父类的无参构造可写可不写
(2)根据继承原则:
a.当有子类对象产生时,默认先产生父类对象,就要调用父类构造方法,然后才会执行子类的构造方法
b.若父类中不存在无参构造,则子类构造方法的首行必须使用super(有参构造),显示调用父类的有参构造
父类:Preson
子类:China
注意:在一个构造方法中无法显式使用this0和super0同时出现。
(2)修饰普通方法:表示从父类中寻找同名方法
super不能只代当前对象的引用
4.final关键字
1.final修饰属性:表示属性的值不能被修改
2.final修饰类:表示这个类无法被继承。final class Person =>Person 无法被继承
三、多态
多态:一个父类引用(指代不同子类对象)可以表现出多种行为/特性=>多态性(继承+方法重写)
本质就是调用不同的子类“对象”,这些子类对象所属的类覆写相应的方法
// Animal.java
public class Animal {
protected String name;
//构造方法
public Animal(String name) {
this.name = name; }
//父类eat方法
public void eat() {
System.out.println(this.name + "正在吃");
}
}
// Bird.java
public class Bird extends Animal {
//构造方法
public Bird(String name) {
super(name);
}
//子类eat方法
public void eat() {
System.out.println(this.name + "正在吃" );
}
}
// Test.java
public class Test {
public static void main(String[] args) {
// 2.作为类的使用者,程序的使用者
//使用向上转型产生子类对象
fun(new Animal("动物"));
fun(new Bird("鸟"));
fun(new Duck("鸭子");
}
// 1.只要是Animal及其子类,都是天然的Animal对象,都满足is a关系
// 通过Animal最顶层的父类引用,指代所有的子类对象。
public static void fun(Animal animal) {
animal.eat();
}
}
}
// 执行结果
动物正在吃
鸭子正在吃
此时fun中animal局部变量的引用调用eat方法时, 当传入不同的对象时,表现出来了不同的eat
方法行为=>多态性
因为在父类和子类中都有eat方法(此时就涉及方法重写),所以在eat方法到底调用的是谁,要看new的是谁的对象
1.向上转型(重点)
(1)产生对象的方式:
a.常见的产生对象的方式:类名称 类引用= new该类对象();
例如:Animal animal = new Animal();//父类
Bird bird = new Bird(); //继承Animal类
Duck duck = new Duck(); //多层继承,直接父类Bird,
b.向上转型产生对象的方式: (同一个场景下给同一个对象取不同的名字(引用))
语法规则:
父类名称 父类引用= new子类实例();
通过父类对象引用指代子类对象
例如:Animal animal1 = new Bird();//(Bird is an Animal)
Bird bird1 = new Duck();//(Bird is an animal)
不一定是直接子类,也可以是子孙
Animal animal2 = new Duck();//(Duck is a Bird is an Animal!)
向上转型发生在有继承关系的类之间:父类名称父类引用= new子类实例();
(2)向上转型的产生:
如果有多个类继承且不用向上转型:
package polymorphism;
public class Test {
public static void main(String[] args) {
// 2.作为类的使用者,程序的使用者
// 没有向上转型,我要使用fun方法的话,我就得了解Animal以及其子类的所有对象
// 我才能知道我到底调用的是谁
Animal animal1 = new Bird();
Animal animal2 = new Duck();
Animal animal3 = new Dog();
Animal animal4 = new Cat();
fun(animal1);
fun(animal2);
fun(animal3);
fun(animal4);
}
//1.作为类的实现者
//fun方法接收Animal以及其子类的对象作为参数
//假设现在没有向上转型,Animal 有多少子类,我就得重载多少次fun方法
//大自然Animal动物子类有几百万种,就有几百万种子类,fun就得写上百万次!
public static void fun(Animal animal) {}
public static void fun(Bird bird) {}
public static void fun(Duck duck) {}
}
使用向上转型后:
package polymorphism;
public class Test {
public static void main(String[] args) {
// 2.作为类的使用者,程序的使用者
//使用向上转型产生子类对象
fun(new Animal());
fun(new Bird());
fun(new Duck());
}
// 1.只要是Animal及其子类,都是天然的Animal对象,都满足is a关系
// 通过Animal最顶层的父类引用,指代所有的子类对象。
public static void fun(Animal animal) {
animal.eat();
}
}
意义 : 在于参数统一化, 降低使用者的使用难度,方便使用,用一个父类引用指向所有子类对象
什么时候使用向上转型:
引用赋值时、方法传参时、方法返回值
2.方法重写
方法重载(overload) :发生在同一个类中, 定义了若干个方法名称相同,参数列表不同的一组方法。
方法重写(override) :发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法称之为方法重写。例如:.针对刚才的 eat 方法来说
1. 关于方法重写后到底调用的是那个类的方法
不用去看前半部分,看当前是通过哪个类new的对象,(要看new的是谁的对象)
a.发生在有继承关系的类之间,当子类new了一个对象,若子类重写了相关方法,则调用的一定是重写后的方法。使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法。
b.若多了一个子类而这个子类中没有重写eat方法,如果要调用eat方法时,则向上搜索碰到第一个父类重写的eat就调用最“接近的eat方法”(就近匹配原则)
2. 重写中子类的方法的访问权限 >= 父类的方法访问权限
private < default) < protected < public
注意:私有权限的方法无法重写;private不包括在重写方法中
3. 普通方法可以重写, static 修饰的静态方法不能重写
多态的本质就是因为调用了不同的子类“对象”,这些子类对象所属的类覆写相应的方法,static与对象无关
4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
5. 注意:重写和重载的区别:
3.向下转型
1.语法规则
子类名称 子类引用名称 = (父类)父类引用;
2.什么时候使用向上转型:
如果子类中扩展了一个父类中没有的方法时,若想使用子类中这个独有的方法时,就需要把父类引用转为子类
例如:类名称 引用名称 = new实例化();
Animal animal = new Bird()//父类Aniaml 子类Bird(向上转型)
animal.fly();// Bird子类中拓展了一个独有的方法 fly();方法
animal这个引用是披着的鸟外衣的动物,本质上是个Bird的对象 , 披了个Animal的外衣,此时只能调用Aninmal的方法
编译过程中, 方法的访问是通过" 类名称. "访问的,而animal 是 Animal父类引用, 此时编译器只知道父类没有fly 方法.虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的. 至于fly();这个方法表现出的行为是什么,要看new的实例(具体new的子类说了算) 综上所述:能 . (调用)哪些方法,有前面类名称说了算,调用方法能表现出什么,由new后面的实例说了算 如何转换:
脱掉这层 Animal外衣,还原为子类引用 =》向下转型
子类名称 子类引用= (子类名称) 父类引用
Biad bird = (Bird) animal; //脱掉animal对应的对象的外衣还原为具体的子类引用
将父类引用强制类型转换为子类引用。
子类isa父类=》天然的Dog is an animal
父类isa子类(不一定) Animal is a Dog?(存在强转风险)
注意:(1)要发生向下转型,首先要发生向上转型。
Animal animal = new Animal0;
Dog dog = (Dog) animal;
毫无关系的两个类之间没法强转(类比基本数据类型int boolean)
(2) 当发生向下转型时会有风险,类型转换异常,使用instanceof关键字
引用名称instanceof类=>返回布尔值,表示该引用指向的本质是不是该类的对象
四、在构造方法中调用重写的方法(一个坑)
一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 fun();
package polymorphism;
//B.java
public class B {
public B() {
fun();
}
public void fun() {
System.out.println("B.fun()");
}
}
//D.java
//D为B的子类
public class D extends B{
private static int num = 10;
public D() {
//父类的无参构造
super();
}
public void fun() {
System.out.println("D.fun,num = " + num);
}
public static void main(String[] args) {
D d = new D();
}
}
//编译结果
// D.fun,num = 10
程序运行步骤:
new D 对象的同时, 会产生父类的对象,调用 B(父类) 的构造方法.
B 的构造方法中调用了 fun 方法, 因为子类中复写了fun方法, 会调用到 D 中的 fun ,此时 D 对象自身还没有调用自身的构造进行初始化, 此时 num 处在未初始化的状态, 值为 0.
总结
封装:
1. 当 private 修饰的成员变量或者成员方法时, 不能被类的调用者使用,只在该类的内部使用用,出了 这个类的{},对外部就隐藏了,外部不知道由其存
2. 当一个属性被private封装后,外部要使用就必须通过该类自己提供的getter和setter方进行使用
继承:
1.当类和类之间满足一个类 is a 另外一个类,一定是存在继承关系
2. 当一个类继承了另一个类,另一个类中所有的属性和方法子类就天然具备
3. super关键字:访问父类中的同名属性或同名方法
4.super修饰构造方法时,当有子类对象产生时,默认先产生父类对象,就要调用父类构造方法,然后才会执行子类的构造方法;若父类中不存在无参构造,则子类构造方法的首行必须使super(有参构造),显示调用父类的有参构造方法
多态:(本质是继承+重写)
1.一个父类引用(指代不同子类对象——向上转型)可以表现出多种行为/特性=>多态性
2.调用不同的子类“对象”,这些子类对象所属的类覆写父类中相应的方法
重载和重写的区别:
方法重载(overload) :发生在同一个类中, 定义了若干个方法名称相同,参数列表不同的一组方法。
方法重写(override) :发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法称之为方法重写。