1.包
包是组织类的一种方式。使用包的主要目的是保证类的唯一性。
例如,你在代码中写了一个Test类,然后你的同事也可能写一个Test类,如果出现两个同名的类,就会导致冲突,导致代码不能编译通过。
1)导入包中的类
Java中已经提供了很多现成的类供我们使用。例如可以用java.util.Date 这种方式引入java.util这个包中的类,但是这种写法比较麻烦,可以直接用import语句导入包。(建议显式的指定要导入的类名)
注意:import和C++的#include差别很大,C++必须#include来导入其他文件内容,但是java不需要,import只是为了写代码的时候更方便,import更类似于C++的namespace和using。
2)静态导入
使用import static可以导入包中的静态方法和字段。
用这种方式可以更方便的写一些代码:
3)将类放到包中
基本规则:
1.在文件的最上方加上一个package语句指定该代码在哪个包中
2.包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(例如:com.bittle.demo)
3.包名要和代码路径相匹配(例如创建一个com.bittle.demo的包,那么会存在一个对应的路径com/bittle/demo来存储代码)
4.如果一个类没有package语句,则该类被放到一个默认包中
4)包的访问权限控制
我们已经了解了类中的public和private。private中的成员只能被类的内部使用,如果某个成员不包含public和private关键字,此时这个成员可以包含在包内部的其他类使用,但是不能在包外部的类使用
常见的系统包:
1.java.lang:系统常用基础类(String、Object)此包从JDK.1后自动导入。
2.java.lang.reflect:java反射编程包。
3.java.net:进行网络编程开发包。
4.java.sql:进行数据库开发的支持包。
5.java.util:是java提供的工具程序包。(集合类等)非常重要
6.java.io:I/O编程开发包
2.继承
背景:代码中创建的类,主要是为了抽象现实中的一些事物(包含属性和方法),有时候客观事物之间就存在一些关联关系,那么在表示成类和对象的时候也会存在一定的关联。
我们可以发现这些代码中存在了大量的冗余代码,发现Animal和Cat以及Bird这几个类中存在了一定的关联关系:
a)这三个类都具备一个相同的eat方法,而且行为是完全一样的
b)这三个类都具备相同的name属性,而且意义是完全一样的
c)从逻辑上讲,Cat和Bird都是一种Animal(is-a语义:可以理解为一只猫是一种动物)
此时我们就可以让Cat和Bird分别继承Animal类,来达到代码重用的效果
那么,Animal这样被继承的类,我们称为父类,基类或超类。对于像Cat和Bird这样的类,我们称为子类或派生类,和现实中儿子继承父亲的财产类似,子类也会继承父类的字段和方法,以达到代码重用的效果。
1)语法规则
基本语法:
class 子类 extends 父类{
}
a)使用extends指定父类;
b)java中一个子类只能继承一个父类(而C++/Python等语言支持多继承)
c)子类会继承父类的所有public的字段和方法;
d)对于父类的private的字段和方法,子类是无法访问的
e)子类的实例中,也包含着父类的实例,可以使用super关键字得到父类实例的引用。
对于上面的代码,可以使用继承进行改进,此时我们让Cat和Bird继承自Animal类,那么Cat在定义的时候就不必再写name和eat方法。
extends英文原意指“扩展”;而我们所写的类的继承,也可以理解成基于父类进行代码上的扩展;例如我们写的Bird类,就是在Animal的基础上扩展了fly类。
如果把name改为private,那么此时子类就不能访问。
2)protected关键字
刚才我们发现,如果把字段设为private,子类就不能访问,但是设为public,又违背了我们“封装”的初衷,那么我们就可以使用protected关键字。
a)对于调用者来说,protected修饰的字段和方法是不能被访问的
b)对于类的子类和同一个包的其他类来说,protected修饰的字段和方法是可以访问的
小总结:java中对于字段和方法共有四种访问权限
a)private:类内部可以访问,类外部不能访问
b)默认(也叫包访问权限):类内部能访问,同一个包中的类可以访问,其他类不能访问;
c)protected:类内部、子类和同一个包中的类可以访问,其他类不能访问;
d)public:类内部和类的调用者都可以访问。
3)更复杂的继承关系
像子类可以进一步的再派生出新的子类的继承方式称为多层继承。
但是一般不希望出现超过三层的继承关系,如果继承层次太多,就需要考虑对代码进行重构了,如果从语法上进行限制继承,就可以使用final关键字。
4)final关键字:
曾经我们学过final关键字,修饰一个变量或者字段的时候,表示常量(不能修改),也可以修饰类,此时表示被修饰的类就不能被继承。
3.组合
和继承类似,组合也是一种表达类之间的方式,也是能够达到代码重用的效果,例如:表示一个学校
public class Student{
…
}
public class Teacher{
…
}
public class School{
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法(诸如extends这样的关键字),仅仅是一个类的实例作为另外一个类的字段,是设计类的一种常用方式之一。
组合表示has-a语义:
像刚才的例子,可以理解为一个学校中“包含”若干个学生和教师
4.多态
1)向上转型
在刚才的例子中,我们写了如下形式的代码:
Bird bird = new Bird(“小鸟”);
这个代码也可以写成:
Bird bird = new Bird(“小鸟”);
Animal bird2 = bird;
//或者下面这种
Animal bird2 = new Bird(“小鸟”);
此时bird2是一个父类(Animal)的引用,指向一个子类(Bird)的实例,这种方法为向上转型
向上转型发生的时机:
a)直接赋值
b)方法传参
c)方法返回
上边的例子就是直接赋值。
方法传参:
public class Test {
public static void main(String[] args){
Bird bird = new Bird("小鸟");
feed(bird);
}
public static void feed(Animal animal){
animal.eat("谷子");
}
此时形参animal的类型是Animal(基类),实际上对应到Bird(父类)的实例。
方法返回:
public class Test {
public static void main(String[] args){
Animal animal = findMyAnimal();
}
public static Animal findMyAnimal(){
Bird bird = new Bird("小鸟");
return bird;
}
}
此时方法findMyAnimal 返回的是一个Animal类型的引用,但是实际上对应到Bird的实例
2)动态绑定
当子类和父类中出现同名方法的时候,再去调用会出现什么情况呢?
对前面的代码稍加修改,给Bird类也加上同名的eat方法,并且在两个eat中分别加上不同的日志。
package Animal;
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);
}
}
class Bird extends Animal{
public Bird(String name) {
//使用super调用父类的构造方法
super(name);
}
public void fly(){
System.out.println("我是一只小鸟");
System.out.println(this.name +"正在飞");
}
}
public class Test {
public static void main(String[] args){
Animal animal1 = new Animal("跳跳");
animal1.eat("谷子");
Animal animal2 = new Bird("俏俏");
animal2.eat("谷子");
}
}
此时,我们发现:
a)animal1和animal2虽然都是Animal类型的引用,但是animal1指向Animal类型的实例,animal2指向Bird类型的实例;
b)针对animal1和animla2分别用eat方法,发现animal1.eat()实际调用了父类的方法,而animal2.eat()实际调用了子类的方法。
因此在java中,调用某个类的方法,究竟执行了哪段代码(是父类方法的代码还是子类方法的代码),要看究竟这个引用指向的是父类对象还是子类对象,这个过程是程序运行时决定的(而不是编译期),因此称为动态绑定。
理解多态:
实例:打印多种形状
public class Shape {
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw(){
System.out.println("O");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
public class Test {
public static void main(String[] args){
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
drawMap(shape1);
drawMap(shape2);
}
public static void drawMap(Shape shape){
shape.draw();
}
}
在这个代码中,当类的调用者在编写drawMap这个方法的时候,参数类型为Shape(父类),此时在该方法内部并不知道,也不关注当前shape引用指向的是哪个类型(哪个子类)的实例,此时shape这个引用调用draw方法可能会有多种不同的表现(和shape对应的实例相关),这种行为就成为多态。
使用多态的好处:
1.类调用者对关类的使用成本进一步降低
a)封装是让类的调用者不需要知道类的实现细节;
b)多态能让类的调用者连这个类的类型是时什么都不需要知道,只需要知道这个对象具有某个方法即可
因此,多态可以理解为封装的更进一步,让类的调用者对类的使用成本进一步降低。
2.能够降低代码的“圈复杂度”,避免使用大量的if-else
3.可扩展能力更强
如果需要增一种新的形状,使用多态的方式代码改动成本也比较低
5.抽象类
语法规则:
在刚才的打印图形例子中,我们发现父类Shape中的draw方法并没有做实际工作,主要的绘制图形是由Shape的各种子类的draw方法来完成,像这样没有实际工作的方法,我们可以把它设计成一个抽象方法,包含抽象方法的类称为抽象类。
abstract class Shape{
abstract public void draw();
}
在draw方法前加上abstract关键字,表示这是一个抽象方法,同时抽象方法没有方法体,没有{},不能执行具体的代码;对于包含抽象方法的类,必须加上abstract关键字,表示这是一个抽象类。
注意:
1)抽象类不能直接实例化(编译出错)
2)抽象方法不能是private的
3)抽象类中可以包含其他的非抽象方法,也可以包含字段,这个非抽象方法和普通方法的规则都是一样的,可以被重写,也可以被子类直接调用。
抽象类的作用:
1)抽象类存在的最大意义就是为了继承
2)抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法
3)抽象类相当于多了一重编译器的校验
6.接口
接口是抽象类的更进一步,抽象类中还可以包含非抽象方法,和字段,而接口中包含的方法都是抽象方法,字段只能包含静态常量。
interface IShape{
void draw();
}
class Cycle implements IShape{
@Override
public void draw(){
System.out.println("O");
}
}
使用interface定义一个借口,接口中的方法一定是抽象方法,因此可以省略abstract;接口中的方法也一定是public,因此也可以省略;Cycle使用implements继承接口,此时表达的含义不再是“扩展”,而是“实现”;在调用的时候同样可以创建一个接口的引用,对应到一个子类的实例,接口不能单独被实例化。
对于字段来说,接口中只能包含静态常量。
interface IShape{
void draw();
public static final int num = 10;
}
其中的public 、static 、final关键字都可以省略。
提示:1)我们创建接口的时候,一般命名以大写字母I开头;
2)接口的命名一般使用“形容词”性的词;
3)阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性