面向过程&面向对象
1.面向过程编程思想:
- 步骤清晰简单:使用线性思维,根据流程一步一步去做。
- 面向过程适合处理一些相对简单的问题
2.面向对象编程思想:
- 是一种分类的思想,思考问题前首先考虑解决问题需要哪些分类,然后对这些分类进行单独思考,最后才对某个分类下的细节进行面向过程的思考。
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题。(类主要包括属性和方法。)
3.面向对象编程主要特点及特性:
- 抽象:将一类事物的共同特点抽取出来变成一个类。
- 封装:将数据包起来,相当于将数据放到一个盒子里面,并留下一个开口供调用时使用。
- 继承:由子类继承父类的功能。
- 多态:同一个事物也具有多种形态。例如,小明,小红都是人,但是他们的成绩不一样,学习方法也不一样。
从认识论角度来讲,是先有的对象后有的类,因为对象是具体的,类是抽象的,但从代码运行角度来考虑,是先有的类,后有的对象,类是对象的模板,对象是类的实例。
方法回顾及加深
1.方法的定义:
修饰符 返回值 方法名 参数列表 (异常抛出)方法体
2.方法的调用:
- 静态方法调用:使用static修饰符修饰的方法,由于由static修饰的变量或者方法是和类同时加载,因此静态方法可以随意在类中调用。
- 非静态方法调用:没有static修饰 。由于方法是非静态的因此,调用时候需要先将方法实例化。
public class Demo {
public static void main(String[] args) {
//调用静态方法
say1();
//调用非静态方法
Demo demo = new Demo();//此方法说:Hey,我是静态方法噻!
demo.say();//此方法说:Hey,我是非静态方法哟!
}
public void say(){
System.out.println("此方法说:Hey,我是非静态方法哟!");
}
public static void say1(){
System.out.println("此方法说:Hey,我是静态方法噻!");
}
}
- 补充:静态方法与非静态方法的相互调用需要注意的事项
public class Demo {
public static void main(String[] args) {
}
//两个非静态方法是可以相互调用的,当然此示例没有意义仅限于在编译阶段不会报错,只说明是可以相互调用,具体使用需要根据具体情况
public void a(){
b();
}
//非静态方法也可以调用静态方法
public void b(){
c();
}
//但是静态方法是无法直接调用非静态方法的,这是因为static修饰的方法是在静态方法区,会和类一起加载,
// 而加载时非静态方法是没有加载的,所以静态方法无法调用自己存在时不存在的方法。
public static void c(){
a();//此时会报错
}
}
- 形参与实参:形式参数与实际参数,当我们定义一个方法时,有时会定义方法中应该输入的值,此时应该用形式参数,可以自己取名字,起到一个占位置的作用,当我们调用方法在里面赋值时,此时我们赋的值就是实际参数。
- 值传递与引用传递
public class Demo {
public static void main(String[] args) {
//在主方法中分别进行值传递和引用传递示例
//值传递
int b = 1;
System.out.println(b);//结果为1
Demo.change(b);
System.out.println(b);//结果仍然为1,而不是调用的方法中的10
/*
这是因为java是值传递,虽然调用了下面的方法,但是只是走了一下流程,方法中的a为形参,
但是并没有返回值,因此无论实际参数是几,调用此方法时,形参并不会反馈回来一个值,因此
即使调用方法,输出结果依旧是我们赋给的初始值。
*/
//引用传递
//首先实例化一个对象
Person person = new Person();
//先尝试调用方法输出一下Person类中的属性name
System.out.println(person.name);//结果为null
//调用change01方法,并代入实参person
Demo.change01(person);
System.out.println(person.name);//结果为 尼可乐
/*
这里是因为实参引用了person这个对象,person.name指向的是Person类中的name属性,
方法中的“尼可乐”不再是单纯的改变形参,而是直接赋值给了Person类中的属性name,于是
输出的结果就是实参指向的类中的属性。
*/
}
//值传递
//返回值为空
public static void change(int a){
a = 10;
}
//引用传递需要的方法
public static void change01(Person a){
//此处的a是一个实例化的形参,这个形参引用了Person类中的name属性并将尼可乐赋值给她
a.name = "尼可乐";
}
}
//定义一个类
class Person{
String name;//此时name并没有赋值因此此时name为默认值null
}
类与对象的关系与创建
-
类是一种抽象的数据类型,它是某一类事物的整体描述,但那时并不能代表某一个具体的事物。
例如:动物、植物、手机、电脑
Person类、Pet类、Car类都是用来描述或者定义某一类具体的事物应该具备的特点和行为。
-
对象是抽象概念的具体实现。
比如张三就是人的一个具体实现,张三家里的旺财就是狗的一个具体实现。
能体现出特色,展现出功能的是具体的实例,而不是一个抽象的概念。
-
创建与初始化对象
使用new关键字创建对象:
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及类中构造器的调用。
-
类中的构造器也称为构造方法,是在创建对象的时候必须要调用的。并且构造器有以下两个特点:
-
必须和类的名字相同。
-
必须没有返回值类型,也不能写void
(构造器很重要这一部分一定要重点掌握!!!)
-
public class Demo {
public static void main(String[] args) {
//我们可以理解类就是一个大类,这里面有无数的实例化的对象,他们具有共同的特点(即属性),以及有共同的行为(即方法)。
//因此我们想要使用类中的属性和方法,我们要先使用new关键字实例化对象,只要有需求我们实例化多少个对象都可以
Students studentA = new Students();
Students studentsB = new Students();
//使用实例化的对象调用类中的方法与属性
System.out.println(studentA.name);//null
System.out.println(studentA.age);//0
studentA.study();//null在学习
//给实例化对象赋值
studentA.name = "小明";
studentA.age = 15;
studentA.study();//小明在学习
}
}
//定义一个学生类
class Students{
//定义属性:字段。比如我们定义两个属性,姓名和年龄
String name;
int age;
//定义方法:比如定义一个xxx在学习的方法
public void study(){
//this类表示当前这个类
System.out.println(this.name+"在学习");
}
}
构造器
- 一个类即使什么都不写,也存在一个构造器。特点如下:
- 构造器名字(构造方法)和类名相同
- 没有返回值(也没有void)
- 构造器的作用:
- new的本质就是在调用类中的构造器
- 用来初始化对象的值
- 注意点:
- 定义有参构造之后,如果想使用无参构造,需要显示的定义一个无参构造器,因为构造方法名是相同的,如果不显示定义会默认只又有参构造,导致无法使用无参构造,而显示定义过后,则就相当于方法的重载,调用时会自动匹配使用的构造方法。
- idea中显示定义无参构造器的快捷键为alt+insert选择Constructor。
- this.x是当前类的属性, = 后面是传进来的值
public class Demo {
public static void main(String[] args) {
//利用无参构造器
Person person = new Person();
System.out.println(person.name);//结果为 尼可乐
//使用有参构造器,我们加入实参,会自动帮我匹配调用有参构造方法,和方法重载的道理相同。
Person person1 = new Person("Nicholas");
System.out.println(person1.name);//结果为 Nicholas
}
}
//首先定义一个类
class Person{
String name;
//显示定义无参构造
public Person() {
//引用传递将Person类中的name属性赋值“尼可乐”
this.name = "尼可乐";
}
//定义有参构造
public Person(String name) {
this.name = name;
}
}
- 创建对象内存分析
简单的内存图如上,java中分堆和栈,栈最底层是main()方法,当我们创建一个类,例如Pet类,会放在堆中,然后每实例化一个对象,会在栈中压入一个引用变量名,但是此时是空值,引用变量名会指向堆中的对象,对象的属性赋值则以及方法调用则是在堆中进行,堆中的方法区有一个静态方法区,这里的方法是由static修饰的方法,静态方法区会和类一起加载,因此,任何类都能调用静态方法区的方法。
封装
封装(隐藏的数据),在是一种“该露的漏,该藏的藏”的思想,程序设计中要追求“高内聚,低耦合”的思想。
-
高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
-
低耦合:紧暴露少量的方法给外部使用。
-
通常,应禁止直接访问一个对象中数据的直接表示,而应该通过接口来访问,这成为信息隐藏。
-
封装通常针对类中的属性进行封装,对方法的封装较少。
属性私有:当属性被private修饰时,我们需要使用get/set来获取及设置数据
- idea中的快捷键是alt+insert然后选择getter and setter。
- idea中快捷键:alt+insert然后选择getter and setter
- 作用:提高程序的安全性,保护数据;隐藏内部代码细节;统一接口;增加系统可维护性。
public class Demo {
public static void main(String[] args) {
//在主方法中调用私有属性
//首先先实例化一个对象
Student Nichols = new Student();
Nichols.setName("尼可乐");
System.out.println(Nichols.getName());//结果可以正常输出尼可乐
}
}
class Student{
private String name;
//在类中按住alt和inser选择getter and setter后选择需要的属性就会自动生成以下代码
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
继承
继承本质是对某一批类的抽象,从而实现对现实世界更好的建模。关键字:extends
Java中只有单继承,没有多继承,就是一个子类只能有一个父类,但是一个父类可以有多个子类。
就好比我们人类,一个人可以有很多孩子,但是只能有一个父亲。
继承是类与类之间的关系,(拓展:类与类之间除了继承关系还有依赖、组合、聚合等。)
public class Demo extends Person{
public static void main(String[] args) {
//实例化一个对象
Demo nicholas = new Demo();
//方法可以直接调用
nicholas.say();
//普通方法也可以直接调用
System.out.println(nicholas.name);
//私有属性不可被继承使用
//System.out.println(nicholas.money);此处已报错
//但是我们可以利用私有属性的调用方法来调用无法直接继承的属性。
System.out.println(nicholas.getMoney());
}
}
//首先创建一个父类
class Person{
//方法
public void say(){
System.out.println("说了一句:你好呀我是say方法");
}
//普通属性
public String name = "尼可乐";
//私有属性
private int money = 1000;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
(补充:1.Object类是所有类的父类,可以理解为祖宗类;如果类被final修饰,则该类不可以被继承,可以理解为断子绝孙)
super()方法详解
主要作用是用于子类对父类的属性和方法的调用。(目前自己的理解,感觉理解还不是很到位)呐,上代码示范:
public class Demo{
public static void main(String[] args) {
//实例化一个对象然后调用Student类中的方法
Student student = new Student();
student.test("里可");//输出结果为:里可 尼可乐 Nicholas
}
}
//首先定义一个父类,并且定义一个受保护的属性
class Person{
protected String name = "Nicholas";
}
//定义一个子类,继承Person类
class Student extends Person{
private String name = "尼可乐";
public void test(String name){
System.out.println(name);//输出实参传入的值
System.out.println(this.name);//输出当前类的name属性
System.out.println(super.name);//super则会调用父类的属性
}
}
由输出结果的值,首先super第一个作用就是用于调用父类的属性,并且权限很高,属性被protected修饰也可以调用。
那么第二个作用继续上代码如下:
public class Demo {
public static void main(String[] args) {
Student student = new Student();
student.test();//输出结果为 Student Student Person
}
}
//首先定义一个父类,并且定义一个方法
class Person {
public void print() {
System.out.println("Person");
}
}
//定义一个子类,继承Person类
class Student extends Person {
//定义一个方法
public void print() {
System.out.println("Student");
}
public void test() {
print();
this.print();
super.print();
}
}
其实这个就是调用父类属性的用法,换汤不换药,由主方法中输出结果也可以看出和 上面第一个例子是类似的结果。但是需要特别注意的是,父类中由private修饰的方法用super也无法继承。下面就是关于this()、super()与构造方法之间的一些关系了。继续上代码:
public class Demo {
public static void main(String[] args) {
Student student = new Student();
//输出结果:父类无参构造器已触发、子类无参构造器已触发
}
}
//首先定义一个父类,并且定义一个方法
class Person {
public Person() {
System.out.println("父类无参构造器已触发");
}
}
//定义一个子类,继承Person类
class Student extends Person {
public Student() {
//super();调用父类构造器中的方法,将super放到这里效果相同,显示调用必须放到第一行。
//this();调用自身构造器中的方法,显示调用也必须要放到第一行。
System.out.println("子类无参构造器已触发");
}
}
主方法中只是实例化了student类的一个对象,但是运行结果却运行了父类的无参构造器。这说明子类中隐藏一个默认调用父类无参构造的方法,而这个方法就是super(), 我们可以尝试把super()方法显示放到子类构造器中的最前面可以出现同样的结果,但是需要注意的一点是:super()只能放到构造器中第一行。
this()是调用自身构造器,但是如果显式调用,也必须要放到第一行,因此显示调用只能使用this()或super()其中的一个。
补充:父类中定义有参构造,并且没有显示定义无参构造的情况下,子类也会无法使用无参构造,同样的super()方法也无法调用父类的无参构造(有参构造可以正常调用)。所以当我们一旦重写了有参构造,一定要先把无参构造加上
注意点:
- super()是调用父类的构造方法,且super只能出现在子类的方法或者构造方法中。
- super和this不能同时调用构造方法。
- this代表本身调用者这个对象,super代表父类对象的引用。
- this()表示本类的构造方法,super()代表的是父类的构造方法。
- this可以直接使用,super是必须在有父类的情况下才能使用。
方法重写
当子类继承父类方法时,难免会有一些方法不适用于子类,因此我们要对方法进行重写,方法重写,顾名思义,其主要目的针对的是方法,而不是属性,因此与属性无关。下面是示例代码:
public class Demo {
public static void main(String[] args) {
//先实例化两个对象并进行静态方法的调用
A a = new A();
a.test();//输出结果为A-->test()
B b = new A();
b.test();//输出结果为B-->test()
//再实例化两个对象并进行非静态方法的调用
A c = new A();
c.test01();//输出结果为D-->test()
B d = new A();
d.test01();//输出结果为D-->test()
}
}
//首先定义一个父类
class B{
//静态方法
public static void test() {
System.out.println("B-->test()");
}
//非静态方法
public void test01(){
System.out.println("C-->test()");
}
}
class A extends B{
//静态方法
public static void test(){
System.out.println("A-->test()");
}
//方法重写非静态方法,快捷键alt+insert选择override,然后更改方法体
@Override
public void test01() {
System.out.println("D-->test()");
}
}
由主方法输出的结果可知,虽然我们创建4个实例对象的时候右边都是对A这个子类创建的,但是对静态方法调用时,输出结果取决于左边指向的类中的方法,但是对于非静态方法的调用,子方法重写父类方法后,再进行调用,输出结果则不会再指向父类中的方法。
方法重写总结
-
前提条件:需要有继承关系,而且是针对方法,子类对父类的方法进行重写,属性没有重写。
-
特点:
- 方法名必须相同
- 参数列表必须相同
- 修饰符范围可以扩大,但是不能缩小,public>protected>default>private
- 重写子类和父类方法要一致,但是方法体不同。
-
为什么要重写:因为父类的功能子类不一定需要或者不一定满足。
快捷键:alt+insert然后选择override。
多态
多态是指同一方法可以根据发送对象不同而采取多种不同的行为方式,一个对象的实际类型是确定的,但可以指向对象引用的类型有很多。
注意:多态是方法的多态,属性没有多态性,并且父类和子类要有联系才可以,否则会出现类型转换异常:ClassCastException。
存在条件:要具有继承关系,方法需要重写,父类引用指向子类对象如:Father x = new Son();
static、final、private修饰的方法等无法重写:static修饰的属于类不属于“实例”不可以重写,final修饰的属于“常量"无法重写,private修饰属于私有方法无法重写。
public class Demo {
public static void main(String[] args) {
//对象的实际类型时确定的,new的是谁就是哪个类中的对象,但是指向引用类型就不一定了。
// 可以是子类引用指向子类,也可以是父类引用指向子类。(引用Object类可以指向所有类)
//子类可以使用的方法只有自己的方法以及从父类继承到的方法
Student a = new Student();
Person b = new Student();
Object c = new Student();
a.run();//输出结果为jogging
b.run();//输出结果为jogging,虽然b对象引用父类,但是由于子类改写了run()方法,所以优先使用子类的方法,因为他依旧是子类中的对象
a.eat();//输出结果为eat
//b.eat();此处会报错,因为b引用父类,父类中没有eat()方法,因此b对象不可以使用eat()方法。
//对象能执行的方法主要看对象左边的类型,左边类中没有则不可以使用。除非使用强转。
((Student)b).eat();//类型的强转
//高转低,父类型转子类型可以使用子类的方法,必须有关系同类型才可以进行强转
}
}
//定义一个父类写入一个方法
class Person{
public void run(){
System.out.println("run");
}
}
//定义一个子类继承父类,重写方法并再写入一个自己的方法
class Student extends Person{
@Override
public void run() {
System.out.println("jogging" );
}
public void eat(){
System.out.println("eat");
}
}
注:主要注意点我都放在代码备注了,右一个点需要强调,就是强转类型可以和之前学过的类型转换一起记忆,比如小数转int类型数字,就需要用到强转,道理是类似的,写法也是类似的。(如:double a = 3.14;System.out.println((int)a);)
关键字:instanceof
instanceof关键词可以判断一个对象是什么类型,由此可以判断两个类之间有没有继承关系。下面来使用代码看看有哪些注意事项:
public class Demo {
public static void main(String[] args) {
//使用instanceof关键字示例
Object A = new Student();
System.out.println(A instanceof Student);//true
System.out.println(A instanceof Person);//true
System.out.println(A instanceof Object);//true
System.out.println(A instanceof Teacher);//false
System.out.println(A instanceof String);//false
//通过结果可以看出,A这个对象属于Student、Person、Object类,而不属于String类。
// 由此可见Student、Person、Object之间是存在继承关系的
Person B = new Student();
System.out.println(B instanceof Student);//true
System.out.println(B instanceof Person);//true
System.out.println(B instanceof Object);//true
System.out.println(A instanceof Teacher);//false
//System.out.println(B instanceof String);//此处报错
// 因为String和Person都属于Object低一级的子类,属于同级别类所以无法比较。
//大小关系:Object > 父类 = String >子类 = 其他子类
}
}
//写一个父类两个子类
class Person{
public void run(){
System.out.println("run");
}
}
class Student extends Person{
public void go(){
System.out.println("go");
}
}
class Teacher extends Person{
}
由代码我们可以看得出来,其实instanceof就是在判断我们一个对象属于哪个类,一个对象一定属于本类以及父类和父类的父类,一定不属于本类中其他的对象,因此比较时,同级无法进行比较,会报错,能比较的结果只有true或者false,true则说明两个类存在父子关系,false则说明两个类无关。由此也引出了类的“高低级别”,因此,就会存在可能父类,子类相互调用方法的情况。而引用类型与基本类型一样,如果存在高低关系,则会存在需要类型转换的情况。
类型转换规则与进本类型转换规则类似,低转高可以自动转换,而高转低则需要强制转换。比如:
public class Demo {
public static void main(String[] args) {
//首先我们先实例化一个对象但是指向父类,但是我想要使用子类的方法没办法直接使用
// 高------------------->低
Person student = new Student();
//student.go();不可以调用
//使用子类方法,则需要父类转换为子类,使用强转,然后通过强转后的对象来调用子类的方法
Student student01 = (Student)student;
student01.go();//可以成功调用
//也可以写成下面这个样子
((Student)student).go();
//子类转父类则会自动转换,但是可能会丢失自己的方法
Student student2 = new Student();
student2.go();//调用成功
Person person01 = student2;
//person01.go();//调用go()方法失败
}
}
//写一个父类一个子类
class Person{
public void run(){
System.out.println("run");
}
}
class Student extends Person{
public void go(){
System.out.println("go");
}
}
学本章的意义在于方便方法的调用,减少重复代码,使代码更简洁。
static关键字详解
-
静态变量与非静态变量
private static int age;//静态变量 public double scaore;//非静态变量
-
静态方法与非静态方法
//静态方法 public static void run(){ //go();不可以直接调用非静态方法 } //非静态方法 public void go(){ run();//可直接调用静态方法 }
注:非静态字段不可以直接通过类调用,需要实例化对象后通过对象来调用。静态方法可以直接调用,也可以通过对象调用。
-
匿名代码块与静态代码块
匿名代码快通常用来赋初始值,静态代码块是与类一起加载的,所以即使放的靠后,也会先执行静态代码块,并且永久只执行一次。
匿名代码块、静态代码块、构造方法的优先级由高到低为:静态代码块–>匿名代码块–>构造方法
public class Demo { { //匿名代码块 System.out.println("匿名代码块执行"); } static{ //静态代码快 System.out.println("静态代码块执行"); } public Demo() { //构造方法 System.out.println("构造方法执行"); } public static void main(String[] args) { Demo demo = new Demo(); //代码执行结果输出:静态代码块执行、匿名代码块执行、构造方法执行 } }
注:代码块:由中括号包起来的代码被称为代码块,静态代码块则由static修饰。
-
静态导入包
import static java.lang.Math.random; public class Demo { public static void main(String[] args) { System.out.println(random()); } }
静态导入包,可以直接使用包中的方法。
抽象类
通过abstract修饰的类,被称为抽象类,而抽象类无法被实例化(就是不能被new出来对象),可以说抽象类仅仅是一种约束,抽象类中可以有抽象方法也可以有非抽象方法。其存在意义是提高开发效率。
抽象方法:由abstract修饰的方法,只有抽象名,没有方法实现,需要子类利用方法重写去代替实现。
public class Demo {
public static void main(String[] args) {
A.say();//可以正常使用
}
}
abstract class Action{
public abstract void dosomething();
public static void say(){
System.out.println("Hello");
}
}
class A extends Action{
@Override
public void dosomething() {
}
}
接口
普通类:只有具体实现。
抽象类:可以实现具体实现也可以做规范。
接口:只有规范,无法自己写方法。约束和实现分离。
接口就是规范,定义一组规则,体现了现实世界中:如果你是。。。则必须。。。的一种思想。本质是一种契约,约定好了大家都要遵守。面向对象的精髓,是对对象的抽象,最能体现这一点的就是接口。
声明接口的关键字是interface。实现接口则是通过类来实现。
public class Demo implements UserService,Time{
@Override
public void a(String name) {
}
@Override
public void b(String name) {
}
@Override
public void teimer() {
}
}
interface UserService{
void a(String name);
void b(String name);
int age = 99;
}
interface Time{
void teimer();
}
首先我们先在下面写两个接口,因为一个程序中只能有一个public所以接口没有用public修饰。下面是注意点:
- 接口关键字使用interface
- 接口中所有方法都默认为public abstract修饰,因此我们不需要写。
- 在接口中定义属性的话,默认由public static final修饰,就会变为常量,因此我们较少使用接口定义属性。
- 我们可以用implements关键字继承接口,但是必须要将接口中所有的方法重写。
- 接口可以多继承,如上述代码,继承了两个接口,也因此我们可以利用接口实现伪多继承,只要重写继承的所有接口的所有方法。
呼总算总结完啦不过感觉有些地方的细节自己可能理解不到位,或者有些细节自己根本没注意,望看过的小伙伴批评指正呀~
加油呐寄几!!!下一部分:内部类与常用类