【疯狂Java讲义】Java学习记录(类)

一、定义类

类中5大成员

语法

[修饰符] class 类名 extends 父类

{

        //成员变量(field)

        //方法(method)

        //构造器(constructor)

        //内部类(nested class)

        //初始化块

}-------类体

- 修饰符:public、final | abstract。有且仅有。

- 类名:只要是标识符即可。从专业角度要求:多个单词连缀而成,每个单词首字母大写。

1、成员变量

语法

[修饰符] 类型 变量名 [= 初始值]

(1)修饰符

private | protected | public、final、static、(transient:序列化相关)。

(2)类型

任意基本类型或引用类型。

(3)变量名

①驼峰(camerlize)写法,首字母小写,后面每个单词的首字母大写。

②用于描述该类或对象的状态,因此通常建议用名词。

例如:

public class Role {

        String name;

        int id;

        //类型是int[],变量名是authorities

        int[] authorities;

}

注意

public class 面试题

{

        //这里定义没问题

        int age = 20;//成员变量

        //下面的定义错了

        double de;//成员变量

        de = 3.4;//赋值语句

}

②成员变量,可以不指定初始值;系统会为之分配默认的初始值,初始值规则与数组元素的初始值完全相同

2、方法

(1)语法

[修饰符] 返回值类型 方法名(形参列表)

{

        //代码:定义变量(包括数组)、变量赋值、流程控制

        //如果声明了返回值类型,必须有return语句

}-------方法体

①修饰符

private | protected | public、final、static。

②返回值类型

- 任意基本类型或引用类型。

- 可使用void声明没有返回值。

③变量名

- 驼峰(camerlize)写法,首字母小写,后面每个单词的首字母大写。

- 用于描述该类或对象的行为,因此通常建议用动词。

④形参列表

- 每个形参都满足“形参类型 形参名”的格式;多个形参之间用逗号隔开。

形参类型1 形参名1,形参类型2 形参名2,...

 - 代表调用方法时要传入的参数。

例如:

public int factorial(int n)

{

        // n的阶乘:1*2*3*4*...*n

        int result = 1;

        for (int i = 0; i < n; i++)

        {

                result *= iii;

        }

        return result;

}

(2)方法的所属性

①方法类似于函数,但与函数不同的是,方法不能独立存在,必须定义在类里面。

②定义在类中的方法,从逻辑上来看,

- 如果该方法有static修饰,该方法属于类本身,应该用类调用。

- 如果该方法无static修饰,该方法属于对象本身。

③方法不能独立执行,一定要有调用者。

【规则】

- 如果你调用同一个类中方法,可以省略调用者,此时系统会添加默认的调用者。

- 如果该方法是无static方法,添加this作为默认的调用者。

例子:

public class Item {

        public void info() {

                System.out.println("这是一个商品");

        }

        //有static方法

        private static int add(int a, int b) {

                return a + b;

        }

        public void test() {

                //调用同一个类中的非static方法,默认添加this作为调用者

                info();//相当于this.info();

                //调用同一个类中的static方法,默认添加类作为调用者

                add(22, 44);//相当于Item.add(22,44)

        }

}

(3)形参个数可变的方法

①语法

类型...形参名

本质就是数组,上面写法等同于类型[] 形参名,然而使用类型[] 形参名不如上面的写法便捷。

②好处

调用方法时更加方便。

- 既可直接传入多个元素,系统会自动将它们封装成数组。

- 也可用数组。

例子:

public class VarArgs {

        //个数可变的参数

        //如果使用String[]而不使用String...,则无法使用va.test(-2, "hello", "java")

        public void test(int a, String... names) {

                // TODO Auto-generated method stub

                System.out.println("a参数为:" + a);

                System.out.println("names数组长度为:" + names.length);

                for (int i = 0; i < names.length; i++) {

                System.out.println(names[i]);

                }

        }

        public static void main(String... args) {

                // TODO Auto-generated method stub

                VarArgs va = new VarArgs();

                va.test(-2, "hello", "java");

                va.test(34, new String[]{"孙悟空", "猪八戒"});

        }

}

③缺点

“类型...”这样的写法只能作为形参列表的最后一个形参。

【暗示】

一个方法最多只能有一个“个数可变的形参”

(4)递归方法

①方法里调用自身——递归带来了隐式循环。

②递归要避免无限递归。一定要在某些情况下。不再调用方法自身

假如我们有:

f(1) = 2;

f(2) = 5;

..

f(n) = f(n + 2) - 2 * f(n + 1);

