javaSE基础-OOP

面向对象

面向过程(Procedure Oriented Programming):强调的是功能行为,以函数为最小单位,考虑怎么做

面向对象(Object Oriented Programming):强调具备功能的对象,以类/对象为最小单位,考虑谁来做

类及类的成员

1、两个要素

类(Calss)和对象(Object)是面向对象的核心概念

类:是对一类事物的描述,是抽象的、概念上的定义

对象:是实际存在的该类实物的每个个体,也称为实例(Instance)

对象是由类派生出来的

类的设计就是设计类的成员

2、类的成员

属性 = 成员变量 = Field = 域、字段

方法 = 成员方法 = method = 函数

创建类的对象 = 类的实例化 = 实例化类

示例:类成员的定义

class Person{
    //属性
    String name;
    boolean isMarried;
    //构造器
    public Person(){}
    public Person(String name, boolean im){
        this.name = name;
        this.isMarried = im;
    }
    //方法,或函数
    public void walk(){
        System.out.println("走路。。。")
    }
    publiv String display(){
        return "姓名:" + name + ",isMarried: " + isMarried;
    }
    //代码块
    {
        name = "lisi";
        age = 18;
        isMarried = false;
    }
    //内部类
    class pet{
        String name;
        float weight;
    }
}

示例:类和对象的使用

1、创建类,设计类的成员

2、创建类的对象

3、通过“对象.属性” 或 "对象.方法" 调用对象的结构

//测试类
public class PersonTest {
    public static void main(String[] args) {
        //2、创建类的对象
        Person p = new Person();
        //3、属性的调用:"对象.属性"
        p.name = "Tim";
        p.isMale = false;
        //方法的调用:"对象.方法"
        p.eat(); // eat...
        String run = p.run();
        System.out.println(run); // running...
        p.speak("chinese"); // person saying chinese
    }
}

//1、实体类
class Person{
    //属性
    String name;
    int age = 18;
    boolean isMale;

    public Person(){
    }
    //方法
    public void eat(){
        System.out.println("eat...");
    }
    public String run(){
        return "running...";
    }
    public void speak(String language){
        System.out.println("person saying " + language);
    }
}

示例:创建多个类对象 - 每个对象拥有一套独立的类属性(除static修饰的)

public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person();
        //属性的调用:"对象.属性"
        p.name = "Tim";
        p.isMale = false;
		System.out.println(p.name); //Tim
        System.out.println(p.age); //18

        //************多个类对象的使用**************
        Person p2 = new Person();
        System.out.println(p2.name); //null
        System.out.println(p2.age); //18
		
        //将p变量的地址值赋给p3,则 p 和 p3 指向堆内存中的同一个对象实体
        Person p3 = p;
        System.out.println(p3.name); //Tim

        p3.age = 10;
        System.out.println(p.age); //10
        System.out.println(p3.age); //10

    }
}

注:修改一个对象的属性a,则不影响另外一个对象属性a

Person类对象的内存解析

对象内存解析

JVM内存结构示意图

编译完源程序生成字节码文件,然后JVM的类加载器和解析器将生成的字节码文件解析运行(即将字节码文件对应的类加载到内存)。

JVM内存结构解析

说明:

虚拟机栈:即俗称栈结构。局部变量存储在栈结构中

堆:将new的结构(如:数组,对象)加载到堆空间中(对象的属性(非static)加载到堆空间)。

方法区:存储类的加载信息,常量池、静态域

属性

1、属性:定义在类的一对{}内

2、属性(成员变量) VS 局部变量

相同点:

  • 相同的变量声明格式:数据类型 变量名 = 变量值;
  • 先声明,后使用
  • 变量都有其对应的作用域

不同点:

成员变量 局部变量
定义的位置 类中直接定义 在代码块中定义(方法内、方法形参、代码块内、构造器形参、构造器内部的变量)
可否使用访问权限修饰符
作用域 当前类中的所有方法 当前代码块
初始化情况 无需初始化,有默认值 声明之后必须经过初始化才能使用
在内存加载位置 堆空间(非static) 栈空间

注:常用权限修饰符:private、public、缺省、protected (体现封装性)

属性默认的初始化值:

整型(byte、short、int、long):0

浮点型(float、double):0.0

字符型(char):0(或'\u0000')

布尔型(boolean):false

引用数据类型(类、数组、接口):null

3、变量的分类

按数据类型

数据类型分类

注:引用类型的变量存储的值:null 或 地址值

按类中声明的位置

类中声明的位置

4、对属性赋值的位置

①默认初始化

②显式初始化 / ③在代码块中赋值

④构造器中初始化

⑤有了对象以后,可以通过“对象.属性” 或 “对象.方法”的方式

执行顺序:① - ② / ③ - ④ - ⑤

