目录
一、包
1.什么是包
2.包的作用
-
把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
-
如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
-
包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
2.导入包中的类
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();// 得到一个毫秒级别的时间戳
//使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类
System.out.println(date.getTime());
}
}
但是这样写比较麻烦,我们可以使用import语句(在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类,import java.io.*;)导入包,如:
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
*是一个通配符,导入这个包底下所有的类。但是它不是一下子全部导入。Java处理的时候,需要谁才会拿谁。但是在C语言里面,通过include关键字,导入后就会把头文件里面的内容全部都拿过来。但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况,如:
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
//Date date = new Date();
//可以这样修改:
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
3.静态导入
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
利用静态导入,有时候写代码可以方便一些:
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);
}
}
4.将类放在包中
规则:
(3)包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
(4)如果一个类没有 package 语句, 则该类被放到一个默认包中
注意:包名要是小写的英文字母
5.常见的系统包
6.import和package的区别
import:“引入”,引入类中需要的类
package:“包”,指类所在的包
二、继承
1.概念
class Dog{
public String name;
public int age;
public void eat(){
System.out.println("eat()");
}
}
class Bird{
public String name;
public int age;
public String wing;
public void eat(){
System.out.println("eat()");
}
punlic void fly(){
System.out.println("fly()");
}
}
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
2.语法规则
class 子类 extends 父类 {
......
}
现在,我们可以对上面的代码,进行优化:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println("eat()");
}
}
class Dog extends Animal{
}
class Bird extends Animal{
public String wing;
punlic void fly(){
System.out.println("fly()");
}
}
3.super关键字
【子类中所有的构造器都会默认调用父类中的无参构造器,因为每一个子类构造器内的第一行都有一条隐式的super();若父类中没有无参构造器,那么子类的构造器内必须通过super语句指定要调用的父类中的构造器】
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("eat()");
}
}
class Dog extends Animal{
//public Dog(){
// super();
//} 当代码中一个构造函数都没有,就会默认调用这个构造函数
public Dog(String name,int age){
super(name,age);//调用父类带有两个参数的构造方法(显示调用构造方法) 必须放在第一行
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);//如果name前面不加super会打印什么呢?是nulfly()3还是hehefly()3.答案是前者,因为当子类和父类有同名的字段时,是优先自己的
}
}
public class TestDemo{
public static void main(String[] args){
Dog dog = new Dog("haha",6);
System.out.println(dog.name);
dog.eat();
Bird bird = new Bird("hehe",3,"我要飞");
System.out.println(dog.name);
bird.eat();
bird.fly();
}
}
编译并运行该程序,输出如下:
接下来,我们画一下它的内存布局图:
(3)super和this的区别
No | 区别 | this | super |
1 | 概念 | 访问本类中的属性和方法 | 由子类访问父类的属性和方法 |
2 | 查找范围 | 先查找本类,如果本类没有就查找父类 | 不查找本类,直接调用父类定义 |
3 | 特殊 | 表示当前对象 | 无 |
4.protected关键字
No | 范围 | private | default(包访问权限) | protected | public |
1 | 同一个包中同一类 | √ | √ | √ | √ |
2 | 同一个包中不同类 | √ | √ | √ | |
3 | 不同包中的子类 | √ | √ | ||
4 | 不同包中的非子类 | √ |
我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者. 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public. 另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用
5.更复杂的继承关系