要计算:f(10)是多少?

令 N = n + 2,

f(N - 2) = f(N) - 2 * f(N - 1);

f(N) = 2 * f(N - 1) + f(N - 2);

public class Recursive {

        public static int fn(int n) {

                if (n == 1) {

                        return 2;

                }else if (n == 2) {

                        return 5;

                }else {

                        //递归

                        //return fn(n + 2) - 2 * fn(n + 1);

                        //这两个公式完全相同

                        return 2 * fn(n - 1) + fn(n - 2);

                }

        }

        public static void main(String[] args) {

                System.out.println(Recursive.fn(10));

        }

}

【难点】

要保证递归一定能出现递归结束的条件。

(5)方法重载(overload)

①同一个类中,有多个同名的方法,但这多个方法的形参列表不同。

②修饰符不同不算重载;返回值类型不同也不算重载。

③当你要确定一个方法时,仅有方法名是不够的,必须要结合参数才能确定。

(6)方法的传参机制

①如果定义方法时声明了形参,调用方法时必须传入对应的参数。

②Java的参数传递机制:值传递,传入的只是参数的副本,并不是参数本身

- 如果传递的参数是基本类型,方法中对参数所做的修改,完全不会影响参数本身。

- 如果传递的参数是引用类型,参数的副本与参数本身指向同一个对象,因此方法通过参数副本修改对象时,会影响参数本身所指向的对象。

例子1-传入参数是基本类型

public class 基本类型传递 {

        public static void swap(int a, int b) {

                //互换a、b的值

                System.out.println("swap开始");

                int tmp = a;

                a = b;

                b = tmp;

                System.out.println("swap中a = " + a);

                System.out.println("swap中b = " + b);

                System.out.println("swap结束");

        }

        public static void main(String[] args) {

                System.out.println("main开始");

                int a = 6;

                int b = 9;

                //传入参数是基本类型

                System.out.println("swap之前a = " + a);

                System.out.println("swap之前b = " + b);

                System.out.println("传入参数是基本类型");

                基本类型传递.swap(a, b);

                System.out.println("swap之后a = " + a);

                System.out.println("swap之后b = " + b);

                System.out.println("main结束");

        }

}

例子2-传入参数是引用类型

public class 引用类型传递 {

        public static void swap(DataWrap dw) {

                //互换dw.a、dw.b的值

                System.out.println("swap开始");

                int tmp = dw.a;

                dw.a = dw.b;

                dw.b = tmp;

                System.out.println("swap中dw.a = " + dw.a);

                System.out.println("swap中dw.b = " + dw.b);

                System.out.println("swap结束");

        }

        public static void main(String[] args) {

                System.out.println("main开始");

                DataWrap dw = new DataWrap();

                dw.a = 6;

                dw.b = 9;

                System.out.println("swap之前dw.a = " + dw.a);

                System.out.println("swap之前dw.b = " + dw.b);

                //传入参数是引用类型

                System.out.println("传入参数是引用类型");

                引用类型传递.swap(dw);

                System.out.println("swap之后dw.a = " + dw.a);

                System.out.println("swap之后dw.b = " + dw.b);

                System.out.println("main结束");

        }

}

(7)方法重写(override)

子类发现父类不适合自己时,就要重写父类的方法。

【方法重写口诀】:2同2小1大

- 2同:方法名相同、形参列表相同。

- 2小:返回值类型相同或更小,声明抛出的异常相同或更小。

- 1大:访问权限相同或更大。

@Override

作用是报错。要求被修饰的方法必须重写父类方法,否则就报错。

3、构造器 

作用:new调用构造器来创建对象

(1)语法

[修饰符] 构造器名(形参列表)

{

        //代码:定义变量(包括数组)、变量赋值、流程控制、数据语

}------构造器体

【修饰符】

private | protected | public

(2)构造器规则

①构造器用于初始化对象。

②构造器如何调用?必须用new来调用构造器,这样就可以返回一个初始化完成的对象。

③如果你不为一个类提供构造器,系统会自动为该类提供无参数的构造器。

(3)构造器重载

一个类可以定义多个构造器(因此构造器名必然相同)——必须要求形参列表不同。

【this引用】

this紧跟一个"."。例如:

this.name

this.walk();

【this调用】

- this紧跟圆括号,this(参数)

- 出现在构造器的第一行,调用同一个类中重载的构造器

public Dog(String name, String color) {

        this.name = name;

        this.color = color;

}

