java学习笔记二、面向对象【OOP】Object Oriented Programming
一、三大特性: 1、封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。 2、继承:提高代码复用性;继承是多态的前提 3、多态:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。 二、五大原则 1、单一职责原则SRP(Single Responsibility Principle) 类的功能要单一,不能包罗万象,跟杂货铺似的。 2、开放封闭原则OCP(Open-Close Principle) 一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。 3、里式替换原则LSP(the Liskov Substitution Principle LSP) 子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~ 4、依赖倒置原则DIP(the Dependency Inversion Principle DIP) 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的是抽象的中国人,而不是你是xx村的。 5、接口分离原则ISP(the Interface Segregation Principle ISP) 设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
三、继承格式 在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下: 3.1、类的继承格式 class 父类 { } class 子类 extends 父类 { } 3.2、继承的特性: 1、子类拥有父类非 private 的属性、方法。 2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。 3、子类可以用自己的方式实现父类的方法。 4、Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是, 例如 B 类继承 A 类,C 类继承 B 类, 所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差) 3.3、继承关键字 1、继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字, 则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。 2、extends关键字 在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。 案例:extends 关键字 public class Animal { private String name; private int id; public Animal(String myName, String myid) { //初始化属性值 } public void eat() { //吃东西方法的具体实现 } public void sleep() { //睡觉方法的具体实现 } } public class Penguin extends Animal{ } 3、implements关键字 使用 implements 关键字可以变相的使java具有多继承的特性, 使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。 案例:implements 关键字 """ public interface A { public void eat(); public void sleep(); } public interface B { public void show(); } public class C implements A,B { } """ 4、super 与 this 关键字 super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。 this关键字:指向自己的引用。 案例: class Animal { void eat() { System.out.println("animal : eat"); } } class Dog extends Animal { void eat() { System.out.println("dog : eat"); } void eatTest() { this.eat(); // this 调用自己的方法 super.eat(); // super 调用父类方法 } } public class Test { public static void main(String[] args) { Animal a = new Animal(); a.eat(); Dog d = new Dog(); d.eatTest(); } } 5.1、final关键字 final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写: 声明类: final class 类名 {//类体} 声明方法: 修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体} 6.1、构造函数 1、子类是不继承父类的构造器,它只是调用(隐式或显式); 2、如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表; 3、如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器 案例: """ class SuperSub{ private int n; //构造函数1 SuperSub() { // TODO Auto-generated constructor stub System.out.println("SuperClass()"); } //构造函数2 SuperSub(int n){ System.out.println("SuperClass(int n)"); this.n=n; } } //继承 class SubClass extends SuperSub{ private int n; //// 自动调用父类的无参数构造器 SubClass() { // TODO Auto-generated constructor stub System.out.println("SubClass"); } public SubClass(int n) { super(300);// 调用父类中带有参数的构造器 System.out.println("SubClass(int n):"+n); this.n=n; } } //SubClass类继承 class SubClass2 extends SuperSub{ private int n; //自动调用父类的无参数构造器 public SubClass2(int n) { // TODO Auto-generated constructor stub System.out.println("SubClass2(int n):"+n); this.n=n; } SubClass2() { super(400); // TODO Auto-generated constructor stub System.out.println("SubClass2()"); } } public class TestSuperSub{ public static void main(String[] args) { System.out.println("------SubClass 类继承------"); SubClass sc1 = new SubClass(); SubClass sc2 = new SubClass(100); System.out.println("------SubClass2 类继承------"); SubClass2 sc3 = new SubClass2(); SubClass2 sc4 = new SubClass2(200); } } /* * * ------SubClass 类继承------ SuperClass() SubClass SuperClass(int n) SubClass(int n):100 ------SubClass2 类继承------ SuperClass(int n) SubClass2() SuperClass() SubClass2(int n):200 * * */ """ 四、Java重写(Override)与重载Overload) 1、 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写! 2、 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。 3、 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常, 因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。 在面向对象原则里,重写意味着可以重写任何现有方法。实例如下: """ class Animal{ public void move() { System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move() { //Super 关键字的使用:当需要在子类中调用父类的被重写方法时,要使用 super 关键字。 super.move(); // 应用super类的方法 System.out.println("狗可以跑"); } public void bark(){ System.out.println("狗可以吠叫"); } } public class TestDog{ public static void main(String args[]) { Animal a = new Animal(); Dog b = new Dog(); a.move(); b.move(); b.bark(); } } """ 1、方法重写的规则 参数列表与被重写方法的参数列表必须完全相同。 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。 父类的成员方法只能被它的子类重写。 声明为 final 的方法不能被重写。 声明为 static 的方法不能被重写,但是能够被再次声明。 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。 构造方法不能被重写。 如果不能继承一个类,则不能重写该类的方法。 2、重载(Overload) 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。 最常用的地方就是构造器的重载。 重载规则: 被重载的方法必须改变参数列表(参数个数或类型不一样); 被重载的方法可以改变返回类型; 被重载的方法可以改变访问修饰符; 被重载的方法可以声明新的或更广的检查异常; 方法能够在同一个类中或者在一个子类中被重载。 无法以返回值类型作为重载函数的区分标准。 案例: """ public class Overloading { public int test(){ System.out.println("test1"); return 1; } public void test(int a){ System.out.println("test2"); } //以下两个参数类型顺序不同 public String test(int a,String s){ System.out.println("test3"); return "returntest3"; } public String test(String s,int a){ System.out.println("test4"); return "returntest4"; } public static void main(String[] args){ Overloading o = new Overloading(); System.out.println(o.test()); o.test(1); System.out.println(o.test(1,"test3")); System.out.println(o.test("test4",1)); } } """ 总结: 方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。 (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。 (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。 (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
五、Java 多态 多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作 多态存在的三个必要条件 1、继承 2、重写 3、父类引用指向子类对象:Parent p = new Child(); 案例: """ class Shape{ void draw(){} } class Circle extends Shape{ void draw(){ System.out.println("Circle.draw()"); } } class Square extends Shape{ void draw(){ System.out.println("Square.draw()"); } } //1、继承extendsextends,2、重写draw、 class Triangle extends Shape{ void draw(){ System.out.println("Triangle.draw()"); } } 多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。 """ 案例3: """ public class Test3{ public static void main(String[] args) { show(new Cat()); // 以 Cat 对象调用 show 方法 show(new Dog()); // 以 Dog 对象调用 show 方法 Animal a = new Cat(); // 向上转型 --父类引用变量可以访问子类中属于父类的属性和方法,但是不能访问子类独有的属性和方法。 a.eat(); // 调用的是 Cat 的 eat Cat c = (Cat)a; // 向下转型 只有当这个对象原本就是子类对象,通过向上转型得到的时候才能够成功转型。 c.work(); // 调用的是 Cat 的 work } public static void show(Animal a) { a.eat(); //类型判断 if (a instanceof Cat) {//猫做 Cat c = (Cat)a; c.work(); }else if (a instanceof Brid) { Brid c = (Brid)a; c.work(); } } } abstract class Animal{ abstract void eat(); } class Cat extends Animal{ public void eat() { System.out.println("eat fish"); } public void work() { System.out.println("抓老鼠"); } } class Brid extends Animal{ public void eat() { System.out.println("吃骨头"); } public void work() { System.out.println("看家"); } } """ 基础知识:Java中的继承机制使得一个类可以继承另一个类,继承的类称为子类, 被继承的类称为父类。在一个子类被创建的时候,首先会在内存中创建一个父类对象, 然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象, 所以子类可以继承父类中所有的属性和方法,包括private修饰的属性和方法, 但是子类只是拥有父类private修饰的属性和方法,却不能直接使用它, 也就是无法直接访问到它(子类可以通过调用父类的public声明的get方法来获取父类的private属性, 但无法访问父类的private方法)。同时子类可以对继承的方法进行重写(@Override),并且新建自己独有的方法。 虚函数: 虚函数的存在是为了多态。 Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数, 动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。 重写: 我们将介绍在 Java 中,当设计类时,被重写的方法的行为怎样影响多态性。 我们已经讨论了方法的重写,也就是子类能够重写父类的方法。 当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。 要想调用父类中被重写的方法,则必须使用关键字 super。 六、Java 抽象类 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。 案例: """ /* 文件名 : Employee.java */ public abstract class Employee { private String name; private String address; private int number; public Employee(String name, String address, int number) { System.out.println("Constructing an Employee"); this.name = name; this.address = address; this.number = number; } public double computePay() { System.out.println("Inside Employee computePay"); return 0.0; } public void mailCheck() { System.out.println("Mailing a check to " + this.name + " " + this.address); } public String toString() { return name + " " + address + " " + number; } public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; } } """ 案例2 """ /* 文件名 : AbstractDemo.java */ public class AbstractDemo { public static void main(String [] args) { /* 以下是不允许的,会引发错误 */ Employee e = new Employee("George W.", "Houston, TX", 43); System.out.println(" Call mailCheck using Employee reference--"); e.mailCheck(); } } """ Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。 抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。 public abstract class Employee { private String name; private String address; private int number; public abstract double computePay(); //其余代码 } 声明抽象方法会造成以下两个结果: 1、如果一个类包含抽象方法,那么该类必须是抽象类。 2、任何子类必须重写父类的抽象方法,或者声明自身为抽象类。 继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。 如果Salary类继承了Employee类,那么它必须实现computePay()方法: Salary.java 文件代码: /* 文件名 : Salary.java */ public class Salary extends Employee { private double salary; // Annual salary public double computePay() { System.out.println("Computing salary pay for " + getName()); return salary/52; } //其余代码 } 抽象类总结规定 1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。 2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。 3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。 4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。 5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。 八、Java 封装 在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法 封装的优点 1. 良好的封装能够减少耦合。 2. 类内部的结构可以自由修改。 3. 可以对成员变量进行更精确的控制。 4. 隐藏信息,实现细节。 采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。 案例: """ /* * 实现Java封装的步骤 * 1. 修改属性的可见性来限制对属性的访问(一般限制为private) * 这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。 * * 2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问 */ public class Person{ private String name; private int age; public int getAge() { return age; } public String getName() { return name; } public void setAge(int age) { //采用 this 关键字是为了解决实例变量(private String name) //和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。 this.age = age; } public void setName(String name) { this.name=name; } } """ 九、Java 接口 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。 接口与类相似点: 一个接口可以有多个方法。 接口文件保存在 .java 结尾的文件中,文件名使用接口名。 接口的字节码文件保存在 .class 结尾的文件中。 接口相应的字节码文件必须在与包名称相匹配的目录结构中。 接口与类的区别: 接口不能用于实例化对象。 接口没有构造方法。 接口中所有的方法必须是抽象方法。 接口不能包含成员变量,除了 static 和 final 变量。 接口不是被类继承了,而是要被类实现。 接口支持多继承。 接口特性 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。 案例: """ import java.lang.*; public interface NameOfInterface{ } """ 接口有以下特性: 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。 接口中的方法都是公有的。 """ interface Animal{ public void eat(); public void travel(); } public class MammalInt implements Animal{ public void eat(){ System.out.println("mammalInt eat"); } public void travel(){ System.out.println("mammalInt travel"); } public int noOfLegs(){ return 0; } public static void main(String [] args){ MammalInt m = new MammalInt(); m.eat(); m.travel(); } } """ 接口的继承 一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。 下面的Sports接口被Hockey和Football接口继承: // 文件名: Sports.java public interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); } // 文件名: Football.java public interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); } // 文件名: Hockey.java public interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); } Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。 相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。 接口的多继承 在Java中,类的多继承是不合法,但接口允许多继承。 在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示: public interface Hockey extends Sports, Event 以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法 九、java 枚举(enum) Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。 Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。 """ enum Color{ RED,GREEN,BLUE; } public class Test{ public static void main(String[] args){ Color c1= Color.RED; System.out.println(c1); } } 或者for循环 public class MyClass{ public static void main(String[] args){ for (Color myVar:Color.values()){ System.out.println(myVar); } } } 或者switch循环 public class MyClass{ public static void main(String[] args){ Color myVar = Color.BLUE; switch (myVar){ case RED: System.out.println("RED"); break; case BLUE: System.out.println("BLUE"); break; case GREEN: System.out.println("GREEN"); break; } } } """ values(), ordinal() 和 valueOf() 方法 enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。 values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中: values() 返回枚举类中所有的值。 ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。 valueOf()方法返回指定字符串值的枚举常量。 枚举类成员: 枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。 枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。 案例: """ enum Color{ RED,BLUE,GREEN; //构造函数 private Color(){ System.out.println("Constructor called for:"+this.toString()); } public void colorInfo(){ System.out.println("Universal Color"); } } public class Test{ public static void main(String[] args){ Color c1 = Color.RED; System.out.println(c1); c1.colorInfo(); } } """ 案例: """ enum Color{ RED{ public String getColor(){//枚举对象实现抽象方法 return "红色"; } }, GREEN{ public String getColor(){//枚举对象实现抽象方法 return "绿色"; } }, BLUE{ public String getColor(){//枚举对象实现抽象方法 return "蓝色"; } }; public abstract String getColor();//定义抽象方法 } //主函数 public class Test{ public static void main(String[] args) { for (Color c:Color.values()){ System.out.print(c.getColor() + "、"); } } } """
Java 包(package)
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用
-
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
-
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
-
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。