我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂. 但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
6.final关键字
(1)修饰一个变量或者字段的时候, 表示 常量 (不能修改).
(2)修饰类, 此时表示被修饰的类就不能被继承(final 关键字的功能是 限制 类被继承)
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承
(3)修饰方法
三、组合
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
四、多态
1.向上转型
(1)向上转型其实就是父类引用 引用子类对象
注意:通过父类引用只能访问父类自己的成员
(2)向上转型发生的时机:
直接赋值 方法传参 方法返回
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("eat()");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);
}
}
public class TestDemo{
public static Animal func(Animal ani){
......
}
//方法返回
public static Animal func(Animal ani){
Dog dog = new Dog("haha",6);
return dog;
}
public static void main(String[] args){
/*Dog dog = new Dog("haha",6);
Animal animal = dog;*/
Animal animal = new Dog("haha",6);//上面两句代码可以浓缩成这一句代码,父类引用引用子类对象 直接赋值
//方法传参
Dog dog = new Dog("haha",6);
func(dog);
}
}
2.动态绑定与方法重写
(1)什么是动态绑定
父类引用 引用 子类的对象,通过父类引用 调用父类和子类同名的覆盖方法
(2)什么是方法重写
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+" eat()");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
@Override 注解
public void eat(){
System.out.println(name+" 狼吞虎咽地eat()");
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);
}
}
public class TestDemo{
public static void main(String[] args){
Animal animal = new Dog("haha",6);
animal.eat();
}
}
编译并运行该代码,输出如下:
haha 狼吞虎咽地eat()
为什么不是haha eat()?因为此时发生了动态绑定。编译的时候还不能确定我们此时到底调用谁的方法,运行的时候才知道,所以动态绑定也叫运行时绑定
注意:
①重写时方法不可以是静态方法
②子类的访问修饰限定符要>=父类的访问修饰限定符
③private方法不能重写;被final修饰的方法不能重写
④子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法;子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法
⑤构造方法不能被重写
⑥当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super
⑦重写的返回值不一定相同(特殊情况:协变类型(返回值构成父子类关系))但是这种题出现的比较少,我们要看题中有没有比它更对的或更错的
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public Animal eat(){
System.out.println(name+" eat()");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
@Override 注解
public Dog eat(){
System.out.println(name+" 狼吞虎咽地eat()");
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);
}
}
public class TestDemo{
public static void main(String[] args){
Animal animal = new Dog("haha",6);
animal.eat();
}
}
这样子返回值构成协变类型也是重写
(3)重写和重载的区别
No | 区别 | 重载 | 重写 |
1 | 概念 | 方法名称相同,参数类型及个数不同 | 方法名称及参数类型、个数完全相同 |
2 | 范围 | 一个类 | 继承关系 |
3 | 限制 | 没有权限要求 | 被重写的方法不能有比父类风严格的访问控制权限 |
利用重载也可以实现多态(静态绑定),这是一种静态多态,根据你给的参数的类型和个数推导出你调用哪个函数(编译时多态)
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
(4)在构造方法中调用重写的方法(坑)
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
eat();
this.name = name;
this.age = age;
}
public Animal eat(){
System.out.println(name+" eat()");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
@Override 注解
public Dog eat(){
System.out.println(name+" 狼吞虎咽地eat()");
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);
}
}
public class TestDemo{
public static void main(String[] args){
//Animal animal = new Animal("haha",6);//①
Dog dog = new Dog("haha",6);//②
}
}
运行①代码,haha eat()
运行②代码,haha 狼吞虎咽地eat(),因为:
3.向下转型
class Animal{
public String name;
public int age;
private int count;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+" eat()");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
}
class Bird extends Animal{
public String wing;
public String name;
public Bird(String name,int age,String wing){
super(name,age);
this.wing = wing;
}
public void fly(){
System.out.println(super.name+"fly()"+age);
}
}
public class TestDemo{
public static void main(String[] args){
/*Animal animal = new Bird("hehe",3,"我要飞");
//animal.fly()//error 通过父类引用只能访问父类自己的成员
Bird bird = (Bird)animal;
bird.fly();*/
/*Animal animal = new Dog("haha",6);
Bird bird = (Bird)animal;
bird.fly();*/
//animal 本质上引用的是一个 Dog 对象, 是不能转成 Bird 对象的. 运行时就会抛出异常
//所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换,instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了
Animal animal = new Dog("haha",6);
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
}
}
4.理解多态
(1)举个例子 打印多种形状
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
//--------------------------分割线--------------------------
public class Test{
public static void drawMap(Shape shape){
shape.draw();//动态绑定
}
public static void main(String[] args){
Shape shape1 = new Rect();//向上转型
Shape shape2 = new Flower();//向上转型
drawMap(shape1);
drawMap(shape2);
}
}
编译应运行该程序,输出如下:
♦
❀
在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的. 当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为 多态
(2)使用多态的好处
- 封装是让类的调用者不需要知道类的实现细节.
- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
class Cycle extends Shape{
@Override
public void draw(){
System.out.println("○");
}
}
public class TestDemo {
public static void main(String[] args) {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
}
现在我们用多态再写一遍代码
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
class Cycle extends Shape{
@Override
public void draw(){
System.out.println("○");
}
}
public class TestDemo {
public static void main(String[] args) {
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()}; // 我们创建一个 Shape 对象的数组
for (Shape shape : shapes) {
shape.draw();
}
}
}