public Dog(String name, String color, int age) {

        //this.name = name;

        //this.color = color;

        this(name, color);

        this.age = age;

}

【注意】

- 如果没有为类写构造器,系统会默认为该类提供一个无参构造器。

- 构造器名必须与类名相同。

判断一个类是否为构造器

要看2点:

①构造器名与类名相同

②是否有返回值

例如:

public final class User {

        private String name;

        int age;

        public User(String n)

         {

               System.out.println("创建User对象");

        }

}

【构造器总结】

- 很像一个特殊的方法。没有返回值类型声明,构造器名必须与类名相同。

- 构造器的作用:用于初始化对象——构造器永远属于实例。它不可能用static修饰。

二、类可以用来做什么?

1、定义变量。

所有类都是引用类型。所有类都可以声明变量。

2、调用static修饰方法或static修饰的变量。

3、创建对象

new 构造器(参数)

4、派生子类

三、对象可以用来做什么?

1、调用无static修饰的成员(实例变量)

2、调用无static修饰方法(实例方法)

四、类是引用类型

- 数组也是引用类型。 

- Java的引用类型非常多,无穷无尽......只要你定义一个类,就多一个引用类。

- 引用类型的赋值,只是将对象的首地址存入变量中。

  

五、this引用 

this可以出现非static的方法、构造器中。作用如下:

- 出现非static方法中,this代表了该方法的调用者。“谁调用该方法,thsi就代表谁“。

例子:

public class Pig {

        String color;

        double weight;

        public void move() {

        System.out.println("猪其实跑得很快");

        }

        public void test(){

                System.out.println("测试方法");

                //主调(主语调用者)

                //谁在调用test方法,this就代表谁

                this.move();

                //谁在调用test方法,this就代表谁

                System.out.println(this.color);

        }

}

public class PigTest {

        public static void main(String[] args) {

                Pig p1 = new Pig();

                p1.color = "白色";

                //p1调用test方法,因此test方法中的this代表p1

                p1.test();

                Pig p2 = new Pig();

                p2.color = "黑色";

                p2.test();

        }

}

- 出现在构造器中,this就代表该构造器正在初始化的对象。

例子:

 

public class Apple {

        String color;

        double weight;

        //构造器用于对该对象执行初始化

        public Apple(String color, double weight) {

        //构造器正在初始化谁,this代表谁

                this.color = color;

                this.weight = weight;

        }

}

public class AppleTest {

        public static void main(String[] args) {

        //此时构造器正在初始化ap,因此Apple构造器中this代表ap

                Apple ap = new Apple("黄色", 0.34);

                System.out.println(ap.color);

                System.out.println(ap.weight);

        }

}

this很重要的作用是,用于区分方法或构造器的局部变量(尤其是与成员变量同名时)

六、封装 

封装、继承、多态——面向对象的3大特征。

封装包含2方面的意思,

①隐藏:隐藏隐藏内部实现细节。

②暴露

- 将一些操作界面暴露出来。

- 如果通过暴露的界面来操作对象,该对象的内部状态不会被破坏。

简而言之:封装要求合理隐藏、合理暴露

1、访问控制符

- private -> 不写 -> protected -> public