方法

方法基础

1、方法:描述类具有的功能

2、分类:按是否有形参及返回值

无返回值 有返回值
无形参 void 方法名() {} 返回值类型 方法名() {}
有形参 void 方法名(形参列表) {} 返回值类型 方法名(形参列表) {}

3、方法的声明

格式:权限修饰符 返回值类型 方法名(形参列表){ 方法体 }

说明:

权限修饰符:规定的4种修饰符-public、protected、缺省、private

返回值类型:有返回值 VS 无返回值

  • 有返回值:必须在声明方法时,指明返回值的类型,并且在方法体内使用return关键字返回指定的类型变量或常量
  • 无返回值:声明方法时,使用void

方法名:属于标识符,见名知意

形参列表:声明0或1或多个形参,多个使用逗号分隔

return关键字的使用:

使用范围:在方法体内

作用:结束方法 / 有返回值类型的方法,使用return可以返回所需的数据

注:return后面的语句不可执行

4、方法中的使用:可以调用当前类的属性或方法(特殊的:方法A又调用方法A,即递归方法),不可以再定义新的方法

示例:方法的使用

class PrintStar{
    public static void main(String[] args){
        PrintStar ps = new PrintStar();
        int sumStar = ps.method(3, 4);
        System.out.println("*的总数为:" + sumStar);
    }
    
    public int method(int m, int n){
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                System.out.print("* ");
            }
            System.out.println();
        }
        return m * n;
    }
}

5、匿名对象的使用

匿名对象:创建对象,没有显式的赋给一个变量名

特点:匿名对象只能调用一次

示例

public class InstanceTest {
    public static void main(String[] args) {
        PhoneMall p = new PhoneMall();
        //匿名对象的使用
        p.show(new Phone());
    }
}

class PhoneMall{

    public void show(Phone phone){
        phone.sendEmail();
        phone.playGame();
    }
}

class Phone{
    public void sendEmail(){
        System.out.println("phone send Email...");
    }
    public void playGame(){
        System.out.println("phone play game...");
    }
}

方法特别应用

1、方法的重载(Overload)

重载:在同一个类中,允许存在一个以上同名的方法,只要他们的参数个数或者参数类型不同即可

特点:与返回值无关,只看参数列表,且参数列表必须不同。调用时,根据方法参数列表的不同来区分。

示例

public class Arrays {
    ...
    public static void sort(int[] a) {}
    public static void sort(int[] a, int fromIndex, int toIndex) {}
    
    public static int binarySearch(char[] a, char key) {}
    public static int binarySearch(Object[] a, Object key) {}
}

2、方法的可变形参

可变形参

格式:数据类型...参数

调用可变个数参数的方法时,传入的参数个数可以0个,1个、2个、多个。

可变个数形参在方法的形参中,必须声明在参数列表的末尾,且最多一个

public void show(String...strs, int i){} //报错

3、方法参数的值传递机制

针对变量值传递

如果变量是基本数据类型,此时赋值的是变量所保存的数据值

如果变量是引用类型数据,此时赋值的是变量所保存的数据的地址值

示例

public class ValueTransferTest {
    public static void main(String[] args) {
        System.out.println("*********基本数据类型**********");
        int a = 1;
        int b = a;
        System.out.println("a = " + a + ", b = " + b);//a = 1, b = 1

        b = 2;
        System.out.println("a = " + a + ", b = " + b);//a = 1, b = 2

        System.out.println("*********引用数据类型**********");

        Order obj = new Order();
        obj.orderId = 1001;
        
        Order obj2 = obj; //引用类型,赋值时,复制了地址值,指向堆内存中同一个对象
        System.out.println("obj.orderId = " + obj.orderId + ", obj2.orderId = " + obj2.orderId); //obj.orderId = 1001, obj2.orderId = 1001

        obj2.orderId = 1002;
        System.out.println("obj.orderId = " + obj.orderId + ", obj2.orderId = " + obj2.orderId); //obj.orderId = 1002, obj2.orderId = 1002
    }
}

class Order{
    int orderId;
}

形参:方法定义时,声明在小括号内的参数

实参:方法调用时,实际传递给形参的数据

针对形参值传递

如果参数是基本数据类型,此时实参赋给形参真实存储的数据值

如果参数是引用类型数据,此时实参赋给形参真实存储的数据地址值

示例:引用数据作为方法形参传递

class Data{
    int n;
    int m;
}
public class ValueTransferTest2 {
    public static void main(String[] args) {
        Data data = new Data();
        data.n = 10;
        data.m = 20;
        System.out.println("data.n =" + data.n + ", data.m =" + data.m);//data.n =10, data.m =20

        //交换对象值
        //int temp = data.n;
        //data.n = data.m;
        //data.m = temp;

        swap(data); //对象引用 作为实参传递
        System.out.println("data.n =" + data.n + ", data.m =" + data.m);//data.n =20, data.m =10

    }
    public static void swap(Data data){
        int temp = data.n;
        data.n = data.m;
        data.m = temp;
    }
}

