一、包
1、导入包中的类
通配符(*):
util下有很多类 Java处理的时候 需要哪个才会拿哪个
C语言 通过include关键字导入 会把这个文件的内容全部拿过来
import java.util.Date;
// import java.util; 导入一个具体的类 不能导入一个具体的包
import java.util.*; // 通配符
public class TestDemo {
public static void main(String[] args) {
Date data = new Date();
System.out.println(data.getTime()); // 得到一个毫秒级别的时间戳
java.util.Date date1 = new java.util.Date(); // util和sql中都存在一个Date这样的类 避免冲突
}
}
2、静态导入
使用 import static 可以导入包中的静态的方法和字段
import static java.lang.System.*;
import static java.lang.Math.*;
public class TestDemo {
public static void main(String[] args) {
out.println("hehe");
out.println(max(10, 20)); // out.println(Math.max(10, 20));
}
}
3、将类放到包中
在文件的最上方加上一个 package 语句指定该代码在哪个包中.
包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
如果一个类没有 package 语句, 则该类被放到一个默认包中
包名必须是小写字母
package com.bit.demo1; // package 指定当前的类在哪个包下
public class TestDemo {
}
同名类导入方法:
4、包的访问权限控制
成员变量不加任何修饰限定词的时候,默认就是包访问权限
同一个包 / 不同包:
此时 val 默认就是包访问权限
常见的系统包:
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
二、继承
面向对象的基本特征:
封装:
不必要公开的数据成员和方法,使用private关键字进行修饰。意义:安全性
继承:
对共性的抽取,使用extends关键字进行处理。意义:可以对代码进行重复使用
1、语法规则
使用 extends 指定父类
Java 中是单继承,一个子类只能继承一个父类 (而C++/Python等语言支持多继承)
子类会继承父类的所有 public 的字段和方法
对于父类的 private 的字段和方法, 子类中是无法访问的
子类构造的同时,要先帮助父类进行构造。可以使用 super 关键字得到父类实例的引用
super
-> super(); // 显示调用构造方法
-> super.func(); // 调用父类的普通方法
-> super.data; // 调用父类的成员方法属性
不能出现在静态方法中,因为super是父类对象的引用,依赖对象
class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name+" ani::eat()");
}
}
class Dog extends Animal{
}
class Bird extends Animal{
public String wing;
public void fly() {
System.out.println(name+" fly() "+age);
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name); // null
dog.eat(); // null ani::eat()
Bird bird = new Bird();
System.out.println(bird.name); // null
bird.eat(); // null ani::eat()
bird.fly(); // null fly() 0
}
}
2、protected 关键字
Java 中对字段和方法共有四种访问权限:
-> private: 类内部能访问, 类外部不能访问
-> 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
-> protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
-> public : 类内部和类的调用者都能访问
3、final 关键字
如果一个类不想被继承,可以设置final修饰
final修饰变量-> final int a = 10; // 常量 不可以被修改
final修饰类-> final class A // 代表整个类不可以被继承
final修饰方法->
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承
三、组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果
例如表示一个学校:
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段
这是我们设计类的一种常用方式之一
组合表示 has - a 语义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师
继承表示 is - a 语义
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物
四、多态
1、向上转型
父类的引用,指向一个子类的实例,这种写法称为 向上转型
向上转型发生的时机:
-> 1. 直接赋值
-> 2. 方法传参
-> 3. 方法返回
public class TestDemo {
public static void func(Animal ani) {
}
public static Animal func2() {
Dog dog = new Dog("haha", 19);
return dog;
}
public static void main(String[] args) {
Animal animal = new Dog("haha", 19); // 父类引用 引用 子类对象
Dog dog = new Dog("haha", 19);
func(dog);
}
}
2、动态绑定,方法重写
-> 父类引用 引用 子类的对象
-> 通过这个父类引用 调用父类 和 子类同名的覆盖方法
同名的覆盖:重写(覆写/重写/覆盖(Override)):
-> 方法名相同
-> 参数列表相同(个数+类型)
-> 返回值相同(或构成协变类型也可)
-> 父子类的情况下
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 void eat() {
System.out.println(name+" ani::eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age); // 调用父类带有2个参数的构造方法
}
@Override
public void eat() {
System.out.println(name+" 狼吞虎咽的eat()");
}
}
class Bird extends Animal{
public String wing;
public Bird(String name, int age, String wing) {
super(name, age);
this.wing = wing;
}
public void fly() {
System.out.println(super.name+" fly() "+super.age);
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog("hehe", 19);
System.out.println(dog.name);
// null 狼吞虎咽的eat()
// hehe
dog.eat(); // hehe 狼吞虎咽的eat()
Bird bird = new Bird("haha", 20, "wingwing");
System.out.println(bird.name);
// null ani::eat()
// haha
bird.eat(); // haha ani::eat()
bird.fly(); // haha fly() 20
}
}
重写注意事项:
1、方法不可以是static
2、子类的访问修饰限定,要大于等于父类的访问修饰限定
3、private 方法不能 重写
4、被final 修饰的关键字,不能被重写
5、重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)-> 协变类型
6、通过父类引用 只能访问自己的成员
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); // 调用父类带有2个参数的构造方法
}
@Override
public void eat() {
System.out.println(name+" 狼吞虎咽的eat()");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal = new Dog("haha", 19); // 调用dog类带有两个参数的构造方法
animal.eat(); // haha eat()
// dog里加上eat自己的方法后运行打印:
// haha 狼吞虎咽的eat()
}
}
Java 中有两种多态:
1. 运行时多态
打开字节码文件:
编译的时候,不能确定此时到底调用的谁的方法
运行的时候才知道,调用哪个方法
运行时绑定->动态绑定
2. 编译时多态
(由重载实现,根据所给参数类型个数不同推导调用哪个函数)
class Dog extends Animal{
public void func(int a) {
System.out.println("int");
}
public void func(int a, int b) {
System.out.println("int, int");
}
public void func(int a, int b, int c) {
System.out.println("int, int, int");
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog("haha", 19);
dog.func(10);
}
}
-> 重载和重写的区别:
3、向下转型
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了
public class TestDemo {
public static void main(String[] args) {
Animal animal = new Dog("hehe", 10);
Bird bird = (Bird)animal;
bird.fly();
Animal animal2 = new Dog("hehe", 10);
// 以免类型转换异常
if(animal2 instanceof Bird) { // 判断animal2引用的是不是Bird的对象
Bird bird2 = (Bird)animal2;
bird2.fly();
}
}
}
在构造方法中调用重写的方法(一个坑)
在父类构造方法里调用父类和子类的重写的eat方法,也会发生动态绑定
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 void eat() {
System.out.println(name+" ani::eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age); // 调用父类带有2个参数的构造方法
}
@Override
public void eat() {
System.out.println(name+" 狼吞虎咽的eat()");
}
}
public class TestDemo {
public static void main(String[] args) {
Dog animal = new Dog("hehe", 10);
// null 狼吞虎咽的eat()
}
}
4、理解多态
shape 这个引用调用 draw 方法可能会有多种不同的表现
(和 shape 对应的实例相关),这种行为就称为 多态
使用多态的好处是什么?
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("❀");
}
}
class Triangle 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();
drawMap(shape1); // ♦
//drawMap(new Rect());
Shape shape2 = new Flower();
drawMap(shape2); // ❀
Shape shape3 = new Triangle();
drawMap(shape3); // △
}
}
2、能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
圈复杂度:
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10
// 创建了一个 Shape 对象的数组
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {triangle, rect, triangle, rect, flower};
for (Shape x : shapes) {
x.draw();
}
}
// if-else 循环
public static void main1(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] shapes = {"triangle", "rect", "triangle", "rect", "flower"};
for (String x : shapes) {
if(x.equals("triangle")) {
triangle.draw();
} else if(x.equals("rect")) {
rect.draw();
} else {
flower.draw();
}
}
}
}
3、可扩展能力更强
快捷键:AIT+INS
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
五、抽象类
1、语法规则 + 抽象类的作用
在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码)
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
abstract class Shape {
public int val;
public void func() {
System.out.println("测试普通方法:");
}
public abstract void draw();
}
- 抽象类:包含抽象方法的类
- 抽象方法:一个没有具体实现的方法,被abstract修饰
- 抽象类不能直接实例化 new
- 只能被继承
- 抽象类中,也可以包含,和普通的类一样的成员和方法
- 一个普通类继承一个抽象类,这个普通类中,需要重写这个抽象类的所有的抽象方法
- 抽象类最大的作用,就是被继承
abstract class Shape {
public int val;
public void func() {
System.out.println("测试普通方法:");
}
public abstract void draw();
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("♦");
}
}
public class TestDemo {
public static void drawMap(Shape shape) {
shape.draw(); // 和动态绑定
}
public static void main(String[] args) {
Shape shape = new Rect(); // 可以向上转型
drawMap(shape);
}
}
- 一个抽象类A,如果继承了一个抽象类B,那这个抽象类A,可以不实现抽象父类B的抽象方法
- 综合上一点,当A类,再次被一个普通类继承,那A和B这两个抽闲类中的抽象方法,必须被重写
abstract class Shape {
public int val;
public void func() {
System.out.println("测试普通方法:");
}
public abstract void draw();
}
abstract class A extends Shape {
public abstract void funcA();
}
class B extends A {
@Override
public void funcA() {
super.func();
}
@Override
public void draw() {
}
}
- 抽象类不能被final修饰,抽象方法也不能被final修饰
- 抽象方法不能是 private 的
普通的类也可以被继承,普通的方法也可以被重写,为啥非得用抽象类和抽象方法呢?
确实如此,但是使用抽象类相当于多了一重编译器的校验
六、接口
1、语法规则
- 使用interface修饰,interface IA {}
- 接口当中的普通方法,不能有具体的实现,非要实现,只能通过关键字default修饰此方法(不能被重写)
- 接口中,可以有static的方法(不能被重写)
- 里面的所有方法都是public的
- 抽象方法 -> 默认是public abstract的(可省略)
- 接口是不可以通过关键字new 去实例化的
interface IShape {
public abstract void draw(); // 抽象方法
default public void func() {
System.out.println("func:");
}
public static void func2() {
System.out.println("func2:");
}
}
- 类和接口之间的关系,通过implements实现
- 当一个类,实现了一个接口,就必须要重写接口中的抽象方法
interface IShape {
public abstract void draw(); // 抽象方法
// void draw(); // 不加也默认是public abstract
}
class Rect implements IShape {
@Override
public void draw() {
System.out.println("♦");
}
}
public class TestDemo {
public static void drawMap(IShape iShape) {
iShape.draw(); // 和动态绑定
}
public static void main(String[] args) {
Rect rect = new Rect(); // 也可向上转型
drawMap(rect); // ♦
}
public static void main1(String[] args) {
IShape iShape = new Rect();
iShape.draw(); // ♦
}
}
- 接口中的成员变量,默认是public static final修饰的(可以省略),需要初识化
interface IA {
public static final int A = 10;
int B = 10;
}
- 当一类实现一个接口后,重写这个方法的时候,这个方法前必须加上public,因为接口的抽象方法默认是public abstract
interface IA {
int A = 10;
void funcA(); // public abstract
}
class AClass implements IA {
@Override
public void funcA() {
System.out.println("A->funcA");
System.out.println(A);
}
}
2、实现多个接口
- 一个类可以通过关键字extends继承一个普通类,或抽象类(需重写抽象类中的抽象方法),但是只能继承一个类
同时,也可通过implements实现多个接口,接口之间使用逗号隔开就可
interface IA {
int A = 10;
void funcA(); // public abstract
}
interface IB {
void funcB();
}
class AClass implements IA, IB {
@Override
public void funcA() {
System.out.println("A->funcA");
System.out.println(A);
}
@Override
public void funcB() {
System.out.println("B->funcB");
}
}
提示:
- 我们创建接口的时候, 接口的命名一般以大写字母 I 开头
- 接口的命名一般使用 “形容词” 词性的单词
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
- 类和类之间,类和接口之间的关系,是通过implements操作的
那么接口和接口之间,可以使用extens操作,意为:拓展
例:一个接口B,通过extends拓展另一个接口A的功能,此时当一个类C通过implements实现这个接时,重写的方法不仅仅是B的抽象方法,还有从C接口拓展来的功能(方法)
interface IA {
void funcA();
}
interface IB extends IA{
void funcB();
}
class C implements IB{
@Override
public void funcA() {
System.out.println("IA:funcA");
}
@Override
public void funcB() {
System.out.println("IB:funcB");
}
}
有的时候我们需要让一个类同时继承自多个父类,这件事情在有些编程语言通过 多继承 的方式来实现的
然而 Java 中只支持单继承,一个类只能 extends 一个父类。但是可以同时实现多个接口, 也能达到多继承类似的效果
例:通过类来表示一组动物
另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”
展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
// 不是所有动物都会飞 所以不能写到Animal类里
// 也不能写到另一个类中 因为一个类不能继承多个类 多以用接口
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Bird extends Animal implements IFlying{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+" 正在飞!");
}
}
class Frog extends Animal implements IRunning,ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name+" 正在跑!");
}
@Override
public void swim() {
System.out.println(this.name+" 正在游泳!");
}
}
class Duck extends Animal implements IFlying,IRunning,ISwimming {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+" 正在飞!");
}
@Override
public void run() {
System.out.println(this.name+" 正在跑!");
}
@Override
public void swim() {
System.out.println(this.name+" 正在游泳!");
}
}
class Roobot implements IRunning {
@Override
public void run() {
System.out.println("机器人在跑!");
}
}
public class Test {
public static void runFunc(IRunning iRunning) {
iRunning.run();
}
public static void swimmingFunc(ISwimming iSwimming) {
iSwimming.swim();
}
public static void flyingFunc(IFlying iFlying) {
iFlying.fly();
}
public static void main(String[] args) {
runFunc(new Duck("小鸭")); // 小鸭 正在跑!
runFunc(new Frog("青蛙")); // 青蛙 正在跑!
swimmingFunc(new Duck("小鸭")); // 小鸭 正在游泳!
swimmingFunc(new Frog("青蛙")); // 青蛙 正在游泳!
flyingFunc(new Duck("小鸭")); // 小鸭 正在飞!
flyingFunc(new Bird("小鸟")); // 小鸟 正在飞!
runFunc(new Roobot()); // 机器人在跑!
}
}
3、三个常用接口
Comparable
Comparator
Cloneable
Comparable 排序
如果自定义的数据类型,进行大小的比较,一定要实现可以比较的接口
此接口有一个很大的缺点:对类的侵入性非常强,一旦写,不敢轻易改动
import java.util.Arrays;
class Student implements Comparable<Student>{
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
// 谁调用compareTo 谁就是this
public int compareTo(Student o) {
return this.age - o.age;
}
}
public class Test {
public static void main1(String[] args) {
Student student2 = new Student(12, "bit", 93.5);
Student student3 = new Student(7, "abc", 13.5);
System.out.println(student2.compareTo(student3));
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12, "bit", 93.5);
students[1] = new Student(7, "abc", 13.5);
students[2] = new Student(38, "zhangsan", 56.5);
System.out.println(Arrays.toString(students));
Arrays.sort(students); // 默认从小到大排序
System.out.println(Arrays.toString(students));
}
}
Comparator 比较器
优点:灵活,对类的侵入性非常弱
class Student {
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class Test {
public static void main1(String[] args) {
Student student2 = new Student(12, "bit", 93.5);
Student student3 = new Student(7, "abc", 13.5);
AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(student2, student3)); // 5
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12, "bit", 93.5);
students[1] = new Student(7, "abc", 13.5);
students[2] = new Student(38, "zhangsan", 56.5);
System.out.println(Arrays.toString(students));
// 根据age比较
AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students, ageComparator); // 根据此比较器比较
System.out.println(Arrays.toString(students));
// 根据score比较
ScoreComparator scoreComparator = new ScoreComparator();
Arrays.sort(students, scoreComparator);
System.out.println(Arrays.toString(students));
NameComparator nameComparator = new NameComparator();
Arrays.sort(students, nameComparator);
System.out.println(Arrays.toString(students));
}
}
Cloneable接口
实例化一个对象:
1、new
2、clone()
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先
实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常
Cloneable接口是一个空接口
作用:标志接口,代表当前类是可以被克隆的
决定是 深拷贝 还是 浅拷贝 不是方法的用途 是代码的实现
class Person implements Cloneable {
public int age;
public void eat() {
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.age = 99;
Person person2 = (Person)person.clone(); // 调用clone需要重写
System.out.println(person2); // Person{age=99}
person2.age = 199;
System.out.println(person); // Person{age=99}
System.out.println(person2); // Person{age=199}
}
}
测试一段代码:
class Money {
public double m = 10.9;
}
class Person implements Cloneable {
public int age;
public Money money = new Money();
public void eat() {
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person2 = (Person)person.clone();
System.out.println(person.money.m); // 10.9
System.out.println(person2.money.m); // 10.9
person2.money.m = 99.9;
System.out.println(person.money.m); // 99.9
System.out.println(person2.money.m); // 99.9
}
}
图书管理代码
Gitee 链接