一、包
包(package) 是组织类的一种方式,使用包的主要目的是保证类的唯一性。 导入包中的类 Java 中已经提供了很多现成的类供我们使用。
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
上面的代码也可以写成这样:
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
如果需要使用 java.util 中的其他类, 可以使用 import java.util.* :其中* 表示通配符,意味着导入这个包底下所有的类,但是Java在处理的时候,需要那个类,才会拿哪个类。而在C语言里面,通过include 关键字导入之后,就会把这个头文件里面的内容全部都拿过来。
1.1静态导入
使用 import static 可以导入包中的静态的方法和字段。 例一:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
例二:
import static java.lang.Math.*;
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println(max(10,20));
}
}
通过静态导入的代码可读性不高,所以静态导入用的很少。
1.2将类放到包中
基本规则:
- 在文件的最上方加上一个
package 语句指定该代码在哪个包中. - 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如
- 包名要和代码路径相匹配. 例如创建
com.company.demo1 的包, 那么会存在一个对应的路径 com/company/demo1 来存储代码. - 如果一个类没有
package 语句, 则该类被放到一个默认包中.
1.3包的访问权限控制
我们已经了解了类中的 public 和 private . private 中的成员只能被类的内部使用. 如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用。 包的访问权限指的是只能在当前包当中进行使用,当你的成员变量不加任何访问修饰限定词的时候,它默认就是一个包访问权限。 同一个包下的成员变量可以访问,其他包下的成员变量无法从外部程序包中对其进行访问。
常见的系统包:
java.lang :系统常用基础类(String、Object ),此包从JDK1.1 后自动导入。java.lang.reflect :java 反射编程包;java.net :进行网络编程开发包。java.sql :进行数据库开发的支持包。java.util :是java提供的工具程序包。(集合类等) 非常重要。java.io :I/O 编程开发包。
二、继承
封装:不必要公开的数据成员和方法使用private 关键字进行修饰。意义:安全性。 继承:对共性的抽取。使用extends 关键字进行处理的。意义:可以对代码进行重复使用。 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了。 基本语法:
class 子类 extends 父类 {
}
其中Cat 这样的类, 我们称为子类, 派生类,Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类。
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 + "正在飞 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "小黑";
System.out.println(cat.name);
cat.eat("猫粮");
Bird bird = new Bird();
bird.name = "圆圆";
System.out.println(bird.name);
bird.fly();
}
}
[注意]:
- 使用
extends 继承父类. Java 中一个子类只能继承一个父类 (而C++/Python 等语言支持多继承).即Java当中是单继承,不能同时继承两个类以上的类,包括两个。- 子类会继承父类的所有
public 的字段和方法. - 对于父类的
private 的字段和方法, 子类中是无法访问的. - 子类的实例中, 也包含着父类的实例. 可以使用
super 关键字得到父类实例的引用。
2.1 super关键字
super :不能出现在静态方法中,其代表的是父类实例的引用。 super的两种常见用法:
- 使用了 super 来调用父类的构造器。
public Cat(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 方法中直接调用 eat (不加super ), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法。即:如果子类和父类字段同名的话,则优先子类的字段,如果想要访问父类的字段,则要加上super 关键字。
子类构造的同时,要先帮助父类来进行构造。需要在子类的构造方法当中,使用super 关键字来显示的调用父类的构造方法。
super有3中表现形式:
- super();//调用父类的构造方法
- super.func();
- super.data;
super与this的区别
super 的概念:表示子类访问父类的属性、方法。不查找本类,而直接调用父类定义。 this 的概念:访问本类中的属性、方法,其先查找本类,如果本类没有就调用父类。
2.2 protected 关键字
protected 关键字:对于类的调用者来说, protected 修饰的字段和方法是不能访问的,对于类的子类和同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的。
Java 中对于字段和方法共有四种访问权限
private : 类内部能访问, 类外部不能访问。- 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected : 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.public : 类内部和类的调用者都能访问。
也就是说: 字段如果是public ,在哪里使用都可以。 private 只有在同一个包的同一个类中才能使用。 default 在同一个包的同一个类及同一包中的不同类中可以使用。 protected 在同一个包的同一个类中,同一包中的不同类中,及不同包中得子类中可以使用。
2.3 final 关键字
如果一个类不想被继承,我们可以设置为final 修饰。 final 关键字修饰类, 此时表示被修饰的类就不能被继承。
1.final int a = 20;
2.final Class A{
}
3.final修饰方法
三、组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。 组合并没有涉及到特殊的语法(诸如extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.这是我们设计类的一种常用方式之一。 如一个学校:
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合表示 has - a 语义。 如:一个学校中 “包含” 若干学生和教师. 继承表示 is - a 语义。 在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物。
四、多态
4.1向上转型
向上转型:表示往父类的方向转。
向上转型发生的时机:
- 直接赋值
在上面的例子中有:
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
}
}
- 作为函数的参数,方法传参
public class Test {
public static void function(Animal ani){
}
public static void main(String[] args) {
Animal animal = new Cat();
Cat cat = new Cat();
function(cat);
}
}
此时形参 ani 的类型是 Animal (基类), 实际上对应到 Cat (父类) 的实例。
- 作为方法返回
public class Test {
public static Animal func2(Animal ani){
Cat cat = new Cat();
return cat;
}
public static void main(String[] args) {
Animal animal = func2();
}
}
此时方法 func2 返回的是一个 Animal 类型的引用, 但是实际上对应到 Cat 的实例。
4.2 动态绑定
编译的时候不能够确定到底调用谁的方法。运行的时候,才知道了调用哪个方法。这就叫做运行时绑定也就是动态绑定。动态绑定也是多态的基础。
1、父类引用引用子类的对象。 2、运行时绑定:通过这个父类引用调用父类和子类同名的覆盖方法。
同名的覆盖方法也叫重写/覆写/覆盖: 1、方法名相同 2、参数列表相同(参数的个数+参数的类型) 3、返回值相同 (特殊:返回值也可以是协变类型) 4、父子类的的情况下
关于重写的注意事项:
- 重写和重载完全不一样.
- 普通方法可以重写,
static 修饰的静态方法不能重写. - 重写中子类方法的访问权限不能低于(大于等于)父类的访问限定.
private 方法不能够重写.- 被
final 修饰的方法不能够重写. - 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
编译时绑定:通过函数的重载实现的。编译的时候,会根据你给的参数的个数和类型,在编译期,确定你最终调用的方法。
重载和重写的区别
重载:方法名相同,参数的类型和个数不同,重载的范围是一个类。 重写:方法名,返回值类型、参数类型及个数完全相同,范围是继承关系,被覆写的方法不能拥有比父类更严格的访问控制权限。
通过父类引用只能访问父类自己的成员。
4.3 向下转型
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途。 比如:
public static void main(String[] args) {
Animal animal = new Bird();
Bird bird = (Bird)animal;
bird.fly();
}
向下转型不安全,不是所有的类型都具有向下转型中得方法,此时,我们可以加上instanceof 判断一下,可以判断出一个引用是否是某个类的实例。如:
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
if(animal instanceof Bird){
Bird bird = (Bird)animal;
bird.fly();
}
}
}
4.4 理解多态
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
public void draw(){
System.out.println("?");
}
}
class Flower extends Shape{
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) {
Rect rect = new Rect();
drawMap(rect);
Flower flower = new Flower();
drawMap(flower);
Triangle triangle = new Triangle();
drawMap(triangle);
}
}
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现 (和 shape 对应的实例相关), 这种行为就称为多态。 使用多态的好处:
- 类调用者对类的使用成本进一步降低
- 封装是让类的调用者不需要知道类的实现细节.
- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
- 能够降低代码的 “圈复杂度”, 避免使用大量的
if - else 圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一个方法的圈复杂度太高, 就需要考虑重构。 如:
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
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 shape:shapes) {
shape.draw();
}
}
}
如果使用 if - else 则实现如下:
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] shapes = {"triangle", "rect", "triangle", "rect", "flower"};
for (String s:shapes) {
if(s.equals("triangle")){
triangle.draw();
}else if(s.equals("rect")){
rect.draw();
}else if (s.equals("flower")){
flower.draw();
}
}
- 可扩展能力更强.
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
五、抽象类
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method)。
abstract class Shape{
public abstract void draw();
}
注意:
- 包含抽象方法的类我们称为抽象类(abstract class)。
- 抽象方法是被abstract修饰的,没有具体实现的方法。
- 抽象类是不可以被实例化的。
- 由于抽象类不能被实例化,所以只能被继承。
- 抽象类当中也可以包含和普通类一样的成员和方法。抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用。
- 一个普通类,继承了一个抽象类,那么这个普通类当中需要重写这个抽象类的所有的抽象方法。
- 抽象类最大的作用,就是为了被继承。
- 一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法。
- 结合第8点,当A类再次被一个普通类继承后,那么A和B这两个抽象类当中的抽象方法,必须被重写。
- 抽象类不能被
final 修饰,抽象方法也不可以被final 修饰。 - 抽象方法不能是
private 修饰的。
使用抽象类相当于多了一重编译器的校验,使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
六、接口
- 接口是使用
interface 来修饰的。interface IA{} - 接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字
default 来修饰这个方法。 - 接口当中,可以有
static 的方法。 - 接口里面的所有的方法都是
public 的,因此可以省略 public 。 - 抽象方法,默认是
public abstract 的。
interface IShape{
public abstract void func1();
default public void draw(){
System.out.println("aaa");
}
public static void func(){
System.out.println("bbb");
}
}
- 接口是不可以被通过关键字
new 来实例化的。 - 类和接口之间的关系是通过
implements 实现了。 - 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
- 接口中的方法一定是抽象方法, 因此可以省略
abstract 。 - 接口当中的成员变量,默认是
public static final 修饰的。 - 当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上
public 。
interface IA{
int A1 = 10;
void funcA();
}
class AClass implements IA{
public void funcA(){
}
}
- 一个类可以通过关键字
extends 继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements 实现多个接口,接口之间使用逗号隔开就好。 - 接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).
如:
interface IA{
public static final int a = 10;
int A = 10;
void funcA();
}
interface IB{
void funcB();
}
class BClass {
}
class AClass extends BClass implements IA,IB{
public void funcA(){
System.out.println("A::funcA()");
System.out.println(A);
}
@Override
public void funcB() {
System.out.println("A::funcB()");
}
}
- 接口和接口之间可以使用
extends 来操作他们的关系,此时,这里面意为:拓展。一个接口IB1 通过 extends 来拓展另一个接口IA1 的功能。此时当一个类C 通过implements 实现这个接口IB1 的时候,此时重写的方法不仅仅是IB1 的抽象方法,还有他从IA1 接口拓展来的功能/方法。
如下所示:
interface IA1{
void funcA();
}
interface IB1 extends IA1{
void funcB();
}
class C implements IB1{
@Override
public void funcA() {
System.out.println("aaa");
}
@Override
public void funcB() {
System.out.println("bbb");
}
}
使用接口也可以实现动态绑定,比如下面的例子:
interface IShape{
void draw();
}
class Rect implements IShape{
public Rect() {
}
@Override
public void draw() {
System.out.println("fangPain");
}
}
class Flower implements IShape {
public void draw(){
System.out.println("hua");
}
}
class Triangle implements IShape{
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("●");
}
}
public class Test {
public static void drawMap( IShape iShape){
iShape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(cycle);
drawMap(triangle);
}
}
实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承的方式来实现的.然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果. 例:
class Animal{
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFlying{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swimming();
}
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 swimming() {
System.out.println(this.name + "正在游泳");
}
}
class Duck extends Animal implements ISwimming,IRunning,IFlying{
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 swimming() {
System.out.println(this.name + "正在游泳");
}
}
public class test04 {
public static void runFunc(IRunning iRunning){
iRunning.run();
}
public static void swimmingFunc(ISwimming iSwimming){
iSwimming.swimming();
}
public static void flyingFunc(IFlying iFlying){
iFlying.fly();
}
public static void main(String[] args) {
runFunc(new Duck("鸭子"));
runFunc(new Frog("青蛙"));
swimmingFunc(new Duck("鸭子"));
flyingFunc(new Bird("小鸟"));
flyingFunc(new Duck("鸭子"));
}
}
有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力。
三个常用的接口
如果自定义的数据类型进行大小的比较一定要实现可以比较的接口。如
Comparable
这个接口有一个很大的缺点:对类的侵入性非常强。一旦写好了,不敢轻易改动。
例:
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
Comparator
灵活,对类的侵入性非常弱。 例:
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", 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 test01 {
public static void main(String[] args) {
Student[] student = new Student[3];
student[0] = new Student("zHang",12,100);
student[1] = new Student("aWang",18,98);
student[2] = new Student("li",56,88);
System.out.println(Arrays.toString(student));
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(student,ageComparator);
System.out.println(Arrays.toString(student));
}
}
Comparable、Comparator 用哪个接口取决于你的业务,一般不叫推荐比较器
Cloneable
未完待续…
以上。
|