基本数据类型传递内存分析示意图

基本数据类型传递内存解析

引用数据类型传递内存分析示意图

引用数据类型内存分析

案例:分别打印1~5半径的圆的面积

//Circle.java
public class Circle {
    double radius;

    public double findArea(){
        return Math.PI*radius*radius;
    }
}

//CircleTest.java
public class CircleTest {
    public static void main(String[] args) {
        Circle c = new Circle();
        printAreas(c, 5);
        System.out.println("now radius is " + c.radius);
    }

    public static void printAreas(Circle c, int time){
        System.out.print("Radius" + "\t\t" + "Areas\n");
        int i = 1;
        for (; i <= time; i++) {
            c.radius = i;
            System.out.println(c.radius + "\t\t" + c.findArea());
        }
        c.radius = i;
    }
}

//结果哦
Radius		Areas
1.0		3.141592653589793
2.0		12.566370614359172
3.0		28.274333882308138
4.0		50.26548245743669
5.0		78.53981633974483
now radius is 6.0

4、递归(recusion)方法

一个方法体内调用自己

方法递归包含一种隐式的循环,它会重复执行某一段代码,这种重复执行无须循环控制

示例

public class RecursionTest {
    public static void main(String[] args) {
        System.out.println(getFactorial(10));//3628800

        System.out.println(getFibonacci(5));// 5

        System.out.println(f(5));//128
    }

    //n! - n的阶乘
    public static int getFactorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return n * getFactorial(n - 1);
        }
    }

    //数列:f(0) = 1,f(1) = 4,f(n+2) = 2*f(n+1) + f(n) ... 要求:n>0,求f(5)的值
    public static int f(int n) {
        if (n == 0) {
            return 1;
        } else if (n == 1) {
            return 4;
        } else {
            return 2 * f(n - 1) + f(n - 2);
        }
    }

    //fibonacci - 1,1,2,3,5,8,13
    public static int getFibonacci(int n) {
        if (n == 1) {
            return 1;
        } else if (n == 2) {
            return 1;
        } else {
            return getFibonacci(n - 1) + getFibonacci(n - 2);
        }
    }
}

构造器(constructor)

1、作用:创建对象,给对象进行初始化

2、如果没有显示的定义类的构造器,则系统默认提供一个空参的构造器

3、格式:权限修饰符 类名(形参列表){}

示例:

class Person{
    private String name;
    private int age;
    
    //无参构造器
    public Person(){
        
    }
    //有参构造器
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    ...
    
}

4、特点

  • 具有与类相同的类名
  • 不声明返回值类型(与声明为void不同)
  • 不能被static、final、synchronized、abstract、native修饰,不能有return返回值

5、一个类中定义的多个构造器,彼此构成重载

6、一旦我们显示的定义了类的构造器,系统不在提供默认的空参构造器

7、一个类中至少有一个构造器

关于JavaBean

是一种Java语言写成的可重用的组件

特点

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get、set方法

URL类图图解

URL类图

代码块

作用:用来初始化类、对象

代码块只能使用static修饰

分类:静态代码块 VS 非静态代码块

静态代码块

  • 代码内可以有输出语句
  • 随着类的加载而执行,且只执行一次
  • 一个类中定义多个静态代码块,按照声明的先后顺序执行,静态代码块优先于代码块执行
  • 静态代码块可以调用静态结构(方法、属性),不能调用非静态结构

作用:初始化类的属性

非静态代码块

  • 内部可以有输出语句
  • 随着对象的创建而执行
  • 每创建一个对象,就执行一次非静态代码块
  • 一个类中定义多个非静态代码块,按照声明的先后顺序执行
  • 非静态代码块可以调用静态结构 或 非静态结构

作用:可以在创建对象时,对对象的属性等进行赋值操作

代码块及构造器执行顺序

父类静态块 -> 子类静态块 -> 父类代码块 -> 父类构造器 -> 子类代码块 -> 子类构造器

示例

class Father{
    static{
        System.out.println("Father static block-1");
    }

    {
        System.out.println("Father block-2");
    }

    public Father(){
        System.out.println("Father() constructor");
    }
}

public class Son extends Father{
    static {
        System.out.println("Son static block-1");
    }

    {
        System.out.println("Son block-2");
    }

    public Son(){
        System.out.println("Son() constructor");
    }

    public static void main(String[] args) {
        System.out.println("************");
        System.out.println("#############");
        new Son();
        System.out.println("**************");
        new Son();
        System.out.println("**************");
        new Father();
    }
}

