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)阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性