①private(类访问权限):该修饰符修饰的成员,只能在该中被访问。(彻底隐藏

②不写(包访问权限):该修饰符修饰的成员,只能在该及其该类所在包中被访问。(部分隐藏

③protected(子类访问权限):该修饰符修饰的成员,只能在该、及其该类所在包、该类的子类中被访问。(部分暴露

④public(公共):该修饰符修饰的成员可以在任意地方被访问。(彻底暴露

private不写protectedpublic
当前类
同一个包×
子类××
任意×××
【制定原则】

①成员变量(实例变量),通常用private修饰,为了隐藏实现细节。

②为每个成员变量提供public的getter、setter方法,用于控制该成员变量的方法。

③需要暴露的方法,通常用public修饰。

④如果希望一个方法主要用于被子类重写,用protected修饰。

2、包

①不同公司完全可以定义同名的类,为了解决不同公司、不同项目的类名重复的问题。

②Java就引入“包”机制——就是在类名添加一个前缀。

③Java程序为类定义包:

- 在源代码中用package包名

- 将生成class文件要放在对应的文件结构下

(1)包名的命名规范

- 语法要求,只要标识符即可。

- 专业要求:推荐用公司域名倒写,再加项目名。

【备注】

一旦你为类指定了包名之后,使用该类时应该用完整类名:包名+ 类名

(2)导入包

- import的作用:为了省略包名。如果不用import,每次用类时都需要使用包名+类名的形式。

①每次导入一个类

import 包名.类名;

②导入指定包的所有类

import 包名.*;

此时*只能代表类,包名不能用*。

③静态导入
- 每次只导入一个静态成员

import static的作用:可以省略写类名,用于导入指定类的所有静态成员。导入之后,可以省略写类名。

import static 包名.类名.静态成员变量;

- 导入指定类所有静态成员

import static 包名.类名.*;

【备注】

Java程序默认已导入java.lang包下所有类。

七、继承

封装、继承、多态——面向对象三大特征。

1、理解继承

是一种类与类之间的关系。是一种由一般到特殊的父类。

2、继承语法

【继承的好处:代码复用】

[修饰符] class 类名 extends 父类

{

}

 【说明】

- Java是单继承,只能有一个直接父类。

- 如果你不显式继承父类,Java默认是继承Object类(JDK系统提供的类)

子类继承父类可以得到父类的:

①成员变量

②方法

八、super限定

与前面this引用非常相似,super用于限定访问父类定义的实例变量或实例方法。

super.父类定义的实例变量

super.父类定义的实例方法(参数)

例子:

 九、子类构造器调用父类的构造器

子类构造器一定会调用父类构造器一次——有且仅有一次

①如果子类构造器没有显式调用父类构造器,系统会自动在子类构造器的第一行先调用父类无参数的构造器。

错误例子:父类没有无参数的构造器,所以报错

②子类构造器的第一行显式使用super调用父类构造器。

【super限定】

this紧跟一个"."。例如:

super.name

super.walk();

【super调用】

super紧跟圆括号,super(参数)

【super和this的区别】

- super调用一定是调用父类的构造器。只能出现在构造器的第一行。

- this调用是调用当前类的。只能出现在构造器的第一行。

【super调用和this调用不可能同时出现】

【备注】

- 如果父类没有无参数的构造器,子类的构造器必须显式调用(super调用)父类指定的构造器。

例子:

十、多态

【变态】

同一个类型的实例、在执行同一个方法,个别对象呈现出变异的行为特征。

【多态】

同一个类型的多个实例、在执行同一个方法,呈现出多种的行为特征。

【为什么有多态?】

Java执行方法时,方法的执行是动态绑定的,方法总会执行该变量实际所指向对象的方法。

【变量的类型】
①编译时类型

- 声明该变量是指定的类型。

- 在Java程序的编译阶段,Java编译器只认编译时的类型。

②运行时类型(实际类型)

- 该变量实际所引用的对象的类型。

【向上转型】

子类对象可以直接赋值给父类变量。

自动完成。

【向下转型(强转)】

父类变量赋值给子类变量。

强制转换。

(类型)变量名

【强转运算符的注意点】

①强转运算符只能在具有编译类型具有继承关系的变量之间进行强转,否则编译报错,不兼容的类型。

②如果在编译类型具有继承关系的变量之间转换时,如果被转变量的实际类型,不是要转的目标类型,程序就会引发“ClassCastException”(类型转换异常)

【instanceof运算符】

- 避免ClassCastException异常

- 当变量所引用的对象是后面类或子类的实例时,该运算符返回true。

变量名 instanceof 类型

- instanceof只能在编译类型具有继承关系之间才能进行判断,否则编译报错,不兼容的类型。

- 该运算符可以保证,被强转的变量确实是可转换的才进行转换,从而避免ClassCastException。

例子:

【Java17新增的instanceof模式匹配】

【疯狂Java讲义】Java学习记录(Java17新增的instanceof模式匹配)_have_to_be的博客-CSDN博客传统instanceof的用法是先判断类型,然后用(type)进行强制类型转换,比较臃肿。- 模式匹配的instanceof将类型判断与类型转换合二为一。if( instanceof ){//使用模式变量——模式变量是目标类型- 在目标类型后增加一个变量,当在目标类型后面声明该模式变量后,就相当于完成强转类型转换。https://blog.csdn.net/have_to_be/article/details/132045754

【Java17新增的instanceof模式匹配】

【疯狂Java讲义】Java学习记录(Java17新增的switch模式匹配)_have_to_be的博客-CSDN博客switch的模式匹配既支持传统语法(case后用冒号),也支持新式语法(case后用箭头)- switch模式匹配既支持switch语句,也支持switch表达式。https://blog.csdn.net/have_to_be/article/details/132045950

十一、初始化块

满足如下两个特征:

- 初始化块的代码不能传入参数——因为它是隐式调用的。

- 初始化块的代码必须是位于构造器的最前面

语法

[修饰符static] {

        各种语句

}

初始化块是没有名字的

修饰符只能出现一个:static。

- 有static叫类初始化块(静态初始化块)

- 无static叫实例初始化块(非静态初始化块)

1、实例初始化块(没有static)

(1) 实例初始化块是“假象”

一个类在编译之后,实例初始化块就会消失——实例初始化块的代码会被还原到每个构造器的所有代码之前。

例子
public class InitTest {
	{
		System.out.println("Java");
	}
	
	public InitTest() {
		System.out.println("无参的构造器");
	}
	
	public InitTest(String name) {
		System.out.println("带String参数的构造器,参数为:" + name);
	}

	public static void main(String[] args) {
		InitTest in = new InitTest();
		InitTest in2 = new InitTest("123");
	}
}

相当于
public class InitTest {
	
	public InitTest() {
		{
			System.out.println("Java");
		}
		System.out.println("无参的构造器");
	}
	
	public InitTest(String name) {
		{
			System.out.println("Java");
		}
		System.out.println("带String参数的构造器,参数为:" + name);
	}

	public static void main(String[] args) {
		InitTest in = new InitTest();
		InitTest in2 = new InitTest("123");
	}
}

(2)实例初始化块的作用

将多个构造器前面部分相同的代码可以提取到实例初始化块中。

(3)实例初始化块何时执行?

只要程序调用构造器创建对象,程序总会先执行实例初始化块——因为实例初始化块被还原到每个构造器的所有代码之前。

(4)定义实例变量时指定的初始值,也是“假象”

- 指定初始值,编译之后就变成构造器所有代码之前的一条赋值语句。

- 实例初始化块的语句要还原到构造器的所有代码之前;定义变量指定的初始值也要还原到构造器的所有代码之前。这二者还原之后的顺序按照它们在源代码中的顺序。

例子
public class InitTest {
    //还原到构造器的所有代码之前
	{
		System.out.println("Java");
		age = 20;
	}
	
	int age = 2;//还原到构造器的所有代码之前
	
	public InitTest() {
		System.out.println("无参的构造器");
	}
	
	public InitTest(String name) {
		System.out.println("带String参数的构造器,参数为:" + name);
	}

	public static void main(String[] args) {
		InitTest in = new InitTest();
		InitTest in2 = new InitTest("123");
		
		System.out.println(in2.age);
	}
}

 相当于

public class InitTest {
	int age;
	
	public InitTest() {
		{
			System.out.println("Java");
			age = 20;
		}
		age = 2;
		System.out.println("无参的构造器");
	}
	
	public InitTest(String name) {
		{
			System.out.println("Java");
			age = 20;
		}
		age = 2;
		System.out.println("带String参数的构造器,参数为:" + name);
	}

	public static void main(String[] args) {
		InitTest in = new InitTest();
		InitTest in2 = new InitTest("123");
		
		System.out.println(in2.age);
	}
}

2、类初始化块(有static)

(1)类初始化块的作用

负责对类执行初始化

- 当程序第一次【主动】使用该类时,系统会为该类分配内存空间、并执行初始化(调用类初始化块)

- 只要你使用该类,基本都算主动使用——除了仅使用类声明变量

(2)类初始化块何时执行?

- 程序第一次【主动】使用该类时,会执行该类的类初始化块。

- 程序运行时,该类初始化块【只执行一次

(3)定义类变量时指定的初始值,也是“假象”

- 指定的初始值,编译之后就变成类初始化中的一条赋值语句,但到底是在初始化块的代码之前,还是代码之后,取决于它在源代码中的顺序。

例子
public class StaticInit {

	static int age = 30;
	
	//类初始化块
	static {
		age = 300;
	}

}

相当于

public class StaticInit {

	static int age;
	
	//类初始化块
	static {
		age = 30;
		age = 300;
	}

}
 【类初始化块与实例初始化块的区别】
执行次数执行先后何时执行
类初始化块1次第一次主动用该类
实例初始化块N次每次调用构造器
例子
class Base{

	static {
		System.out.println("Base的类初始化块");
	}
	
	{
		System.out.println("Base的实例初始化块");
	}
	
	public Base() {
		System.out.println("Base的无参构造器");
	}
	
	public Base(String name) {
		System.out.println("Base的String参数构造器");
	}
}

class Mid extends Base{
	
	static {
		System.out.println("Mid的类初始化块");
	}
	
	{
		System.out.println("Mid的实例初始化块");
	}
	
	public Mid() {
		super("java");
		System.out.println("Mid的无参构造器");
	}
	
	public Mid(int age) {
		this();
		System.out.println("Mid的int参数构造器");
	}
}

class Sub extends Mid{
	
	static {
		System.out.println("Sub的类初始化块");
	}
	
	{
		System.out.println("Sub的实例初始化块");
	}
	
	public Sub() {
		System.out.println("Sub的无参构造器");
	}
	
	public Sub(double d) {
		this();
		System.out.println("Sub的double参数构造器");
	}
}

public class Test {

	public static void main(String[] args) {
		new Sub(3.4);
		
		new Sub(1.0);
	}

}

【注意】类初始化块只执行一次 

 【观点】

- 初始化任何类之前,一定先从Object开始初始化,依次初始化它所有祖先类,最后才到它自己。

- 创建任何对象之前,一定先从Object构造器开始执行,执行它所有祖先类的构造器,最后才执行它自己的构造器。

十二、包装类

Java有8个基本类型:byte、short、int、long、double、char、boolean;这8个基本类型不能当成对象使用,而且也不接受null值。

为了解决上面问题,Java为8个基本类型提供了对应的包装类——可将它们包装成对象。

byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean

1、自动装箱

- 基本类型的值可以自动当成包装类的实例使用。

【注意】

2、自动拆箱

包装类的实例可以自动当成基本类型的值使用。

【建议】

- 以后做项目时,通常来说建议使用包装类来声明变量。

- 好处是,反正基本类型能做的,它都可以做;它还可以当成对象使用,还可以接受null。

3、包装类的方法

①parseXxx

- 可将字符串转成对应的基本类型值。

- NumberFormatException:要转的字符不符合数值格式,将会引发该异常。

②缓存机制

- 当程序对Integer使用自动装箱时,它有一个缓存机制,它会缓存值-128~127之间的对象。

public class 面试题 {

	public static void main(String[] args) {
		Integer i = 20;//在-128~127之间,缓存
		Integer j = 20;//直接用缓存中对象
		System.out.println(i == j);

		Integer k = 200;//不在-128~127之间,不缓存
		Integer l = 200;//重新创建,不是同一个
		System.out.println(k == i);
	}

}

十三、Object的两个常用方法

1、toString()方法

- 当你输出一个对象,或将一个对象与字符串进行连接时

——Java默认调用该对象的toString()方法将该对象自动转成字符串。

- 很多时候,都会重写Object的toString()方法。

(1)【默认的toString()】

Object提供的toString返回

类名@hashCode方法返回值

class Apple {
	private String color;
	private double weight;
	
	//无参数的构造器
	public Apple() {

	}
	
	//初始化全部成员变量变量的构造器
	public Apple(String color, double weight) {
		this.color = color;
		this.weight = weight;
	}

	//setter和getter方法
	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}	
//	public int hashCode() {
//		return 20;
//	}
}

public class AppleTest {
	public static void main(String[] args) {
		Apple ap = new Apple("红色", 2.3);
		
		//下面两行代码完全相同
		System.out.println(ap);
		System.out.println(ap.toString());
	}
}

 重写hashcode方法后(20的十六进制是14)

 (2)【重写toString()】

@Override
public String toString() {
    return "Apple{color=" + color
            + ",weight=" + weight
            /*列出所有的成员变量*/
            + "}";
}

2、equals()方法

【equals()与==的区别】

==如果判断两个引用变量,要求两个变量指向同一个对象时,才会返回true。

例子
public class StringEquals {
	public static void main(String[] args) {
		String str1 = new String("java");
		String str2 = new String("java");
		
        //String已经重写了equals方法
		System.out.println(str1 == str2);
		
		System.out.println(str1.equals(str2));
	}
}

(1)【默认的equals方法】

- Object提供的equals方法判断两个对象相等的标准与==是完全一样的。

(2) 【重写equals()】

- 根据业务规则来提供两个对象相等的标准。

- 实际项目中,用来作为equals比较的关键成员变量,通常并不需要使用全部的成员变量——只要用它们关键的成员变量即可。

class Goat{
	private String color;
	private double weight;
	
	public Goat() {

	}

	public Goat(String color, double weight) {
		this.color = color;
		this.weight = weight;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
	
	//obj代表被比较对象
	@Override
	public boolean equals(Object obj) {
		//this和obj指向同一个对象
		if (this == obj) {
			return true;
		}
		//要求obj不为null
		if (obj != null && obj.getClass() == Goat.class) {
			//对obj强转为Goat
			Goat target = (Goat) obj;
            //业务要求有几个关键属性,此处就只比较几个关键属性
			return this.color.equals(target.color)
					&& this.weight == target.weight;
		}
		return false;
	}
}

public class EqualsTest {

	public static void main(String[] args) {
		Goat goat1 = new Goat("黑色", 78.2);
		Goat goat2 = new Goat("黑色", 78.2);
		
		//goat1与goat2分别指向两个不同的对象,因此==判断返回false
		System.out.println(goat1 == goat2);
		
		System.out.println(goat1.equals(goat2));
	}

}

十四、static

- static并不是静态的意思。static是类的意思,有static的成员(成员变量、方法、初始化块、内部类)属于类成员,没有static的成员属于实例成员。

1、static是否可以修饰

局部变量外部类成员变量方法构造器初始化块内部类
×××

- 所有类成员都只能用类名调用!

  Java语法是不好,允许通过对象来调用类成员,其实没有意义!

  面试的笔试题中,如果遇到使用对象来调用类成员的情形,先把对象改成类——题目马上一目了然。

2、static考点

- static成员不能访问非static成员;非static成员可以访问static成员。

- static成员(4种)不能访问非static成员(5种)

例子1——无法从静态上下文中引用非静态成员变量
错误:无法从静态上下文中引用非静态成员变量age
错误:无法从静态上下文中引用非静态成员变量age

 例子2——无法从静态上下文中引用非静态方法
错误:无法从静态上下文中引用非静态方法info()

 例子3——无法从静态上下文中引用非静态变量
无法从静态上下文中引用非静态变量this

例子4——无法从静态上下文中引用非静态变量
无法从静态上下文中引用非静态变量b
例子5——无法从静态上下文中引用非静态方法
无法从静态上下文中引用非静态方法test()

例子6——无法从静态上下文中引用非静态变量
无法从静态上下文中引用非静态变量this

3、设计模式

 对于一批经常出现的设计场景,前人总结出来的比较成功的设计。后面的人就应该学习并模仿,从而提高我们的代码质量。

(1)单例模式

- 在某些场景下,某些类只需要(只能)创建一个实例。

   比如系统的窗口管理器、数据库引擎访问点、Java程序所在的JRE环境......都只要产生一个实例

①如何设计单例模式?

- 隐藏构造器——避免被创建实例(不能创建多个实例)。

- 暴露一个static的方法,该方法用于创建实例(该方法还需要保证,该类只会产生一个实例)。

 例子
public class Singleton {
	
	private static Singleton s;
	
	//1、隐藏构造器
	private Singleton() {
		
	}

	//2、暴露一个static方法,用于创建实例
	public static Singleton instance() {
		if (s == null) {
			//还没创建实例
			s = new Singleton();
		}
		return s;
	}
}
public class SingletonTest {

	public static void main(String[] args) {
		//由于隐藏了构造器,这里只能使用static类方法创建实例
		Singleton s1 = Singleton.instance();
		Singleton s2 = Singleton.instance();
		
		System.out.println(s1.equals(s2));
	}
}
s1和s2是同一个实例

十五、final

- 可以修饰变量(各种变量)、方法、类

- final与abstract互斥,永远不能同时出现!

1、final修饰变量

- 该变量被赋初始值之后,不能被重新赋值!

- final修饰的变量必须被赋值,且只能赋值一次

2、final修饰成员变量

(1)非final的成员变量

- 程序员可以不显式指定初始值,系统会为之分配默认初始值,初始值分配规则与数组元素的初始值分配规则完全相同。

【初始值分配规则(数组元素的初始值分配规则)】
整数类型(byte、short、int和long)0
浮点类型(float、double)0.0
字符类型(char)'\u0000'
布尔类型(boolean)false
引用类型(类、接口和数组)null

(2)final的成员变量

- 程序员必须显式指定初始值

①final实例变量

必须显式指定初始值。只能在以下3个位置的其中之一指定:

- 定义时指定初始值

- 实例初始化块

- 构造器

上面3个位置的本质只有一个——构造器

例子

②final类变量

必须显式指定初始值。只能在以下2个位置的其中之一指定:

- 定义时指定初始值

- 类初始化块

上面2个位置的本质只有一个——类初始化块

【注意】

- 实例初始化块可以访问final类变量,但不能指定初始值

 - final实例变量不能在static方法中指定初始值

 3、final修饰局部变量

(1)非final的局部变量

 - 程序员必须先显式指定初始值,然后才能使用。

(2)final的局部变量

 - 程序员必须先显式指定初始值,然后才能使用。

- final的局部变量不能被重新赋值

4、final修饰的是引用类型的变量

- final只保证该引用变量本身不会被重新赋值该变量所引用的对象完全可以被修改

例子

5、final修饰的宏替换变量

(1)final修饰的宏替换满足的3个条件

如果一个变量满足以下3个条件,这个变量就会消失(所有出现该变量的地方,在编译时就会替换成该变量的值)

①变量有final修饰

②声明变量时指定了初始值

③变量的初始值可以在编译时确定(初始值的表达式中没有变量)

例子1
public class Final宏变量 {

	public static void main(String[] args) {
		//有final修饰,指定了初始值,且初始值在编译时即可确定
		final int MAX = 100;
		
		System.out.println(MAX);
	}
}

- final 

-无final 

例子2
public class FinalTest {
	public static void main(String[] args) {
		String st1 = "java";//第一次用字符串,该字符串进入“池”
		String st2 = "java";//第二次直接用”池“中字符串
		System.out.println(st1 == st2);
		
		String st3 = "java";//第一次用字符串,该字符串进入“池”
		String st4 = "ja" + "va";//编译阶段就会计算结果:java。因此会直接使用“池”中字符串
		System.out.println(st3 == st4);
		
		String st5 = "ja";
		String st6 = "va";
		String str = st5 + st6;//st5、st6是变量,要等运行时才能计算结果,因此无法使用“池”中字符串
		System.out.println(st3 == str);
		
		final String st7 = "ja";
		final String st8 = "va";
		String str2 = st7 + st8;//st7、st8会消失,这行代码相当于:String str2 = "ja" + "va";
		System.out.println(st3 == str2);
	}
}

例子3
public class FinalTest2 {
	public static void main(String[] args) {
		//下面代码有几个变量?几个对象?
		//0个变量,1个对象
		final String s1 = "java";
		final String s2 = s1 + " is";
		final String s3 = s2 + " a";
		final String s4 = s3 + " very";
		final String s5 = s4 + " good";
		final String s6 = s5 + " language";
		
		System.out.println(s6);
		//上面代码相当于
		System.out.println("java is a very good language");
	}
}

6、final修饰方法

- 表明该方法不允许被子类重写——该方法可以被重载,也可以被子类调用

例子1——该方法不允许被子类重写

 例子2——该方法可以被重载

例子3——该方法可以被子类调用

【备注】final修饰prviate方法纯属多余,但java是允许的

- private方法已经被隐藏在该类的内部,子类无法访问该方法,因此不可能被重写。

 7、final修饰类

- 表明该类不能派生子类。jdk里面很多类都是final:String、Math、System

【Object是否为final?】

- 一定不是final

十六、abstract(抽象)

- 它只能修饰2个东西:方法和类(抽象方法和抽象类)

- abstract与final是互斥的,永远不可能同时出现!

1、抽象类

(1)抽象类与普通类的区别

只有4个字——有得有失:

- 有得:得到一个新功能:抽象类可拥有抽象方法,普通类不可以拥有抽象方法

- 有失:抽象类失去了一个功能:创建对象

(2)抽象类的作用

①定义变量,只能用它的子类的实例,向上转型

②调用类方法和类变量

③派生子类——主要目的

   子类构造器一定要调用父类构造器一次,因此抽象类必须有构造器

例子
public abstract class Animal {
	//抽象方法
	public abstract void move();

	public static void info() {
		System.out.println("我是一个动物");
	}
}
public class Bird extends Animal{
	@Override
	public void move() {
		System.out.println("鸟在天上飞");
	}
}
public class Camerl extends Animal {
	@Override
	public void move() {
		System.out.println("骆驼在沙漠走");
	}
}
public class AnimalTest {
	public static void main(String[] args) {
		Animal.info();//调用类方法
		
		//向上转型
		Animal an1 = new Bird();
		Animal an2 = new Camerl();
		
		//an1、an2的类型是Animal,体现出多态
		an1.move();
		an2.move();
	}
}

(3)抽象类派生子类

【规则】

- 子类要么重写抽象父类中所有的抽象方法,要么子类也只能是抽象的。

2、抽象方法

- 只有方法签名,没有方法体的方法

- 一定要交给子类去实现,否则不能调用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值