包
工作中要用到无穷多的类,这些类很可能会出现重名的情况。这时候怎么办?Java用“包”机制来解决这个问题
包(package)提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。
只有类名并不代表具体的类,而应该是**“包名+类名”才能对应到具体的某个类**(包名+类名才是完整的类名)
位于包中的类,在文件系统中也必须有包名层次相同的目录结构
导入包中的类
想使用别人写好的类,有三种方式
-
引用前写包的全称,并用 . 分割(写明完整的类名)
public class TestDemo { public static void main(String[] args) { //java.util是包名,Date是用到的类名 java.util.Date date = new java.util.Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
-
用import + 包名 + 类名(导入包中指定的那个类),再直接引用
import java.util.Date; public class TestDemo { public static void main(String[] args) { Date date = new Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
-
用import + 包名 + .*(导入包中所有的类,.*称为通配符) 再直接引用。这时也可以同时调用该包里的其他类
import java.util.*; public class TestDemo { public static void main(String[] args) { Date date = new Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
注意:如果导入了两个包,两个包里有重名的类名,则调用这个类是会产生冲突,这时必须在类前面加上全部的包名。
import java.util.*; import java.sql.*; public class TestDemo { public static void main(String[] args) { // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错 Date date = new Date(); System.out.println(date.getTime()); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTUxG6wc-1637722022239)(assets/image-20211119121858-m2jgfci.png)]
应该这样写:import java.util.*; import java.sql.*; public class TestDemo { public static void main(String[] args) { java.util.Date date = new java.util.Date(); System.out.println(date.getTime()); } }
静态导入(import static)
- import static 可以导入包中的静态方法和字段(很少用)
import static java.lang.Math.*; public class TestDemo { 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); } }
import与package
import 和 package的区别
- import
- import 是导入一个具体的类(import java.util.Arrays),不能导入一个包
- import java.util.*表示导入util包下所有的类。但并不是一下全部导入,需要哪个类,才会拿哪个类。
- 而C语言是全部导入
- 但如果导入多个包时,要注意不同包里可能有同名的类。这时要写包的全名(如util和sql都含有Data的类)
- 一旦Java源文件中用了package,则意味着该源文件里定义的所有的类都属于这个包
- package java.util
- 只有类名并不代表具体的类,而应该是“包名+类名”才能对应到具体的某个类
把类导入到包中(用package)
位于包中的类,在文件系统中也必须有包名层次相同的目录结构
- 创建一个包
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 )
- 包名要和代码路径相匹配。例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存****储代码**.**
- 如果一个类没有 package 语句, 则该类被放到一个默认包中
- 在IDEA中创建一个包
- 在 IDEA 中先新建一个包: 右键src -> 新建 -> 包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPz1ZiyG-1637722022240)(assets/image-20211119133257-onuicor.png)] - 在弹出的对话框中输入包名, 例如com.bit.demo
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKBLzQRD-1637722022241)(assets/image-20211119133924-4ugeb8p.png)] - 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcDykgWT-1637722022241)(assets/image-20211119134024-ouh5tnn.png)] - 此时目录结构已经被IDEA创建出来了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y3HAhHdt-1637722022242)(assets/image-20211119134127-7wy4d7p.png)] - 同时在新建的.java文件的最上方,也出现了package语句,表明该文件下的所有类,都在这个包里。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9MJ7QMqg-1637722022242)(assets/image-20211119134248-sebv80c.png)]
- 在 IDEA 中先新建一个包: 右键src -> 新建 -> 包
- #TODO#如果自己写的类名与导入的包中类名重名该怎么办?
访问控制符
Java有三个访问控制符:private、protected和public。对应三个访问控制级别。
另外还有一个不加任何访问控制符的访问控制级别(默认default)
访问控制级别由小到大:private -> default -> protected -> public。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ic3iFWLo-1637722022243)(assets/image-20211119153557-6cbqkbc.png)]
如果一个方法能用 private, 就尽量不要用public
这四种权限可以分别与static关键字搭配,构成静态的变量。
下面分别介绍这四种访问控制权限
private(当前访问权限)
如果类里的一个成员(包括成员变量、方法和构造方法等)被private修饰,则该成员只能在当前类中被使用。
default(包访问权限)
如果类里的一个成员或者一个外部类不适用任何访问控制符修饰,那就称它是包访问权限的。这个成员变脸只能在被包内的其他类使用,不能被包外部的类使用。
实例:
- 包的目录结构如下(test.java和test2.java位于同一个包内,而TestDemo.java不在此包内)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e4pSleIF-1637722022243)(assets/image-20211119135604-82wue6k.png)] - test.java
package com.bit.demo1; public class test { int value1 = 1; private int value2 = 2; }
- test2.java
package com.bit.demo1; public class test2 { public static void main(String[] args) { test demo = new test(); System.out.println(demo.value1); } } // 执行结果, 能够访问到 value 变量 10
- TestDemo.java
不再同一个包里,即便用import导入了此包,也不能调用test类里默认范围的变量import com.bit.demo1.test; public class TestDemo { public static void main(String[] args) { test demo = new test(); System.out.println(demo.value1); } } //编译出错 java: value1在com.bit.demo1.test中不是公共的; 无法从外部程序包中对其进行访问
protected(子类访问权限)
如果一个成员被protected访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。
实例:
- demo1和demo是两个不再一个包中的类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zz5eq53-1637722022244)(assets/image-20211119213140-xn6jppf.png)] - demo.java
package com.bit.demo2; public class demo { protected int val1 = 10; protected static int val2 = 20; }
- demo1.java
package com.bit.demo1; import com.bit.demo2.*; public class demo1 extends demo{ public void func() { System.out.println("val1 = " + this.val1); } public static void main(String[] args) { demo a = new demo(); System.out.println("val2 = " + a.val2); } }
- 课件protected修饰的变量可以被不同包的子类引用
- 但是要注意:static方法(如main方法)不能引用普通成员变量
通常情况下,用protected修饰一个方法,是希望其子类能重写这个方法。
public(公共访问权限)
这是最宽松的一个访问控制级别。如果一个成员变量或外部类被public修饰,则这个成员变量或外部类就可以被所有类访问,不管访问类和被访问类是否在同一个包中,是否具有父子继承关系。
常见的系统包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入(因此不用import也能直接调用String、System类)。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包
继承
对类中的共性进行抽取。使用extends关键字进行处理。可以对代码进行重复使用
下面是一个实例, Animal 和 Cat 以及 Bird 这两个类中存在一定的关联关系。因此可以让Cat和Bird继承Animal,来达到代码重用的效果。
此时Animal被称为:父类、基类或超类
Cat,Bird被称为:子类、派生类。
class Animals {
public String name;
public int age;
public Animals(String myname, int myage) {
this.name = myname;
this.age = myage;
}
public void eat() {
System.out.println(name + ", " + age + " 岁 " + "正在吃饭");
}
}
class Cat extends Animals {
public Cat (String myname, int myage) {
super(myname, myage); //调用父类的构造方法
}
}
class Bird extends Animals {
public String color;
public Bird (String myname, int myage, String color) {
super(myname, myage);
this.color = color;
}
public void fly() {
System.out.println("this " + color + "Bird, named " + name + ", is flying!");
}
}
public class TestDemo {
public static void main(String[] args) {
Cat cat1 = new Cat("小白", 5);
Bird bird1 = new Bird("八哥", 8, "Red");
cat1.eat();
bird1.eat();
bird1.fly();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2a5Iibu-1637722022244)(assets/image-20211119142719-ff2udbt.png)]
语法规则
- 基本格式
class 子类 extends 父类 { }
- 注意:
- Java 中一个子类只能有一个直接父类 (而C++/Python等语言支持多继承**)**,但是可以有多个间接父类(多层继承)
- 子类会继承父类的所有 public 的字段、方法和内部类(包括内部接口、枚举),但不能获得父类的构造方法和初始化块。
- 子类要先写构造方法(用super()进行“显式调用父类的构造方法”)
- 如果子类没有写构造方法,则会默认调用父类的构造函数
- 对于父类的 private 的字段和方法, 子类中是无法访问的(调用父类的构造方法不影响)
- 例如把上面例子中Animal父类中的age改为private修饰。编译通过。这是因为子类中除了构造方法,没有别的方法调用了age。
- 但如果把name改为private修饰,编译就会报错。因为Bird子类中,fly调用了name,这是不被允许的。
class Bird extends Animal { public Bird(String name) { super(name); } public void fly() { System.out.println("this " + color + "Bird, named " + name + ", is flying!"); } } // 编译出错 Error:(19, 32) java: name 在 Animal 中是 private 访问控制
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用。(例如构造函数的引用)
- 子类和父类有同名的成员变量时,会优先使用自己的。如果想用父类的,则需要用super.data
- 可以有多层继承,但是建议最多不超过三层
final关键字
-
修饰一个变量或者字段的时候, 表示常量 (不能修改)
final int a = 10; 则a是常量,不能被修改 -
修饰类的时候,表示被修饰的类不能被继承
final class A; 代表整个类不可以被继承final public class Animal { ... } public class Bird extends Animal { ... } // 编译出错 Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
1
-
#TODO#修饰方法:后面再说
组合
与继承类似,组合也是表达类之间关系的一种方式,也能达到代码重用的效果。
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
继承要表达的是一种“是(is - a)”的关系,组合表达的是“有(has - a)”的关系
多态
向上转型
- 父类引用 引用 子类对象
package com.bit.demo3; class Animal { public String name; public int age; public Animal(String myname, int myage) { this.name = myname; this.age = myage; } public void eat() { System.out.println(name + ", " + age + " 岁 " + "正在吃饭"); } } class Cat extends Animal { public Cat (String myname, int myage) { super(myname, myage); //调用父类的构造方法 } } class Bird extends Animal { public String color; public Bird (String myname, int myage, String color) { super(myname, myage); this.color = color; } public void fly() { //System.out.println("this " + color + "Bird, named " + name + ", is flying!"); } } public class Test { public static void main(String[] args) { Animal animal = new Cat("小黑", 10); //向上转型(父类引用 引用子类类型) } }
- 发生时机:
- 直接赋值
public static void main(String[] args) { Animal animal = new Cat("小黑", 10); //直接赋值 }
- 方法传参
public class Test { //方法传参 public static void func(Animal animal) { //接收是Animal父类 animal.eat(); } public static void main(String[] args) { Cat cat = new Cat("花花", 8); func(cat); //传过去的是Cat子类 } }
- 方法返回
public class Test { //方法返回 public static Animal findMyAnimal() { Bird bird = new Bird("圆圆", 5, "Green"); return bird; //传回去的是Bird子类 } public static void main(String[] args) { Animal animal = findMyAnimal(); //接受是Animal父类 } }
- 直接赋值
动态绑定(运行时绑定)
当子类和父类出现同名方法的时候,就会发生动态绑定。
对于子类实现父类的同名方法,并且参数类型和个数完全相同,也称为方法重写/覆盖(override)(重写具体见下一小节)
代码实例(三个文件在一个包里):
- Animal.java
package com.bit.Test1; 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
package com.bit.Test1; public class Bird extends Animal { public Bird(String name) { super(name); } @Override public void eat(String food) { System.out.println("我是一只小鸟"); System.out.println(this.name + "我正在吃" + food); } public void fly () { System.out.println("我要的飞翔"); } }
- Test.java
package com.bit.Test1; public class Test { public static void main(String[] args) { Animal animal = new Animal("小黑"); animal.eat("肉"); Animal bird = new Bird("圆圆"); bird.eat("玉米"); } }
- 可以看到animal 和 bird 都是Animal类型,但是animal指向Animal实例,bird指向Bird实例。
- 两个对象都调用eat方法是,animal调用了父类的方法,bird则调用了Bird子类的方法。
动态绑定注意:
- 父类引用 引用 子类的对象
- 通过这个父类引用 调用父类 和 子类 同名的覆盖方法(就是重写)
- 为什么叫“动态绑定”(也叫运行时绑定)
- 编译的时候不能确定 此时调用的谁的方法
- 在运行的时候决定的 -> 动态绑定
- 与之对应的还有静态绑定(也叫编译时绑定)
- 根据你给的参数的类型+个数,推导出你调用的哪个函数。
- 常见的是一个构造方法被重载时,根据传参推断你调用的哪个构造函数。
- 这个过程时编译的时候就决定的 -> 静态绑定
- 通过父类引用,只能访问父类自己的成员
-
上面的例子中,bird依然是Animal(父类)实例,不能调用Bird子类中的fly()方法。
bird.fly();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ipBexMDX-1637722022244)(assets/image-20211120103331-4crmiwc.png)]
-
同样,如果在上面的代码上,给Bird加了一个新的成员变量,然后尝试用bird调用这个变量,则会编译错误
-
方法重写
- 重写要求
- 方法名相同
- 参数列表相同(个数+类型)
- 返回值(尽量)相同
- 父子类的情况下
- 注意:
- 以下方法不能重写
- static 方法不能重写
- 父类中 private 方法不能被重写
- 被 final 修饰的关键字,不能被重写
- 子类的访问控制符要大于等于父类的访问控制符。
- 比如上面的例子,如果把Bird子类中的重写eat()方法从public改为private,则报错。
// Animal.java public class Animal { public void eat(String food) { ... } } public class Bird extends Animal { private void eat(String food) { ... } } //编译错误 java: com.bit.Test1.Bird中的eat(java.lang.String)无法覆盖com.bit.Test1.Animal中的eat(java.lang.String) 正在尝试分配更低的访问权限; 以前为public
- 比如上面的例子,如果把Bird子类中的重写eat()方法从public改为private,则报错。
- 返回值其实可以不同,叫协变类型(但很少见,目前也不推荐)
- 以下方法不能重写
- @override 注解
- 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 **(**比如写成aet), 那么此时编译器就会发现父类中没有aet 方法, 就会编译报错, 提示无法构成重写
@Override //加上注解后,下面的重写方法名写错了 public void ate(String food) { System.out.println("我是一只小鸟"); System.out.println(this.name + "我正在吃" + food); } //编译错误 java: 方法不会覆盖或实现超类型的方法
- 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 **(**比如写成aet), 那么此时编译器就会发现父类中没有aet 方法, 就会编译报错, 提示无法构成重写
- 重载和重写的区别
二者其实没有什么联系,只是单纯的名字比较像。这里作为复习还是总结一下。- 重载(overload)是在一个类中,有方法名相同,但参数的类型或数量不同的方法。且重载方法之间没有权限要求。
- 重写(override)是在继承关系之间,子类可以重写父类的方法,要求方法名称,参数类型及其个数完全相同(返回值类型也尽量相同)。另外子类重写方法的访问权限要大于或等于父类方法的访问权限。
- 重写快捷方法、
在子类的空白处右键,点generate,然后点override methods,选择要重写的方法
向下转型
上面重写的例子中,Animal类型的bird是不能使用Bird子类的方法的。但如果还想用,则需要向下转型
public class Test {
public static void main(String[] args) {
Animal animal = new Bird("小黑");
Bird bird = (Bird) animal; //向下转型
bird.fly();
}
}
animal 强制类型转化为Bird类型,然后赋给新定义的bird。
但最好还是少用,因为强转之前必须确定 强转对象在向上转型之前 和接收的对象是同一个类型。看下面的代码,把cat强转为Bird类型是会出现编译错误的
public class Test {
public static void main(String[] args) {
Animal animal = new Cat("小黑");
//animal向上转型前是Cat子类,所以不能再向下转型为Bird子类
Bird bird = (Bird) animal;
bird.fly();
}
}
//编译错误
Exception in thread "main" java.lang.ClassCastException: com.bit.Test1.Cat cannot be cast to com.bit.Test1.Bird
at com.bit.Test1.Test.main(Test.java:6)
instanceof 关键字
可以判定一个引用是否是某个类的实例. 如果是, 则返回 true.
可以用instanceof判定向下转型的对象是否安全
Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
super关键字
表示获取父类对象的引用
注意:super不能出现在静态方法中!!
- super(): 调用父类的构造方法
- super()只能放在子类的构造方法里的第一行
- super.func();调用父类的普通方法
- 当子类方法中有与父类方法重名的时候,父类方法会被重写。这时如果还想调用父类方法则需要用到super.func()
- 不用super.func(),则子类会调用自己的方法
//Cat.java public class Cat extends Animal{ public Cat(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) { Cat cat = new Cat("小白"); cat.eat("鱼"); } } //输出 我是一只猫 小白正在吃鱼
- 使用super.func(),可以调用父类的方法
//Cat.java public class Cat extends Animal{ public Cat(String name) { super(name); } public void eat(String food) { super.eat(food); System.out.println("我是一只猫"); System.out.println(this.name + "正在吃" + food); } } //Test.java public class Test { public static void main(String[] args) { Cat cat = new Cat("小白"); cat.eat("鱼"); } } //输出 我是一只小动物 小白正在吃鱼 我是一只猫 小白正在吃鱼
- super.data
super与this的区别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I4LQxW6D-1637722022246)(assets/image-20211120154534-5xu2fj4.png)]
理解多态
多态主要是通过向上转型、动态绑定和重写实现的(向下转型很少用)
小栗子
下面举一个小栗子,来展示一下多态的使用场景
现在想要实现一个打印各种团的功能,所以先建立了一个Shape的父类,又根据各种形状建立子类
以下部分是类的编写者要写的代码
class Shape {
public void draw() {
//do nothing
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
以下代码是类的调用者要写的代码
- 不用多态来打印
则需要在一个循环里进行多次 if 条件判断public class TestDemo { public static void drawMap(Shape shape) { shape.draw(); } public static void main(String[] args) { Flower flower = new Flower(); Rect rect = new Rect(); Cycle cycle = new Cycle(); Shape[] shapes = {flower, rect, cycle, rect, flower}; for (Shape shape:shapes) { if (shape.equals("cycle")) { cycle.draw(); } else if (shape.equals("rect")) { rect.draw(); } else if (shape.equals("flower")) { flower.draw(); } } } }
- 用多态
可以看到代码简洁了非常多。而且如果要添加图形,只需要在数组里面添加元素,就可以了。不用再添加额外的 if 条件public class TestDemo { public static void drawMap(Shape shape) { shape.draw(); } public static void main(String[] args) { Flower flower = new Flower(); Rect rect = new Rect(); Cycle cycle = new Cycle(); Shape[] shapes = {flower, rect, cycle, rect, flower}; for (Shape shape:shapes) { shape.draw(); } } }
作用总结
- 可以进一步降低类的使用者对类的使用成本。
- 封装是让类的调用者不需要知道类的实现细节.
- 多态能让类的调用者连这个类的类型是什么都不必知道,只需要知道这个对象具有某个方法即可
比如上面的例子中,调用者想打印图案,不用具体知道圆形是Cycle子类型,方框是什么类型等等,只用知道各种形状的实例都是Shape的子类就行了,调用的时候就可以直接无脑用Shape去调用各个子类里被重写的方法。 - 当然这个例子还可以让代码更简洁,让Shape父类变成一个接口(下面会写)
- 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else。
不要再构造方法中调用重写的方法
这是一个有坑的代码
class B {
public B() {
//父类的构造方法里调用了func()方法
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
//子类对func()方法进行了重写
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0
过程:
- 构造对象D的时候,会调用B的构造方法
- B中的构造方法调用了func()方法,但由于D中对func()方法进行了重写,所以会出发动态绑定
- 所以B中的构造方法会调用D中的func()
- 而此时D对象自身还没有被构造(也就是num成员变量还没来得及被赋值)所以,这时num还在未初始化的状态,默认为0。
- 所以尽可能不要再构造方法里调用其他方法,否则可能会出现奇怪的问题,而且及其难以发现。
抽象类
语法规则
没有实际工作(do nothing)的方法,可以将其设计为抽象方法。
包含抽象方法的类叫抽象类。
abstract class Shape {
abstract public void draw();
}
注意:
- 抽象方法不能有方法体(没有{}),也不能执行代码
- 如果一个类继承了抽象类,则必须重写抽象类的所有抽象方法。否则编译会报错。
- 抽象类不能直接被实例化
Shape shape = new Shape(); // 编译出错 Error:(30, 23) java: Shape是抽象的; 无法实例化
- 抽象方法不能是private的(本来重写的方法就不能是private的)
abstract class Shape { abstract private void draw(); } // 编译出错 Error:(4, 27) java: 非法的修饰符组合: abstract和private
- 抽象类中可以包含非抽象方法,也可以包含成员变量。这里的非抽象方法和普通方法的规则是一样的,可以被重写,也可以被子类调用。
abstract class Shape { abstract public void draw(); void func() { //抽象类中的非抽象方法 System.out.println("func"); } } class Rect extends Shape { ... } public class Test { public static void main(String[] args) { Shape shape = new Rect(); shape.func(); } } // 执行结果 func
- 抽象类也可以继承抽象类
- 一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法。
- 但当A再次被一个普通类继承后,则A和B的两个抽象类忠的抽象方法,必须被重写。
- 抽象类不能被final修饰(final+类就是不能被继承)
- #TODO#抽象方法也不能被final修饰(final+方法就是不能被重写)
抽象类的作用
- 抽象类的最大意义就是被继承
- 抽象类本身不能被实例化,必须创建该抽象类的子类。然后让子类重写抽象类中的方法。
- 所以为什么不直接用普通类呢?也能被继承,也能被重写
- 使用抽象类相当于多了一重编译器的校验
- 有些工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的
- 父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如final、@Override等,
充分利用编译器的校验, 在实际开发中是非常有意义的。
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量
语法规则
interface Ishape {
void draw(); //省略了abstract public
int num = 10; //省略了public static final
}
class Cycle implements Ishape {
@Override
public void draw() {
System.out.println("o");
}
}
public class Test {
public static void main(String[] args) {
Ishape shape = new Cycle();
shape.draw();
}
}
- 用 interface 定义一个接口
- 接口中只能包含静态常量(static final),当然public static final也可以省略
- 接口中的方法一定是public,因此可以省略public
- 接口中的抽象方法可以省略abstract
- 接口中的普通方法,不能有具体时间。如果非要实现,必须用 default 来修饰这个方法
- 接口中可以有 static 方法。
- Cycle 使用 implements 继承接口,其含义不是“扩展”而是“实现”
- 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
- 接口不能单独被(通过new)实例化
- 要注意子类中重写抽象方法的访问权限,也只能是public,且public不能省略!(省略就是包访问权限了,相当于访问权限升级了)
- 接口与接口之间可以通过extends来拓展接口的功能
- 接口B通过extends来拓展另一个接口C之间的关系时。
- 当一个类D通过implements实现接口B的时候,此时必须重写B和C两个接口的抽象方法。
实现多个接口
继承(extends)一次只能继承一个父类,如果想实现“多继承”,Java可以用同时实现多个接口来达到这个效果
- 一个类可以通过extends继承一个抽象类或者普通类,同时也可以通过implements实现多个接口,接口之间使用 , 隔开。
- 一个实例
class Animal { protected String name; public Animal(String name) { this.name = name; } } //不是所有的动物都会飞,所以fly()不能写到animal当中 //如果写到另一个类中,也不行,因为一个类不能继承多个类 //所以用接口来实现这些功能最合适 interface IFlying { void fly(); } interface IRunning { void run(); } interface ISwimming { void swim(); } class Dog extends Animal implements IRunning { public Dog(String name) { super(name); } @Override public void run() { 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 Bird extends Animal implements IFlying { public Bird(String name) { super(name); } @Override public void fly() { System.out.println(this.name + "正在飞!"); } } class Robot implements IRunning { @Override public void run() { System.out.println("机器人在跑"); } } public class Test1 { public static void runFunc(IRunning irunning) { irunning.run(); } public static void main(String[] args) { runFunc(new Dog("狗子")); runFunc(new Frog("青蛙")); runFunc(new Robot()); //机器人不是Animal的子类,但也可以调用runFunc函数 } }
常用接口
比较的时候,用哪个接口取决于业务需求,但一般推荐比较器接口(Comparator)
-
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 //谁调用compareTo谁就是this public int compareTo(Student o) { /* if (this.age > o.age) { return 1; } else if (this.age == o.age) { return 0; } else { return -1; } */ //return this.name.compareTo(o.name); return this.age - o.age; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + ", score=" + score + '}'; } } public class Test { public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student(12, "zhangsan", 91.5); students[1] = new Student(13, "wangwu", 90.5); students[2] = new Student(11, "lisi", 89.3); System.out.println(Arrays.toString(students)); Arrays.sort(students); System.out.println(Arrays.toString(students)); } public static void main1(String[] args) { int[] array = {1, 21, 3, 14, 5, 16}; System.out.println(Arrays.toString(array)); Arrays.sort(array); System.out.println(Arrays.toString(array)); } }
- 作用
-
Comparator
-
特点
对类的侵入性很小,而且很灵活 -
实例
还是刚才的例子,但是要单独写一个排序的类
如果需要按年龄排序,则写一个AgeComparator,class AgeComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o1.age - o2.age; } }
如果要根据成绩排序,则写一个ScoreComparator
class ScoreComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return (int)(o1.score - o2.score); } }
如果根据姓名排序,则写一个NameComparator
class NameComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o1.name.compareTo(o2.name); } }
然后就可以按照你想要的排序方式去比较
public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student(12, "zhangsan", 91.5); students[1] = new Student(13, "wangwu", 90.5); students[2] = new Student(11, "lisi", 89.3); System.out.println(Arrays.toString(students)); //按年龄排序 AgeComparator ageComparator = new AgeComparator(); Arrays.sort(students, ageComparator); System.out.println(Arrays.toString(students)); //按成绩排序 ScoreComparator scoreComparator = new ScoreComparator(); Arrays.sort(students, scoreComparator); System.out.println(Arrays.toString(students)); //按姓名排序 NameComparator nameComparator = new NameComparator(); Arrays.sort(students, scoreComparator); System.out.println(Arrays.toString(students)); }
-