//结果
Father static block-1
Son static block-1
************
#############
Father block-2
Father() constructor
Son block-2
Son() constructor
**************
Father block-2
Father() constructor
Son block-2
Son() constructor
**************
Father block-2
Father() constructor

内部类

类的内部成员:内部类

java中允许类A 声明在另一个类B中,A就是B的内部类

内部类的分类:成员内部类 VS 局部内部类(方法内、 代码块、 构造器内)

示例:成员内部类

public class InnerClassTest {
    public static void main(String[] args) {
        //创建Dog实例(静态的成员内部类)
        SubPerson.Dog dog = new SubPerson.Dog();
        dog.show();
        //创建Bird实例(非静态的成员内部类)
        SubPerson p = new SubPerson();
        SubPerson.Bird bird = p.new Bird();
        bird.sing();

        System.out.println();
        bird.display("麻雀");
    }
}

class SubPerson {
    String name = "lisi";
    int age;

    public void eat(){
        System.out.println("SubPerson eat()...");
    }

    //成员内部类 - 静态
    static class Dog{
        String name;
        int age;

        public void show(){
            System.out.println("Dog show()...");

        }
    }

    //成员内部类 - 非静态
    class Bird{
        String name = "鹦鹉";
        public Bird(){

        }
        public void sing(){
            System.out.println("Bird sing()...");
            //eat();
            SubPerson.this.eat();//调用外部类非静态方法
            System.out.println(age);
        }

        public void display(String name){
            System.out.println("name: " + name);//形参
            System.out.println(this.name);//内部类的属性
            System.out.println(SubPerson.this.name);//外部类的属性
        }
    }

    public void method(){
        //局部内部类
        class AA{

        }
    }

    {
        class BB{

        }
    }

    public SubPerson(){
        class CC{

        }
    }
}

示例:局部内部类的使用

public class InnerClassTest2 {

    //返回一个实现了Comparable接口的类的对象
    public Comparable getComparable(){
        //创建一个实现了Comparable接口的类:局部内部类
        //方式一
        //class MyComparable implements Comparable{
        //    @Override
        //    public int compareTo(Object o) {
        //        return 0;
        //    }
        //}
        //return new MyComparable();

        //方式二
        return new Comparable() {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };
    }
}

三大特性

封装性(Encapsulation)

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。

设计类的特点:

高内聚:类的内部数据操作细节自己完成,不允许外部干涉

低耦合:仅对外暴露少量的方法用于使用

1、封装性的体现

体现一:将类的属性私有化(private),同时提供公共的(public)方法来获取(getXxx)和设置(setXxx)属性

class Aminal{
    String name;
    int age;
    private int legs;
    
    //获取
    public int getLegs(){
        return legs;
    }
    
    //设置
    public void setLegs(int l){
        if(l > 0){
            legs = l;
        }else{
            legs = 0;
        }
    }
}

体现二:不对外暴露私有方法

体现三:单例模式(将构造器私有化)

体现四:将类设置为缺省,防止包外调用

2、4种权限修饰符

在类、类的属性以及类的方法前面加上一个修饰符(modifier),来控制访问

修饰符 同一个类中 同一个包中 子类中 全局
private Yes
缺省 Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes

注:修饰类只能使用:缺省、public

示例

public class PersonTest2 {
    public static void main(String[] args) {
        Person2  p = new Person2();
        p.setAge(23);
        System.out.println("person age is " + p.getAge());//person age is 23
    }
}

class Person2{
    private int age;

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        if (age > 0 || age < 130){
            this.age = age;
        }else {
            throw new RuntimeException("传入的数据不满足要求");
        }
    }
}

继承性(Inheritance)

1、继承性的作用

  • 减少代码的冗余,提高了代码的复用性
  • 便于功能的拓展
  • 为之后的多态性的使用,提供了前提

2、格式: class A extends B{}

A:子类、派生类、subclass

B:父类、超类、superclass

注:子类A继承父类B之后,子类A中就可以获取父类B的结构:属性、方法,子类不能直接调用父类private修饰的属性方法

3、继承性的规定

  • 一个父类可以派生多个子类
  • 一个子类只能有一个父类,体现单继承性
  • 子父类是相对的概念

4、所有的java类(除Object)都直接或间接的继承于java.lang.Object类,意味着所有的java类都有该类的功能

多层继承示意图

多层继承

示例

//Person.java
public class Person {
    private String name;
    private int age;
    private Date birthDate;
    ...
    public String getInfo(String str){
        return str;
    }
}

//Student.java
public class Student extends Person{
    private String school;
    ...
}

//Graduate.java
public class Graduate extends Student{
    private String major;
    ...  
    public void register(){}
}

方法的重写(override)

