Java基础笔记(二)---类、对象、接口和继承
(1)类和对象
(1.1)创建对象
(1)Hero h = new Hero();
创建过程中JVM的对应处理:
- Hero h创建对象对应在内存中开辟栈内存,用来存放对象;
- new Hero()实例化对象对应在内存中开辟堆内存,并且把类的非静态属性方法等放进去;
- =就是让栈内存的引用指向堆内存
(2)Java有5种方式创建对象
- 使用 new 关键字(最常用): ObjectName obj = new ObjectName();
- 使用反射的Class类的newInstance()方法: ObjectName obj = ObjectName.class.newInstance();
- 使用反射的Constructor类的newInstance()方法: ObjectName obj = ObjectName.class.getConstructor.newInstance();
- 使用对象克隆clone()方法: ObjectName obj = obj.clone();
- 使用反序列化(ObjectInputStream)的readObject()方法: try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) { ObjectName obj = ois.readObject(); }
(1.2)访问修饰符
(1)private 私有的
自身:是可以访问的
同包子类:不能继承
不同包子类:不能继承
同包类:不能访问
其他包类:不能访问
(2)package/friendly/default 不写
(3)protected 受保护的
(4)public 公共的
总结
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- package用的不多
(1.3)类属性(静态属性static)
当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性。如果一个属性声明成类属性,那么所有的对象,都共享这么一个值 。
例如:给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司。
如果一个属性,每个英雄都不一样,比如name,这样的属性就应该设计为对象属性,因为它是跟着对象走的,每个对象的name都是不同的。
如果一个属性,所有的英雄都共享,都是一样的,那么就应该设计为类属性。比如血量上限,所有的英雄的血量上限都是 9999,不会因为英雄不同,而取不同的值。 这样的属性,就适合设计为类属性
(1.4)类方法(静态方法static)
又叫做静态方法,访问一个对象方法,必须建立在有一个对象的前提的基础上。而访问类方法,不需要对象的存在,直接就访问。
静态方法static不能调用对象的变量,因为静态方法在类加载的时候就初始化了,对象变量需要在新建对象后才能使用。也就是说不能再静态方法中引用非静态的域。
public class Test{
private float f=1.0f;
int n=12;
static int n=1;
public static void main(String args[]){
Test t=new Test();
//静态方法引用静态的变量,其他变量不能引用
Test.n;
}
}
例如:常用的main方法,不需要有对象,可以直接使用,属于静态方法
如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法。如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法。
(2)接口与继承
(2.1)对象转型
(1)子类转父类(一定可以)
Hero h = new ADHero();
(2)父类转子类(要进行强转)
ADHero ad = new Hero();
(2.2)继承
(2.2.1)简单介绍继承
(1)java中只允许单继承,也就是说一个子类只能继承一个父类。但是允许多重继承,例如一个子类有一个父类,它的父类还可以有自己的父类。
1-为什么需要继承?企鹅和老鼠都有eat、run等方法,这些代码是重复的,后期维护起来就改动两份一样的代码。要解决这个问题,就可以把同类型类的相同方法向上提取到父类里,后期维护的时候只需要修改父类里的方法就可以了。
2-为什么有了抽象类可以继承以后,又出现了更加抽象的接口,抽象类只允许单继承,一个类却可以实现多个接口,更加灵活。并且继承会提高类之间的耦合性,导致代码之间的联系更加紧密,代码独立性变差
(2)子类通过继承,可以直接调用父类中public的属性和方法。但是构造函数不能被继承,构造方法只能被显式或隐式的调用。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
(3)子类是不继承父类的构造器的,它只是调用(隐式或显式)。子类对象实例化的时候,会默认先调用父类的构造器,然后才会调用自身的构造器。
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
例如在子类构造器中有一个super()方法,就是在在调用父类的构造器,不过不写也没事,会默认调用。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
// SubClass 类继承
class SubClass extends SuperClass{
private int n;
SubClass(){ // 自动调用父类的无参数构造器
System.out.println("SubClass");
}
public SubClass(int n){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
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 调用父类方法
}
}
(4)final关键字
final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。
使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写:
final 定义的类,其中的属性、方法不是 final 的。
(5)构造器
(2.2.2)方法的覆写(继承:重写override,体现方法的加强性)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!又叫覆盖 override。
方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
(2.2.3)方法的重载(单个类中:overload,体现方法的多样性)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
方法重载的规则
- 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
- 对返回值没有要求。如果参数的个数、类型、次序都相同,只有返回值不相同,无法构成重载。
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;与权限修饰符也无关。
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
调用重载的方法时通过传递给她们不同的参数个数、参数类型、次序来决定具体使用哪个方法,这叫多态。
(2.2.4)super关键字
(1)只要是被子类重写的方法,不被super调用都是调用子类方法
向上转型:父类只能调用父类方法或者子类覆写后的方法,而子类中的单独方法则是无法调用
public class Base
{
public void methodOne()
{
System.out.print("A");
//没用super调用,所以子类对象b调用的是子类的方法,输出的是:BD
methodTwo();
}
public void methodTwo()
{
System.out.print("B");
}
}
public class Derived extends Base
{
public void methodOne()
{
super.methodOne();
System.out.print("C");
}
public void methodTwo()
{
super.methodTwo();
System.out.print("D");
}
}
Base b = new Derived(); 调用执行b.methodOne()后,输出结果是:ABDC
继承关系中,用多态创建子类对象,我们在调用一个父类被子类重写的方法时,不管是在子类中调用还是在父类中调用,不被super调用都是调用子类方法,只有用super调用时才是调用父类的方法。
(2.3)重写和重载的区别
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
(2.4)多态
(1)多态的种类
1-操作符的多态 :+ 可以作为算数运算,也可以作为字符串连接
2-类的多态 :父类引用指向子类对象(对象转型)
(2)要实现类的多态,需要如下条件:
1-父类(接口)引用指向子类对象:Parent p = new Child();
2-调用的方法有继承和重写
(3)多态的案例一
有时候想把一个对象不当做它所属的特定类型来对待,而是把它当做其父类的对象来对待。例如“把自行车看作是交通工具”。
但是在这种【向上转型】的时候如何确定是什么类型的子类转上来的呢,用到一个概念【后期绑定】,就是说当向对象发送消息时,被调用的diamante直到运行的时候才能确定。
void doSomething (Shape shape) {
shape.erase();
shape.draw();
}
在调用这个方法的时候,可以把子类对象当做参数
Circle circle = new Circle();
Line line = new Line();
doSomething (circle);
doSomething (line);
(4)多态的案例二
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
public class Test {
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 Dog) { // 狗做的事情
Dog c = (Dog)a; //向下转型
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
(5)多态的案例三
public class Employee {
private String name;
private String address;
private int number;
public Employee(String name, String address, int number) {
System.out.println("Employee 构造函数");
this.name = name;
this.address = address;
this.number = number;
}
public void mailCheck() {
System.out.println("邮寄支票给: " + this.name
+ " " + this.address);
}
}
public class Salary extends Employee
{
private double salary; // 全年工资
public Salary(String name, String address, int number, double salary) {
super(name, address, number);
setSalary(salary);
}
public void mailCheck() {
System.out.println("Salary 类的 mailCheck 方法 ");
System.out.println("邮寄支票给:" + getName()
+ " ,工资为:" + salary);
}
}
public class VirtualDemo {
public static void main(String [] args) {
Salary s = new Salary("员工 A", "北京", 3, 3600.00);
Employee e = new Salary("员工 B", "上海", 2, 2400.00);
System.out.println("使用 Salary 的引用调用 mailCheck -- ");
s.mailCheck();
System.out.println("\n使用 Employee 的引用调用 mailCheck--");
e.mailCheck();
}
}
Employee 构造函数
Employee 构造函数
使用 Salary 的引用调用 mailCheck –
Salary 类的 mailCheck 方法
邮寄支票给:员工 A ,工资为:3600.0
使用 Employee 的引用调用 mailCheck–
Salary 类的 mailCheck 方法
邮寄支票给:员工 B ,工资为:2400.0
即使向上转型了,调用的还是子类重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super。
容器设定存储对象的类型,这样可以避免转型的危险和开销。
ArrayList<Shape> shapes = new ArrayList<Shape>();
(2.4)抽象类和接口
(2.4.1)抽象类
(1)抽象类介绍
抽象类的作用类似“模板”,目的是让设计者依据它的格式来修改并创建新的类。但是并不能直接由抽象类创建对象,只能通过子类来创建对象。
规则如下:
- 当一个类有抽象方法的时候,该类必须被声明为抽象类。抽象类不一定必须有抽象方法【有抽象方法的必是抽象类,抽象类不一定必有抽象方法】
- 抽象类可以有构造方法,只不过不能new实例化抽象类对象,所以说抽象类只能声明对象,不能实例化对象
- 抽象类和抽象方法都必须使用abstract关键字声明
- 抽象方法只需要声明,而不需要实现
- 抽象类必须被子继承,子类(如果不是抽象类)必须覆写抽象类中的全部抽象方法【抽象类的子类没得选,接口的实现类有的选】
- 抽象类不能使用final关键字声明,因为final修饰的类不能被继承,也不能使用private声明
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
public abstract class Employee {}
(2)抽象方法介绍
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
public abstract double computePay();
(3)抽象类总结
- 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
(2.4.2)接口
(1)接口的介绍
抽象类可以实现轻量化,但是只能继承,且只能单继承,并且抽象类里还是可以有具体方法的,还不够完全抽象,继承的重写还会使代码之间的关系更加紧密,提高耦合度。而接口可以打破单继承的局限性,可以一个类实现多个接口,并且所有的方法都是抽象的,更加的轻量,更加的灵活,使得代码之间的关系没有那么紧密了,实现解耦。
- 接口可以理解成一种特殊的抽象类,由全局常量和public的抽象方法组成,是一种用来解决多继承局限的手段。接口中的方法必须定义为public。
- 一个子类可以实现多个接口,摆脱了抽象类的单继承局限
- 接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
单继承和多实现的例子:
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable
(2)抽象类和接口的区别
类继承可以实现类的复用和拓展
有时候确定一个类型的父类,父类中的方法设计成抽象类,只有定义但没有方法体,具体的实现放在子类继承的时候再做,这样可以节省很多的代码量,使得父类更轻量
但是抽象类还是不够抽象,还是可以有很多有方法体的方法,我们想让父类更加的轻量,绝对的抽象,那就有了接口,接口的好处就是没有方法实现,更加自由的调用和组合使用
- 子类只能继承一个抽象类,不能继承多个;子类可以实现多个接口
- 抽象类和接口中都可以包含静态成员变量。抽象类可以定义public,protected,package,private,静态和非静态属性,final和非final属性;但是接口中声明的属性,只能是public、静态、final的【抽象类中可以有普通成员变量,接口中没有普通成员变量】
- 抽象类中可以包含静态方法,接口中不能包含静态方法
- 抽象类可以有构造方法,接口中不能有构造方法。
- 抽象类中可以有普通成员变量,接口中没有普通成员变量
- 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
- 优先选用接口,尽量少用抽象类
- 抽象类和接口都不能被实例化。但是可以创建对象,向上转型,让其子类或者实现类实例化
- 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范
(3)接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
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接口。
(4)接口的多继承
在Java中,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。
public interface Hockey extends Sports, Event