Java基础系列文章
Java基础(一):语言概述
Java基础(二):原码、反码、补码及进制之间的运算
Java基础(三):数据类型与进制
Java基础(四):逻辑运算符和位运算符
Java基础(六):数组
Java基础(七):面向对象编程
Java基础(八):封装、继承、多态性
Java如何实现数据封装
可见性范围
public
、protected
、缺省
、private
修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包非子类 |
---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
缺省 | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
(先有鸡还是先有蛋)
成员变量/属性私有化
概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能
private
修饰成员变量public class Person {
private String name;
private int age;
private boolean marry;
}
getXxx
方法 / setXxx
方法,可以访问成员变量public class Person {
private String name;
private int age;
private boolean marry;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
public void setMarry(boolean m){
marry = m;
}
public boolean isMarry(){
return marry;
}
}
public class PersonTest {
public static void main(String[] args) {
Person p = new Person();
//实例变量私有化,跨类是无法直接使用的
/* p.name = "张三";
p.age = 23;
p.marry = true;*/
p.setName("张三");
System.out.println("p.name = " + p.getName());
p.setAge(23);
System.out.println("p.age = " + p.getAge());
p.setMarry(true);
System.out.println("p.marry = " + p.isMarry());
}
}
成员变量封装的好处:
访问数据
,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问便于修改
,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改this是什么?
表示调用该方法的对象
表示该构造器正在初始化的对象
什么时候使用this
成员变量
和局部变量
同一个类中构造器互相调用
public class Student {
private String name;
private int age;
// 无参构造
public Student() {
// this("",18);//调用本类有参构造器
}
// 有参构造
public Student(String name) {
this();//调用本类无参构造器
this.name = name;
}
// 有参构造
public Student(String name,int age){
this(name);//调用本类中有一个String参数的构造器
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
注意:
语法格式
通过 extends
关键字,可以声明一个类B继承另外一个类A,定义格式如下:
[修饰符] class 类A {
...
}
[修饰符] class 类B extends 类A {
...
}
继承性的细节说明
1、子类会继承父类所有的实例变量和实例方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征
从下往上
找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板
2、子类不能直接访问父类中私有的(private)的成员变量和方法
3、在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”
4、Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
5、一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
6、Java只支持单继承,不支持多重继承
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error
重写 (override、overwrite)
。也称为方法的重置
、覆盖
方法重写的要求
必须
和父类被重写的方法具有相同的方法名称
、参数列表
小于等于
父类被重写的方法的返回值类型。(例如:Student < Person)
大于等于
父类被重写的方法的访问权限。(例如:public > protected > 缺省 > private)
小于等于
父类被重写方法的异(例如:RuntimeException < Exception)父类对象:
public class Person {
protected Person eat() throws Exception{
System.out.println("人吃饭");
return null;
}
}
子类对象:
public class Student extends Person {
@Override
public Student eat() throws RuntimeException{
System.out.println("学生干饭");
return null;
}
}
多态创建对象:
public class Test {
public static void main(String[] args) {
Person p = new Student();
try {
p.eat();
} catch (Exception e) {
e.printStackTrace();
}
}
}
super的理解
注意:
super的使用场景
super.
才能调用父类被重写的方法,否则默认调用的子类重写的方法举例:
public class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
//smartphone:智能手机
public class SmartPhone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
//保留父类来电显示号码的功能
super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
}
}
总结
直接访问
父类中声明的实例变量,也可以用this.
实例访问,也可以用super.
实例变量访问总结
局部变量
,本类去找成员变量
父类声明的成员变量
(权限修饰符允许在子类中访问的)特别说明:应该避免子类声明和父类重名的成员变量
阿里开发规范
“super(形参列表)”
的方式调用父类指定的构造器首行
没有显示调用"this(形参列表)"
,也没有显式调用"super(形参列表)"
,默认调用"super()"
,即调用父类中空参的构造器开发中常见错误:
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则编译出错
举例:
// 生物类
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}
}
// 动物类
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}
}
// 猫类
public class Dog extends Animal {
public Dog() {
super("汪汪队阿奇", 3);
System.out.println("Dog无参数的构造器");
}
public static void main(String[] args) {
new Dog();
}
}
内存图:
所有
的属性和方法,在权限允许的情况下,可以直接调用问题:在创建子类对象的过程中,一定会调用父类中的构造器吗?
答案:是的
问题:创建子类的对象时,内存中到底有几个对象?
答案:只有一个对象!即为当前new后面构造器对应的类的对象
对象的多态性
对象的多态性:父类的引用指向子类的对象
父类类型 变量名 = 子类对象;
举例:
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
多态的理解
编译时类型
和运行时类型
声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象
决定举例
宠物类
public class Pet {
private String nickname; //昵称
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void eat(){
System.out.println(nickname + "吃东西");
}
}
猫类
public class Cat extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("猫咪" + getNickname() + "吃鱼仔");
}
//子类扩展的方法
public void catchMouse() {
System.out.println("抓老鼠");
}
}
狗类
public class Dog extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("狗子" + getNickname() + "啃骨头");
}
//子类扩展的方法
public void watchHouse() {
System.out.println("看家");
}
}
1、方法内局部变量的赋值体现多态
public class TestPet {
public static void main(String[] args) {
//多态引用
Pet pet = new Dog();
pet.setNickname("小白");
//多态的表现形式
/*
编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
*/
pet.eat();//运行时执行子类Dog重写的方法
// pet.watchHouse();//不能调用Dog子类扩展的方法
pet = new Cat();
pet.setNickname("雪球");
pet.eat();//运行时执行子类Cat重写的方法
}
}
2、方法的形参声明体现多态
public class Person{
private Pet pet;
public void adopt(Pet pet) {//形参是父类类型,实参是子类对象
this.pet = pet;
}
public void feed(){
pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同
}
}
3、方法返回值类型体现多态
public class PetShop {
//返回值类型是父类类型,实际返回的是子类对象
public Pet sale(String type){
switch (type){
case "Dog":
return new Dog();
case "Cat":
return new Cat();
}
return null;
}
}
Student m = new Student();
m.school = "pku"; //合法,Student类有school成员变量
Person e = new Student();
e.school = "pku"; //非法,Person类没有school成员变量
// 属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
不能
覆盖父类中定义的实例变量public class TestVariable {
public static void main(String[] args) {
Base b = new Sub();
System.out.println(b.a); // 1
System.out.println(((Sub)b).a); // 2
Sub s = new Sub();
System.out.println(s.a); // 2
System.out.println(((Base)s).a); // 1
}
}
class Base{
int a = 1;
}
class Sub extends Base{
int a = 2;
}
为什么要类型转换
编译期间
,就会出现类型转换的现象不能调用
子类拥有,而父类没有的方法了编译通过
如何向上或向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog
//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse
Dog d = (Dog) pet;
System.out.println("d.nickname = " + d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse
Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}
instanceof关键字
instanceof
关键字//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A
public class TestInstanceof {
public static void main(String[] args) {
Pet[] pets = new Pet[2];
pets[0] = new Dog();//多态引用
pets[0].setNickname("小白");
pets[1] = new Cat();//多态引用
pets[1].setNickname("雪球");
for (int i = 0; i < pets.length; i++) {
pets[i].eat();
if(pets[i] instanceof Dog){
Dog dog = (Dog) pets[i];
dog.watchHouse();
}else if(pets[i] instanceof Cat){
Cat cat = (Cat) pets[i];
cat.catchMouse();
}
}
}
}