Java面向对象(下)
继承
java三大特性之一 继承 在逻辑上具有 is-a 关系的类 可以声明成继承关系
继承好处:代码的复用性 扩展性
使用关键字 extends 来表示当前父类是谁
在Java中只有单一继承 :
一个子类只能有一个父类 而一个父类 可以派生出多个子类
但继承关系可以向下传递 父 —> 儿 -->孙 -->…–>n
class Employee {
private String name; //姓名
private double salary = 2000.0; //薪水
public Employee(String name, double salary){
this.name = name;
this.salary = salary;
}
public Employee(){}
public double getSalary(){ return salary; }
}
class Manager extends Employee{
private double bonus; //奖金
public void setBonus(double bonus){ this.bonus = bonus; }
}
public class InheritanceTest {
public static void main(String [] args){
Manager manager = new Manager();
manager.getSalary();
}
}
父类中除了构造器之外其他的所有信息都能被子类继承
子类在继承父类的信息后 可以在原有基础上进行扩展 扩展的内容只能子类自己使用 父类无法访问
在创建子类的过程中 一定会先创建父类 如果没有显示声明 则会默认掉用父类的无参构造来进行创建
如果父类没有无参构造 则必须手动显示声明调用父类的已有构造器
super
在Java中 除了this关键字 之外 还有一个super关键字
super 只能出现在继承关系的子类中 表示某个参数或者某个方法 是父类的属性或方法
super关键字 在一般情况下可写可不写 一般不写
在以下两种情况下 必须要声明super关键字
- 子类方法 重写父类方法的同时 还希望调用父类原有方法体内容 此时必须用super关键字来声明哪 个是父类方法 否则会出现无出口递归 造成栈溢出异常
class Employee {
…
public void displayInfo(){ System.out.println(“name=” + name); }
}
class Manager extends Employee{
private double bonus; //奖金
private String position; //职位
public Manager(String name, double salary, String position){
super(name, salary);
this.position = position;
super.displayInfo();
}
public void setBonus(double bonus){ this.bonus = bonus; }
}
- 在父类只有有参构造时 子类必须使用super关键字 并在当前构造器的第一行进行 父类构造的调用
class Employee {
private String name; //姓名
private double salary = 2000.0; //薪水
public Employee(String name, double salary){
this.name = name;
this.salary = salary;
}
public Employee(){}
public double getSalary(){ return salary; }
public void displayInfo(){ System.out.println(“name=” + name); }
}
class Manager extends Employee{
private double bonus; //奖金
private String position; //职位
public Manager(String name, double salary, String position){
super(name, salary);
this.position = position;
}
public void setBonus(double bonus){ this.bonus = bonus; }
}
访问修饰符
所有的类、属性、方法都是有使用范围的 由访问修饰符类决定
ava中有4大访问修饰符:
位置 | private | 默认 | protected | public |
---|---|---|---|---|
同一个类 | 是 | 是 | 是 | 是 |
同一个包内的类 | 是 | 是 | 是 | |
不同包内的子类 | 是 | 是 | ||
不同包并且不是子类 | 是 |
方法的重写
当子类不满足父类提供的方法时 需要重写父类方法 该过程叫做方法的重写 override
在重写中 Java专门定义了一个注解 @Override 用来在编译过程中校验当前方法是否构成重写
构成重写的语法条件
- 发生在继承关系中
- 方法名相同
- 参数列表相同
- 返回值类型兼容
- 访问范围不能被缩小
- 异常范围不能被扩大
注意:静态方法只能重写父类的静态方法。
class Employee {
private String name; //姓名
private double salary = 2000.0; //薪水
public Employee(String name, double salary){
this.name = name;
this.salary = salary;
}
public double getSalary(){ return salary; }
}
class Manager extends Employee{
private double bonus; //奖金
private String position; //职位
public Manager(String name, double salary, String position){
super(name, salary);
this.position = position;
}
public void setBonus(double bonus){ this.bonus = bonus; }
public double getSalary(){
return super.getSalary() + bonus;
}
}
static关键字
对于希望数据共享的属性或方法 可以使用static关键字进行修饰 其内容会随着类的加载而初始化 整个 程序只有一份static数据
static既可以修饰属性 该属性可被共享
static也可修饰方法 该方法只能调用static修饰的成员 而不能调用非static修饰的内容
static修饰的内容 可以称之为类下属的属性和方法
两种调用方式: 通过类名直接调用 (建议) 通过实例名进行调用(不建议)
对于static修饰的属性 提供了静态代码块 可以进行静态属性初始化
public class Dog{
private static int count = 100;
private int id;
private String name;
public Dog(String name){
this.name = name;
id = count++;
}
public void displayInfo(){
System.out.print(“名字是:” + name + 编号是" + id);
}
public static void currentCount(){
System.out.println("当前编号是" + count);
}
public static void main(String[] args){
Dog.currentCount();
Dog dog = new Dog("小黑");
dog.displayInfo();
Dog dog2 = new Dog(“旺财”);
dog2.displayInfo();
Dog.currentCount();
}
}
static 修饰的方法中 不能使用this和super关键字
static代码块 每次类加载时 只执行一次 而普通代码块 随着对象的创建 每次都会执行
执行顺序:
静态代码块 --> 普通代码块 -->构造方法
--> 普通代码块 --> 构造方法 …
public class Student{
private static String country;
private String name;
static {
country = "中国";
System.out.println("Student类已经加载");
}
public Student(String name){
this.name = name;
}
public void displayInfo(){
System.out.println("我的名字是:" + name + ",国家是:" + country);
}
public static void main(String[] args){
//Student stu = new Student("张三");
//stu.displayInfo();
}
}
final关键字
final修饰的变量(成员变量或局部变量)的值不能改变,即等同于常量,在定义时就必须要进行初始化, 之后就再也不改变它的值了。在后期版本中可以将声明和初始化分开执行 但是每个步骤都不能省略 且 一旦赋值完成后 值不能修改
final修饰方法的参数叫最终参数。调用这个方法时给最终参数赋值之后,在方法体内再也不能改变它的 值。
final修饰的方法不能被子类重写。
final修饰的类不能被继承。
public class TestFinal {
public static void main(String[] args) {
T t = new T();
//t.i = 100;
}
}
final class T {
final int i = 10;
public final void m() { //j = 11; }
}
class TT extends T {}
单例模式
在项目中经常会遇到一些功能性的类,那么可以将该类制作成一个单例模式的类,全项目只适用这一个 类来提供服务,节省内存开销。
单例模式的写法有很多
- 饿汉式
- 懒汉式
- 静态内部类
- 线程锁
- 双重锁
- 枚举方式
//饿汉式 --- 简单易用 建议使用
public class Singleton01{
//私有化构造器
private Singleton01(){}
//提供私有静态变量 用来接收单例对象
private static Singleton01 s1 = new Singleton01();
//提供静态公共访问方式
public static Singleton01 getInstance(){
return s1;
}
}
//懒汉式 对于饿汉式的加载时创建对象 懒汉式提出使用时创建对象 但存在线程安全问题
public class Singleton02{
private Singleton02(){}
private static Singleton02 s2 ;
public static Singleton02 getInstance(){
if(null == s2){
s2 = new Singleton02();
}
return s2;
}
}
多态
Java三大特性之一 多态
同一个事物具有不同形态
对象的多态性:
父类的变量指向子类的对象 或 子类的对象赋值给父类的变量
子类对象和父类对象之间的相互转换 称之为类型转换
子类转父类 无需手动处理 该过程称之为向上转型
但是父类转子类 必须强制类型转换 该过程称之为向下转型 且需要判定当前父类是否是由该子类伪装而 成
判定一个父类是否指向某个具体的子类 需要使用关键字 instanceof 返回值为boolean
•语法:对象变量名 instanceof 类名(或接口名)
class Animal {
private String name;
Animal(String name) {
this.name = name;
}
public String getName(){
return name;
}
}
class Cat extends Animal {
private String eyesColor;
Cat(String n,String c) {
super(n); eyesColor = c;
}
public String getEyesColor(){
return eyesColor;
}
}
class Dog extends Animal {
private String furColor;
Dog(String n,String c) {
super(n); furColor = c;
}
public String getFurColor() {
return furColor;
}
}
public class CastingTest{
public static void main(String args[]){
Animal a = new Animal("动物");
Cat c = new Cat("猫","black");
Dog d = new Dog("狗","yellow");
System.out.println(a instanceof Animal);
System.out.println(c instanceof Animal);
System.out.println(d instanceof Animal);
System.out.println(a instanceof Cat);
//向上转型
Animal an = new Dog("旺财","yellow");
System.out.println(an.getName());
System.out.println(an.getFurColor()); //error!
System.out.println(an instanceof Animal); //true
System.out.println(an instanceof Dog); //true
//向下转型,要加强制转换符
//-- 为了安全起见,要加 instanceof 判断
Dog d2 = (Dog)an;
//Cat c2 = (Cat)an;
System.out.println(d2.getFurColor());
}
}
使用多态 能够大大提升当前项目的扩展性 并提升代码的复用性
方法的动态绑定
随着程序的执行 父类的方法会获取当前正在指向的子类 并动态绑定至该子类的同名重写方法
动态绑定的要求:
- 需要继承关系
- 子类必须重写父类方法
- 必须有多态性 – 父类变量指向子类对象
- 父类变量 调用子类重写父类的方法
class Animal {
private String name;
public Animal(String name) {•this.name = name;
}
public void enjoy(){
System.out.println("叫声......");
}
}
class Cat extends Animal {
private String eyesColor;
public Cat(String n) {super(n);}
public void enjoy(){
System.out.println("喵~喵~");
}
}
class Dog extends Animal {
private String furColor;
public Dog(String n) {super(n);}
public void enjoy(){
System.out.println("汪~汪~");
}
}
class Lady{
private String name;
private Animal pet;
public Lady(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
public void myPetEnjoy(){
pet.enjoy();
}
}
public class DynamicBindingTest {
public static void main(String args[]){
Cat c = new Cat("catname","blue");
Lady l = new Lady(“张女士”, c);
l.myPetEnjoy();
}
}
抽象类
当一个类中有某些方法不知道如何实现时,可以将该方法定义成抽象方法,只需使用abstract修饰符进 行修饰
抽象方法形式:(没有方法体)
[访问修饰符] abstract 返回类型 方法名(参数列表);
在一个类中如果出现了抽象方法 则该类 必须是抽象类
声明抽象类语法:
[访问修饰符] abstract class 类名{…… }
抽象类特征:
- 有构造方法 但是无法调用
- 可以有以实现的方法
- 可以定义自己的成员属性
- 抽象方法的访问修饰符不能是private
用到抽象类其实就是面向父类编程的思想
抽象类的使用:
无法直接通过new来创建对象 需要定义一个类 继承自抽象类 之后重写抽象类中所有未实现方法 如果有 任意一个 抽象方法未实现 则当前类也必须定义为抽象类
abstract class Shape { //形状类
protected double length; //长
protected double width; //宽
public Shape(double length, double width){
this.length = length; this.width = width;
}
public abstract double area(); //计算面积
}
class Rectangle extends Shape { //矩形
Rectangle(final double num, final double num1) { super(num, num1);
}
public double area() { return length * width; }
}
class Triangle extends Shape{ //三角形
Triangle(final double num, final double num1) { super(num, num1);
}
public double area() { return length * width/2; }
}
public class TestAbstract{
public static void main(String[] args){ … }
}
思考:
- 抽象类中可不可以没有抽象方法?
- 有抽象方法的类可以可以不是抽象类?
接口
接口就是某个事物对外提供的一些功能的申明 。相当于一个说明书
可以利用接口实现多态,同时也弥补Java单一继承的弱点
使用interface关键字定义接口。
JDK8之前,在接口中所有的方法必须都是抽象方法 且修饰符必须是 public abstract
所有的属性 都必须是静态常量 修饰符为 public static final
如果不定义修饰符 则JVM会在编译过程中自动添加 但是不能出现和语法修饰符相悖的修饰符
接口没有构造方法,所以不能被实例化(不能用来创建对象)。
//方法接口
public interface Runner{
public void run();
}
//定义常量的接口
public interface Constants{
public static final int COLOR_RED = 1;
public static final int COLOR_GREEN = 2;
public static final int COLOR_BLUE = 3;
}
使用接口可以提升扩展性 降低程序的耦合度
使用接口 也可以认为是面向接口编程
接口的使用
用关键字 implements 实现接口。如:
•class Car implements Runner
每个类只能有一个父类,但可以实现多个接口。如果实现多个接口,则用逗号隔开接口名称,如下所 示:
•class Car implements Runner, Constants
一个类实现了一个接口,它必须实现接口中定义的所有方法,否则该类必须声明为抽象类。
接口可以继承自其它的接口,并添加新的常量和方法。接口支持多重继承。
class Car implements Runner,Constants{ //实现两个接口
public void run(){
System.out.println("车颜色是:" + COLOR_RED);
System.out.println("用四个轮子跑...");
}
}
interface Animal extends Runner{ //接口的继承
void breathe(); //呼吸
}
class Fish implements Animal{
public void run(){
System.out.println("颜色是:" + COLOR_BLUE);
System.out.println(“游啊游...");
}
public void breathe(){
System.out.println("冒气泡来呼吸");
}
}
嵌套类
声明在类的内部的类称之为嵌套类(nested class)
//语法定义
[public] class OuterClass{
...
[public|proteceted|private] [static] class NestedClass{
...
}
}
嵌套类主要可以分为两大类
- 静态嵌套类:用static修饰的嵌套类
- 非静态嵌套类:内部类
嵌套类在编译后会生成OuterClass$NestedClass.class类文件
内部类
内部类作为外部类的一个成员存在,与外部类的成员变量、成员方法并列
class Outer {
private int outer_i = 100;
private int j = 123;
public void test() { System.out.println("Outer:test()"); }
public void accessInner(){
Inner inner = new Inner();//外部类中使用内部类也需要创建出它的对象
inner.display();
}
public class Inner {
private int inner_i = 100;
private int j = 789; //与外部类某个属性同名
public void display() {
//内部类中可直接访问外部类的属性
System.out.println("Inner:outer_i=" + outer_i);
test(); //内部类中可直接访问外部类的方法
//内部类可以用this来访问自己的成员
System.out.println("Inner:inner_i=" + this.inner_i);
System.out.println(j); //访问的是内部类的同名成员
//通过“外部类.this.成员名”来访问外部类的同名成员
System.out.println(Outer.this.j);
}
}
}
//调用测试
public class MemberInnerClassTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
outer.accessInner();
//在外部类以外的地方创建内部类的对象
Outer.Inner inner = outer.new Inner();
inner.display();
}
}
静态嵌套类
静态嵌套类中可声明static成员或非静态成员,但只能访问外部类中的静态成员
[public ] class OuterClass{
...
static class StaticNestedClass { //静态嵌套类
...
}
}
方法类
•方法类(局部内部类):在方法体内声明的类
可访问它所在方法中的final参数和final局部变量
由于线程与并发的原因,局部内部类仅能方法中的final参数和final局部变量。
public class Outer{
public void test(final int y){
final int x = 9;//可以访问final修饰的变量
class FunctionInner{//方法类(局部内部类)
public void show()
{
System.out.println(x+" "+y);
}
}
FunctionInner f = new FunctionInner();//创建方法类对象
f.show();//调用方法类中的方法
}
匿名内部类
匿名内部类:没有声明名称的内部类
匿名类与方法类相同,仅能访问方法中final的局部变量。
public class AnnonymoseInnerClassTest {
public static void main(String[] args) {
(new AClass("redhacker") {
public void print() { //对父类的print方法进行覆盖
System.out.println("the anonymose class print");
super.print(); //调用父类中的print方法
}
}).print(); //调用覆盖后的print方法
}
}
class AClass {
private String name;
AClass(String name) { this.name = name;}
public void print() {
System.out.println("SuperClass:The name = " + name);
}
}
说明
- 方法类和匿名内部类最常见的用处就是实现一个接口,而这个接口通常都只会使用一次,所以不用 专门用一个类去实现它。
- 比起方法类,匿名类更加简洁。但当类体较长时,这时候就不适合使用匿名类了,而是应该使用方 法类。