1、重写:在子类中可以根据需要对父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

示例

//Person.java
public class Person {
    private String name;
    private int age;
    private Date birthDate;
    ...
    public String getInfo(String str){
        return str;
    }
}

//Student.java
public class Student extends Person{
    private String school;
    ...
    public String getInfo(String str){ //重写Person的方法
        return "Student " + str;
    }
}

2、重写的规定

//方法声明格式
权限修饰符 返回值类型 方法名(形参列表)throws 异常类型{
    //方法体 - 不同
}

子类重写的方法的方法名和形参列表 与 父类被重写的方法的方法名和形参列表相同

子类重写的方法的权限修饰符不小于父类重写的方法的权限修饰符

子类不能重写父类声明为private的方法

返回值类型:

父类被重写的方法返回类型是void,则子类重写的方法的返回类型只能是void

父类被重写的方法返回值类型是A类,则子类重写的方法的返回值类型可以是A类 或 A类的子类

返回值类型如果是基本数据类型,子类重写方法的返回值类型也必须是相同的

子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常

子父类重写的方法要么都声明非static(可能重写),要么都声明为static(不是重写)

多态性(Polymorphism)

1、表示为:一个事物的多种形态

2、对象的多态:父类的引用指向子类的对象

3、特点:在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法(编译看左边,执行看右边)。

对象的多态性,只适用于方法,不适用于属性(调用属性,编译运行都看左边)

Person p = new Student();
p.walk();
p.study();

4、多态性的使用前提: 类的继承关系 / 有方法的重写

示例:多态性使用

class Animal {
    public void eat() {
        System.out.println("Animal eat...");
    }

    public void shout() {
        System.out.println("Animal shout...");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头。。");
    }

    @Override
    public void shout() {
        System.out.println("汪汪叫。。。");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("吃喵粮");
    }

    @Override
    public void shout() {
        System.out.println("喵喵。。。");
    }
}

public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test = new AnimalTest();
        test.action(new Dog());

        test.action(new Cat());
    }

    public void action(Animal animal) {//此处体现多态性的便捷特点
        animal.eat();
        animal.shout();
    }
}

//结果
狗吃骨头。。
汪汪叫。。。
吃喵粮
喵喵。。。

虚拟方法调用(Virtual Method Invocation)

在多态的条件下,子类重写父类的方法,运行期动态的调用子类的方法,父类的方法就是虚拟方法

虚拟方法调用

向下转型与向上转型

有了对象的多态性以后,内存中实际上加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。向上转型体现多态性。

如何解决调用子类特有的属性和方法?

向下转型,使用强制类型转换符

类型转换

注意:

使用向下转型,可能会出现ClassCastException异常,所有要借助instanceof进行类型判断,返回true,就可以进行向下转型

instanceof关键字

使用强转时,可能会出现ClassCastException的异常,使用instanceof关键字解决向下转型

a instanceof A:判断对象a是否是类A的实例,如果是,返回true。

示例

if(p instanceof Student){
    Student s = (Student)p;
    s.getInfo("student");
}

其它关键字

this

this关键字可以调用:属性、方法、构造器

this表示为:当前对象

对于属性和方法:在类中可以使用“this.属性” 或 “this.方法” 的方式,调用当前对象属性或方法

对于构造器:在类的构造器中,可以显式的使用 “this(形参列表)” 方式,调用本类中指定的其他构造器

示例

public class PersonTest3 {
    public static void main(String[] args) {
        Person3 p = new Person3("lisi", 19);
        System.out.println(p.getName() + " " + p.getAge());
    }
}

class Person3{
    private String name;
    private int age;

    public Person3() {
        System.out.println("info...");
    }

    public Person3(String name){
        this(); //this调用无参构造器
        this.name = name;
    }

    public Person3(String name, int age) {
        this(name);//this调用有参name属性的构造器
        this.age = age;
    }

    public String getName() {
        return this.name;//this可以省略。调用属性
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

//结果
info...
lisi 19

package

包:方便更好的实现项目中类的管理

使用package来声明类或接口所属的包,声明在源文件的首行

包,属于标识符,遵循标识符的命名规则、规范,见名知意

package java.lang;

注:同一个包下,不能定义同一个类或接口名

jdk主要的包

主要包

import

在源文件中显式的使用import结构导入指定包下的类、接口

声明位置在 包 和 类之间

package java.lang;

import java.util.Arrays;

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}

import static:导入类或接口下的静态结构(属性、方法)

import static java.lang.System.*;

out.println("hello");

super

super表示为:父类的

super可以用来调用:属性、方法、构造器

对于属性或方法的调用:

通常使用“super.属性” 或 “super.方法” 的方式,显式的调用父类中声明的属性方法。一般情况下都习惯省略“super.”。特殊情况下,子类重写父类的方法,想在子类调用父类的被重写的方法,必须显式的使用 “super.方法”

对于构造器:

在子类的构造器中显式的使用"super(形参列表)" 的方式,调用父类中声明的指定的构造器

“super(形参列表)”的使用,必须声明在子类构造器的首行。

在类的构造器中,针对“this(形参列表)” 或 "super(形参列表)" 只能二选一,不能同时出现

在子类的构造器的首行,没有显式的声明调用"super(形参列表)",默认就是调用"super()"

示例

//Person.java
public class Person {
    private String name;
    private int age;
    private Date birthDate;
    int id;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }
    ...
    public String getInfo(String str){
        return str;
    }
}

