Java的第一遍学习笔记 面向对象编程

       运用数据抽象的思想编写代码(定义和使用数据类型,将数据类型的值封装在对象中)的方式称为面向对象编程。

        数据类型指的是一组值和一组对值的操作的集合。对象是能够存储任意该数据类型的值的实体,或数据类型的实例。

IDEA安装

    安装链接:JetBrains: Essential tools for software developers and teams

 根据系统下载安装包,修改安装路径,然后什么也不用选,安装好就可以了。

更改字体

   FILE——Settings——Editor——font——23

新建项目

1. 为了在合适的文件夹下新建文件,我们首先要找到文件夹(以JavaProject为例)

可以在Module JDK处找到我们JDK的安装目录(找到根目录即可,不需要bin)。

 

   注意:Module name与root要保持一致,如果文件夹里没有JavaProject文件,那么就会根据路径创造一个。 

然后就是在JavaProject中创建一个项目文件夹,我们假设为Hello。 

同样的,name与root保持一致,否则会出现错误。

   在Hello中的src文件夹里创造Java Class(注意那个Hello.iml不能删除,否则src会失效)。然后起一个名字,在里面书写代码即可。

     在Bulid中查看代码运行结果(或者直接点箭头)。

修改菜单字体

把"Use custom font"选上,调整字体即可。

修改主题

 

修改编码

快捷键

1. 删除当前行,默认是ctrl + Y,可以自己配置为 ctrl +d。

 

 2. 在下一行复制当前行,默认ctrl+d,自己可以配置成 ctrl + alt + 向下箭头(似乎笔记本不行)。

3. 补全代码:alt + /

4. 添加注释:ctrl + /

5. 导入改行需要的类(比如Scanner):先配置auto import,然后使用 alt + enter(默认)。

把两个对号都选上即可。

6. 快速格式化代码 ctrl + alt + L

7. 快速运行程序:默认为 shift + F10,自己定义: alt + R,要注意一点,新写完的程序第一次运行需要手动操作,因为没有这个程序运行的记录,手动运行一次后就可以使用快捷键了。

8. 生成构造器:alt + insert  --> constructor --> alt多选(选中的元素将成为参数)

9. 查看一个类的层级关系:ctrl + H

10. 将鼠标把一个方法名拉住,输入 ctrl + B,可以定位到该方法定义的位置。

11. 通过在new后面加 .var 可以自动分配变量名。

12. 生成封装的 Set 和 Get 函数:alt + Insert ---> Getter and Setter ---> 按住 ctrl 多选。

13. 去掉构造器的提示信息: 把"Show parameter hins for"去掉就好了。

14. 生成for循环:XX.for

15. 每次创建类都生成版权信息:在Includes里书写

模板 

    file -> settings -> editor -> Live templates -> Java:查看有哪些模板快捷键,也可以自己增加模板。常用的比如 main,sout,fori 等。

 用右上角的加号添加自己的模板:

  在左下角的 define 中,添加作用域。

生成输出语句:

生成for循环: 

生成main方法:

包的作用

1. 区分相同名字的类,可以在不同的包中定义相同名字的类。

2. 当类很多时,可以很好的管理类(类似Java API文档)。

3. 控制访问范围。

基本语法

package com.hspedu;  其中package为关键字,表示引入包。com.hespedu表示包名。

本质分析

包实际上就是创建不同的文件夹来保存类文件。

 创建一个包:

 

 com.xiaoming,其中这个 '.' 表示分级,com为一级目录,xiaoming为二级目录。

举例:

import com.shang.Person;
public class HelloWorld {
    public static void main(String[] args) {
          Person a = new Person();   //导入的可以直接使用
          com.xiaoming.Person b = new com.xiaoming.Person();  //没有导入就要写全名字
          a.showName();
          b.showName();
    }
}

去除 import后自动隐藏

 把 "Optimize imports on the fly" 取消即可。

命名

命名规则:

只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字。

命名规范:

com.公司名.项目名.业务模块名,全部用小写字母。

com.sina.crm.uese; //用户模块
com.sina.crm.order; //订单模块
com.sina.crm.utils; //工具类

常用包

