14 - Java 面向对象(中级)
包(package)
声明
java的包,类似电脑系统中的文件夹,包里存放的是类文件。
当类文件很多的时候,通常会采用多个包进行存放管理,这种方式称为分包管理。
在项目中,我们将相同功能的类放到一个包中,方便管理。并且日常项目的分工也是以包作为边界。
类中声明的包必须与实际 class 文件所在的文件夹情况相一致,即类声明在 a 包下,则生成的.class文件必须在a文件夹下,否则,程序运行时会找不到类。
声明格式
包的命名规则同样是英文和数字的组合,最好是一个域名的格式,比如我们经常访问的 www.baidu.com,后面的baidu.com就是域名,我们的包就可以命名为com.baidu,当然,各位小伙伴现在还没有自己的域名,所以说我们随便起一个名称就可以了。
类中包的声明格式:
package 包名.包名.包名…;
比如 com.test:
我们之前都是直接创建的类,所以说没有包这个概念,但是现在,我们将类放到包中,就需要注意了:
package com.test; //在放入包中,需要在类的最上面添加package关键字来指明当前类所处的包
public class Main { //将Main类放到com.test这个包中
public static void main(String[] args) {
}
}
这里又是一个新的关键字 package ,这个是用于指定当前类所处的包的,注意,所处的包和对应的目录是一一对应的。
访问
在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)。
包名.包名….类名
java.util.Scanner
//带有包的类,创建对象格式:包名.类名 变量名 = new包名.类名();
java.util.Scanner scan = new java.util.Scanner(System.in);
类的简化访问
- 要使用一个类,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是java.lang包中的类时通常可以省略掉包名,直接使用该类。
- 要使用的类,与当前程序不在同一个包中(即不同文件夹中),要访问的类必须用public修饰才可访问。
导入(import)
- 类的全限定名:包名.类名 java.util.Arrays。
- 在一个类中使用非同包的类和非 java.lang 包下的类,要使用类的全限定名。
- 使用 import 可以省略写包名;而使用 import static 则可以连类名都省略。
- import 语句应该出现在 package 语句(如果有的话)之后、类定义之前。
导入指定包下某个类或全部类
import java.util.Arrays; // 导入 java.util 包下的 Arrays 类
import java.util.*; // 导入 java.util 包下所有被当前类使用到的类
注意:
- Java 默认为所有源文件导入 java.lang 包下的所有类,但不包括其子包下的类。
- 导入指定类中的 static 成员(语法糖),但无法导入与 java.lang.Object 类中的方法名相同的方法,如 toString,equals。
mport static 类的全限定名.该类中的 static 成员名;
import static java.util.Arrays.sort;
import static java.util.Arrays.*;
Java 的常用包
- java.lang:Java 语言的核心类,如 String、Math、System 和 Thread 类等
- java. util:Java 的大量工具类/接口和集合框架类/接口,如 Arrays 和 List、Set 等
- java. net:Java 网络编程相关的类/接口
- java.io:Java 输入/输出编程相关的类/接口
- java. text:Java 格式化相关的类
- java.sql:Java 进行 JDBC 数据库编程的相关类/接口
- java. awt:抽象窗口工具集的相关类/接口,用于构建图形用户界面(GUI)程序
- java.swing:Swing 图形用户界面编程的相关类/接口,用于构建平台无关的 GUI 程序
this 关键字
当一个对象创建之后,JVM 会分配一个引用自身的引用:this
存在位置
- 构造方法中,代表正在初始化的对象
- 实例方法中,代表调用此方法的对象
this
this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this 的用法在 Java 中大体可以分为3种:
普通的直接引用。
这种就不用讲了,this 相当于是指向当前对象本身。
形参与成员名字重名,用 this 来区分。
class Person {
private int age = 10;
public Person(){
System.out.println("初始化年龄:"+age);
}
public int GetAge(int age){
this.age = age;
return this.age;
}
}
public class test1 {
public static void main(String[] args) {
Person Harry = new Person();
System.out.println("Harry's age is "+Harry.GetAge(12));
}
}
运行结果:
初始化年龄:10 Harry's age is 12
可以看到,这里 age 是 GetAge 成员方法的形参,this.age 是 Person 类的成员变量。
引用构造函数
这个和 super 放在一起,见下面。
super
super 可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
super 也有三种用法
普通的直接引用
与 this 类似,super 相当于是指向当前对象的父类,这样就可以用 super.xxx 来引用父类的成员。
2.子类中的成员变量或方法与父类中的成员变量或方法同名
class Country {
String name;
void value() {
name = "China";
}
}
class City extends Country {
String name;
void value() {
name = "Shanghai";
super.value(); //调用父类的方法
System.out.println(name);
System.out.println(super.name);
}
public static void main(String[] args) {
City c=new City();
c.value();
}
}
运行结果:
Shanghai China
可以看到,这里既调用了父类的方法,也调用了父类的变量。若不调用父类方法 value(),只调用父类变量 name 的话,则父类 name 值为默认值 null。
引用构造函数
- super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
- this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt("父类·无参数构造方法: "+"A Person.");
}//构造方法(1)
Person(String name) {
prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
}//构造方法(2)
}
public class Chinese extends Person {
Chinese() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类"无参数构造方法": "+"A chinese coder.");
}
Chinese(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类"含一个参数的构造方法": "+"his name is " + name);
}
Chinese(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
Chinese cn = new Chinese();
cn = new Chinese("codersai");
cn = new Chinese("codersai", 18);
}
}
运行结果:
父类·无参数构造方法: A Person.
子类·调用父类”无参数构造方法“: A chinese coder.
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
子类:调用子类具有相同形参的构造方法:his age is 18
从本例可以看到,可以用 super 和 this 分别调用父类的构造方法和本类中其他形式的构造方法。
例子中 Chinese 类第三种构造方法调用的是本类中第二种构造方法,而第二种构造方法是调用父类的,因此也要先调用父类的构造方法,再调用本类中第二种,最后是重写第三种构造方法。
super 和 this的异同
- super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
- this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
- super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参) this:它代表当前对象名(在程序中易产生二义性之处,应使用 this 来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用 this 来指明成员变量名)
- 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用 super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
- super() 和 this() 类似,区别是,super() 从子类中调用父类的构造方法,this() 在同一类内调用其它方法。
- super() 和 this() 均需放在构造方法内第一行。
- 尽管可以用this调用一个构造器,但却不能调用两个。
- this 和 super 不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。
- 从本质上讲,this 是一个指向本对象的指针, 然而 super 是一个 Java 关键字。
封装
封装是面向对象三大特征之一,是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。
封装原则,将不需要对外提供的内容都隐藏起来,把属性隐藏,提供公共方法对其访问,成员变量private,提供对应的getXxx()/setXxx()方法。
封装的好处,通过方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性。
public class Person {
private String name; //现在类的属性只能被自己直接访问
private int age;
private String sex;
public Person(String name, int age, String sex) { //构造方法也要声明为公共,否则对象都构造不了
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name; //想要知道这个对象的名字,必须通过getName()方法来获取,并且得到的只是名字值,外部无法修改
}
public String getSex() {
return sex;
}
public int getAge() {
return age;
}
}
可以来试一下:
public static void main(String[] args) {
Person person = new Person("小明", 18, "男");
System.out.println(person.getName()); //只能通过调用getName()方法来获取名字
}
也就是说,外部现在只能通过调用我定义的方法来获取成员属性,而我们可以在这个方法中进行一些额外的操作,比如小明可以修改名字,但是名字中不能包含"小"这个字:
public void setName(String name) {
if(name.contains("小")) return;
this.name = name;
}
我们甚至还可以将构造方法改成私有的,需要通过我们的内部的方式来构造对象:
public class Person {
private String name;
private int age;
private String sex;
private Person(){} //不允许外部使用new关键字创建对象
public static Person getInstance() { //而是需要使用我们的独特方法来生成对象并返回
return new Person();
}
}
继承
介绍
继承是面向对象三大特征之一,它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承需要符合的关系是:is-a,父类更通用,子类更具体。
类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
class 父类 { }
class 子类 extends 父类 { }
继承类型
需要注意的是 Java 不支持多继承,但支持多重继承。
继承中的构造方法
一个对象在实例化的时候,需要先去实例化从父类继承到的成员,因为子类继承父类,会继承父类的非私有成员。
而子类在初始化的时候,可能会使用父类的数据,如果父类数据没有先初始化,子类就不能使用这些数据,所以,在子类初始化之前,一定要先完成父类数据的初始化。
在实例化父类部分的时候,默认使用父类中的无参构造
问题:如果父类中没有无参构造,或者父类中的无参构造子类无法访问(使用private修饰无参构造),则此时子类对象无法完成实例化。
解决:
- 给父类中添加一个子类能够访问到的无参构造方法
- 在子类的构造方法中,手动调用父类中能够访问到的构造方法,来实例化父类部分
public class Father {
public Father() {
System.out.println("Father无参构造方法");
}
public Father(String name) {
System.out.println("Father带参构造方法");
System.out.println(name);
}
}
public class Son extends Father {
public Son() {
//super();
super("杨幂");
System.out.println("Son无参构造方法");
}
public Son(String name) {
//super();
super("杨幂");
System.out.println("Son带参构造方法");
System.out.println(name);
}
}
public class ExtendsTest {
public static void main(String[] args) {
Son s = new Son();
System.out.println("---------");
Son s2 = new Son("小幂");
}
}
继承中成员方法
通过子类对象去访问一个方法
- 首先在子类中找
- 然后在父类中找
- 如果还是没有就会报错
public class Father {
public void show() {
System.out.println("Father show");
}
}
/*
* Java继承中成员方法的访问特点:
* A:子类中方法和父类中方法的声明不一样,这个太简单
* B:子类中方法和父类中方法的声明一样,调用的到底是谁的呢?
* 执行的是子类中的方法。
*/
public class Son extends Father {
public void method() {
System.out.println("Son method");
}
public void show() {
System.out.println("Son show");
}
}
public class ExtendsTest {
public static void main(String[] args) {
Son s = new Son();
s.method();
s.show();
//s.function();
}
}
方法重写
用子类的方法实现覆盖掉父类的实现。
方法重写:子类中出现了和父类中一摸一样的方法声明。
应用:当子类需要父类的功能,而功能主体子类有自己特有的内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
@Override注解
是一个注解,常用在方法的重写中。表示在进行方法重写之前,进行一个验证。验证这个方法,到底是不是在重写父类中的方法。这个注解,可以添加,也可以不添加。但是,一般情况下,我们都是要加上去的,
- 表明该方法的重写父类的方法
- 方法重写的注意事项
- 父类中私有方法不能被重写
- 子类重写父类方法时,访问权限不能更低
- 子类重写父类方法时,建议访问权限一摸一样
注意
- 访问权限问题:子类方法的访问权限不能比父类方法中的访问权限低,要大于等于父类方法的访问权限public > protected > default > private
- 关于返回值类型:在重写的时候,要求方法名和参数必须和父类中方法相同子类方法的返回值类型可以和父类方法中返回值类型相同。也可以是父类方法中返回值类型的子类型。
重写
public class Phone {
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
public class NewPhone extends Phone {
public void call(String name) {
System.out.println("开启视频功能");
super.call(name);
}
}
/*
* 方法重写:子类中出现了和父类中一模一样的方法声明的情况。
*
* 方法重写的应用:
* 当子类需要父类的功能,而功能主体子类又有自己的特有内容的时候,就考虑使用方法重写,
* 这样即保证了父类的功能,还添加了子类的特有内容。
*/
public class PhoneTest {
public static void main(String[] args) {
Phone p = new Phone();
p.call("Momo");
System.out.println("-----------");
NewPhone np = new NewPhone();
np.call("孙Momo");
}
}
继承的特点
- Java语言是单继承的,一个类只能有一个父类,一个类可以有多个子类在某些语言中是支持多继承的。例如:C++、python...但是在多继承中,会有一个问题:二义性。虽然很多语言都抛弃了多继承,但是还是会用其他的方式来间接的实现类似多继承。例如:在java中是用接口实现的。
- Java中所有的类都直接或者简介的继承自 Object 类
- 子类可以访问到父类中能看的见的成员:被public或者protected修饰的
- 构造方法不能继承。
多态
介绍
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作。
多态性是对象多种表现形式的体现。
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象: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()");
}
}
class Triangle extends Shape {
void draw() {
System.out.println("Triangle.draw()");
}
}
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
多态的优缺点
优点:提高了程序的扩展性。
缺点:不能访问子类特有功能。
/*
* 多态的好处:提高了程序的扩展性
* 具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
* 多态的弊端:不能使用子类的特有功能
*/
public class TestDemo {
public static void main(String[] args) {
AnimalOperator ao = new AnimalOperator();
Cat c = new Cat();
ao.useAnimal(c);
Dog d = new Dog();
ao.useAnimal(d);
Pig p = new Pig();
ao.useAnimal(p);
}
}
public class AnimalOperator {
/*
public void useAnimal(Cat c) { //Cat c = new Cat();
c.eat();
}
public void useAnimal(Dog d) { //Dog d = new Dog();
d.eat();
}
*/
public void useAnimal(Animal a) { //Animal a = new Cat();
a.eat();
}
}
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Dog extends Animal {
public void eat() {
System.out.println("狗啃骨头");
}
public void lookDoor() {
System.out.println("狗看门");
}
}
public class Pig extends Animal {
public void eat() {
System.out.println("猪拱白菜");
}
}
多态中的转型
体现
- 父类的引用可以指向子类的对象
- 接口的引用可以指向实现类的对象
转型
- 向上转型
- 由子类类型转型为父类类型,或者由实现类类型转型为接口类型
- 向上转型一定会成功,是一个隐式转换
- 向上转型后的对象,将只能访问父类或者接口中的成员
- 向下转型
- 由父类类型转型为子类类型,或者由接口类型转型为实现类类型
- 向下转型可能会失败,是一个显式转换
- 向下转型后的对象,将可以访问子类或者实现类中特有的成员
/*
* 向上转型
* 从子到父
* 父类引用指向子类对象
* 向下转型
* 从父到子
* 父类引用转为子类对象
*/
public class TestDemo {
public static void main(String[] args) {
//多态
Animal a = new Cat(); //向上转型
a.eat();
//a.playGame();
//多态的弊端:无法访问子类特有方法
//现在我就想使用子类特有方法,怎么办呢?
//创建子类对象就可以了
/*
Cat c = new Cat();
c.eat();
c.playGame();
*/
//现在的代码虽然可以访问子类的特有功能,但是不合理
//因为我们发现内存中有两个猫类的对象
//这个时候,我们得想办法把多态中的猫对象还原
//这个时候,就要使用多态中的转型了
//父类引用转为子类对象
Cat c = (Cat)a;
c.eat();
c.playGame();
}
}
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
instanceof关键字
针对于向下转型的。
如果向下转型不成功,会怎样?会有一个异常 ClassCastException
如何避免这种情况?在向下转型之前,我们先判断一下这个对象是不是要转型的类型怎么判断?
Animal animal = new Dog();
if (animal instanceof Dog) {
// 说明animal的确是一个Dog
}
如果一个类中重写了父类的某一个方法。此时:
- 如果用这个类的对象来调用这个方法,最终执行的是子类的实现。
- 如果用向上转型后的对象来调用这个方法,执行的依然是子类的实现。
- 向上转型后的对象,归根到底还是子类对象。
public class TestDemo {
public static void main(String[] args) {
//多态
Animal a = new Cat(); //向下转型
Cat c = new Cat();
if(a instanceof Cat){
c = a;
}
c.eat();
c.playGame();
}
}
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
重写(Override)和重载(Overload)
重写(Override)
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。 即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。这样,在使用子类对象调用该方法时,将执行子类中的方法而不是父类中的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。
重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
Super 关键字的使用
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}
以上实例编译运行结果如下:
动物可以移动
狗可以跑和走
重载(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多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。