//Student.java
public class Student extends Person{
    private String school;

    int id;

    public Student() {
    }

    public Student(String name, String school) {
        super(name);//super() - 调用父类的构造器
        this.school = school;
    }
    ...
    public void show(){
        System.out.println("this.id = " + this.id + "super.id" + super.id);
        super.getInfo("person getInfo()");//super.方法
    }
}

static

1、static:静态的,修饰方法、属性、代码块、内部类

2、使用static修饰属性:静态变量(或类变量)

按static修饰的属性分:静态属性 VS 非静态属性(实例变量)

3、特点

实例变量:创建类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性,不会导致其他对象中同样的属性值的修改

静态变量:创建类的多个对象,多个对象共享一个静态的变量。通过对象修改静态变量时,会导致其他变量调用的静态属性也修改

  • 静态变量随着类的加载而加载,可以通过"类.静态变量"的方式调用
  • 静态变量的加载要早于对象的创建
  • 类只会加载一次静态变量,则静态变量在内存中也只会存在一份,存在方法区的静态域中

静态变量常用例子:System.out / Math.random等

静态变量 VS 实例变量内存解析示意

静态变量内存分析

示例

public class StaticTest {
    public static void main(String[] args) {
        Chinese.nation = "中国";
        Chinese c1 = new Chinese();
        c1.name = "李四";
        c1.age = 20;
        System.out.println(c1.nation);//中国

        Chinese c2 = new Chinese();
        c2.name = "张三";
        c2.age = 18;
        System.out.println(c2.nation);//中国

        c1.nation = "CHN";
        System.out.println(c2.nation);//CHN
        c2.nation = "CHINA";
        System.out.println(Chinese.nation);//CHINA

        c2.eat();
        Chinese.show();
    }
}

class Chinese{
    String name;
    int age;
    static String nation;

    public void eat(){
        System.out.println("吃中餐。。。");
        show();
        System.out.println(nation);
    }

    public static void show(){
        System.out.println("是一个中国人。。。");

    }

    public void info(){
        System.out.println("name:" + name + ", age:" + age);
    }
}

4、使用static修饰方法:静态方法

随着类的加载而加载,可以通过"类.静态方法"的方式调用

静态方法中,只能调用静态的方法或属性

注意:

在静态的方法内不能使用this、super关键字

静态结构的生命周期与类的一样,与对象不同

子父类中同名同参数的方法要么都声明为非static的(重写),要么都声明为static(不是重写)

示例

public class CircleTest2 {
    public static void main(String[] args) {
        Circle2 c1 = new Circle2();
        Circle2 c2 = new Circle2();

        System.out.println("c1 的id: " + c1.getId());
        System.out.println("c2 的id: " + c2.getId());

        Circle2 c3 = new Circle2(2);
        System.out.println("c3的面积:" + c3.findArea());

        System.out.println("圆的个数:" + Circle2.getTotal());
    }
}

class Circle2{
    private double radius;
    private int id;

    public Circle2(){
        this.id = init++;
        total++;
    }

    public Circle2(double radius){
        this();
        //this.id = init++;
        //total++;
        this.radius =radius;
    }

    private static int total; //统计圆的个数
    private static int init = 1001; //共享的初始化值,赋值给id,作为Circle的标识

    public static int getTotal() {
        return total;
    }

    public int getId() {
        return id;
    }

    public double findArea(){
        return Math.PI*radius*radius;
    }
}

如何确定一个属性是否要声明为static的?

属性可以被多个对象所共享的,不会随着对象的不同而不同的

类中的常量声明为static

如何确定一个方法是否要声明为static的?

操作静态属性的方法,通常设置为static

工具类的方法,习惯上声明为static,如Arrays、Collections

final

final:最终的

final可以用来修饰的结构:类、方法、变量

final用来修饰一个类,就不能被其他类所继承,如String类、System类

final用来修饰的方法,表明此方法不能被重写,如Object类的getClass()