包的使用细节  

   1. package语句的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。

   2. import指令放在package的下面,在类定义前面,可以有多句并且没有顺序要求。我们用 import 引入包,主要是为了使用包中的类。不建议使用 * 引入全部的类。

访问修饰符

访问范围

注意子类说的是不同包里的子类,同一个包里的子类是可以访问父类的默认属性的。 

注意事项

1. 修饰符可以用来修饰类中的 属性,成员方法以及类。成员方法与属性都符合上图的范围。

2. 只有默认和public才能修饰类默认类只有在同一个包中才能调用,在不同包中,即使使用了import导入该类,也不能用而public修饰的类不仅在本包中可以调用,也可以在不同包中进行调用,但是需要import导入类

封装

简介

    封装就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(方法),才能对数据进行操作。

好处

   1. 隐藏实现细节,使用者只需要调用即可。类似的,电视机也是一种封装,用户想要开机等操作,只需要在遥控器上按一下就可以了,不用知道电视机内部进行的复杂操作

2. 可以对要进行赋值的数据进行检验,保证安全合理。

int age = 12000; //这就是没有封装的坏处,没有对赋值的数据进行检验

封装步骤

1. 将属性进行私有化 private,这样就不能直接修改属性了。

2. 提供一个公共的(public) set方法,用于检验数据并给属性赋值。

public void setXxx(类型 参数名){
    //加入数据验证的业务逻辑
    属性 = 参数名;
}

3. 提供一个公共的 get方法,用于获取属性的值。

public 数据类型 getXxx(){
    return Xxx;
}

封装与构造器

    为了防止调用构造器绕过set函数,我们可以把set函数写在构造器中。

public Person(String name,int age,double salary){
    this.setName(name);
    this.setAge(age);
    this.setSalary(salary);
}   //在构造器中写入封装方法
 

继承

基本介绍

    继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可

示意图

基本语法

class 子类 extends 父类{}

1. 子类会自动拥有父类定义的属性和方法(当然受访问修饰符的限制)。

2. 父类又叫超类,基类。

3. 子类又叫派生类。

4. 假设A继承B,B继承C,那么C也算A的父类。

注意事项

1. 子类继承了父类所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

2. 子类必须调用父类的构造器,完成父类的初始化。

3. 当创造子类对象时,不管使用子类哪个构造器,默认情况下都会去调用父类的无参构造器(相当于有一个默认语句 super())

public class Pupil extends Person{
     double score;
     Pupil(){
         System.out.println("调用pupil构造器");
     }

    public static void main(String[] args) {
        Pupil pupil = new Pupil();   // 调用子类Pupil的构造器
    }
}

 发现首先调用的是父类的构造器,然后再调用子类的构造器。

4. 如果父类没有提供无参构造器,则必须在子类的构造器中用super指定使用父类的某个构造器完成对父类的初始化工作,否则编译不会通过

    Person(int age,String name){  //父类的构造函数,有参数
        this.age = age;
        this.name = name;
    }


public class Pupil extends Person{  //继承的子类
     double score;
     Pupil(){
         System.out.println("调用pupil构造器");
     }  

    public static void main(String[] args) {
        Pupil pupil = new Pupil();   //直接报错,因为没有调用父类的构造器
    } 
}    

     Pupil(){
         super(10,"wang");
         System.out.println("调用pupil构造器");
     }   //正确,用super调用父类构造器

5. 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)。

6. super在使用时,必须放在构造器第一行( super() 调用构造器这种方法只能在构造器中使用)

     Pupil(){
         System.out.println("调用pupil构造器");
         super(10,"wang");   //报错,super调用构造器必须放在第一行
     }

7. super() 和 this() 都是调用构造器的方法,都必须放在构造器的第一行,因此这两个方法不能共存在一个构造器

8. Java中所有类都是Object类的子类(可以用ctrl + H查看)。

9. 调用子类构造器时,会向上一直调用父类的构造器,一直追溯到Object类。

    public static void main(String[] args) {
        Pupil pupil = new Pupil();  //从上往下开始调用构造器
    }

10. 子类最多只能继承一个父类(直接继承,删除了C++的多重继承),Java中是但继承机制。那么任何让A类同时继承B类和C类? A 继承 B,B 继承 C。 

