方法
方法是定义在类中的一段独立代码,能完成某个事情。
定义方法时,提供方法的名称、返回值类型、参数列表,这三点称为方法的三要素,定义的方法都需要这三部分。
当再使用该方法中的代码时,只需通过该方法名调用即可。
方法能够减少代码冗余。
调用方法
通过类名调用
Matn类
Math类是Java中的工具类,用于数学计算。
该类中的方法和属性都使用static修饰的,可以直接通过类名调用。
//常用方法和属性
System.out.println(Math.PI);//圆周率
System.out.println(Math.E);//自然常数
System.out.println(Math.abs(-5));//绝对值
System.out.println(Math.max(1,2));//最大值
System.out.println(Math.min(2.5,3.2));//最小值
System.out.println(Math.ceil(2.1));//3 向上取整
System.out.println(Math.floor(3.9));//3 向下取整
System.out.println(Math.round(-1.5));//-1 四舍五入
System.out.println(Math.random());//[0,1)直接的随机数
System.out.println(Math.sqrt(9));//3 平方根
System.out.println(Math.cbrt(27));//3 开立方
System.out.println(Math.pow(2,3));//8 2的3次幂
通过对象调用
创建对象:类名 对象名=new 类名([参数]);
Scanner sc = new Scanner(System.in);
sc.方法名();
Random rd = new Random();
rd.方法名();
在一个类中,方法A直接调用方法B
class Test{
void funA(){
funB();
}
void funB(){
System.out.println("我是B方法");
}
}
方法调用总结
调用方式
通过类名调用
通过对象调用
直接调用
参数
无参数
Scanner sc = new Scanner(System.in);
sc.next();//调用无参数方法
有参数
Random rd = new Random();
rd.nextInt(10);//调用有参数方法
返回值
没有返回值,方法名的前面用void表示
class Test{
void funB(){
System.out.println("我是B方法");
}
public static void main(String[] args){
Test t = new Test();
t.funB();
}
}
有返回值
Random rd = new Random();
int num=rd.nextInt(10);
自定义方法
方法的三要素:
方法名
使用驼峰命名法
方法返回值类型
如果有返回值,使用Java中的任意数据类型,方法体中使用return关键字返回对应类型的数据
如果没有返回值,使用void关键字
方法参数列表
参数写法:数据类型 形参名,数据类型 形参名
[修饰符] 返回值类型 方法名(参数列表){
方法体;
}
方法的类型
无参数无返回值
void 方法名(){
//方法体
return;//遇到return立即结束方法
}
无参数有返回值
int 方法名(){
return new Random().nextInt();
}
有参数无返回值
void 方法名(数据类型 形参名,数据类型 形参名){
//方法体
return;
}
有参数有返回值
//生成a,b中包含最小值,不包含最大值范围内的随机数
double rand(int a,int b){
double num=Math.floor(Math.random()*Math.abs(a-b)+Math.min(a,b));
return num;
}
注意
有返回值的方法,必须要在方法体中加入return并能够执行,同时要return一个该方法返回值类型对应的数据。
没有返回值的方法,可以不用加入return关键字。如果加入return关键字,不能跟上数据。
方法中出现return,会立即结束方法。所以return语句之后不能再有代码。
定义方法时的参数称为形式参数,简称形参;调用方法时,实际传递的参数称为实际参数,简称实参。实参只需保证数据类型和形参相同即可。
编程思想
面向过程编程
Procedure Oriented Programming 简称POP。
是一种基本的编程思想,将一件事情按流程按步骤执行,逻辑清晰。
每一步都是基于上一步的流程去继续实现。
注重于分析问题的步骤。
如果逻辑复杂,如xxx管理系统,使用POP就会变得很麻烦。
C语言就是一门面向过程的编程语言
面向对象编程
Object Oriented Programming 简称OOP。
是一种编程思想,核心是创建解决问题的对象,赋予对象行为和特征,让这些对象互相配合执行。
每个对象的行为实际也是面向过程的。
注重于全局如何创建完成某件事情的对象,如果适时地调用。
这种思想致力于将计算机中的世界,描述的和现实中一致的思想。
例:
洗衣服
pop:得到衣服--得到洗衣服--洗--晾晒
oop:得到衣服对象 、得到洗衣机对象、调用洗衣机对象的洗衣服行为
总结
面向过程:亲力亲为,侧重于分析完成事情的过程
面向对象:所有事情交给相应的对象完成,侧重于如何创建解决问题的对象
类和对象
类Class
具有相同属性和行为的对象的集合。相当于模板。
属性
描述这个类的特征值,在程序中,通过定义变量实现。
行为
描述这个类的动作,在程序中,通过定义方法实现。
创建一个class文件,就是创建一个类。
定义类
[修饰符] class 类名{
//属性(定义变量)
//行为(定义方法)
}
/*
* 定义车的模板
* 属性:变量
* 品牌
* 颜色
* 座位数
* 排量
* ...
对象Object
对象由某个类创建出来的具体实例。
创建对象
类名 对象名 = new 构造方法([参数]);
创建出的对象,通过"."操作符访问类中的非私有属性和方法。
类和对象的关系
*
* 行为:方法
* 跑
* 说话
* 飞
*
* */
public class Car {
//定义变量 数据类型 变量名 ;
String brand;
String color;
int seat;
//定义方法
void run(){
System.out.println("车在跑。。。");
}
void introduce(){
//在方法中可以直接访问同一个类中的属性
System.out.println("我是一辆"+brand+"牌的"+color+"色的"+seat+"座车");
}
}
对象Object
对象由某个类创建出来的具体实例。
创建对象
类名 对象名 = new 构造方法([参数]);
创建出的对象,通过"."操作符访问类中的非私有属性和方法。
public class Test4 {
public static void main(String[] args) {
// 创建Car类的对象
//类名 对象名 = new 类名();
Car benz = new Car();
//对象.类中的属性和方法
benz.seat = 5;
benz.color="白";
benz.brand="奔驰";
benz.introduce();
benz.run();
}
}
类和对象的关系
对象是类的具体表现,类是对象的模板。
如制作月饼的模具就是一个类,每次用这个模具创建出来的月饼就是一个对象。
先定义类,才能通过该类获取对象
成员变量和局部变量
成员变量
定义在类中的变量,称为成员变量,有默认值。
数据类型 | 默认值 |
整形 | 0 |
浮点型 | 0.0 |
布尔型 | false |
引用类型(雷、数组、接口) | null |
class Person{
String name;//成员变量,String是类类型,属于引用类型,默认为null
int age;//整型,默认0
double[] list;
void fun(){
System.out.println(name);//这里能通过编译,可以使用name,输出null
System.out.println(list[0]);//这里能通过编译,由于list为null,会报空指针异常
}
}
局部变量
定义在方法中的变量,称为局部变量。没有默认值,赋值后才能使用。
class Test{
public static void main(String[] args){
int num;//定义在方法中的变量称为局部变量,赋值后才能使用
System.out.println(num);//无法通过编译
}
}
成员变量和局部变量相关面试题
简述成员变量和局部变量的区别以及生命周期
成员变量是定义在类中的变量,有默认值,不赋值也能使用
局部变量是定义在方法中的变量,没有默认值,需要赋值后才能使用
成员变量的生命周期:类创建对象,成员变量就会初始化;类的对象被回收,成员变量就会销
毁
局部变量的生命周期:方法调用,对局部变量赋值,局部变量初始化,方法调用结束,局部变
量失效。
构造方法
概念
构造方法也称为构造函数、构造器、constructor
它是一个特殊的方法。
没有返回值部分,方法名和类名一致,在创建对象时通过new调用,给类的成员变量赋值
class Person{
//成员变量
String name;
int age;
//这就是一个无参数的构造方法,用于new Person()
Person(){
}
//这就是一个带参数的构造方法,用于new Person("admin",20)
Person(String name,int age){
this.name=name;//将参数的值赋值给成员变量
this.age=age;
}
}
特点
创建对象时必须通过new配合构造方法
构造方法没有返回值部分,名称和类名相同
构造方法可以存在多个,但是参数不能相同。这些构造方法之间的关系称为方法重载
每个类默认有一个隐藏的无参数的构造方法,方法体中隐含了一句super()。用于创建无参数的对象
如果自己写了带参数的构造方法,默认无参数的构造方法就会失效。如果想要同时拥有带参数和不
带参数的构造方法,就需要再次显式地写出来
带参数的构造方法常用于初始化成员变量(给类中的变量赋值)
构造方法可以限制创建对象时携带的参数
构造方法无法通过"."操作符访问,只能通过new关键字创建对象时调用
面向对象语言的三大特性--封装
封装
使用private关键字对成员变量进行修饰。再提供一组get和set的方法,用于对该属性读取和赋值。
可以防止除自身类之外的地方对private修饰的属性进行访问。
这样就能保护关键属性或给属性赋一个合理的值。
步骤
1.创建类,编写成员变量,对成员变量使用private修饰
class Person{
private String name;
private int age;
}
2.给所有成员变量添加set方法,用于赋值
void setName(String name){
this.name=name;
}
void setAge(int age){
this.age=age;
}
3.给所有成员变量添加get方法,用于读取
String getName(){
return name;
}
int getAge(){
return age;
}
创建该类对象后,无法直接通过.访问属性,只能通过get/set读取和赋值
public static void main(String[] args){
Person p = new Person();
//p.name;//无法通过.访问被private修饰的属性
p.setName("王海");
String name = p.getName();
}
面向对象三大特性--继承
概念
类B使用extends(延伸)关键字"继承"类A。
语法:class 类B extends 类A{}
类B称为类A的子类,衍生类,subClass
类A称为类B的父类,超类、supClass
继承后,子类就能访问父类中的非私有(没有使用private修饰)成员变量和成员方法。
class A{
private String secret="父类中的私有属性";
String name="父类";
void info(){
System.out.println("父类中的非私有方法");
}
}
class B extends A{
void fun(){
System.out.println(secret);//无法访问父类中的私有成员变量
System.out.println(name);//可以访问非私有成员变量
info();//可以直接调用父类中的非私有方法
}
}
将多个类中的公共代码提取出来保存到一个公共类中,这些类使用extends"继承"这一个公共类,从而减
少这些类中的冗余代码。
如猫类、狗类都有类型、昵称等属性,也有吃、睡等方法,那就可以定义一个动物类,将这些公共的属
性和方法定义在动物类这个父类中,
再让猫类和狗类这些子类继承动物类。这样就能直接通过子类访问父类中的内容
特点
如果多个类之中有相同的代码,可以将这些代码提取出来到一个公共的类中,这个类就是父类。再
让那些类去extends继承这个父类,那些类就是子类。子类就无需再写重复代码
子类中或子类对象可以直接访问父类中非私有(不用private修饰)属性和方法
创建子类对象时,会先执行父类中相应的构造方法
子类继承父类后,通常会对对父类中的方法进行拓展或覆盖,这称为方法重写。重写后,子类再调
用该方法时,执行的是重写后的内容
Java中是单继承。一个子类只能extends一个父类,一个父类可以有很多子类
Java中可以多重继承,类A可以继承类B,类B继承类C,这是类A既是类B的子类,也是类C的子
类,类A可以访问类B和类C中的非私有成员
任何类都是Object类的子类
方法重写和方法重载
方法重写override
当子类继承父类后,可以对父类中的方法进行扩展或覆盖。这个过程称为方法重写。
方法重写要求
方法名、返回值、参数列表必须和父类一致
访问权限不能比父类更严格(访问修饰符的范围要么一致要么更大)
不能抛出比父类更大的异常
IDEA中如果要重写方法,使用CTRL + O在弹出的窗口中选择要重写的方法
方法重载overload
在一个类中,如果多个方法的方法名相同,参数列表不同时,这些方法称为重载的方法。
同名不同参
重载用于,在一个类中,某个方法在不同的条件下,执行不同的内容
方法重载要求
方法名相同
参数必须不同(数量、类型)
与返回值无关
重载和重写相关面试题
说出重载与重写的异同
相同点:方法名不变
不同点:
重载在一个类中,重写在继承关系中子类重写父类
重载参数必须不同,重写参数必须相同
重载返回值无要求,重写返回值必须相同
构造方法能重载吗?能重写吗?
构造方法可以重载。构造方法不能重写。
构造方法在执行时,一定会创建对象吗?
不一定。创建子类时会自动执行父类构造方法,但不会创建父类对象。
以下代码执行会输出什么结果
class Father(){
Father(){
fun();
}
void fun(){
System.out.println("父类中的普通方法");
}
}
class Son extends Father{
Son(){
fun();
}
void fun(){
System.out.println("子类中的普通方法");
}
}
class Test{
public static void main(String[] args){
//1.创建父类对象,调用父类中无参构造方法
//2.无参构造方法中调用fun(),调用父类中的方法,输出“父类中的普通方法”
new Father();//父类中的普通方法
//1.创建子类对象,先调用父类中无参构造方法
//2.无参构造方法中调用fun(),由于fun()子类对其进行了重写,调用的是重写后的方法,
//输出"子类中的普通方法"
//3.执行子类中无参构造方法,调用重写后的fun(),输出“子类中的普通方法”
new Son();//"子类中的普通方法"打印两次
}
}
//最终输出
//父类中的普通方法
//子类中的普通方法
//子类中的普通方法
this和super关键字
这两个关键字,都可以当做对象使用,也可当做构造方法使用。
当做对象使用
用法:this.属性或this.方法,super.属性或super.方法
此时的this表示当前类的对象,super表示当前类的父类对象
class Person{
private String name;
public void setName(String name){
//这里的this表示当前类的对象Person对象
//相当于Person p = new Person();中的p
this.name=name;
}
public String getName(){
return name;
}
}
class Man extends Person{
void fun(){
//这里的super表示当前类的父类对象
//Person p = new Person();
//super表示上句话中的p
System.out.println(super.getName());
}
}
当做构造方法使用
用法:this([参数])或super([参数])
此时的this([参数])表示当前类的某个构造方法。如this()表示当前类的无参构造方法。
super([参数])表示当前类的父类的某个方法。如super()表示当前类的父类的无参构造方法。
如果当做构造方法使用时,只能写在另一个构造方法的第一行。
class Person{
String name;
int age;
Person(){
}
Person(String name,int age){
this.name=name;
this.age=age;
}
}
class Woman extends Person{
Woman(){
super("admin",20);
}
}
class Main{
public static void main(String[] args){
//调用Woman中的无参构造方法
//super("admin",20),执行父类中的 Person(String name,int age)
new Woman();
}
}
注意
如果父类中有无参数的构造方法,在子类的构造方法中,可以不写super(),默认自动调用。
如果父类中有带参数的构造方法,没有无参数的构造方法,在子类的构造方法中,必须要有super([参
数])。
父类和子类中都没有构造方法(只有默认的无参构造方法)
class Father{
//默认会有
/*
public Father(){}
*/
}
class Son extends Father{
//默认会有
/*
public Son(){
super();
}
*/
}
父类中没有无参构造方法,子类中就必须调用父类中对应的构造方法
class Father{
String name;
//由于该构造方法的出现,默认无参数的构造方法就会失效
Father(String name){
this.name=name;
}
}
//这时子类继承会报错
class Son extends Father{
//因为每个类中都有这段代码
/*
Son(){
//但当前继承的父类中没有无参构造方法
super();
}
*/
}
//解决方式1.给父类中添加无参构造方法
class Father{
String name;
Father(String name){
this.name=name;
}
//解决方式2.子类中添加同样的构造方法,目的是为了调用父类中的对应构造方法
class Son extends Father{
Son(String name){
super(name);
}
}
访问修饰符
访问修饰符可以限制某个类、属性或方法的访问权限
用法:
修饰类:访问修饰符 class 类名{}
修饰属性:访问修饰符 数据类型 变量名;
修饰方法:访问修饰符 返回值类型 方法名(){}
访问修饰符 | 含义 | 可以修饰 |
public | 公共的 | 类、方法、属性 |
protected | 受保护的 | 方法、属性 |
不写 | 默认的 | 类、属性、方法 |
private | 私有的 | 方法、属性 |
访问权限表
同一个类中 | 同一个包中的不同类 | 不同包中的子类 | 不同包中的非子类 | |
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
不写 | √ | √ | × | × |
private | √ | × | × | × |
访问权限从大到小
public >>> protected >>> 默认的 >>> private
final关键字
修饰属性
当final修饰属性时,该属性的值不可更改,这个属性称为常量。
常量在程序运行过程中,保存的值不能编号,所以定义常量时需要初始化。
常量名所有字母大写,多个单词之间用_隔开。
final 数据类型 常量名;
final int NUM = 123;
final double PI = 3.14;
修饰方法
当final修饰方法时,该方法不能被重写。
在方法的返回值前加上final。
public class Father{
public final void fun(){
}
}
public class Son extends Father{
//会报错,无法对final方法进行重写
@Override
public void fun(){
}
}
修饰类
当final修饰类时,该类不能被继承。
定义类,在class前加final
public final class Father{
public final void fun(){
}
}
//会报错,提示该类无法继承Father
public class Son extends Father{
}
创建对象时的内存变化
![](https://i-blog.csdnimg.cn/blog_migrate/588d6165149326c9873e076abdd4582a.png)
Object类
是java中所有类的父类。每个类都是这个类的子类,但没有使用extends体现出来
该类中定义了很多方法,通常需要进行重写。
常用方法 | 返回值 | 作用 |
toString() | String | 在输出某个对象时,会默认调用该方法。该方法默认输出"包名. 类名@十六进制哈希码"。通常用于重写后输出对象的属性。 |
equals(Object obj) | boolean | 判断两个对象是否相同。Object类中默认使用==比较,通常需要 重写该方法,自定义比较的规则。 |
hashcode() | int | 得到对象的哈希码。哈希码可以理解为对象的内存地址,经过一 个特定的算法得到的一个特定值。 |
getClass() | Class | 得到某个对象所在的类。 |
notify() | void | 唤醒某个线程 |
notifyAll() | void | 唤醒所有线程 |
wait(long time) | void | 让线程休眠,直到被唤醒 |
finallize() | void | 当某个对象被垃圾回收机制GC回收前执行的方法 |
对象造型/对象转型/cast
类似于原始类型中的数据类型转换。对象A转换为对象B的过程,称为对象转型。
在非继承关系的两个对象中,无法转型。
向下转型
父类对象转换为子类对象的过程,称为向下转型。强制转换
//一个父类对象
Object obj = new Object();
//默认无法直接将obj使用Person对象接收
//"强制转换"
Person p = (Person) obj;
向上转型
子类对象转换为父类对象的过程,称为向上转型。自动转换
//一个子类对象
Person p = new Person();
//默认子类对象可以用父类变量接收 多态
Object obj = p;
重写equals方法
如果两个对象的属性全部相同,在日常的业务逻辑中,可以视为这两个对象是同一个对象。
但是默认使用new创建的对象,就算属性一致,也是不同的内存地址,
如果用==比较,比较的是对象的内存地址,地址不同,返回false。
所以对象比较相同不能使用==
这时就需要自定义一套比较的方法,Object中有一个equals方法,用于比较两个对象是否相同,
但是Object中的equals方法用==比较,所以对该方法进行重写。
如两个Student的id、name、sex都一致,返回true.
@Override
public boolean equals(Object obj) {
//将Object对象转换为当前类Student对象
if (this == obj) {
return true;
}
//判断参数是否为Student类型
//对象 instanceof 类 检测对象是否属于指定类型
if (obj instanceof Student) {
//使用"强制转换"将父类对象转换为子类对象,称为向下转型
Student student = (Student) obj;
if (this.id == student.id &&
this.name.equals(student.name) &&
this.sex.equals(student.sex)) {
return true;
}
}
return false;
}
面向对象三大特性--多态
子类的对象保存在父类的变量中。
父类 变量 = new 子类();
public class Father{
}
public class Son extends Father{
}
public class Test{
public static void main(String[] args){
//多态:子类的对象保存在父类的变量中
Father f = new Son();
}
}
多态的应用
当某个方法的参数为父类变量时,可以传递一个子类对象。
这样就能在传递不同的子类对象时,表现出不同的形态。
如 要定义动物发出叫声的方法,参数是猫,输出猫对象的叫的方法"喵喵",参数是狗,输出狗对象的叫
的方法"汪汪"。
不用多态,需要写很多重载的方法,参数为猫或狗或其他类型。
使用多态,只需一个方法,参数为动物类,在动物类中定义叫的方法,让子类猫类狗类对其进行重写。
这时调用动物的叫的方法,实际会根据动物子类对象,调用具体子类重写后的方法
多态的前提
在继承关系中
父类的变量保存子类的对象(向上转型)
abstract抽象的
修饰方法
使用:访问修饰符 abstract 返回值类型 方法名(参数列表);
如果一个方法的方法体无法描述,是由其子类进行重写后使用,可以将这个方法定义为抽象方法。
该方法就可以去掉方法体部分,该方法的所在类,也必须是一个抽象类,使用abstract修饰。
修饰类
使用:访问修饰符 abstract class 类名{}
如果一个类中有抽象方法,这个类必须也是一个抽象类。
//当一个类中有抽象方法时,这个类也必须是抽象类
public abstract class Fruit{
//当一个方法没有方法体时,这个方法定义为抽象方法
public abstract void eatIt();
}
abstract关键字特点
修饰类:被修饰的类称为抽象类
抽象类不能被实例化(不能创建对象);
抽象类中有构造方法,在创建其子类对象时自动调用。
抽象类中可以有普通方法,通过其子类对象主动调用。
抽象类中定义的所有抽象方法,子类要么全部进行重写,要么也定义为抽象类。
修饰方法:被修饰的方法称为抽象方法
抽象方法没有方法体
抽象方法只能出现在抽象类中
abstract不能修饰构造方法和静态方法
抽象相关面试题
抽象类的特点?
抽象类是使用abstract修饰的类,除了不能创建对象、能定义抽象方法外,与普通类一样。
抽象方法的特点?
抽象方法是使用abstract修饰的方法,没有方法体。非抽象子类必须要对父类中的抽象方法进
行重写。
抽象类中有构造方法吗?
有构造方法,但不是通过new该类对象时调用,而是在new其子类对象时自动调用。
执行某个类的构造方法时,一定会创建这个类的对象吗?
不一定
如果是普通类,在执行构造方法时,一定会创建对象
如果是抽象类,在执行构造方法时,不会创建自身对象,只会创建其子类对象
接口interface
在Java中,数据类型分为基本类型和引用类型。
引用类型包含:数组、类和接口。
所以接口是一种数据类型,类似于类,在定义接口的时候,使用interface替换class。
由于Java是单继承,如果类A既要继承类B中的内容,也要继承类C中的内容时,
如果用"extends class名",只能选择一个类继承,
但使用implements interface名1,interface名2...就能同时"继承"多个"父类".这里的"父类"就是接口。
通常用extends表示类A继承类B,用implements表示类A实现接口A,接口B...
一个类可以同时implements实现("继承")多个接口。
extends和implements
类A extends 类B
类A当做类B的子类,称为继承
类A implements 接口A,接口B...
类A当做类B的实现类,称为实现
接口A extends 接口B
接口A继承接口
类A extends 类B implements 接口A,接口B...
类A是类B的子类,同时也是接口A,接口B的实现类
什么时候使用接口
如果想要让某个类作为多个"类"的子类时,将这些"父类"定义为接口
如果某个类中的所有方法都是抽象方法时,将这个抽象类改为接口
定义接口
public interface 接口名{
//接口中只能定义公开的静态常量并且要赋值
//接口中的方法一般都是公开的抽象方法 public abstract ,如果要定义,默认就有这两个关键字
//public abstract void fun();
void fun();//这句话相当于上一句,默认接口中的抽象方法使用public abstract修饰
//jdk1.8后,可以在接口中定义"默认"方法,使用default修饰的方法
default void test(){
}
//jdk1.8后,可以在接口中定义静态方法,使用static修饰的方法
staic void test2(){
}
}
抽象类和接口的异同
抽象类是一个类,用abstract class定义
有构造方法,不能创建对象,在创建子类对象时自动调用父抽象类中的构造方法
抽象类中可以有非抽象方法
抽象类被子类继承时,用extends关键字。子类需要重写父抽象类中的所有抽象方法
子类只能继承一个抽象类
抽象类中可以定义成员变量
接口不是一个类,用interface定义
没有构造方法,不能创建对象
接口中定义抽象方法时,无需加public abstract修饰符
接口中可以存在被default或static修饰的方法
接口被子类实现时,用implements关键字。子类需要重写父接口中的所有抽象方法
子类可以实现多个接口,用逗号隔开
接口中定义的属性都是公共的静态常量,被public static final修饰,必须要有初始值
相同点
接口和抽象类都无法创建对象
接口的实现类和抽象类的子类,都需要重写抽象方法
接口是一个完全抽象类。JDK1.8之后可以在接口中定义有方法体(被default或static修饰)的方法。
static静态的
每次new创建一个对象,都会在堆空间中开辟一块区域,这个过程是需要花费时间和空间的。
在栈空间中,只会定义变量,保存堆空间中某块区域的地址。通过变量访问堆空间中对应地址的数据。
如果多个对象都有相同的属性或方法时,可以将这些公共的属性和方法使用static修饰,
让其成为静态数据,在类加载的时候就保存在静态区中
在不使用静态成员时
![](https://i-blog.csdnimg.cn/blog_migrate/0433aba089eca848960e2b0812063003.png)
如上图的"重庆大学"这个字符串,每个对象都会使用,就可以将其使用static关键字,定义为静态属性。
静态属性或静态方法,在类加载时就会保存到内存中,无论是否创建对象都能访问,通过类名直接访
问。
使用静态成员时
![](https://i-blog.csdnimg.cn/blog_migrate/501786064249e90feb9a37ab05a1b43c.png)
概念
static是一个修饰符,可以修饰属性、方法、代码块。
被static修饰的内容,称为静态成员。静态成员在类加载时就保存到内中。
访问静态成员时,可以不用创建对象,直接通过类名访问。
如Math中的所有属性和方法,都是静态的,都通过Math直接访问。
定义和访问
public class DBUtil{
//静态属性,通常配合public final定义为公共的静态常量
public static final int NUM=5;
//静态方法
public static Connection getConn(){
//连接数据库操作
return conn;
}
//静态代码块
static{
//代码;
}
}
public class Test{
public static void main(String[] args){
//静态属性和方法直接通过类名访问
System.out.println(DBUtil.NUM);
//调用静态方法或创建对象时,自动执行一次静态代码块
DBUtil.getConn();
//也可以通过对象访问静态成员,但通常通过类名访问
new DBUtil().getConn();
}
}
什么时候使用static
如果某个属性或方法被高度重用时,可以将其定义为static静态的。
这样这些属性和方法在类加载时就会加载到内存中,从而直接通过类名即可访问。
static特点
静态方法中只能使用静态成员,不能使用非静态成员
public class Student{
static String STR="静态成员变量";
int num=123;
public static void fun(){
System.out.println(STR);//可以访问
System.out.println(num);//不能访问
}
}
非静态方法中,可以访问非静态或静态成员
public class Student{
static String STR="静态成员变量";
int num=123;
public void fun(){
System.out.println(STR);//可以访问
System.out.println(num);//可以访问
}
}
静态方法中不能使用this或super关键字
成员变量、局部变量、静态常量
成员变量:定义在类中的变量
成员变量随着对象的创建而存在,随着对象的回收而销毁。
作用范围在类内部,成员变量有初始值。
局部变量:定义在方法中的变量
局部变量随着方法的调用而存在,随着方法执行结束而销毁。
作用范围在方法内容,局部变量没有初始值,必须赋值后才能使用。
静态常量:被final、static修饰的成员变量
静态常量随着类加载而存在,随着类的销毁而销毁(通常程序运行结束).
作用范围在程序运行周期中,静态量有初始值,静态常量必须赋值。
public class ZOO {
//静态常量
public static final String ZOO_NAME = "重庆动物园";
//成员变量
private String address;
public ZOO(String address) {
this.address = address;
}
//静态方法
public static void welcome() {
//只能使用静态常量
System.out.println("欢迎光临" + ZOO_NAME);
}
//成员方法
public void sayHello(Animal animal) {
//局部变量
String str = "你好";
System.out.println(ZOO_NAME + "中的" + animal.getType() + "打招呼说" +
str);
System.out.println("地址:"+address);
}
}
public class Animal {
private String type;
public Animal(String type){
this.type=type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public class Main {
public static void main(String[] args) {
ZOO.welcome();
Animal bear = new Animal("棕熊");
new ZOO("杨家坪").sayHello(bear);
}
}
可变参数
当某个方法的参数是同一种类型且数量未知时,参数列表中可以使用可变参数定义。
修饰符 返回值类型 方法名(数据类型... 形参名){
//这时的参数的数量是可变的,[0,∞)
//整个参数是一个数组
}
特点
可变参数只能出现一次,且是最后一个参数
可变参数实际是一个数组
调用参数为可变参数的方法时,传递的实参要使用逗号隔开
public class Main {
/*
* 计算一些数字的总和
* */
public int getSum(int... nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
public static void main(String[] args) {
//
Main main = new Main();
//调用可变参数的方法,实参之间使用逗号隔开
System.out.println(main.getSum(1,2,3,4));
}
}
枚举
Java中的枚举是一个特殊的类,是一些常量的集合。
如星期可以用数字1-7表示,也可以用"周一到周天"表示,
也可以用SUN,MON,TUE,WED,THU,FRI,SAT这些来表示。
这三种都可以称为枚举,对星期这个概念进行枚举。
定义枚举类型
public enum 枚举类型名{
常量1,常量2...常量N
}
使用枚举类型
枚举类型名.常量名;
package test1;
/*
* 定义枚举类型
* 使用enum关键字替换class
* 枚举是一个特殊的"类",
* 将某个事物使用指定的内容进行一一例举
* */
public enum Week {
//定义一组枚举类型的常量集合
SUN,MON,TUE,WED,THU,FRI,SAT
}
package test1;
public class Main {
public static void main(String[] args) {
//遍历自定义的枚举类型
//for (Week value : Week.values()) {
// System.out.println(value);
//}
System.out.println("请输入星期的简写");
Scanner sc = new Scanner(System.in);
String input = sc.next();
//根据字符串获取对应的枚举数据
Week week = Week.valueOf(input);
//判断枚举类型
switch (week) {
case SUN:
System.out.println("星期天");
break;
case MON:
System.out.println("星期一");
break;
case TUE:
System.out.println("星期二");
break;
case WED:
System.out.println("星期三");
break;
case THU:
System.out.println("星期四");
break;
case FRI:
System.out.println("星期五");
break;
case SAT:
System.out.println("星期六");
break;
default:
System.out.println("单词有误");
}
}
}
内部类
内部类,是指定义在类中的类。
内部类通常使用private修饰,定义在类中,用于隐藏类的实现细节,将类进行封装
public class Outer{
//内部类通常private私有化,只能当前外部类中才能使用
private class Inner{
//这就是一个内部类
//这里可以定义属性和方法
}
}
内部类分为:成员内部类,静态内部类,局部内部类和匿名内部类。
匿名内部类
没有名称的内部类称为匿名内部类。
当某个方法的参数为接口或抽象类对象时,通常会先定义接口的实现类或抽象类的子类,再创建子类对
象作为方法的参数。
如果使用匿名内部类,就可以不用创建子类,而是直接通过匿名内部类作为参数使用。
使用案例
如
USB接口
//该接口中只有一个方法
public interface USB {
void start();
}
Computer类
public class Computer {
/*
* 当前方法的参数为接口类型对象
* */
public void powerOn(USB usb) {
usb.start();
}
}
如果要通过Computer对象调用powerOn()方法,就需要一个USB接口类型的实现类
创建一个USB的实现类--鼠标类
/*
USB的实现类,重写抽象方法
*/
public class Mouse implements USB{
@Override
public void start() {
System.out.println("鼠标接入");
}
}
这时调用Computer对象调用powerOn()方法
public class Main{
public static void main(String[] args){
Computer com = new Computer();
USB usb= new Mouse();
com.powerOn(usb);
}
}
当使用匿名内部类,就无需创建Mouse类
public class Main{
public static void main(String[] args){
Computer com = new Computer();
//不用创建USB的实现类,使用匿名内部类作为参数
//当前powerOn()小括号中的所有内容就是一个匿名内部类
com.powerOn(new USB(){
@Override
public void start() {
System.out.println("USB设备接入");
}
});
}
}
Lambda表达式
JDK8中的核心升级点。
通常用于简化匿名内部类的写法。
要简化的匿名内部类必须是函数式接口(只有一个抽象方法的接口)。
如上方的案例中,USB接口中只有一个抽象方法start()。
可以简化为
public class Main{
public static void main(String[] args){
Computer com = new Computer();
//使用lambda表达式后,简化匿名内部类的写法
computer.powerOn(() -> {
System.out.println("某USB设备接入");
});
}
}
Lambda表达式语法
(参数类型 参数名) -> {代码语句;}
小括号部分表示参数列表
->部分表示要执行什么
{}部分表示执行的内容
使用lambda表达式遍历集合
package test3;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
String[] names = {"张敏", "王海", "赵玉梅", "白玉文"};
//将数组转换为集合
List<String> nameList = Arrays.asList(names);
//遍历集合
//1.普通for循环化遍历下标 for(int i=0;i<names.length;i++){}
//2.增强for循环遍历元素 for(String name : names){}
//3.
//如集合对象.forEach()方法,参数为Consumer,是一个接口,这里不创建该接口的实现类,
//而是使用匿名内部类
nameList.forEach(new Consumer<String>() {
/*
* accept方法表示接受集合中的所有元素,方法参数就是每个元素
* */
@Override
public void accept(String s) {
System.out.println(s);
}
});
System.out.println("--------");
//如果要使用匿名内部类来表示参数为接口类型且该接口中只有一个抽象方法的时候,简化匿名内
//部类的写法
//Java8中引入了Lambda表达式 兰姆达 ,可以简化匿名内部类的写法
//()->{} 小括号部分相当于参数列表 ->表示执行某个操作 {}部分表示要执行的内容
nameList.forEach((String s) -> {
System.out.println(s);
});
System.out.println("-------");
//如果lambda表达式中只有一个参数,可以省略参数类型
nameList.forEach((s) -> {
System.out.println(s);
});
System.out.println("-------");
//如果只有一个参数,且省略了类型,可以省略小括号
nameList.forEach(s -> {
System.out.println(s);
});
System.out.println("-------");
//如果要执行的内容只有一句话,可以省略大括号
nameList.forEach(s -> System.out.println(s));
//最终简化
System.out.println("-------");
nameList.forEach(System.out::println);
}
}
作业案例
电子宠物
Pet类
/*
* 电子宠物类
* 属性
* 种类
* 昵称
*
* 行为
* 吃食物
* 玩玩具
* 唱歌
* */
public class Pet {
String type;
String nickName;
void eat(String food){
System.out.println(nickName+"正在吃"+food);
}
void play(String toy){
System.out.println(nickName+"正在玩"+toy);
}
void sing(){
System.out.println("我叫"+nickName+"♪♪,是一只"+type+"♪♪");
}
}
PetMachine类
import java.util.Scanner;
/*
* 宠物机
* 方法
* 创建宠物,得到宠物对象
* 主界面,需要一个宠物对象参数,调用该参数的行为
* */
public class PetMachine {
//定义在类中的变量,称为成员变量,有默认值
//整数 0
//浮点数 0.0
//boolean false
//引用 null
//Pet pet;
Scanner sc = new Scanner(System.in);
/*
* 创建宠物
* */
Pet createPet() {
//创建一个宠物对象
Pet pet = new Pet();
//定义方法中的变量,称为局部变量,必须先赋值再使用
String type = "喵咪";
System.out.println("请选择宠物类型");
System.out.println("1.猫咪");
System.out.println("2.小狗");
System.out.println("3.鹦鹉");
int input = sc.nextInt();
switch (input) {
case 1:
break;
case 2:
type = "小狗";
break;
case 3:
type = "鹦鹉";
break;
default:
System.out.println("输入有误");
}
pet.type = type;
System.out.println("请输入宠物昵称");
pet.nickName = sc.next();
return pet;
}
/*
* 主菜单
* */
void menu(Pet pet) {
//得到宠物
//Pet pet= createPet();
System.out.println("请选择功能");
System.out.println("1.喂食");
System.out.println("2.玩耍");
System.out.println("3.唱歌");
System.out.println("4.退出");
switch (sc.nextInt()) {
case 1:
System.out.println("请输入食物");
String food = sc.next();
pet.eat(food);
break;
case 2:
System.out.println("请输入玩具");
String toy = sc.next();
pet.play(toy);
break;
case 3:
pet.sing();
break;
case 4:
System.out.println("已退出");
//停止整个程序
System.exit(0);
break;
}
}
}
Test类
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//创建一个宠物对象
Pet pet = new Pet();
//定义方法中的变量,称为局部变量,必须先赋值再使用
String type = "喵咪";
System.out.println("请选择宠物类型");
System.out.println("1.猫咪");
System.out.println("2.小狗");
System.out.println("3.鹦鹉");
int input = sc.nextInt();
switch (input) {
case 1:
break;
case 2:
type = "小狗";
break;
case 3:
type = "鹦鹉";
break;
default:
System.out.println("输入有误");
}
pet.type = type;
System.out.println("请输入宠物昵称");
pet.nickName = sc.next();
while (true) {
System.out.println("请选择功能");
System.out.println("1.喂食");
System.out.println("2.玩耍");
System.out.println("3.唱歌");
System.out.println("4.退出");
switch (sc.nextInt()) {
case 1:
System.out.println("请输入食物");
String food = sc.next();
pet.eat(food);
break;
case 2:
System.out.println("请输入玩具");
String toy = sc.next();
pet.play(toy);
break;
case 3:
pet.sing();
break;
case 4:
System.out.println("已退出");
return;
}
}
}
}
Main类
public class Main {
public static void main(String[] args) {
//创建宠物机对象
PetMachine pm = new PetMachine();
//得到宠物
Pet pet = pm.createPet();
//循环菜单
while (true) {
pm.menu(pet);
}
}
}
登录注册管理员
Manager类
import java.util.Objects;
/*
* 登录系统的管理员类
* 属性
* 账号
* 密码
* 方法
* 全参构造
* 无参构造
* getter/setter
* toString
* */
public class Manager {
private String username;
private String password;
@Override
public String toString() {
return "Manager{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Manager manager = (Manager) o;
return Objects.equals(username, manager.username) && Objects.equals(password, manager.password);
}
public Manager() {
}
public Manager(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Shop类
/*
* 属性
* 管理员列表
* */
public class Shop {
//管理员列表
private Manager[] managers = new Manager[5];
/*
* new Shop();
* 定义无参构造方法,在创建当前类对象的同时,在数组中保存一个Manager对象
* */
public Shop() {
managers[0] = new Manager("admin", "123123");
}
/*
* 注册
* 实际是添加,将参数创建为完整对象,添加到数组中
* */
public boolean register(String name, String pwd) {
//判断用户名是否存在
for (Manager manager : managers) {
if (manager != null && manager.getUsername().equals(name)) {
System.out.println("该用户名已存在");
return false;
}
}
//将参数包装为一个完整对象
Manager manager = new Manager(name, pwd);
//保存到数组
for (int i = 0; i < managers.length; i++) {
if(managers[i]==null){
managers[i]=manager;
System.out.println("注册成功");
return true;
}
}
System.out.println("人数超过上限");
return false;
}
/*
* 登录
* */
public boolean login(String name, String pwd) {
//将登录时输入的账号密码封装为一个对象
Manager loginManager = new Manager(name, pwd);
//遍历管理员列表
for (Manager manager : managers) {
//如果有对象的参数和当前方法参数一致,说明登录成功
//if (manager != null && manager.getUsername().equals(name) && manager.getPassword().equals(pwd)) {
//调用重写后的equasl方法,如果两个对象的所有参数一致,视为同一个对象
if (loginManager.equals(manager)) {
return true;
}
}
return false;
}
}
Main类
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Shop shop = new Shop();
while (true) {
System.out.println("1.注册\t2.登录");
switch (sc.nextInt()) {
case 1:
System.out.println("请输入账号");
String name = sc.next();
System.out.println("请输入密码");
String pwd = sc.next();
shop.register(name, pwd);
break;
case 2:
System.out.println("请输入账号");
String loginName = sc.next();
System.out.println("请输入密码");
String loginPwd = sc.next();
if (shop.login(loginName, loginPwd)) {
System.out.println("登录成功");
} else {
System.out.println("用户名或密码错误");
}
break;
}
}
}
}
模拟游戏厅
Player类
import java.util.Objects;
/*
* 玩家类
* 属性
* 用户名
* 密码
* 余额
* */
public class Player {
private String name;
private String password;
private double balance;
/*
* 如果用户名和密码相同,equals比较结果为true
* */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Player player = (Player) o;
return Objects.equals(name, player.name) && Objects.equals(password, player.password);
}
@Override
public String toString() {
return "Player{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", balance=" + balance +
'}';
}
/*
* 只包含用户名和密码的构造方法,用于登录
* */
public Player(String name, String password) {
this.name = name;
this.password = password;
}
/*
* 全参构造,用于给所有参数赋值
* */
public Player(String name, String password, double balance) {
this.name = name;
this.password = password;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
Game类
/*
* 游戏类
* 属性
*
* 方法
* 开始游戏
*
* */
public class Game {
/*
* 游戏父类,定义玩游戏的方法,参数为玩家对象
* */
public void start(Player player) {
}
}
GameDemo类
import java.util.Random;
import java.util.Scanner;
/*
* 游戏子类:石头剪刀布
* */
public class GameDemo extends Game {
@Override
public void start(Player player) {
//生成1-3的随机数代表电脑出拳石头、剪刀、布
int com = new Random().nextInt(3) + 1;
System.out.println("请出拳:1.石头\t2.剪刀\t3.布");
Scanner sc = new Scanner(System.in);
int input = sc.nextInt();
if (input < 1 || input > 3) {
System.out.println("请输入正确的数字");
return;
}
//扣钱
player.setBalance(player.getBalance()-2);
//判断电脑出拳
switch (com) {
case 1:
//判断玩家出拳
if (input == 1) {
System.out.println("你出的是石头,电脑出的是石头,平局!");
} else if (input == 2) {
System.out.println("你出的是剪刀,电脑出的是石头,你输了!");
} else {
System.out.println("你出的是布,电脑出的是石头,你赢了!");
player.setBalance(player.getBalance()+4);
}
break;
case 2:
if (input == 1) {
System.out.println("你出的是石头,电脑出的是剪刀,你赢了!");
player.setBalance(player.getBalance()+4);
} else if (input == 2) {
System.out.println("你出的是剪刀,电脑出的是剪刀,平局!");
} else {
System.out.println("你出的是布,电脑出的是剪刀,你输了!");
}
break;
case 3:
if (input == 1) {
System.out.println("你出的是石头,电脑出的是布,你输了!");
} else if (input == 2) {
System.out.println("你出的是剪刀,电脑出的是布,你赢了!");
player.setBalance(player.getBalance()+4);
} else {
System.out.println("你出的是布,电脑出的是布,平局!");
}
break;
}
}
}
GuessNum类
import java.util.Random;
import java.util.Scanner;
public class GuessNum extends Game {
/*
* 重写玩游戏的方法,实际是该类表示的游戏子类的具体玩法
* */
@Override
public void start(Player player) {
//随机1-100猜数
int ans = new Random().nextInt(100) + 1;
Scanner sc = new Scanner(System.in);
while (true) {
//判断余额
if (player.getBalance() <= 0) {
System.out.println("余额不足,请充值");
break;
}
//扣钱
double newBalance = player.getBalance() - 2;
player.setBalance(newBalance);
//开始游戏
System.out.println("请猜数");
int input = sc.nextInt();
if (input > ans) {
System.out.println("猜大了!");
} else if (input < ans) {
System.out.println("猜小了!");
} else {
System.out.println("猜对了!");
//加钱
player.setBalance(player.getBalance() + 10);
break;
}
}
}
}
GameHouse类
import java.util.Scanner;
/*
* 游戏厅类
* 属性
* 玩家集合
*
* 方法
* 注册
* 登录
* 玩游戏
* 充值
* */
public class GameHouse {
//玩家对象数组
private Player[] playerList = new Player[10];
/*
* 注册
* */
public boolean register(Player player) {
//遍历当前玩家集合
for (Player p : playerList) {
//如果注册的玩家名称已存在,无法注册
if (p != null && p.getName().equals(player.getName())) {
System.out.println("该用户已存在");
return false;
}
}
//添加参数到数组中
for (int i = 0; i < playerList.length; i++) {
if (playerList[i] == null) {
playerList[i] = player;
System.out.println("注册成功");
return true;
}
}
System.out.println("人数已达上限");
return false;
}
/*
* 登录
* 参数为只有账号密码的玩家对象
* 返回值为null或有账号密码和余额的玩家对象
* */
public Player login(Player player) {
for (Player p : playerList) {
if (p != null && p.equals(player)) {
//返回已存在的玩家对象
return p;
}
}
return null;
}
/*
* 充值
* */
public void charge(Player player){
System.out.println("请输入充值金额");
Scanner sc = new Scanner(System.in);
int money = sc.nextInt();
if(money>0){
player.setBalance(player.getBalance()+money);
}else{
System.out.println("不能是负数");
}
}
/*
* 玩家玩游戏
* */
public void playGame(Game game,Player player){
game.start(player);
}
}
Main类
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Main main = new Main();
main.menu();
}
Scanner sc = new Scanner(System.in);
GameHouse gh = new GameHouse();
/*
* 系统菜单
* */
public void menu() {
System.out.println("1.登录\t2.注册\t3.退出");
switch (sc.nextInt()) {
case 1:
System.out.println("请输入用户名");
String loginName = sc.next();
System.out.println("请输入密码");
String loginPwd = sc.next();
//登录对象
Player player = new Player(loginName, loginPwd);
//登录后的对象
Player login = gh.login(player);
if (login == null) {
System.out.println("用户名或密码错误");
} else {
//进入玩家菜单
playerMenu(login);
}
break;
case 2:
System.out.println("请输入用户名");
String regName = sc.next();
System.out.println("请输入密码");
String regPwd = sc.next();
//注册对象
Player regPlayer = new Player(regName, regPwd);
//调用注册
gh.register(regPlayer);
//注册成功,跳转到本方法
break;
case 3:
System.exit(0);
break;
}
menu();//递归调用
}
/*
* 玩家菜单
* */
public void playerMenu(Player player) {
System.out.println("1.查看个人信息\t2.充值\t3.玩游戏\t4.返回主界面");
switch (sc.nextInt()) {
case 1:
System.out.println(player);
break;
case 2:
gh.charge(player);
break;
case 3:
//进入游戏菜单
gameMenu(player);
break;
case 4:
//调用主菜单
menu();
break;
}
playerMenu(player);
}
/*
* 游戏界面
* */
public void gameMenu(Player player){
System.out.println("1.猜数游戏\t2.猜拳游戏\t3.返回玩家界面");
switch (sc.nextInt()) {
case 1:
GuessNum guessNum = new GuessNum();
gh.playGame(guessNum,player);
break;
case 2:
gh.playGame(new GameDemo(),player);
break;
case 3:
//进入玩家菜单
playerMenu(player);
break;
}
gameMenu(player);
}
}