final用来修饰的变量,此时的"变量"为常量

  • final修饰的属性,可以声明的位置:显示初始化,代码块中初始化,构造器中初始化(在构造器中初始化常量时,所有的构造器都要初始化常量)
  • final修饰局部变量,在方法中的形参,变量使用final修饰,就无法进行修改操作

static final用来修饰属性:全局常量

示例

public class FinalTest {

    final int WIDTH = 10;
    final int LEFT;

    {
        final String name = "lisi";
        LEFT = 11;
    }

    public FinalTest(){}

    public void show(){
        final int num = 10;
        //num += 1;//报错
    }

    public void show(final int num){
        //num += 1; //报错
        System.out.println(num);
    }

    public static void main(String[] args) {
        FinalTest test = new FinalTest();
        test.show(10);
    }
}

抽象(abstract)

【抽象】https://www.cnblogs.com/bione/p/16789711.html
abstract:抽象的,可以用来修饰类、方法

抽象类

  • 此类不能实例化
  • 抽象类一定有构造器,便于子类实例化时调用(涉及子类对象的实例化全过程)
  • 开发中,都会提供抽象类的子类,让子类对象实例化

抽象方法

  • 抽象方法只有方法的声明,没有方法体
  • 包含抽象方法的类,一定是一个抽象类。反之,抽象类可以没有抽象方法
  • 若子类重写了父类中的所有抽象方法后,此子类方可实例化

示例

//抽象类
abstract class Animal{
    private String name;
    private int age;

    public Animal(){}

    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }

    //抽象方法
    public abstract void eat();

    public void sleep(){
        System.out.println("sleep()..." );
    }
}

//子类Dog
class Dog extends Animal{

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("dog eat()...");
    }
}

注意:

abstract不能用来修饰属性、构造器等结构

abstract不能修饰私有方法、静态方法、final的方法、final的类

接口(interface)

【接口】https://www.cnblogs.com/bione/p/16789736.html
interface来定义接口,接口与类是两个并列结构

定义接口的中成员:全局常量、抽象方法、默认方法、静态方法

接口的使用体现了多态性

接口,实际上可以看作是一种规范

实际开发中,面向接口编程

java类可以实现多个接口

class A extends B implements C,D{}

接口与接口之间可以多继承

interface A extends B,C{}

示例

interface Flyable{
    //全局常量
    public static final int MAX_SPEED = 7900;
    public static final int MIN_SPEED = 1;

    //抽象方法
    public abstract void fly();

    //默认方法
    void stop();
    
    //静态方法
    static void sleep(){
        System.out.println("Flyable: sleep() - static");
    };
}

注意:

接口中不能定义构造器,意味着不能实例化对象

开发中,接口通过让类实现(implements)的方式来使用

如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化

Object类

1、Object类是所有Java类的根父类

如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

Object类只声明了一个空参的构造器

public class Person{}
//等价于
public class Person extends Object{}

2、Object类的通用功能

方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()

equals() 是一个方法,而非运算符,只适用于引用数据类型。

Object类中的equals()的定义:

public boolean equals(Object obj){
    return (this == obj); //比较两个对象的地址值
}

说明:Object类中定义的equals() 和 == 的作用是相同的,比较两个对象的地址值是否相等

像String、Date、File、包装类等都重写了Object类中的equals() 方法,重写以后比较两个对象的实体内容是否相同

equals()方法的重写规则

@Override
public boolean equals(Object obj){
    if(this == obj){
        return true;
    }
    if(obj instanceof Customer){
        Customer cust = (Customer)obj;
        //比较两个对象的每个属性是否相同
        return this.age == cust.age && this.name.equals(cust.name);
    }
    return false;
}

3、toString()方法的使用

输出一个对象的引用时,实际上输出的是当前对象的toString()

Object类中toString()的定义

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

示例

Graduate graduate = new Graduate("lisi", "南工", "physics");
System.out.println(graduate);//com.bingeone.javaSE.OOP.exer.Graduate@6d6f6e28

String str = new String("hello");
System.out.println(str);//hello

像String、Date、File、包装类等都重写了Object类中的toString() 方法,,使得在调用对象的toString()时,返回“实体内容信息”。

示例:重写toString()方法

@Override
public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
}

包装类

针对八种基本数据类型定义相应的引用类型 - 包装类(封装类),使得基本数据类型具有类的特征。

包装类

基本数据类型、包装类 与 String的转换

包装类与String转换

基本数据类型、包装类 --> String : valueOf(Xxx x)

String --> 基本数据类型、包装类: parseXxx(String str)

示例

//基本数据类型 -> 包装类:调用包装类的构造器
@Test
public void test1(){
    int num = 19;
    Integer in = new Integer(num);
    System.out.println(in);

    Integer in2 = new Integer("125");
    System.out.println(in2);
}