11. 不能滥用继承,子类和父类之间必须满足 “is - a”(也就是必须有共同点)的逻辑关系。

Music extends Person //不合理
Cat extends Animal //合理

本质分析

分析当子类继承父类,创建子类对象时,内存中到底发生了什么。

Son son = new Son();
System.out.println(son.name);  //本类中有该信息,返回 "小头儿子"
System.out.println(son.age);   //父类中有该信息,返回 39
System.out.println(son.hobby); //父类的父类有该信息,返回 "旅游"

要按照查找关系来返回信息:

     1. 首先看子类是否有该属性,如果子类有这个属性,则返回信息(因为是在本类中,因此绝对可以访问,即使是private)

     2. 如果子类没有这个属性,就看父类有没有这个属性,如果父类有该属性,并且可以访问,就返回信息,如果该信息不可访问(private),那么就直接报错(can not access),不会再向上查找了

     3. 如果父类没有该信息,那么就按照 (2) 的规则,继续找上级,直到Object,如果到最顶端也没有查找到,则提示该方法不存在。

super关键字

基本介绍

super代表父类的引用,用于访问父类的属性、方法、构造器。

基本语法

1. 访问父类的属性,但不能访问父类的private属性:super.属性名;

2. 访问父类的方法,但不能访问父类的private方法:super.方法名(参数列表);

3. 访问父类的构造器(前面介绍过):super(参数列表); 只能放在构造器的第一句,只能出现一句

细节/好处

1. super调用父类构造器,可以使分工明确。父类属性由父类初始化,子类的属性由子类初始化。 

2. 当子类中由和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super如果没有重名,使用super、this、直接访问是一样的效果(只不过this和直接访问是从本类开始找,super是从父类开始找)。 调用类的属性的规则在上面已经说明过了,调用类的方法的规则与其相同,只不过把属性换成了方法。

子类确实可以写一个与父类完全相同的方法,虽然左边会有隐患提示,但是编译没有问题。

  下面对于子类调用父类的方法举一个例子(属性因为很简单就不举例了)

  定义一个父类Person:

public class Person {  //有四个不同修饰符的方法
    public void showName1(){
        System.out.println("调用Person的public方法");
    }
    void showName2(){
        System.out.println("调用Person的无修饰符方法");
    }
    protected void showName3(){
        System.out.println("调用Person的protected方法");
    }
    private void showName4(){
        System.out.println("调用Person的private方法");
    }
}

  定义一个子类Student,先测试在main方法中调用方法(必须用声明的变量调用):

public class Student extends Person{
    public static void main(String[] args) {
        Student student = new Student();
        student.showName1();   //成功调用
        student.showName2();   //成功调用  
        student.showName3();   //成功调用
        student.showName4();   //报错,不能调用父类的private方法
    }
}

    然后在子类的方法中继续测试,由于 this 和 super 都不能在主方法中调用,因此声明一个test方法,在该方法中测试this和super。

    public void test(){
        this.showName1();    //成功调用
        this.showName2();    //成功调用
        this.showName3();    //成功调用
        this.showName4();    //报错,不能调用private方法
        super.showName1();   //成功调用
        super.showName2();   //成功调用 
        super.showName3();   //成功调用
        super.showName4();   //报错,不能调用private方法
    }

  当然,该方法还是需要用main方法中声明的变量调用。

    同样的,最重要的一点——在子类调用一个方法时,如果子类没有该方法,会一直向上寻找如果找到Object类还没有找到,就会报错(super直接忽略本类)如果在中间找到了一个private的方法,即使上面还有该方法,也不会再寻找了,而是直接报错

 举一个例子:

private void showName1(){  //在Student类中加入一个与Person类同名但不同修饰符的函数
    System.out.println("调用Student的private函数");
}
public class Pupil extends Student{  //继承Student
    public static void main(String[] args) {
        Pupil pupil = new Pupil();
        pupil.showName1();  //直接报错,寻找到Student就停了,而不是继续向Person寻找
    }
}

 3. 如果多个父类中有相同的成员,那么super的访问根据就近原则。

4. super和this的区别

方法重写/覆盖(override)

基本介绍

    方法覆盖就是子类有一个方法和父类的某个方法 名称、返回类型、参数一样,这样我们就说子类的这个方法覆盖了父类的那个方法。

