1.面向过程和面向对象
面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。
生活中洗衣服:
面向过程:需要自己写关于放衣服,开洗衣机,清洗衣服,烘干衣服的函数,注重洗衣服的整个过程,如果再增添一些其他的函数,对于整个洗衣服过程的开发和维护非常麻烦
面向对象:我们把洗衣服过程分为两个类(人和洗衣机),定义的人 类中有放衣服,开洗衣机等函数(方法),定义的洗衣机 类有清洗和烘干的函数(方法),把这两个类定义好后,通过类创建对象,让对象去执行以上的方法。相比于面向过程,面向对象编程,把事务先分解到对象身上,描述各个对象的作用,然后才是它们之间的交互。
或者这样理解:面向过程是编年体,面向对象是纪传体。
2.类
类是对实体对象的描述。其中有成员方法和成员属性
// 类的定义和实例化
class Student {
// 成员变量/属性
public int age;
public String name;
// 成员方法
public void eat() {
System.out.println(name +"正在吃饭");
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.name = "张三";
student1.age = 10;
System.out.println(student1.name);
System.out.println(student1.age);
student1.eat();
Student student2 = new Student();
student2.name = "李四";
student2.age = 11;
System.out.println(student2.name);
System.out.println(student2.age);
student2.eat();
}
}
3.this
this的一些用法:
1.通过this访问当前对象的 成员变量
2.通过this访问当前对象的 非静态成员方法
3.通过this访问当前对象的 其他构造方法(this在访问其他对象的构造方法时必须在第一行,不能形成环)
public class Date {
public int year;
public int month;
public int day;
public void setDate(int y,int m,int d) {
year = y;
month = m;
day = d;
} // 这样子可以打印正确的日期
public void setDate1(int year,int month,int day) {
year = year;
month = month;
day = day;
} // 但是这样子是打印的0年0月0日,为什么呢?是形参自己给自己赋值
// 没有修改到对象当中去,那我们怎样改
public void setDate2(int year,int month,int day) {
this.year = year;
this.month = month;
this.day = day;
} // 正确的打印
public void printDate() {
System.out.println(year+" 年"+month+" 月"+day+" 日");
}
public static void main(String[] args) {
Date date = new Date();
date.setDate2(2001,1,1);
date.printDate();
}
}
第二条
public class MyClass {
private int number;
public MyClass(int number) {
this.number = number;
}
public void printNumber() {
System.out.println("Number: " + this.number);
}
public void setNumber(int number) {
this.number = number;
}
}
4.构造方法
含有this的第三条:
public class Date {
public int year;
public int month;
public int day;
// 无参构造
public Date() {
this.year = 2000;
this.month = 1;
this.day = 1; // 打印2000-1-1
System.out.println("这是一个无参构造");
}
// this访问其他构造方法
public Date() {
this(2011,2,21); // 打印2011-2-21
System.out.println("这是一个无参构造");
}
// 带参构造
public Date(int year,int month,int day) {
this.year = year;
this.month = month;
this.day = day;
System.out.println("这是带有三个参数的构造");
}
public void printDate() {
System.out.println(year+"-"+month+"-"+day);
}
public static void main(String[] args) {
Date date1 = new Date(); // 调用无参构造
Date data2 = new Date(2022,3,3); // 调用含有参数的构造
date1.printDate();
data2.printDate();
}
}
5.封装
对外隐藏类的内部实现细节,只是对外提供了一些可以公开访问的内容
这其中涉及到访问修饰限定符(public,private,protected),要理解这些访问修饰限定符的作用,首先要了解包的概念
包
包是用来组织类和接口的一种机制,可以通俗的看成文件夹,文件夹里的文件就是相关的类和接口,包形成了一个独立的空间,可以避免命名冲突,而且可以很好的管理代码
导入包中的类:
// 方法一:直接导入包中的类
import java.util.Date(推荐)
但是不推荐使用 import java.util.*
例如:如果不同的包中存在相同名称的类,导入所有类可能会导致命名冲突。这会使得代码难以理解和维护,因为无法确定使用的是哪个类
// 方法二:
java.util.Date date = new java.util.Date();
先是创建了一个person包,之后在包中创建了一个Test01的类,在Test这个包中去引用Test01()这个类(导入自定义包)
浅谈封装
由于private将类给封装好,外界无法直接访问,需要通过给的接口进行访问,例如
class Student {
private String name;
private int age;
private String stuNum;
public static String className;
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 getStuNum() {
return stuNum;
}
public void setStuNum(String stuNum) {
this.stuNum = stuNum;
}
public void show() {
System.out.println(" 姓名:"+this.name+" 年龄:"+this.age+" 学号:"
+this.stuNum);
}
}
public class Main {
public static void main(String[] args) {
Student student = new Student();
// student.name = "haha"; err
// System.out.println(student.name); err
student.setName("张三");
student.setAge(20);
student.setStuNum("001");
System.out.println(student.getName());
System.out.println(student.getAge());
System.out.println(student.getStuNum());
}
}
default表示前面没有修饰时的默认情况
现阶段不讨论子类的问题,#表示有此功能
private | default | protected | public | |
同一个包的同一个类 | # | # | # | # |
同一个包的不同类 | # | # | # | |
不同包的子类 | # | # | ||
不同包的非子类 | # |
6.static成员
静态成员变量
被static修饰的成员,不属于对象,是属于静态的成员变量(类变量),或者说是属于类的,存在与方法区中,类加载时就会被初始化。
可以通过对象的引用来访问,也可以通过类名来进行访问,但是通过类名来访问更加的合理
public class Main {
public static void main(String[] args) {
Student student = null;
Student.className = "1班";
System.out.println(Student.className);
}
}
以上正确打印
public class Main {
public static void main(String[] args) {
Student student = null;
student.name = "李四";
System.out.println(Student.name);
}
}
先把name前面的private改为public,但由于name必须通过对象访问,因此有误
静态成员方法
1.静态成员方法当中 不能够直接调用非静态方法,因为静态方法不依赖与对象,可以直接通过类名访问,但是非静态成员方法依赖对象,需要通过对象的引用访问;例如
2.static 方法中不能使用this关键字
静态成员变量的初始化
1.构造方法初始化
class Student {
public String name;
public int age;
public String stuNum;
// 构造方法
public Student(String name, int age, String stuNum) {
this.name = name;
this.age = age;
this.stuNum = stuNum;
public void show() {
System.out.println("姓名:" + this.name + " 年龄:" + this.age + " 学号:" + this.stuNum);
}
public static void main(String[] args) {
// 创建对象时调用构造方法进行初始化
Student student = new Student("张三", 20, "20240001");
student.show();
}
}
2.get,set初始化--见浅谈封装
3.直接初始化
static int age = 5;
4.代码块初始化
要想知道什么是代码块初始化,首先了解什么是代码块
代码块
class stu{
public String name;
public int age;
{
this.name = "lihua";
this.age = 22;
System.out.println("2.实例代码块");
}
public stu(String name, int age) {
this.name = name;
this.age = age;
System.out.println("3.构造方法(代码块)");
}
static {
System.out.println("1.静态代码块");
}
}
public class The {
public static void main(String[] args) {
stu s = new stu("王五",5);
System.out.println("==============");
stu s1 = new stu("王五",5);
}
}
那么它们之间的执行顺序是什么,按照绝对顺序:静态代码块,实例代码块,构造方法,而且静态的只执行一次,这个类只加载一次,如果都是实例代码块,和定义顺序有关
6.对象的打印
public class The {
String name;
String gender;
int age;
public The(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public static void main(String[] args) {
The person = new The("Jim","男", 18);
System.out.println(person);
}
}
实际上是打印了该对象的内存地址而不是对象的内容。这是因为在默认情况 System.out.println()方法会调用对象的toString() 方法来获取对象的字符串表示形式,类中的toString()方法会返回对象的内存地址。那应该怎样修改,加上即可
@Override
public String toString() {
return "The{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
由于println是一个方法,我们需要看它的实现
当我们实现自己的toString时,是会调用我们自己的,如果自己没有实现,则会调用官方的
7.继承
如何继承
在现实世界中,我们常常会发现一些事物的共性,例如狗和🐟都是动物,如果我们想要描述它们的一些行为或者其他特性。如果采用这样的代码(如下)
public class Dog {
public int age = 3;
public String name = "旺财";
public void eat() {
System.out.println(this.name+"正在吃食物");
}
public void bark() {
System.out.println(this.name+"正在汪汪叫");
}
}
public class Fish {
public int age = 1;
public String name = "小金鱼";
public void eat () {
System.out.println(this.name+"正在吃食物");
}
public void swim() {
System.out.println(this.name+"正在水中游");
}
}
显得代码冗杂,我们发现,其中有些共同的特征,我们把这些共同的特征抽离出来,实现代码的复用,因此我们可以修改(需要借助extends关键字)为(如下):我们先定义一个动物类,表示🐟和🐕的父类,我们把🐟和🐕称之为子类
public class Dog extends Animal{
public void bark() {
System.out.println(this.name+"正在汪汪叫");
}
}
public class Fish extends Animal{
public void swim() {
System.out.println(this.name+"正在水中游");
}
}
public class Animal {
public int age;
public String name;
public void eat() {
System.out.println(this.name+"正在吃食物");
}
}
那么子类和父类都有一个相同的成员变量,访问哪一个呢(打印出的是20 2 10,为什么呢)
public class Base {
public int a = 1;
public int b = 2;
}
public class Derived extends Base{
public int c = 10;
public int a = 20;
public void func() {
System.out.println(this.a);
System.out.println(this.b);
System.out.println(this.c);
}
}
上述情况访问子类的成员变量,那么如何访问父类的成员呢,加上super就可以了
我们可以这样子理解super和this的关系
那么对于成员方法呢,对的,也是一样优先访问子类,如果是构成了重载(例如fun1),根据调用 方法适传递的参数选择合适的方法访问
// 子类
public void fun1() {
System.out.println("the Base fun1");
}
public void fun2() {
System.out.println("the Base fun2");
}
// 父类和结果
public void fun1(int a) {
System.out.println("the Derived fun1 + int");
}
public void fun2() {
System.out.println("the Derived fun2");
}
public void fun3() {
fun1(); // 无参 the Base fun1
fun1(2); // 传参 -- the Derived fun1 + int
fun2(); // the Derived fun2
super.fun2(); // the Base fun2
}
总结下来一句话:就近原则
super关键字
1.只能在类的非静态方法中使用,用来访问非静态成员方法和字段(由于静态的不依赖于对象,而用super指代的依赖于对象)
2.只能指代当前的父类,不能指代父类的父类
3.调用父类构造方法
注:super和this必须在方法的第一行,否则会报错,也隐含了super和this不能共存
如何理解呢,当子类 继承 父类之后,子类需要显示调用父类的构造方法,帮助父类成员进行初始化,那么如何进行初始化?
如果没有提供任何构造方法,那么Java 会提供,如果自己写了一个,则不会提供
一些执行顺序
按图说话了--看最后的结果
下面我们把dog执行两次
注:java不支持多继承,但可以通过接口的方式支持
接口下篇博客写,点个关注吧(doge)
protect关键字
我们知道:private只能在当前类中使用,default只能在当前包中使用,public可以在任何地方使用,那么protect可以在当前包中使用又可以在不同包中的子类使用是什么意思呢?如图
final关键字
1.如果不想被继承,使用final关键字 -- 还是拿动物,🐕,🐟举例子,由上面可知,🐟和🐕继承了动物,那么我们加上final关键字就会
2.将变量修饰为常量
8.多态
我们应该如何理解多态,通俗来讲就是完成某个行为,不同的对象会产生不同的状态
在认识多态前,我们首先要了解重写,向上转型和向下转型等概念
向上转型 -- 父类引用了子类对象
以下代码为了说明转型
package demo2;
public class Animal {
public int age;
public String name;
public void eat() {
System.out.println(this.name+"正在吃食物");
}
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "名字: " + name + ", 年龄: " + age;
}
}
package demo2;
public class Dog extends Animal {
public Dog(String name,int age){
super(name,age);
}
public void bark() {
System.out.println(this.name+"正在汪汪叫");
}
}
package demo2;
public class Fish extends Animal {
public Fish(String name,int age) {
super(name,age);
}
public void swim() {
System.out.println(this.name+"正在水中游");
}
}
方法1:直接赋值
方法2:方法传参
方法3:返回值
缺点:
重写和重载
重写需要满足什么条件?
1.方法名字一样
2.参数列表相同
3.返回值相同
那么我们看两组代码(这里包含向上转型和动态绑定),我们调用的是animal的eat方法。为什么打印 旺财正在吃骨头
public static void main(String[] args) {
Animal animal = new Dog("旺财",2);
animal.eat();
}
------------测试类--------------------------
public void eat() {
System.out.println(this.name+"正在吃食物");
}
-------------父类Animal---------------------
public void eat() {
System.out.println(this.name+"正在吃骨头");
}
-------------子类Dog-----------------------
父类和子类的方法构成了重写
一般在重写的方法前加上@Override,只是一个标注,如果不是重写则会报错
由此我们可以得到:程序在编译的时候,确实调用的是父类的eat,当代码运行时,通过父类的引用,调用了父类和子类重写的哪个方法,结果实际调用了子类的方法,此时 我们把这个情况叫做动态绑定
那么可以动态绑定的条件是什么?
1.父类引用 引用 子类对象
2.通过父类的引用,调用了父类
注意事项:
1.不能是一个静态方法
2.被finnel修饰不能被重写
3.如果子类重写父类方法 子类权限要大于等于父类的权限
4.被private修饰的方法不能被重写
5.构成父子类关系也是重写
重写和重载的区别
1.重写是针对继承关系,重载是在同一个类针对方法参数不同给予的多种实现
2.重写是覆盖父类中的方法,满足以上条件(见上--重写与重载开始)+返回类型可以是子类类型。它是对于父类改写满足子类需求。
3.重载是方法名相同,参数列表不同,返回值可以相同也可以不同。它是为了适应不同参数组合。
而且它是静态方法的代表。
静态方法:在编译时,根据用户的传参调用的方法。例如:Add1(int a ,int b); Add 2(int a,int b,intc)我们计算加法时,如果是两数相加调用Add1,三个数调用Add2
向下转型 -- 强转
但是不都是可以成功的 :上面是引用的Fish用Fish强转,但下面引用的是Dog用Fish强转
我们可以这样修改
多态
引用的对象不用样,但是调用的同一个方法,表现出的现象不一样,这种思想叫做多态
public class Shape {
public void draw(){
System.out.println("画一个图形");
}
}
public class Round extends Shape{
@Override
public void draw(){
System.out.println("画一个圆");
}
}
public class Rectangle extends Shape{
@Override
public void draw(){
System.out.println("画一个矩形");
}
}
public class Square extends Shape{
@Override
public void draw(){
System.out.println("画一个正方形");
}
}
public class test {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Round round = new Round();
Rectangle rectangle = new Rectangle();
Square square = new Square();
drawMap(round);
drawMap(rectangle);
drawMap(square);
}
}
或者
public static void main(String[] args) {
Round round = new Round();
Rectangle rectangle = new Rectangle();
Square square = new Square();
Shape[] shape = {rectangle,round,square};
for(Shape shape1 :shape) {
shape1.draw();
}
}
结果---------------------------------------
画一个圆
画一个矩形
画一个正方形
这就是多态
如果不使用多态则会使用大量的if-else(如下)
优点:可扩展能力强
public static void main(String[] args) {
Round round = new Round();
Rectangle rectangle = new Rectangle();
Square square = new Square();
String[] strings = {"round","rectangle","square","square","rectangle"};
for(String s:strings){
if(s.equals("round")) {
round.draw();
}else if(s.equals("square")) {
square.draw();
}else {
rectangle.draw();
}
}
}
// 结果-----------------------------------------------
画一个圆
画一个矩形
画一个正方形
画一个正方形
画一个矩形
缺点:代码的运行效率降低
1.属性没有多态性:当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性。
2.构造方法没有多态性
一个有坑的代码
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class test {
public static void main(String[] args) {
D d = new D();
}
}
运行结果
D.func() 0
完成收工,点个关注把^o^....