文章目录
- 面向对象进阶
- 一、 static 静态
-
- 1 静态变量
-
- (1)基本定义和用法
- (2)静态变量内存图
- 2 静态方法
-
- (1)基本定义和用法
- (2)工具类
-
- 练习:按下面需求写一个工具类
- 3 static注意事项
- 4 重新认识main方法
- 二、继承
-
- 1 继承的概念
- 2 继承的特点
- 3 继承到底能继承父类中的哪些内容?
- 4 继承中的内存图
- 5 继承中的语法特点(主要讲述继承中的语法)
-
- (1)继承中:成员变量的访问调用
- (2)继承中:成员方法的访问调用和子类对父类方法的重写
- (3)构造方法在继承调用语法(采用继承中的方法重写手段)
-
- 基本用法
- 子类中的构造方法java虚拟机会自动先默认无参构造
- (4)this 、super使用总结
- 6 小练习:写一个完整的带继承关系的javabean类
- 三、多态
-
- 1 基本知识--概念+多态的两种参数声明方式(第二种才是重点,今后我们统一使用第二种方式)
- 2 多态中多态声明传参方式2的强制转换
- 3 判断对象是不是某一种类或者其子类:instanceof 运算符(限制只能在多态第二种传参调用开发中用:因为我发现第一种传参用这个运算符并列情况连编译都过不了,直接报错,非要用时记住这个并列会报错就可以了)
-
- 语法
- 使用场景
- 4 一个多态项目练习(标志的多态书写案例)
- 四、包和final关键字
-
- 1 包
-
- (1)什么是包
- (2)java导包使用其他包里面的类
- 2 final关键字
- 五、权限修饰符和代码块
-
- 1 权限修饰符
- 2 代码块
- 六、 抽象类和抽象方法
- 七、 接口
-
- 1 接口基本概念
- 2 接口基本使用演示(成员变量、抽象方法)语法
- 3 接口和类之间的关系
- 4 接口的一般应用场景
- 5 接口设计练习
- 6 接口多学三招
-
- (1)接口中的默认方法(jdk7以后才有的)
- (2)接口中的静态方法(不重要)
- (3)接口中的私有方法(不重要)
- 小总结
- 7 接口的多态
- 8 适配器设计模式(编程套路:用来解决接口和实现类间矛盾的编程套路)
- 八、 内部类
-
- 1 成员内部类(了解即可)
-
- (1)基本概念
- (2)怎么获取成员内部类对象
- (3)成员内部类如何获取外部类的成员变量(基本不可能用到)
- (4)内部类内存图
- 2 静态内部类(了解即可)
- 3 局部内部类(了解即可)
- 4 匿名内部类(重要!!!!,需要掌握,今后会经常用到)
面向对象进阶
这块是java编程基础!!!!
一、 static 静态
关于静态这个东西前面已经简单讲过,这里再详细讲一讲。
1 静态变量
(1)基本定义和用法
- 定义:被static修饰的成员变量,叫做静态变量。
- 特点:**被该类所有对象共享(简单来说你修改任意一个对象里面的static成员变量,所有对象的该成员变量的值都会变)
- 调用方式**
(1)直接类名调用(推荐)
(2)对象名调用
关于被该类所有对象共享这个举个例子说明一下。
一个学生类,里面有一个静态成员变量是老师,修改任意一个学生对象的老师这个成员变量,其余学生对象的老师也会统一修改。简单来说,静态成员变量是属于这个类,是属于所有对象,修改一个对象就会影响所有对象。(现实中学生类一个班老师肯定都是一样的,就可以使用静态成员变量修饰)
package cn.hjblogs.demo;
public class Student {
private String name;
private int age;
public static String teacher_name; // 静态变量
// static String teacher_name; // 不用public修饰就这样写就可以了
public Student() {
}
public Student(String name, int age) {
this.name = name;
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 static String getTeacher_name() {
return teacher_name;
}
public static void setTeacher_name(String teacher_name) {
Student.teacher_name = teacher_name;
}
public void show(){
System.out.println("name: " + name + ", age: " + age + ", teacher_name: " + teacher_name);
}
}
package cn.hjblogs.demo;
public class Test {
public static void main(String[] args) {
Student stu1 = new Student("张三", 18);
Student stu2 = new Student("李四", 20);
stu1.show(); // name: 张三, age: 18, teacher_name: null
stu2.show(); // name: 李四, age: 20, teacher_name: null
stu1.setTeacher_name("张老师");
stu1.show(); // name: 张三, age: 18, teacher_name: 张老师
stu2.show(); // name: 李四, age: 20, teacher_name: 张老师
// Student.setTeacher_name("张老师");
// stu1.show(); // name: 张三, age: 18, teacher_name: 张老师
// stu2.show(); // name: 李四, age: 20, teacher_name: 张老师
}
}
可以看到修改一个学生对象的老师其余学生对象的老师也变了。或者直接修改这个类的老师
(2)静态变量内存图
- 静态变量是随着内的加载而加载的,优于对象出现在内存里面的。
- 其实所有内存图都一样都是地址套地址,这里所有对象都拿着一个静态区里面静态变量的地址去找静态变量,这样静态变量还是个字符串,那又要拿着地址去串词里面找字符串(因为之前讲过字符串是存在串词里面的)所以这里面是一个地址套一个地址(一种比喻,反正肯定存在一个映射关系)
2 静态方法
(1)基本定义和用法
- 定义:被static修饰的成员方法,叫做静态方法。
- 特点:
(1)多用在测试类和工具类中
(2)javabean类中很少会用 - 调用方式:
(1)直接类名调用(推荐)
(2)对象名调用
(2)工具类
工具类
目前我们已经学习了java中的三种类:
- javabean类:用来描述一类事物的类,比如:Student、Dog、老婆
- 测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口
- 工具类:不是用来描述一类事物的,而是帮我们做一些事情的类(里面,java.lang里面的Math类就是这样一个工具类,帮我们封装了一些数学函数)
书写工具类有一些注意事项
- 类名见名知义
- 构造方法私有化要,因为工具类创建对象没有任何意义,那么我们就将构造方法私有化直接不让创建对象就可以了
- 方法定义为静态
下面的这个练习详细写了一个工具类的构造
练习:按下面需求写一个工具类
package cn.hjblogs.demo;
public class ArrayUtil {
private ArrayUtil() {
}
public static String printArray(int[] arr) {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]);
if (i != arr.length - 1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
public static double getAverage(double[] arr) {
double sum = 0;
for (double v : arr) {
sum += v;
}
return sum / arr.length;
}
}
package cn.hjblogs.demo;
public class TestDemo {
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, 5};
System.out.println(ArrayUtil.printArray(arr)); // [1, 2, 3, 4, 5]
double[] arr2 = {
1.1, 2.2, 3.3, 4.4, 5.5};
System.out.println(ArrayUtil.getAverage(arr2)); // 3.3
}
}
3 static注意事项
关键:静态只能访问静态,不能访问动态;动态可以访问所有
- 静态方法只能访问静态变量和静态方法(静态方法中,只能访问静态)
因为静态变量和方法是随着类的加载而加载的,优于对象出现在内存里面的。我们一般都会限定不能实例化,那么除了静态变量和方法其他的都没实例化,都没有进入内存,访问个屁。 - 静态方法中是没有this关键字的
前面讲this内存时将过this指向实例化的那个对象,但现在静态方法压根不需要实例化对象,this自然也就没有 - 非静态方法可以访问所有
一句话:静态的东西随着类的加载而加载。当类被编译成字节码文件里面方法放进方法区,所有静态的就全部加载到内存(静态区里面去了),这里方法都没进栈呢,对象压根还没到创建的时候,静态的就早早进内存了,但非静态的东西是和对象有关的,对象都没创建进内存,访问个屁。
参考视频
4 重新认识main方法
- public :被JVM调用,访问权限足够大
- static:被JVM调用,静态,不用创建对象,直接类名访问
因为main方法是静态的,所以测试类中其他方法也需要是静态的(因为静态只能访问静态,不能访问动态) - void:被JVM调用,不需要JVM返回值
- main:一个通用的名称,虽然不是关键字,但是被JVM识别
- String[] args:以前用于接收键盘录入数据的,现在没用; String[] 数组 args 变量名
二、继承
1 继承的概念
继续面向对象三大特性之一继承的学习
面向对象三大特性:封装、继承、多态
那么继承怎么理解呢?
继承:
- java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系
- 语法:public class Student extends Persons { }
- Student称为子类(派生类),Persons称为父类(基类或超类)
使用继承的好处:
- 可以把多个子类中重复的代码抽取到父类中了,提高了代码的复用性。
- 子类可以在父类的基础上,增加其他功能,使子类更强大。
学好继承主要有两个点:
- 自己设计
- 能会用别人的
什么时候考虑用继承呢?
2 继承的特点
- java只支持单继承,不支持多继承,但支持多层继承(A继承B,B继承C这种)。
- 单继承:一个子类只能继承一个父类
- 不支持多继承:子类不能同时继承多个父类
- 支持多层继承(A继承B,B继承C这种):但这种关系有一个头的,java的设计中java的每一个类都间接或者直接的继承与Object这个总类。
- 子类继承父类的所有属性和方法,你实例化子类但父类不一定实例化父类,所以你set改变了子类中的东西,父类是没有任何变化的,因为父类都没有实例化。所以如果你对继承有疑惑,修改子类对象东西会影响到父类吗?答案是你修改子类,父类都不一定实例化了,根本没有任何影响。但是反过来父类是影响子类的,
(1)修改子类不会影响父类
(3)父类发生变化会影响子类
(3)子类继承父类的几乎所有属性和方法(具体能继承哪些,后面有一节会详细说明,另外还一般是非私有的才能继承),可以直接 子类.属性或者子类.父类方法调用
比如 public class Student { } 如果我们没有写继承,但java内部这个类默认是会直接继承Object这个类的。
【注】:java不支持多继承,但是Python和C++支持多继承。
下面来一个练习,看是不是真的理解了
先看一下结构
package cn.hjblogs.Animal;
public class Animal {
public Animal() {
}
public void eat(){
System.out.println("吃饭");
}
public void drink(){
System.out.println("喝水");
}
}
package cn.hjblogs.Animal;
public class Cat extends Animal {
public Cat() {
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package cn.hjblogs.Animal;
public class Dog extends Animal {
public Dog() {
}
public void seeDoor() {
System.out.println("看家");
}
}
package cn.hjblogs.Animal;
public class DragenLiCat extends Cat {
public DragenLiCat() {
}
}
package cn.hjblogs.Animal;
public class RagdollCat extends Cat{
public RagdollCat() {
}
}
package cn.hjblogs.Animal;
public class HuskyDog {
public HuskyDog() {
}
public void downHome() {
System.out.println("拆家");
}
}
package cn.hjblogs.Animal;
public class TeddyDog extends Dog {
public TeddyDog() {
}
public void wipe(){
System.out.println("蹭一蹭");
}
}
package cn.hjblogs.Animal;
public class Test {
public static void main(String[] args) {
// 创建对象
RagdollCat a = new RagdollCat();
// 调用方法
a.eat(); // 吃饭
a.drink(); // 喝水
a.catchMouse(); // 抓老鼠
}
}
3 继承到底能继承父类中的哪些内容?
先说结论吧,能继承的东西无非就是 构造方法、成员变量、成员方法中考虑就是了
内容 | 非私有 | 私有 |
---|---|---|
构造方法 | 不能 | 不能 |
成员变量 | 能 | 能 |
成员方法(只有虚方法才能被继承) | 能 | 不能 |
内容 | 虚方法(非private、static、final修饰的方法) | 非虚方法 |
---|---|---|
成员方法(只有虚方法才能被继承) | 能 | 不能 |
其实在不同语言中基本上私有的都继承不了
1、java这里面有个特例,但没关系,私有就算能继承但由于是私有继承了子类也访问不了,这个继承了和没继承效果是一样的。(对于继承下来的私有变量非要用就只能通过get和set方法进行拿和修改了,前提是父类中的get和set不是私有,要是也是私有那完了,彻底访问不了了)
2、所以我们记住,私有的不能被继承(不是很严谨,但是够用)
这三个里面比较复杂的就是构造方法的问题了,这里面涉及到了方法的重写和super调用。具体的语法详情我们在(5)继承中的语法中会详细演示
4 继承中的内存图
参考视频
成员变量
和之前主要有两个不同之处
- public class Zi extends Fu{}: 加载字节码文件时同时会将父类的字节码文件一起存到方法区中
- new 创建对象在堆中一个地址分成了两块,一块存父的所有属性和方法,另一块存子独有的
成员方法
开始这个之前先要了解一下java中的虚方法表,java在继承中调用方法并不是一级一级不断往上找,因为这样如果继承链过长,会导致效率过慢。java的设计解决方法是从最上层每个类将自己的虚方法(能被继承方法)放进自己的虚方法表,子类继承这个表再在里面添加自己的虚方法,这样每个子类调用方法去自己的虚方法表里面找效率一下就提高了,不是虚方法表再回自己独有的去找,找不到就报错。
5 继承中的语法特点(主要讲述继承中的语法)
(1)继承中:成员变量的访问调用
成员变量的访问特点很简单就一句话:就近原则,先在本类中就近原则(先在方法内部找,方法内部找不到去类里面成员变量区找),本类找不到(成员变量区都找不到)再去父类中找。
下面这个例子就是典型的就近原则,但是现实中写代码不可能这么写啊(妥妥的超级危险代码),这里演示就近原则才举一个这么极端的例子:
小练习:
package cn.hjblogs.Business;
public class Test {
public static void main(String[] args) {
// 创建对象
Zi z = new Zi();
}
}
class Fu{
String name = "Fu";
String hobby = "喝茶";
}
class Zi extends Fu{
String name = "Zi"; // 子类继承属性,属性是可以重写的属性
String game = "吃鸡";
public Zi(){
//如何打印Zi
System.out.println(name); // Zi(就近原则)
System.out.println(this.name); // Zi
//如何打印Fu
System.out.println(super.name); // Fu
//如何打印喝茶
System.out.println(hobby); // 喝茶(就近原则)
System.out.println(this.hobby); // 喝茶(继承父类属性,并且没有重写)
System.out.println(super.hobby); // 喝茶
//如何打印吃鸡
System.out.println(game); // 吃鸡(就近原则
System.out.println(this.game); // 吃鸡
}
}
可以看到就近原则的存在,使得用法很灵活,但就近原则这个东西虽然java给你做了一层就近原则的防护,但这是危险代码的事实不会改变,因此我们平时应该养成习惯,就近原则尽量不要使用,用this、和super不香吗,没必要折腾给代码增加风险。
总结一下就近原则:
- (1)直接变量名访问,先在调用的方法的局部找,找不到再到类的成员变量区找,还找不到就只能到父类里面去找了
- (2)使用this访问,直接到类的成员变量区去找(并且如果类继承了父类,直接this也能找到对应属性;理解成this的成员变量区有部分属性是隐藏的,带上this还是能够访问到)。
- (3)使用super访问:直接到父类去找了。
(2)继承中:成员方法的访问调用和子类对父类方法的重写
成员方法的调用还是一样的规则,使用就近原则。(所以子类对父类方法的重写就是典型的调用案例)
结合下面的直接调用,完全重写和部分重新的代码,一下子就清晰了
package cn.hjblogs.Business;
public class Test {
public static void main(String[] args) {
// 创建对象
ChinesePerson a = new ChinesePerson();
// 调用方法
a.lunch(); // 吃饭 喝水 吃饭 喝水
System.out.println("=====================================");
ForeignPerson b = new ForeignPerson();
b.lunch(); // 外国人吃牛排 喝水 外国人吃牛排 喝水 吃饭 喝水
}
}
class Person {
public void eat() {
System.out.println("吃饭");
}
public void drink() {
System.out.println("喝水");
}
}
// 演示继承中方法调用的优先级 this和super
class ChinesePerson extends Person {
public void lunch(){
// this这里方法调用同样是就近原则,先调用子类的方法,如果子类没有再调用父类的方法
this.eat();
this.drink();
// 直接调用父类的方法
super.eat();
super.drink();
}
}
// 演示继承中方法的重写的两种方式 1.完全重写 2.部分重写(在父类的部分上进行修改 一般结合super)
class ForeignPerson extends Person {
// 完全重写
public void eat(String a) {
System.out.println(a); // 外国人吃牛排
}
// 部分重写(在父类的部分上进行修改 一般结合super)
@Override
public void drink() {
super.drink(); // 调用父类的方法
System.out.println("外国人喝红酒");
}
public void lunch(){
// this这里方法调用同样是就近原则,先调用子类的方法,如果子类没有再调用父类的方法
String food = "外国人吃牛排";
this.eat(food); // 外国人吃牛排
this.drink(); // 喝水 外国人喝红酒
// 直接调用父类的方法
super.eat(); // 吃饭
super.drink(); // 喝水
}
}
【注】:关于方法继承重写,添加新的方法参数没有任何关系,如果要用super调用父类中的方法,该有的参数就都要传全才行
另外关于方法重写在java中还有一些语法规范:
- 当父类的方法不能满足子类现在的需求时,需要进行方法重写
- 书写格式:函数名一样即可(严格来说就是子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法)。java中重写方法的名称、形参列表必须与父类中的一致。(不一致就不是方法的重写了,叫做创建了一个新方法)。这点来看Python要比java灵活多了。
- @Override重写注释(不加也没任何关系)
(1)@Override是放在重写后的方法上,校检子类重写时语法是否正确。
(2)加上注解后如果有红色波浪线,表示语法错误。
(3)建议重写方法都加@Override注解,代码安全,优雅!但是我个人觉得不加这个注解会更加灵活好用,下面是解释,反正我以后没有特殊规定是不会写的。
另外,在java中@Override认为如果方法重写过程中参数变多,或者参数顺序变量,那么@Override认为这就不是方法的重写,这是一个新的属于子类的方法。(结合前面的方法重载来解释,好像java中的确是将参数不同,顺序不同的同名函数不是看成一个函数,而是多个函数),这是一个小细节。
Python中就没这些规定了,语言不同,一些细微差别还是有的,所以建议@Override注解如果没有强制要求,可以不写,加了@Override注解反而丧失了灵活性,代码给自己看的,何必管别人。
我喜欢将java中的这种方法重写叫做狭义的方法重写,Python中的那种更加灵活叫做广义的方法重写。java中想实现广义的方法重写也简单,不要加@Override注解就行了,参数顺序随便变,随便加参数,新方法就新方法呗,够灵活就可以了。
- 子类重写父类方法时,访问权限子类必须大于等于父类(空着不写<protected<public),通常都是public,public,所有权限都给你
- 子类重写父类方法时,返回值类型必须小于等于父类(不重要,针对狗类里面有方法返回动物类这种离谱现象的操作,子类里面有个方法的返回值是父类,很扯)
- 只有被添加到虚方法表(非private、static、final修饰的方法)中的方法才可以被重写。
最后,不要将眼光限定在java一种规则上,如果其他语言的规则更好用,我们为什么不直接使用那种规则,更加灵活当然更好啊。
(3)构造方法在继承调用语法(采用继承中的方法重写手段)
基本用法
- java中构造方法不能继承很显然,毕竟子类和父类构造方法不可能重名,这种涉及天然导致java中的构造方法不能被继承。因此在子类中只能进行构造方法的重写。
- Python中的构造方法_init_如果在子类中没有被显式写出,那么Python会默认继承父类的_init_方法;但是我们写Python类怎么可能不写_init_方法,因此,还是要进行_init_方法的重写
在子类中进行构造方法的重写,有分先后顺序必须要干的三件事情(严格按照顺序)
- 第一件事情就是父类中构造方法传入的参数,子类的构造方法也必须要传入
- 第二件事情:用super调用父类的构造方法把继承来的父类属性初始化了
- 第三件事情:初始化子类其他的属性或者其他操作
看下面代码示例就很好理解了,java和Python版都有:
【注】:java可以一个文件里面写多个类(但是只有一个类能被public修饰),这里为了好讲述才这样写一下,实际java开发中还是要一个类一个文件夹. 算了还是分多个文件class避免误解吧!
java版
public class Parent {
int value;
Parent(int value) {
this.value = value;
}
}
public class Child extends Parent {
int childValue;
Child(int value, int childValue) {