使用细节

1. 子类方法的 形参列表,方法名称 要和父类的完全一样。

2. 子类方法的 返回类型 可以和父类方法的返回类型一样,或者是父类返回类型的子类

     public Object showName(){
         System.out.println("返回值为Object的父类方法");
         return "OK";
     }  //父类的方法

    public String showName(){
          System.out.println("返回值为String的子类方法");
          return "OK";
    }  //子类的方法

    public static void main(String[] args) {
        Student student = new Student();
        student.showName(); //调用
    }

    public String showName(){
         System.out.println("返回值为Object的父类方法");
         return "OK";
     }

    public Object showName(){
          System.out.println("返回值为String的子类方法");
          return "OK";
    }  //报错

3. 子类方法不能缩小父类方法的访问权限(但扩大可以)public > protected > 默认 > private

     public void showName(){
         System.out.println("修饰符为public的父类方法");
     } 

    protected void showName(){
          System.out.println("修饰符为protected的子类方法");
    }  //报错,减小了范围

 4. 重载和重写的比较

多态

       对于解决某些问题,比如 假设需要创建一个方法给宠物喂食,那么用传统方法来说,给猫喂鱼,狗喂骨头等等,需要每一个都写一个方法这样导致代码的复用性不高,而且不利于代码的维护,因此需要多态

       多态指 方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

方法的多态

重写和重载就体现了多态。

    // 方法重载体现了多态——在同类中方法有多种形态
    public int sum(int num1,int num2){
        return num1 + num2;
    }

    public int sum(int num1,int num2,int num3){
        return num1+num2+num3;
    }

    // 方法重写体现了多态——在不同类中方法也有多种形态

    public int sum(int num1,int num2)
    {
        return num1 + num2;
    } //父类方法

    public int sum(int num1,int num2)
    {
        return num1 + num2;
    } //子类方法
     

对象的多态

1. 一个对象的编译类型和运行类型可以不一致编译类型看定义时 =号 的左边,运行类型看 =号 的右边。

Animal animal = new Dog();  // animal 编译类型是Animal,运行类型为 Dog

2. 编译类型在定义对象时就确定了,不能改变。

3. 运行类型是可以变化的。

animal = new Cat(); // animal的运行类型变成了Cat,编译类型仍然是Animal

4. 对象多态的前提:两个对象(类)之间存在继承关系。 

因为对象的多态,很多操作就简洁许多了。以上面的喂食例子,我们正常写的话应该是这样:

public void feed(Cat cat, Fish fish){
    ...;  //输出喂食信息
}

public void feed(Dog dog, Fish fish){
    ...;  //输出喂食信息
}
...... //所有动物类与食物类都要写一遍,写在Master类中

而如果用对象的多态方法来写,则是下面这样:

public void feed(Animal animal, Food food){
    ...;
} // 只需在Master类中写一个即可

当调用时,仍然是传入Animal类和Food类的子类,这时就用到了多态机制。

5. 多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。

向上转型

1. 本质:父类的引用指向了子类的对象。

2. 语法:父类类型  引用名  =  new  子类类型();

3. 特点:

1)编译类型看左边,运行类型看右边。

2)可以调用父类中的所有成员(当然前提是遵守访问权限)。

3)不能调用子类中特有成员(如果子类重写了父类的方法,或者子类有和父类一样的属性,那么由于动态绑定机制,调用父类的成员和方法会先从子类找,因此子类重写的方法和相同的属性是可以被调用的),因为在编译阶段,能调用哪些成员,是由编译类型来决定的

Animal animal = new Cat();
animal.catchMouse();  //错误,因为catchMouse是子类的特有方法,向上转型不能调用

4)最终运行效果看子类的具体实现,即调用方法时,从子类(运行类型)开始查找方法,规则与前面的方法调用规则一致。

向下转型

1. 语法:子类类型  引用名  =  (子类类型) 父类引用。

Cat cat = (Cat)animal; 

  这里注意一点,向下转型后,cat的编译类型和运行类型都是Cat,cat和animal都指向Cat的对象(注意animal没有消失)。