//包装类 -> 基本数据类型:调用包装类的XxxValue()
@Test
public void test2(){
    Integer in = new Integer(12);
    int num = in.intValue();
    System.out.println(num);
}

//jdk5.0新特性:自动装箱与拆箱
@Test
public void test3(){
    //自动装箱:基本数据类型 -> 包装类
    int in = 10;
    Integer num = in;

    boolean b1 = false;
    Boolean b = b1;

    //自动拆箱:包装类 -> 基本数据类型
    int in2 = num;
    boolean b2 = b;
}

//基本数据类型,包装类 -> String类型:调用String的valueOf()方法
@Test
public void test4(){
    int in = 10;
    String intStr = String.valueOf(in);
    System.out.println(intStr);

    Boolean b = new Boolean("true");
    String booleanStr = String.valueOf(b);
    System.out.println(booleanStr);

}

//String类型 -> 基本数据类型,包装类:调用包装类的parseXxx(String s)方法
@Test
public void test5(){
    String str = "123";
    Integer num = Integer.parseInt(str);
    System.out.println(num);

    String str2 = "true";
    boolean b = Boolean.parseBoolean(str2);
    System.out.println(b);
}

示例:易错问题

@Test
public void test1(){
    Object o1 = true? new Integer(1): new Double(2.0);
    System.out.println(o1);//1.0 -> 编译的时候对类型进行提升统一
}

@Test
public void test2(){
    Object o1 = null;
    if(true)
        o1 = new Integer(1);
    else
        o1 = new Double(2.0);
    System.out.println(o1);//1
}

@Test
public void test3(){
    Integer i = new Integer(1);
    Integer j = new Integer(1);
    System.out.println(i == j);//false

    /**
     * 说明:
     * Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
     * 保存了从-128~127范围内的整数,如果使用自动装箱的方式,给Integer赋值的范围
     * 在-128~127范围内时,可以直接使用数组中的元素,不用new新建内存,目的是提高效率
     */
    Integer n = 1;
    Integer m = 1;
    System.out.println(n == m);//true

    Integer x = 128;//相当于 new Integer(128)
    Integer y = 128;
    System.out.println(x == y);//false
}

main方法

main()方法作为程序的入口

main()方法也是一个普通的静态方法

main()方法也可以作为与控制台交互的方式

示例

public class MainTest {
    public static void main(String[] args) {//程序入口
        Main.main(new String[9]);
        for (int i = 0; i < args.length; i++) {
            System.out.println("+++++" + args[i] + "\t");
        }
    }
}

class Main{
    public static void main(String[] args) {
        String[] arg = new String[10];
        for (int i = 0; i < arg.length; i++) {
            arg[i] = "arg_" + i;
            System.out.println(arg[i]);
        }
    }
}

main()方法语句解析

public static void main(String[] args)

权限修饰符: private 缺省 protected public ,体现封装性

修饰符:static、final、abstract、native 修饰方法

返回值:无返回值、有返回值,使用return关键字

方法名:标识符命名规则、“见名知意”

形参列表:重写、重载,参数的值传递机制,体现对象的多态性

方法体:主要体现方法的功能

拓展

1、重写与重载的区别

重载:指同一个类中不同的函数使用相同的函数名,但是函数的参数个数或类型不同

重写:在子类中可以根据需要对父类中继承来的方法进行改造。在程序执行时,子类的方法将覆盖父类的方法。

重载是一个类中多态性的表现,而重写体现子父类之间多态性,重写的方法只有在运行期才确定。

区别:重载实现于一个类中;重写实现于子类中。

2、子类对象实例化的过程

从结果上来看(体现继承性):

子类继承父类以后,就获取了父类中声明属性或方法

创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

从过程来看:

当我们通过子类的构造器创建子类对象时,我们一定会直接或间接调用其父类的构造器,进而调用父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。

注:创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即new的子类对象

3、== 和 equals() 的区别

== :运算符

可以使用在基本数据类型和引用数据类型变量中

如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等(不一定类型要相同)

对于比较两个引用类型的变量,比较两个对象的地址值是否相同

equals():是一个方法,只能用在引用类型的变量上,比较两个变量是否相等

在不重写Object的equals()的方法下,比较两个变量的地址值是否相等

重写了equals()方法,就比较重写方法里面的内容是否相等

4、final、finally、finalize的区别

final:是java的关键字,修饰符。被修饰的结构,如类、方法、属性是不可更改变的

finally:是java异常处理机制的结构,常常使用try-catch-finally来处理有可能出现异常的代码,而finally结构里的的代码内容,是一定会执行的。

finalize:是Object类的一方法名,该方法是在垃圾回收器回收对象内存之前,调用此方法进行资源回收,如文件流资源的关闭

posted @ 2022-11-19 22:29  Bingeone  阅读(87)  评论(0编辑  收藏  举报