文章目录
第二部分 面向对象篇
第7章 类与对象
7.1 面向过程与面向对象思想
面向过程
当我们在解决一个问题时,会按照预先设定的想法和步骤,一步一步去实现,在这里每一步具体的实现中都需要我们自己去亲自实现和操作。
面向对象
当我们在解决一个问题时,可以去找具有相对应的功能的事物帮着我们去解决问题,至于这个事物如何工作,与我们无关,我们只看结果,不关心解决流程。
面向过程是面向对象的基础,问题可以自己不解决托给别人解决,别人也可以在再托给别人,但最
终,事情必须要处理掉,一旦处理就是面向过程
面向对象和面向过程差异
- 面向对象是一种符合人们思考习惯的思想
- 面向过程中更多的体现是执行者,面向对象中更多的体现是指挥者。
- 面向对象可以将复杂的问题进行简单化,更加贴近真实的社会场景
7.2 类与对象的关系
对象
面向对象编程语言主要是使用对象们来进行相关的编程。对象,万事万物中存在的每一个实例,一个电脑、一个手机、一个人、抖音里的一个短视频、支付宝里的一个交易记录、淘宝里的订单。(可以把它象限成一个存在的个体)
对象的内容:
- 对象的属性:就是对象的相关参数,可以直接用数据来衡量的一些特征——常量|变量来表示(成员变量)
- 对象的行为:就是过将对象的属性进行联动,产生出一系列的动作或行为(可以理解为对属性进行操作的函数)——函数(成员函数)
什么是类
具有相同属性特征和行为的对象们的统称。
什么是对象
对象就是某类描述下具体存在的一个事物。
7.3 封装与private关键字
封装-包装
常见的封装体现:
- 函数
- 类
封装的好处:
- 提高了安全性
- 向外界隐藏了一些不需要被外界获知的内容
- 提高了代码的复用性
面向对象的三大特点:封装 继承 多态
总结:
- 类中不需要对外界提供的内容,最好都私有化(用private修饰)
- 后续对私有的内容进行更改,用setter修改器,getter访问器
不可变对象或类
指其内部的数据是私有的,没有向外界提供任何修改内部的方法(典型的就是String)
- 所有的数据都是私有的
- 没有修改器的方法
7.4 局部变量与成员变量
public class Sample {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("小强");
p1.setAge(10); p1.speak();
}
}
class Person {
private String name;
private int age;
public void speak() {
System.out.println("我是" + name + ",我今年" + age + "岁");
}
public void setName(String name) {
if (name.equals("旺财")) {
this.name = "哈士奇";
} else {
this.name = name;
}
}
public void setAge(int age) {
if (age < 0) {
this.age = 0;
} else {
this.age = age;
}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
代码执行流程:
- javac 编译Sample.java源代码 生成Sample.class和Person.class两个字节码文件
- 如果java Person ,运行Person字节码文件,则报错,没有主函数不是主类
- 只能java Sample 运行Sample程序
- 将相关的字节码(Sample.class Person.class)文件加载进JVM中内存下的方法区
- 在方法区中Sample字节码所在的区域里,找主函数,将主函数的栈帧加载进栈内存开始运行
- 开始执行主函数的第一句代码,创建Person对象
- 在堆内存中开辟一个空间并分配地址,在该空间中创建成员变量并默认初始化
- 在主函数空间中创建局部变量p1,并将该对象的地址传给p1
- 接着执行主函数第二句代码,调用p1对象的setName方法
- 从方法区中的Person里,将setName函数栈帧加载进栈,主函数暂停运行
- setName进栈后,创建局部变量name(形参),并将实参“小强”这个字符串在字符串常量池中的地址赋予name
- 因为setName成员函数只有一份在方法区中Person所属区间里,之后可以被多个同类对象调用,为了区分到底是哪个对象调用的该方法,所以在每一个成员函数中,都会有一个隐藏的关键字数据 this ,this相当于一个变量来存储当前对象的地址。(当前对象的引用)
- 执行setName中的内容,如果数据没有问题的话,就将局部变量的值赋值给当前对象的成员变量
- setName函数执行最后一行隐藏的return,表示函数结束并弹栈
- 主函数成为当前栈顶,继续执行主函数
- 执行p1调用setAge函数,从方法区中Person所属空间里找setAge这一段代码,将该函数栈帧加载进栈内存成为新的栈顶,则主函数暂停,该函数运行。先创建形参age的局部变量,接收实参传来的值10,为了区分对象的调用关系,自带this关键字数据,this存的还是p1的地址,如果age没有问题,则将10传给this所指向的对象中age这个成员变量。setAge执行最后一行隐藏的return,表示函数结束并弹栈
- 主函数称为新的栈顶继续执行,调用p1的speak函数进栈
- 在方法区中Person字节码所属空间里读取speak代码,将该栈帧加载进占内存中,主函数暂停,该函数执行,无形参只能表示没有形参的局部变量,但是在函数内部也可以创建其他的局部变量,并且有this关键数据存的是p1的地址,然后去打印name和age,由于speak空间中已经没有其他名为name或age的局部变量,所以找不到,接着找this对象中的数据,找到了则打印。直至函数结束并弹栈
- 已经没有其他名为name或age的局部变量,所以找不到,接着找this对象中的数据,找到了则打印。直至函数结束并弹栈
局部变量和成员变量的区别
- 生命周期
- 成员变量随着对象的创建而创建,随着对象的消亡而消失
- 局部变量随着函数的进栈而创建,随着函数的出栈而消失
- 存储位置
- 成员变量在堆内存中对象所属空间里
- 局部变量在栈内存中函数所属空间里
- 定义位置
- 成员函数在类中,函数外定义
- 局部变量在函数中定义
- 初始化
- 成员变量有默认初始化
- 局部变量必须初始化之后再调用
7.5 构造函数
构造函数:构造函数主要是在创建对象的时候执行的函数,在该函数中也可对成员变量进行一些操作
构造函数的格式:
权限修饰符 类名(参数列表) {
构造函数的代码块
}
7.6 对象的创建流程及内存图解
示例:定义一个栈
public class Sample {
public static void main(String[] args) {
Stack stack = new Stack();
System.out.println(stack);
for (int i = 1; i <= 10; i++) {
stack.push(i);
}
System.out.println(stack.toString());
System.out.println(stack.pop());
System.out.println(stack);
System.out.println(stack.peek());
}
}
class Stack {
private int[] data; //栈的容器
private int top = -1; //栈顶元素的角标 开始为-1
private static int capacity = 10; //栈容器的最大容量 top + 1 <= capacity
public Stack() {
this(capacity);
}
public Stack(int capacity) {
data = new int[capacity];
}
//向栈中进栈一个元素e
public void push(int e) {
if (size() == data.length) {
//需要扩容
resize(data.length * 2);
}
top++;
data[top] = e;
}
public int pop() {
if (isEmpty()) {
System.out.println(">>>栈已空!无法弹出元素!");
return -1; //表示出错
}
int e = data[top];
top--;
if (size() == data.length / 4 && data.length > capacity) {
resize(data.length / 2);
}
return e;
}
public int peek() {
if (isEmpty()) {
System.out.println(">>>栈已空!无法获取栈顶元素!");
return -1; //表示出错
}
return data[top];
}
private void resize(int len) {
int[] arr = new int[len];
for (int i = 0; i <= top; i++) {
arr[i] = data[i];
}
data = arr;
}
//获取有效元素的个数
public int size() {
return top + 1;
}
//判断栈是否为空
public boolean isEmpty() {
return top == -1;
}
//打印一个对象 其实就是在打印这个对象toString方法的结果
public String toString() {
if (isEmpty()) {
return "[]";
} else {
String s = "[";
for (int i = 0; i <= top; i++) {
if (i == top) {
s = s + data[i] + "]";
} else {
s = s + data[i] + ", ";
}
}
return s;
}
}
}
7.7 static关键字——静态关键字
主函数有static修饰(静态函数),全局变量有static修饰
注意:局部变量不能用static修饰,否则编译会出错
作用:
主要用于修饰成员变量(对象的特有属性)和成员函数,变为静态变量和静态函数
静态变量最大的特点是同类下多个对象之间的共有属性 (类特有)
1.什么时候定义静态变量?
在同一类下,多个对象之间有相同的属性和值,那么就可以将该属性和值从成员变量变为静态变量,目的就是为了节省空间。(不属于某一个对象,是这个类所持有的)
2.什么时候定义静态函数?
当一个成员函数不访问成员时,即可定义为静态函数!静态函数一旦定义出来,可以直接用类名去调用(Math.sqrt()),当然也可以通过创建对象来去调用静态函数!
静态优先于对象存在,且在同一类中,静态无法访问非静态(成员),非静态是可以访问静态(静态函数不能调用成员变量)
静态函数中不存在this,因为它不属于哪一个对象,而是属于这个类。
- 当通过对象去调用一个属性时,先找成员,再找静态,最后找父类
- 如果从成员函数中去调用一个属性时,先找局部,再找成员,再找静态,最后找父类
静态的好处:
- 节省堆内存中的空间
- 可以不用费力气去创建对象来调用功能
- 可以对类进行一些初始操作(结合代码块来做)
7.8 静态变量与成员变量
存储位置
成员变量存储在堆内存中对象所属空间里
静态变量存储在静态方法区中对应的字节码空间里
生命周期
成员变量随着对象的创建而创建,随着对象的消亡而消亡
静态变量随着类的加载而存在,随着程序的结束而消失
所属不同
成员变量属于对象的,称之为是对象的特有属性
静态变量属于类的,称之为是类的属性,或者叫对象的共有属性
调用方式不同
成员变量在外界必须通过创建对象来调用,内部的话成员函数可以直接调用成员变量,但是静态函数不能直接调用成员变量,如果非要在静态函数中调用成员的话,只能创建对象,通过对象来调用
静态变量在外界可以通过对象调用,也可以通过类来调用,内部的话静态函数/成员函数可以调用静态变量
7.9 编程练习题
Demo108 风扇
public class Demo108{
public static viod main(String[] args){
Fan f1 = new Fan();
System.out.println(f1.toString());·
}
}
class Fan{
public static final int SLOW = 1;
public static final int MEDIUM = 2;
public static final int FAST = 3;
private int speed = SLOW;
private boolean on = false;
private double radius = 5;
private String color = "blue";
public Fan(){}
public String toString(){
if (on) {
return speed + color +radius;
} else {
return "fan is off" + color + radius;
}
}
public int getSpeed(){
return speed;
}
public void setSpeed(int speed){
this.speed = speed;
}
public boolean getOn(){
return on;
}
public void setOn(boolean on){
this.on = on;
}
public double getRadius(){
return radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public String getColor(){
return color;
}
public void setColor(String color){
this.color = color;
}
}
Demo109 多边形的面积和周长
public class Demo109{
public static void main(String[] args) {
RegularPolygo r = new RegularPolygo();
System.out.println(r.getArea());
}
}
class RegularPolygo{
private int n = 3;
private double side = 1;
private double x = 0;
private double y = 0;
public RegularPolygo(){}
public RegularPolygo(int n,double side){
this.n = n;
this.side = side;
}
public RegularPolygo(int n,double side,double x,double y){
this.n = n;
this.side = side;
this.x = x;
this.y = y;
}
public double getPerimeter(){
return n * side;
}
public double getArea(){
return (n * side *side) / (4 * Math.tan(Math.PI / n));
}
public int getN(){
return n;
}
public void setN(int n){
this.n = n;
}
public double getSide(){
return side;
}
public void setSide(double side){
this.side = side;
}
public double getX(){
return x;
}
public void setX(double x){
this.x = x;
}
public double getY(){
return y;
}
public void setY(double y){
this.y = y;
}
}
Demo112
public class Demo112{
public static void main(String[] args) {
Time t1 = new Time();
System.out.println(t1.toString());
}
}
class Time{
private long hour;
private long minute;
private long second;
public Time() {
long millis = System.currentTimeMillis();
}
public Time(long millis){
hour = cacluHour(millis);
minute = cacluMinute(millis);
second = cacluSecond(millis);
}
public Time(long hour, long minute, long second){
this.hour = hour;
this.minute = minute;
this.second = second;
}
private long cacluHour(long millis){
return millis / 1000 / 60 / 60 % 24;
}
private long cacluMinute(long millis){
return millis / 1000 / 60 % 60;
}
private long cacluSecond(long millis){
return millis /1000 % 60;
}
public String toString(){
return "time ——> " + hour + ":" + minute + ":" + second;
}
public void setTime(long elapseTime){
hour = cacluHour(elapseTime);
minute = cacluMinute(elapseTime);
second = cacluSecond(elapseTime);
}
public long getHour(){
return hour;
}
public long getMinute(){
return minute;
}
public long getSecond(){
return second;
}
}
Demo114
public class Demo114{
public static void main(String[] args){
MyPoint p1 = new MyPoint();
MyPoint p2 = new MyPoint(1,0);
System.out.println(p1.distance(1,0));
System.out.println(p1.distance(p2));
}
}
class MyPoint{
private int x = 0;
private int y = 0;
MyPoint(){}
MyPoint(int x, int y){
this.x = x;
this.y = y;
}
public double distance(MyPoint myPoint){
return Math.sqrt(Math.hypot(x-myPoint.getX(),y-myPoint.getY()));
}
public double distance(int x,int y){
return Math.sqrt(Math.hypot(x-this.x,y-this.y));
}
public int getX(){
return x;
}
public int getY(){
return y;
}
}
Demo115 队列
public class Demo115 {
public static void main(String[] args) {
Queue queue = new Queue();
System.out.println(queue);
for(int i = 1; i <= 10; i++) {
queue.enqueue(i);
}
System.out.println(queue);
for (int i = 1; i <= 6; i++) {
System.out.println(queue.dequeue());
System.out.println(queue);
}
}
}
class Queue {
private int size; //队列中有效元素的个数
private int capacity = 8; //队列容器的最小容量 默认
private int[] element; //队列容器用于存储元素 element.length == size 表示队列已满
public Queue() {
element = new int[capacity];
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
public void enqueue(int v) {
if (size == element.length) { //满了需要扩容
resize(element.length * 2);
}
element[size++] = v;
}
public int dequeue() {
if (isEmpty()) {
return -1; //-1表示一种错误
}
int ret = element[0];
for (int i = 1; i < size; i++) {
element[i - 1] = element[i];
}
size--;
if (size <= element.length / 4 && element.length > capacity) { //需要缩容
resize(element.length / 2);
}
return ret;
}
private void resize(int newlength) {
int[] newelement = new int[newlength];
for (int i = 0; i < size; i++) {
newelement[i] = element[i];
}
element = newelement;
}
public String toString() { //封装对象的信息 用于打印
String s = "[";
if (isEmpty()) {
s += "]";
} else {
for (int i = 0; i < size; i++) {
if (i == size - 1) {
s = s + element[i] + "]";
} else {
s = s + element[i] + ",";
}
}
}
return s;
}
}
Demo117 字符串
public class Demo117 {
public static void main(String[] args) {
char[] chars = {'a','b','c'};
MyString s1 = new MyString(chars);
MyString s2 = new MyString("ab");
System.out.println(s1.compareTo(s2));
MyString s3 = new MyString("ABCD123abcdKKK");
s3.show();
MyString s4 = s3.toLowerCase();
s4.show();
s3.show();
MyString s5 = new MyString("ABC");
MyString s6 = new MyString("abd");
System.out.println(s5.compareToIgnoreCase(s6));
s5.concat(s6).show();
MyString s7 = new MyString("123456");
MyString s8 = new MyString("789");
System.out.println(s7.contains(s8));
MyString s9 = new MyString("xxx.avi");
MyString s10 = new MyString("avi");
System.out.println(s9.endsWith(s10));
MyString s11 = new MyString("abc");
MyString s12 = new MyString("abd");
System.out.println(s11.equals(s12));
MyString s13 = new MyString("abc");
MyString s14 = new MyString("ABC");
System.out.println(s13.equalsIgnoreCase(s14));
MyString s15 = new MyString("123123123");
s15.replace('2','4').show();
System.out.println(s15.startsWith(new MyString("123")));
s15.substring(3,7).show();
}
}
class MyString {
//字符串本身就是一个字符数组 只不过我们不能在该数组中修改元素
private char[] data;
public MyString(char[] chars) {
data = new char[chars.length];
for (int i = 0; i < chars.length; i++) {
data[i] = chars[i];
}
}
public MyString(String s) {
data = new char[s.length()];
for (int i = 0; i < s.length(); i++) {
data[i] = s.charAt(i);
}
}
public MyString(MyString s) {
data = new char[s.length()];
for (int i = 0; i < s.length(); i++) {
data[i] = s.charAt(i);
}
}
public MyString substring(int beginIndex,int endIndex) {
//[begin,end) [1,4)
char[] chars = new char[endIndex - beginIndex];
int index = 0;
/*123456789
b e
i
*/
for (int i = beginIndex; i < endIndex; i++) {
chars[index++] = data[i];
}
return new MyString(chars);
}
public boolean startsWith(MyString s) {
/*
123456
i
123
j
*/
int i = 0;
int j = 0;
while (true) {
if (charAt(i) == s.charAt(j)) {
i++;
j++;
if (j >= s.length()) {
return true;
}
} else {
return false;
}
}
}
public boolean endsWith(MyString s) {
/*
xxxx.avi
i
avi
j
*/
int i = length() - 1;
int j = s.length() - 1;
while (true) {
if (charAt(i) == s.charAt(j)) {
i--;
j--;
if (j < 0) {
return true;
}
} else {
return false;
}
}
}
//自己写
public MyString replace(MyString oldString,MyString newString) {
/*
123123123
23-66
166166166
*/
char chars[];
int mount = 0;
int index = 0;
//记录被替换的string的个数,方便计算新字符数组的长度;
for(int i = 0; i <= length() - oldString.length(); i++){
if(this.substring(i, i + oldString.length()).equals(oldString)){
mount++;
}
}
chars = new char[length() - (oldString.length() - newString.length()) * mount];
char[] k1 = oldString.data;
char[] k2 = newString.data;
for (int i = 0; i <= length() - k1.length; i++) {
boolean flag = false;
if (data[i] == k1[0]) {
int j = i + 1;
int l = 1;
for ( ; l < k1.length; l++,j++) {
if (k1[j] != k2[l]) {
flag = false;
break;
}
}
if(l == k1.length){
flag = true;
}
}
if(false){
for(int k = 0; k < k2.length; k++){
chars[index++] = k2[k];
}
i = i + k1.length - 1;
} else {
chars[index++] = data[i];
}
}
return new MyString(chars);
}
public MyString replace(char oldChar,char newChar) {
/*
123123123
143143143
2->4
*/
char[] chars = new char[length()];
for (int i = 0; i < length(); i++) {
if (charAt(i) == oldChar) {
chars[i] = newChar;
} else {
chars[i] = data[i];
}
}
return new MyString(chars);
}
public int indexOf(char c) {
for(int i = 0; i < length(); i++) {
if(charAt(i) == c) {
return i;
}
}
return -1;
}
//自己写
public int lastIndexOf(char c) {
for(int i = length() - 1; i >0; i--) {
if(charAt(i) == c) {
return i;
}
}
return -1;
}
//自己写
public int indexOf(MyString s) {
//123456
// 45
//->3
for(int i = 0; i < length()-s.length(); i++) {
if(substring(i, i + s.length()).equals(s)) {
return i;
}
}
return -1;
}
//自己写
public int lastIndexOf(MyString s) {
//123456784578
// 45
//->8
for(int i = length() - s.length; i > 0; i--) {
if(substring(i, i + s.length()).equals(s)) {
return i;
}
}
return -1;
}
public boolean equalsIgnoreCase(MyString s) {
return compareToIgnoreCase(s) == 0;
}
public boolean equals(MyString s) {
return compareTo(s) == 0;
}
public boolean contains(MyString s) {
//KMP算法
/*
i
k1 12345678
j
k2 456
l
*/
char[] k1 = data;
char[] k2 = s.data;
for (int i = 0; i <= k1.length - k2.length; i++) {
if (k1[i] == k2[0]) {
int j = i + 1;
for (int l = 1; l < k2.length; l++,j++) {
if (k1[j] != k2[l]) {
return false;
}
}
return true;
}
}
return false;
}
public MyString concat(MyString s) {
char[] chars = new char[length() + s.length()];
int index = 0;
for (int i = 0; i < length(); i++) {
chars[index++] = data[i];
}
for (int i = 0; i < s.length(); i++) {
chars[index++] = s.charAt(i);
}
return new MyString(chars);
}
public MyString toLowerCase() {
char[] chars = new char[length()];
for (int i = 0; i < data.length; i++) {
char c = data[i];
if (isUpperLetter(c)) {
//'a' 97
//'A' 65
chars[i] = (char)(c + 32);
} else {
chars[i] = c;
}
}
return new MyString(chars);
}
/*自己写 */
public MyString toUpperCase(){
char[] chars = new char[length()];
for (int i = 0; i < data.length; i++) {
char c = data[i];
if (isLowerLetter(c)) {
//'a' 97
//'A' 65
chars[i] = (char)(c - 32);
} else {
chars[i] = c;
}
}
return new MyString(chars);
}
private boolean isUpperLetter(char c) {
return c >= 'A' && c <= 'Z';
}
private boolean isLowerLetter(char c) {
return c >= 'a' && c <= 'z';
}
public void show() {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]);
}
System.out.println();
}
public int compareToIgnoreCase(MyString s) {
MyString temp1 = toLowerCase();
MyString temp2 = s.toLowerCase();
return temp1.compareTo(temp2);
}
public int compareTo(MyString s) {
int i = 0;
int j = 0;
while(true) {
if (charAt(i) == s.charAt(j)) {
i++;
j++;
if (i == length() && j == s.length()) {
return 0;
}
if (i < length() && j >= s.length() || i >= length() && j < s.length()) {
return length() - s.length();
}
} else {
return charAt(i) - s.charAt(j);
}
}
}
public char charAt(int index) {
return data[index];
}
public int length() {
return data.length;
}
}
第8章 继承
8.1 继承概述
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
类的继承格式
class 父类 {
}
class 子类 extends 父类 {
}
继承类型
类与类之间只支持单继承, 接口与接口之间可以多继承
继承的好处
继承的出现提高了代码的复用性
继承的出现让类与类之间产生关系,也为我们后面多态提供了前提
注意:
- this表示的是当前对象,存的是当前对象在堆内存中的地址
- super不表示父类的对象,因为在此我们并没有去创建父类的对象!super仅仅表示父类空间,并没有创建父类的对象!
当创建子类对象时,在子类的构造函数执行之前,父类的构造函数先执行,(此时并非创建父类对象)
- 每一个类中的构造函数第一句如果不是 this() 调用本类中其他构造函数的话,默认第一句是隐藏的 super()
- 是因为在创建子类对象的时候,需要父类为继承给子类的一些数据进行初始化。
注意:在构造函数中,第一句要么是this(),要么是super()
this()与super()本身不冲突的,如果构造函数之间有调用关系,那么最后一个被调用的构造函数就不能再回调,那么其第一句就不能是this(),只能是super()
没有构造函数引发的错误:如果父类中,没有无参构造函数的存在,只有有参数的构造函数的话,那么子类中默认的super() 就调用不到父类无参构造函数!引发错误!
子父类中,成员函数的特点
-
如果父类有,子类没有,调用的是父类的
-
如果父类没有,子类有,调用的是子类的
-
如果父类有,子类也有,调用的是子类的(重写 override)
-
如果父类有,但是为私有,则子类继承不到,除非子类自己写一个
子父类中,静态变量的特点与成员变量是一致的
静态函数的特点与成员函数是一致的
重写的作用:
严格意义上而言,子类并非是父类的一个子集。子类的内容很大程度上,很多情况
下,都是父类的一种扩展或增量,重写仅仅去保留了父类的功能声明,但是具体的功能内容由子类来决定
重写需要注意的地方:
如果子父类中有同名函数且参数列表相同(私有例外),编译器就认为是重写关系!
- 重写的时候,子类的权限必须大于等于父类的权限(权限不能变小)
- 重写的时候,返回值类型不能更改
成员变量 和 父类变量 和 局部变量 重名时以上就行。这里面有一个特殊的 super, this表示的是当前对象,存的是当前对象在堆内存中的地址
super不表示父类的对象,因为在此我们并没有去创建父类的对象!super仅仅表示父类空间,并没有创建父类的对象!
子类创建时执行构造函数
当创建子类对象时,在子类的构造函数执行之前,父类的构造函数先执行,虽然父类的构造函数执行但不代表父类对象的创建。
- 每一个类中的构造函数第一句如果不是 this() 调用本类中其他构造函数的话,默认第一句是隐藏的 super()
- 是因为在创建子类对象的时候,需要父类为继承给子类的一些数据进行初始化。
注意:
- 在构造函数中,第一句要么是this(),要么是super()
- 有没有可能每一个构造函数第一句都是this()?没有,否则递归调用
- 有没有可能每一个构造函数第一句都是super()?有,构造函数之间不调用
- this()与super()本身不冲突的,如果构造函数之间有调用关系,那么最后一个被调用的构造函数就不能再回调,那么其第一句就不能是this(),只能是super()
易错的一个地方
如果父类中,没有无参构造函数的存在,只有有参数的构造函数的话,那么子类中默认的super() 就调用不到父类无参构造函数!从而·引发错误!
所以建议每一个类 都把它的 无参构造函数 写出来!
子父类中,成员函数的特点(成员变量也是如此)
- 如果父类有,子类没有,调用的是父类的
- 如果父类没有,子类有,调用的是子类的
- 如果父类有,子类也有,调用的是子类的
- 如果父类有,但是为私有,则子类继承不到,除非子类自己写一个
上面三点所说的就是函数的重写(override)
为什么要重写
子类的内容很大程度上,很多情况下,都是父类的一种扩展或增量,重写仅仅去保留了父类的功能声明,但是具体的功能内容由子类来决定!
如何重写
- 如果子父类中有同名函数且参数列表相同(私有例外),编译器就认为是重写关系!
- 重写的时候,子类的权限必须大于等于父类的权限
- 重写的时候,返回值不能更改
子父类中,静态成员的特点
- 静态变量的特点与成员变量是一致的
- 静态函数的特点与成员函数是一致的
8.2 final关键字
final翻译叫做最终,final可以修饰变量、函数、类
1.final修饰变量
表示该变量的值不可被改变
变量主要分为两种,基本数据类型、引用数据类型的变量
- final修饰的是基本数据类型变量 表示变量所存储的常量值不能改变
- final修饰的是引用数据类型变量 表示变量所存储的对象地址值不能改变但是可以改变该对象中的数据(如果对象中的数据也是final 则也不能修改)
一般而言,当我们在定义常量(字面量 用变量+final来表示),定义成静态变量
public static final 数据类型 变量名 = 常量数据;
对于常量的变量名起名规则为: 全部单词大写 单词与单词之间用下划线分隔
public static final double PI = 3.14;
public static final int MAX_VALUE = 10
2.final修饰函数
不可在子类中重写,
如果某一个类中的函数,不想让其子类去重写的话,该函数就可以声明为final类型
3.final修饰类
该类不能被继承
8.3 抽象类
抽象:指的就是不具体,模糊不清这种含义 看不懂 不明白
不能直接将抽象和类挂钩, 之所以有抽象类的存在,是因为有抽象函数!
具有抽象函数的类,称之为叫抽象类!
abstract class 类名 {
}
抽象函数
函数仅保留函数声明,但不保留函数体,那么该函数就是抽象函数,用abstract关键字来修饰。
抽象类
具有抽象函数的类称为抽象类,也必须用abstract修饰。抽象类不能创建对象,只有其实现子类能够创建对象。
抽象类的特点
- 抽象类和抽象函数都需要被abstract修饰,抽象方法一定在抽象类中
- 抽象类不能创建对象,因为如果一旦创建对象,在调用其函数时,函数没有具体执行内容
- 只有覆盖了抽象类中所有的抽象函数后,子类才可以实例化。否则,该子类还是一个抽象类(重写抽象方法)
抽象类与一般类唯一的区别就是抽象类中抽象函数,其他一律相同(抽象类不能创建对象)!
抽象类和一般类的异同点:
- 相同点:
- 它们都是用来描述事物的
- 它们之中都可以定义属性和行为
- 不同点:
- 一般类可以具体的描述是,抽象类描述事物时会有一些不具体的信息
- 抽象类比一般类可以多定义一个成员:抽象函数
- 一般类可以创建对象,而抽象类不能创建对象
抽象类中是否可以不定义抽象函数?
可以有。有抽象函数的类一定是抽象类,抽象类不一定有抽象函数!
抽象关键字abstract不能与那些其他的关键字共存
- final:final修饰类,表示该类不能被继承;final修饰函数时,表示函数不能被重写;不能,抽象类本就是父类,并且其中的抽象函数就等着被子类重写
- private:private修饰函数,表示函数被私有,不能被子类继承;不能,抽象函数就等着被子类重写。
- static:static修饰的函数,属于类的,随着类的加载从而被加载方法区中,和对象没
有关系了,可以直接用类来调用静态成员,如果抽象函数被静态修饰,被类调用时没意义。
8.4 接口
从代码的角度上而言,接口其实就是抽象类的一种特殊表现形式
当一个抽象类中,所有的方法都是抽象函数时,那么,该类就可以用接口来表示
interface 接口名{
}
class 类名 implements 接口名A,接口名B{
}
接口还是类吗?
不是类了,一些类的功能和操作不再适用于接口。
接口中没有成员函数,没有成员变量,没有构造函数,没静态函数,没有静态变量接口也不能直接去创建对象
接口中的变量和函数特殊的含义
- 接口中的变量 默认是公共静态常量 public static final类型 就算不写这些关键字 也是默认的
- 接口中的函数 默认是公共抽象的函数 public abstract 类型 就算不写这些关键字 也是默认的
接口和类的关系
- 类与类之间是单继承关系,类与接口之间是多实现关系,接口与接口之间是多继承关系
- 类与接口之间的实现关系用implements关键字表示
在实现接口时需注意
- 要么这个类实现接口中所有的方法
- 要么这个类声明为abstract 这一点和继承抽象类一致的
接口中的特点
- 接口中的变量都是全局静态常量
- 接口中的方法都是全局抽象函数
- 接口不可以创建对象
- 子类必须覆盖接口中所有的抽象方法,或声明为abstract
- 类与接口之间可以存在多实现关系
- 接口与接口之间可以存在多继承关系
- 类与类之间只能是单继承关系
接口的存在的意义, 主要解决的就是类与类之间只有单继承的关系
8.5 多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作(同一个类的子类/同一个接口引用对象具有不同的行为)
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态存在的三个必要条件
继承(继承自同一个父类)
重写(各个子类重写父类的方法)
父类引用指向子类对象: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 test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断 instanceof类型判断
if (a instanceof Cat) { // 喵喵喵做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 汪汪汪做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal { //动物类--父类
abstract void eat(); //所有动物都可以吃东西
}
class Cat extends Animal { //继承动物类的喵喵喵类
public void eat() { //重写动物淦饭的方法
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠"); //喵喵喵特有的功能
}
}
class Dog extends Animal { //继承动物类的汪汪汪类
public void eat() {
System.out.println("吃骨头"); //重写父类淦饭的类
}
public void work() {
System.out.println("看家"); //汪汪汪的功能
}
}
多态的实现方式
- 方式一:重写:
- 方式二:接口
- 方式三:抽象类和抽象方法
注意:子类向上转型后,父类引用指向子类对象的变量不能调用子类特有的方法,否则会出错
8.6 内部类
内部类的定义:
将一个类定义在另一个给类里面或者方法里面,这样的类就被称为内部类。
内部类的分类:
成员内部类、局部内部类、匿名内部类、静态内部类
1.成员内部类
格式:
class C{
class D{
}
}
成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法(内可以访问外——包括私有的函数和属性,外访问内需要new对象)
成员内部类的访问权限
- private:仅外部类可访问。
- protected:同包下或继承类可访问。
- default:同包下可访问。
- public:所有类可访问。
注意:成员内部类里面是不能含静态属性或静态方法的。
2.局部内部类
局部内部类存在于方法中。
他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
class C{
public void say(){
class D{
}
}
}
注意:局部内部类就像局部变量一样,前面不能加访问修饰符以及static修饰符。
3.匿名内部类
public class Test {
public static void main(String[] args) {
driveCar(new Car(){
@Override
public void drive() {
System.out.println("驾驶着BMW汽车");
}
});
}
public static void driveCar(Car car){
car.drive();
}
}
interface Car {
void drive();
}
注意:
- 在需要时new一个,并写出它的方法体,在某类只调用1次时可以用匿名内部类
- 匿名内部类没有构造方法。也是唯一没有构造方法的内部类
- 匿名内部类和局部内部类只能访问外部类的final变量
4.静态内部类
静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
class C {
static class D {
}
}
8.7 包与权限
8.7.1.包
什么是包?
包相当于操作系统的文件夹
包的好处
- 管理java文件的:方便寻找(包名+类名)、解决重名的问题
- 保护资源 (结合着访问控制符,来限定资源的访问)
包的使用
- 定义包名:
- 一般都用小写英文
- 见名之义 公司域名的倒写+ 【部门名称】+项目名称+模块 、用.分隔,不能以“.”开头
www.taobao.com---- com.taobao+qianniu
学生管理系统 com.openlab.student.entity
- 创建:
1. new Package
2. 创建类的同时创建包 com.openlab.student.util StudentUtil.java - 包的声明:package com.openlab.student.entity;
必须位于类的第一行非注释语句 - 包的导入:
- import java.util.Scanner;–类的完全限定名
- import java.util.*; --导入包下的所有java类,但是不会导入子包中的类
- 不同包下的资源相互使用,需要先导包
- 导包快捷键 Ctrl+Shift+O
- 导入的类重名,来自不同的包,需要显式的写出 :
java.util.Date dateUtil;
java.sql.Date dateSql
8.7.2权限
public 共有的 类内部、同包、不同包
protected 受保护的 类内部、同包、不同包的子类
default 默认的 类内部、同包
private 私有的 类内部(ok) 同包(否) 不同包(否)
权限大到小:public>protected>default>private
8.8 枚举类型
enum 可以当成数据类型来用
作用:管理一系列的常量(限定取值)
枚举的由来:
枚举出来之前都是拿class或者interface来组织管理常量—基本的数据类型
枚举的缺点:
只要数据类型合适,就会编译通过,不考虑实际的业务,可能造成错误
enum 限定取值
枚举类型本质是Enum的子类:不能再继承别的类
public final String name() {
return name;
}
public final int ordinal() {//序号
return ordinal;
}
自定义枚举
public enum Role2 {
ROLE_NOMAL,
ROLE_VIP,
ROLE_SUPER_VIP
}
可以当成数据类型来用
private Role2 role;
限定取值
user.setRole(Role2.ROLE_NOMAL);
枚举值的比较 equals或者==都可以
Role2 r=user.getRole();
if(r==Role2.ROLE_SUPER_VIP) {
System.out.println("超级用户。。。。。。。。。。。。");
}else if(r==Role2.ROLE_NOMAL) {
System.out.println("普通用户。。。。。。。。。。。。");
}
用switch更方便
switch(r) {//byte int short String(1.7+),enum
case ROLE_SUPER_VIP:
System.out.println("超级用户。。。。。。。。。。。。");
break;
case ROLE_NOMAL:
System.out.println("普通用户。。。。。。。。。。。。");
break;
case ROLE_VIP:
System.out.println("VIP用户。。。。。。。。。。。。");
break;
}