2. 只能强转父类的引用,不能强转父类的对象。父类的对象是创建在堆区中的,这个是不能改变的(因为已经创建了),但是可以改变指向该对象的指针,向下转型就是让它指向一个子类的对象

3. 要求父类的引用必须指向的是当前目标类型的对象。

Animal animal = new Cat();
Dog dog = (Dog)animal;  //报错
Cat cat = (Cat)animal;  //正确

    就比如这个例子,父类的引用本来就指向Cat类的对象,因此向下转型只能使用Cat按照错误语句的理解,让一只狗指向猫对象,那肯定是错误的

4. 当向下转型后,可以调用子类类型中所有的成员(当然要符合访问范围)。

5. 下面是错误的向下转型写法,不能让没有引用的对象进行向下转型。

Cat cat = (Cat)(new Animal());
Cat cat = (Cat)new Animal();
// 这两种写法都是错误的,不能让没有引用的对象进行向下转型
// 编译器报告 cannot be cast to 错误

Java的动态绑定机制

1. 当调用对象方法的时候(不管是直接调用还是方法里调用),该方法会和该对象的内存地址(运行类型)绑定,根据运行类型进行调用,如果没有该方法,就启用继承机制。

2. 当在方法中使用对象属性时,没有动态绑定机制,调用哪个类的方法,就用哪个类的属性,如果没有就启用继承机制。

用一个例子解释清楚

public class Computer {
    public int i = 10;
    public int sum(){
        return getI() + 10; //注意这里进行了方法的调用
    }
    public int sum1(){
        return i + 10;
    }
    public int getI(){
        return i;
    }
}

public class NotePad extends Computer{
    public int i = 200;
    public int sum(){
        return i + 100;
    }
    public int getI(){
        return i;
    }
    public int sum1(){
        return i + 200;
    }
}

  父类和子类的成员完全相同,只不过内容有变化,注意sum不是子类的特有方法,因此向上转型可以调用

    public static void main(String[] args) {
        Computer a = new NotePad();    //向上转型,运行类型为 NotePad
        System.out.println(a.sum());   //输出 300,用的子类的方法和子类的i
        System.out.println(a.sum1());  //输出 400
        System.out.println(a.i);       //输出 10,为父类的i
    }

  可以发现,由于运行类型是NotePad,当调用对象方法的时候,该方法会和该对象的内存地址(运行类型)绑定,因此调用的方法直接从子类开始找,由于当在方法中使用对象属性时,没有动态绑定机制,调用哪个类的方法,就用哪个类的属性。子类的sum方法用到了 i,根据这条定理,应该用子类的 i。但是注意一点,如果直接调用a的i,由于属性没有重写,调用属性要看编译类型,因此使用父类的 i。

  然后把子类的sum方法和sum1方法全部删除。

public class NotePad extends Computer{
    public int i = 200;
    public int getI(){
        return i;
    }
}
public class Test {
    public static void main(String[] args) {
        Computer a = new NotePad();
        System.out.println(a.sum());   //输出210,父类的sum方法调用的是子类的getI方法
        System.out.println(a.sum1());  //输出20,用父类的i
    }
}

       当把子类的sum方法和sum1方法都删除后,调用这些方法就要启动继承机制了,在父类中寻找这两个方法。注意父类的sum方法调用了一个 getI 函数在方法里调用的方法也和运行类型绑定,因此调用的是子类的getI方法,返回的是子类的i,因此结果为210,而父类的sum1方法调用了属性 i,由于属性没有动态绑定机制,因此调用自己的 i,结果为20。

     注:属性没有重写之说,调用哪个属性要看编译类型

public class Computer {
    String name = "我是一台电脑";  //在父类定义一个属性
}

public class PC extends Computer{
    String name = "我是一台PC";  //在子类定义一个相同名字的属性
}

public class Test {
    public static void main(String[] args) {
        Computer c1 = new Computer();  //编译类型为Computer
        Computer c2 = new PC();        //向下转型,编译类型为Computer
        PC c3 = new PC();              //编译类型为PC
        PC c4 = (PC)new Computer();    //向上转型,编译类型为PC
        System.out.println(c1.name);
        System.out.println(c2.name);
        System.out.println(c3.name);
        System.out.println(c4.name);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值