【大数据】学习笔记
文章目录
1 Java SE
第6章 面向对象基础(中)
6.2 继承
6.2.1 继承的概述
生活中的继承:
- 财产:富二代
- 样貌:…
- 才华:…
继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。
Java中的继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 表示类与类之间的is-a关系
6.2.2 继承的语法格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
【1】父类
package com.dingjiaxiong.inherited.grammar;
/**
* @Projectname: BigDataStudy
* @Classname: Animal
* @Author: Ding Jiaxiong
* @Date:2023/4/24 17:12
*/
/*
* 定义动物类Animal,做为父类
*/
public class Animal {
// 定义name属性
String name;
// 定义age属性
int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
【2】子类
package com.dingjiaxiong.inherited.grammar;
/**
* @Projectname: BigDataStudy
* @Classname: Cat
* @Author: Ding Jiaxiong
* @Date:2023/4/24 17:13
*/
/*
* 定义猫类Cat 继承 动物类Animal
*/
public class Cat extends Animal {
int count;//记录每只猫抓的老鼠数量
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已经抓了" + count + "只老鼠");
}
}
【3】测试类
package com.dingjiaxiong.inherited.grammar;
/**
* @Projectname: BigDataStudy
* @Classname: TestCat
* @Author: Ding Jiaxiong
* @Date:2023/4/24 17:13
*/
public class TestCat {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
cat.name = "Tom";
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫继承来的eat()方法
cat.eat();
// 调用该猫的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
}
}
6.2.3 继承的特点
【1】子类会继承父类所有的实例变量和实例方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
- 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
- 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。
【2】Java只支持单继承,不支持多重继承
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error
【3】Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
顶层父类是Object类。所有的类默认继承Object,作为父类。
【4】一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
6.2.4 权限修饰符限制问题
权限修饰符:public、protected、缺省、private
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √(本包子类非子类都可见) | × | × |
protected | √ | √(本包子类非子类都可见) | √(其他包仅限于子类中可见) | × |
public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法等:public、protected、缺省、private
【1】外部类要跨包使用必须是public,否则仅限于本包使用
(1)外部类的权限修饰符如果缺省,本包使用没问题
(2)外部类的权限修饰符如果缺省,跨包使用有问题
【2】成员的权限修饰符问题
(1)本包下使用:成员的权限修饰符可以是public、protected、缺省
(2)跨包下使用:要求严格
(3)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义
【3】父类成员变量私有化(private)
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:
父类代码:
package com.dingjiaxiong.inherited.modifier;
/**
* @Projectname: BigDataStudy
* @Classname: Person
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:23
*/
public class Person {
private String name;
private int 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;
}
}
子类代码:
package com.dingjiaxiong.inherited.modifier;
/**
* @Projectname: BigDataStudy
* @Classname: Student
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:24
*/
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getInfo() {
// return "姓名:" + name + ",年龄:" + age;
//在子类中不能直接使用父类私有的name和age
return "姓名:" + getName() + ",年龄:" + getAge();
}
}
测试类代码:
package com.dingjiaxiong.inherited.modifier;
/**
* @Projectname: BigDataStudy
* @Classname: TestStudent
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:24
*/
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
IDEA在Debug模式下查看学生对象信息:
学生对象中有name、age、score 三个实例变量,name 和 age是父类中声明的。
6.2.5 方法重写(Override)
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)
【1】方法重写
比如新的手机增加来电显示头像的功能,代码如下:
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: Phone
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:37
*/
public class Phone {
public void sendMessage() {
System.out.println("发短信");
}
public void call() {
System.out.println("打电话");
}
public void showNum() {
System.out.println("来电显示号码");
}
}
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: Smartphone
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:38
*/
//smartphone:智能手机
public class Smartphone extends Phone {
//重写父类的来电显示功能的方法
public void showNum() {
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
测试类
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: TestOverride
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:38
*/
public class TestOverride {
public static void main(String[] args) {
// 创建子类对象
Smartphone sp = new Smartphone();
// 调用父类继承而来的方法
sp.call();
// 调用子类重写的方法
sp.showNum();
}
}
【2】在子类中如何调用父类被重写的方法
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: Smartphone
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:38
*/
//smartphone:智能手机
public class Smartphone extends Phone {
//重写父类的来电显示功能的方法
public void showNum() {
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
//保留父类来电显示号码的功能
super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
}
}
【3】IDEA 重写方法快捷键:Ctrl + O
@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
【4】重写方法的要求
-
必须保证父子类之间重写方法的名称相同。
-
必须保证父子类之间重写方法的参数列表也完全相同。
-
子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
- 子类方法的权限必须【大于等于】父类方法的权限修饰符。
注意:public > protected > 缺省 > private
父类私有方法不能重写
跨包的父类缺省的方法也不能重写
【5】方法的重载和方法的重写
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:见上面。
(1)同一个类中
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: TestOverload
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:44
*/
public class TestOverload {
public int max(int a, int b) {
return a > b ? a : b;
}
public double max(double a, double b) {
return a > b ? a : b;
}
public int max(int a, int b, int c) {
return max(max(a, b), c);
}
}
(2)父子类中
package com.dingjiaxiong.inherited.method;
/**
* @Projectname: BigDataStudy
* @Classname: TestOverloadOverride
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:44
*/
public class TestOverloadOverride {
public static void main(String[] args) {
Son s = new Son();
s.method(1);//只有一个形式的method方法
Daughter d = new Daughter();
d.method(1);
d.method(1, 2);//有两个形式的method方法
}
}
class Father {
public void method(int i) {
System.out.println("Father.method");
}
}
class Son extends Father {
public void method(int i) {//重写
System.out.println("Son.method");
}
}
class Daughter extends Father {
public void method(int i, int j) {//重载
System.out.println("Daughter.method");
}
}
6.2.6 实例初始化
【1】普通代码块
和构造器一样,也是用于实例变量的初始化等操作。
【2】普通代码块的语法格式
【修饰符】 class 类{
{
普通代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
【3】静态代码块
如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。
[1] 语法格式
在代码块的前面加static,就是静态代码块。
【修饰符】 class 类{
static{
静态代码块
}
}
[2] 静态代码块的特点
每一个类的静态代码块只会执行一次。
静态代码块的执行优先于非静态代码块和构造器。
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: Chinese
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:47
*/
public class Chinese {
// private static String country = "中国";
private static String country;
private String name;
{
System.out.println("非静态代码块,country = " + country);
}
static {
country = "中国";
System.out.println("静态代码块");
}
public Chinese(String name) {
this.name = name;
}
}
测试类:
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: TestStaticBlock
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:47
*/
public class TestStaticBlock {
public static void main(String[] args) {
Chinese c1 = new Chinese("张三");
Chinese c2 = new Chinese("李四");
}
}
【4】类初始化
(1)类的初始化就是为静态变量初始化。实际上,类初始化的过程时在调用一个<clinit>()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化<clinit>()方法体中。
- 静态类成员变量的显式赋值语句
- 静态代码块中的语句
(2)每个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。
(3)类的初始化一定优先于实例初始化。
[1] 类初始化代码块只执行一次
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: Fu
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:49
*/
public class Fu {
static {
System.out.println("Fu静态代码块1,a = " + Fu.a);
}
private static int a = 1;
static {
System.out.println("Fu静态代码块2,a = " + a);
}
public static void method() {
System.out.println("Fu.method");
}
}
测试类:
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: TestClassInit
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:49
*/
public class TestClassInit {
public static void main(String[] args) {
Fu.method();
}
}
[2] 父类优先于子类初始化
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: Zi
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:51
*/
public class Zi extends Fu {
static {
System.out.println("Zi静态代码块");
}
}
测试类:
[3] 类初始化优先于实例初始化
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: Fu
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:49
*/
public class Fu {
static {
System.out.println("Fu静态代码块1,a = " + Fu.a);
}
private static int a = 1;
static {
System.out.println("Fu静态代码块2,a = " + a);
}
{
System.out.println("Fu非静态代码块");
}
public Fu() {
System.out.println("Fu构造器");
}
public static void method() {
System.out.println("Fu.method");
}
}
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: Zi
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:51
*/
public class Zi extends Fu {
static {
System.out.println("Zi静态代码块");
}
{
System.out.println("Zi非静态代码块");
}
public Zi() {
System.out.println("Zi构造器");
}
}
测试类
package com.dingjiaxiong.keyword;
/**
* @Projectname: BigDataStudy
* @Classname: TestZiInit
* @Author: Ding Jiaxiong
* @Date:2023/4/24 19:51
*/
public class TestZiInit {
public static void main(String[] args) {
Zi z1 = new Zi();
Zi z2 = new Zi();
}
}
6.2.7 Object根父类
【1】如何理解根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。
- Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
- 所有对象(包括数组)都实现这个类的方法。
- 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
【2】Object类其中的5个方法
API(Application Programming Interface),应用程序编程接口。
Java API是一本程序员的字典
,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码。
根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:
(1)toString()
方法签名:public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
例如自定义的Person类:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
(2)getClass()
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
(3)equals()
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
class User{
private String host;
private String username;
private String password;
public User(String host, String username, String password) {
super();
this.host = host;
this.username = username;
this.password = password;
}
public User() {
super();
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
(4)hashCode()
public int hashCode():返回每个对象的hash值。
如果重写equals,那么通常会一起重写hashCode()方法,hashCode()方法主要是为了当对象存储到哈希表(后面集合章节学习)等容器中时提高存储和查询性能用的,这是因为关于hashCode有两个常规协定:
- ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
- ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
重写equals和hashCode方法时,要保证满足如下要求:
- ①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
- ②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
- ③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
}
(5)finalize()
protected void finalize():用于最终清理内存的方法
面试题:对finalize()的理解?
- 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
- 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
- 每一个对象的finalize方法只会被调用一次。
- 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
(6)重写toString和equals
【3】标准JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
(4)重写toString方法
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//getXxx()
//setXxx()
//其他成员方法
}
编写符合JavaBean
规范的类,以学生类为例,标准代码如下:
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
//其他成员方法列表
public String toString(){
return "姓名:" + name + ",年龄:" + age;
}
}
测试类,代码如下:
public class TestStudent {
public static void main(String[] args) {
// 无参构造使用
Student s = new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s);
// 带参构造使用
Student s2 = new Student("赵丽颖", 18);
System.out.println(s2.getName() + "---" + s2.getAge());
System.out.println(s2);
}
}
6.2.8 final关键字
【1】final的意义
final:最终的,不可更改的
【2】final修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
【3】final修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
【4】final修改变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
package com.dingjiaxiong.finals;
/**
* @Projectname: BigDataStudy
* @Classname: TestFinal
* @Author: Ding Jiaxiong
* @Date:2023/4/24 20:02
*/
public class TestFinal {
public static void main(String[] args) {
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022, 2, 14);
System.out.println(m2.getInfo());
}
}
class MyDate {
//没有set方法,必须有显示赋值的代码
private final int year;
private final int month;
private final int day;
public MyDate() {
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo() {
return year + "年" + month + "月" + day + "日";
}
}