模块二 Java面向对象
类和对象
类和对象及引用
对象主要指现实生活中客观存在的实体,在Java语言中对象体现为内存空间中的一块存储区域。
类的定义方法
class 类名{类体;
}class Person{ //类名命名:通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写}
成员变量的定义
class Person{//成员变量命名:当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写 。String name;
}
对象的创建
new Person(); //创建改类的对象,这个创建的过程叫类的实例化
- 当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个过程叫做类的实例化。
- 创建对象的本质就是在内存空间的堆区申请一块存储区域, 用于存放该对象独有特征信息。
引用的定义
Person p = new Person();
p.name = "张飞";
System.out.println(p.name);
- 使用引用数据类型定义的变量叫做引用型变量,简称为"引用"。
- 引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。
成员变量的初始值
类型 | 初始值 |
---|---|
byte、short、int、long、float、double、char | 0 |
boolean | false |
引用类型 | null |
案例题目
编程实现Point类的定义,特征有:横纵坐标(整数),要求在main方法中声明Point类型的引用指向Point对象并打印特征,然后将横纵坐标修改为3和5后再次打印
public class Point{int x;int y;public static void main(String[] args){System.out.println("横坐标为" + x + "纵坐标为" + y);//输出横坐标为0纵坐标为0Point p = new Point();p.x = 3;p.y = 5;System.out.println("横坐标为" + x + "纵坐标为" + y); //输出横坐标为3纵坐标为5}
}
成员方法
成员方法的定义
class Person(){void run(){ //成员方法System.out.println("跑步");}void runFast(){ //当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写。System.out.println("快速跑步");}
}
返回值类型
- 基本类型(byte、short、int、long、float、double、boolean、char)
- 引用类型(String、Person等)
- 无返回值类型 void
无参方法
class Person(){void run(){ // 无参无返回值System.out.println("跑步" + m + "米");}String run(){ // 无参有返回值return "跑步";}
}
有参方法
class Person(){void run(int m){ // 有参无返回值System.out.println("跑步" + m + "米");}String run(int m){ // 有参有返回值String s = "跑步" + m + "米";return s;}
}
方法和封装
构造方法
与类名名称完全相同并且没有返回值类型,void也不能有
class 类名{类名(形参列表){构造方法体;}
}
class Person{Person(){ //构造方法构造方法体;}
}
- 默认构造方法:编译器会自动添加一个空的构造方法 -->Person(){}
- 如果类中出现了构造方法,编译器将不会提供任何构造方法
- 创建类的对象时会默认调用构造方法
public class Person{Person(){System.out.println("构造方法");}public static void main(String[] args){Person p = new Person(); // 输出“构造方法”}
}
成员变量初始化
public class Person{String name;int agePerson(String s, int a){age = a;name = s;}public static void main(String[] args){Person p = new Person("张飞", 30); //输出姓名为张飞年龄为30System.out.println("姓名为" + p.name + "年龄为" + p.age);}
}
方法重载
方法名称相同,参数列表不同,这样的方法之间构成重载关系(Overload)
public class Person{void run(){System.out.println("run()");}void run(int a){ //参数个数不同。可以构成重载System.out.println("run(int)");}void run(int a, int b){ //参数个数不同。可以构成重载System.out.println("run(int, int)");}void run(int a, double b){ //参数类型不同。可以构成重载System.out.println("run(int, double)");}void run(double b, int a){ //参数顺序不同。可以构成重载System.out.println("run(double, int)");}/* int run(double b, int a){ //参数返回值不同。不可以构成重载System.out.println("run(double, int)");} */public static void main(String[] args){Person p = new Person();p.run(); //输出run()p.run(1); //输出run(int)p.run(1, 2); //输出run(int, int)p.run(1, 3.14); //输出run(int, double)p.run(3.14, 1); //输出run(double, int)}
}
重载的实际意义
调用者只需要记住一个方法名,通过参数不同控制不同版本,实现不同的功能;
this关键字
- 如果在构造方法中出现了this关键字,则代表当前正在构造的对象
- 如果在成员方法中出现了this关键字,则代表当前正在调用的对象
- this关键字本质上就是当前类类型的一个引用
public class ThisTest {ThisTest() {//this代表当前正在构造的对象System.out.println("构造方法 this = " + this);}void show() {//this代表当前正在调用的对象System.out.println("成员方法 this = " + this);}public static void main(String[] args) {ThisTest tt = new ThisTest(); //构造方法 this = ThisTest@6bf256fatt.show(); //成员方法 this = ThisTest@6bf256faSystem.out.println("main方法 tt = " + tt); //main方法 tt = ThisTest@6bf256fa}
}
在构造方法、成员方法中访问成员变量时,编译器会加上==this.==的前缀,当不同的对象调用同一个方法时,由于调用的对象不同导致this关键字不同,从而使this.方式访问的结果不同。
就近原则
- 当局部变量与成员变量名相同时,在方法体内会优先使用局部变量
- 如果希望使用成员变量,需要在成员变量前面加==this.==关键字
public class Person{String name;int agePerson(String name, int age){this.name = name;this.age = age;}public static void main(String[] args){Person p = new Person("张飞", 30); //输出姓名为null,年龄为0System.out.println("姓名为" + p.name + "年龄为" + p.age); //实际使用的是形参非实参}
}
使用this调用构造方法
不能无参构造器调用有参构造器且有参构造器调用无参构造器,会造成递归构造器调用
// 造成递归构造器调用报错
public class Person{String name;int agePerson(){this("张飞",30); // 无参构造器调用有参构造器System.out.println("无参构造");}Person(String name, int age){this(); // 有参构造器调用无参构造器this.name = name;this.age = age;System.out.println("有参构造");}public static void main(String[] args){Person p = new Person(); //输出有参构造System.out.println("姓名为" + p.name + "年龄为" + p.age); //实际使用的是形参非实参}
}
空指针异常
public class Person{String name;int agePerson(){}void run(){System.out.println("跑步");}public static void main(String[] args){Person p = null; //空的引用,无内存空间p.run(); //NullPointException}
}
阶乘
for循环方式实现
//计算1~n的阶乘
public class FactorialTest {int factorial(int n) {int sum = 1;for (int i = 1; i < n; i++) {sum *= i;}return sum;}public static void main(String[] args) {FactorialTest factorialTest = new FactorialTest();int sum = factorialTest.factorial(10);System.out.println(sum);}
}
n*(n-1)的阶乘
//计算1~n的阶乘
public class FactorialTest {int factorial(int n) {if (1 == n){return n;}return n*factorial(n-1); //调用自身}public static void main(String[] args) {FactorialTest factorialTest = new FactorialTest();int sum = factorialTest.factorial(10);System.out.println(sum);}
}
递归基本概念:自身调用自身
费式数列
递归方式实现
public class FeiShiTest {int show(int n) {int num = 0;if (n == 1 || n == 2) {return 1;}
// num += show(n - 1) + show(n - 2);
// return num;return show(n - 1) + show(n - 2);}public static void main(String[] args) {FeiShiTest fst = new FeiShiTest();int num = fst.show(8);System.out.println(num);}
}
***递归计算量大会影响性能***:调用方法不停的申请栈帧,消耗性能,尽量使用递推
递推方式实现
public class FeiShiTest {int show(int n) {int ia = 1;int ib = 1;if (n == 1 || n == 2) {return 1;}for (int i = 3; i <= n; i++) {int ic = ia + ib;ia = ib;ib = ic;}return ib;}public static void main(String[] args) {FeiShiTest fst = new FeiShiTest();int num = fst.show(8);System.out.println(num);}
}
封装
- 通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生活不符。
- 为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。
private关键字
public class Student {private int id; //成员变量私有化private String name; //成员变量私有化public int getId() { //提供get、set方法访问和设置成员变量return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
案例题
编程实现学生信息的录入和打印
public class StudentTest2 {public static void main(String[] args) {System.out.println("请输入学生数量:");Scanner sc = new Scanner(System.in);int num = sc.nextInt();Student[] students = new Student[num];for (int i = 0; i < num; i++) {System.out.println("请输入第" + (i + 1) + "个学生姓名:");String name = sc.next();System.out.println("请输入第" + (i + 1) + "个学生学号:");int id = sc.nextInt();students[i] = new Student(id, name);}System.out.println(Arrays.toString(students));}
}
static关键字和继承
static关键字
成员变量从对象层提升为类层级,整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关
public class Person{private String name;private int id;private static String country; // 静态成员变量 被所有对象共享public static void show(){System.out.println("静态show()方法");}
}
可以使用==引用.的方式调用,但更推荐使用类名.==的方式调用
Person p = new Person();
p.country;
Person.country; //推荐使用
Person.show();
- 在非静态成员方法中,既能访问静态成员变量也能访问非静态成员变量
public class StaticTest {private int cnt = 1; // 隶属于对象层级,也就是每个对象都拥有独立的一份private static int snt = 2; // 隶属于类层级,也就是所有对象都共享同一份// 自定义非静态的成员方法 需要使用引用.的方式访问public void show() {// 打印1System.out.println("cnt = " + this.cnt); // 打印2 静态成员被所有对象共享,this关键字可以省略System.out.println("snt = " + this.snt); public static void main(String[] args) {StaticTest st = new StaticTest();st.show(); }
}
- 静态成员方法中,只能访问静态的成员变量,不能访问非静态的成员变量,因为非静态的成员变量需要使用引用去调用,使用==类名.==的方式调用时没有引用,无法访问非静态成员变量
public class StaticTest {private int cnt = 1; // 隶属于对象层级,也就是每个对象都拥有独立的一份private static int snt = 2; // 隶属于类层级,也就是所有对象都共享同一份public static void test() {// StaticTest st = new StaticTest();// 报错 静态成员方法中没有this关键字,因为是可以通过类名.方式调用的System.out.println("cnt = " + cnt); // 打印2 System.out.println("snt = " + snt);}public static void main(String[] args) {StaticTest.test(); //这里调用的使用还没有引用}
}
构造块和静态代码块
构造块
- 在类体中直接使用=={}==括起来的代码块
- 当需要执行构造方法体之前做一些准备工作时,则将准备工作的写在构造块中
- 构造块执行顺序 > 构造方法
- 每创建一个对象都会执行一次构造块
public class BlockTest {{System.out.println("构造块");}public BlockTest(){System.out.println("===构造方法体");}public static void main(String[] args) {BlockTest bt = new BlockTest();//输出构造块//输出===构造方法体}
}
静态代码块
- 使用static关键字修饰的构造块
- 会随着类的加载准备就绪,只执行一次!!
- 加载顺序静态代码块 > 构造块 > 构造方法
- 需要执行代码块之前随着类的加载做一些准备工作时,编写到静态代码块里
public class BlockTest {{System.out.println("构造块");}static {System.out.println("###静态代码块");}public BlockTest() {System.out.println("===构造方法体");}public static void main(String[] args) {BlockTest bt = new BlockTest();BlockTest bt2 = new BlockTest();//###静态代码块 (只加载一次)//构造块//===构造方法体//构造块//===构造方法体}
}
单例模式(重点)
- 一个类对外只提供一个对象
饿汉式
- 不论是否被调用都会初始化一个对象(线程安全,推荐使用)
public class Singleton {private Singleton(){ //私有化构造方法,无法使用new创建对象}private static Singleton sin = new Singleton(); //私有静态对象,随着类加载只生成一个对象public static Singleton getInstance(){ //只能通过Singleton.getInstance获取对象return sin;}
}
懒汉式
- 当被调用时再去创建对象(线程不安全)
public class Singleton {private Singleton() {}private static Singleton sin = null; //初始设置引用为nullpublic static Singleton getInstance() {if (sin == null) { //被调用时发现引用为null再创建对象sin = new Singleton();}return sin; //不为null直接返回,为null则创建一个对象再返回}
}
继承
基本概念
-
当多个类有共同的特征或者特性时,提取成为一个公共类,让其他子类继承
-
使用extends关键字来表示继承关系
public class Person{ //基类}
class Worker extends Person{ //子类}
- 使用继承提高了代码的可复用性、可维护性、扩展性,是多态的前提条件
- 子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承,只是不能直接访问
- 子类默认调用父类的无参构造方法,来初始化从父类中继承的成员变量,相当于在子类构造方法第一行加了super()(编译器自动添加,无需手动编写)
- 子类的有参构造方法会调用父类的有参构造方法
方法重写(Override)
- 当父类中继承下来的方法不满足子类需求,重新写一个方法覆盖父类中的原始方法,这种方式叫做方法的重写
- 使用@Override注解标注
- 重写的方法名必须与父类中方法名称相同
- 重写的方法返回值类型可以是父类或父类的子类类型(java5之后支持)
- 重写方法的权限修饰符不能变小,可以相同或者变大(如父类为public,子类不能为private)
- 重写的方法不能抛出更大的异常
public class Person{ //基类public void show(){System.out.println("show人类");}
}
class Worker extends Person{ //子类@Override //标注/注解 是对父类方法重写的说明,若没有则造成编译报错public void show(){System.out.println("show工人");}
}
构造块和静态代码块的考点
- 先执行父类的静态代码块,再执行子类的静态代码块。
- 执行父类的构造块,执行父类的构造方法体。
- 执行子类的构造块,执行子类的构造方法体。
访问控制
修饰符 | 本类 | 同一个包中的类 | 子类 | 其他类 |
---|---|---|---|---|
private | 可以访问 | 不能访问 | 不能访问 | 不能访问 |
protected | 可以访问 | 可以访问 | 可以访问 | 不能访问 |
public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
默认 | 可以访问 | 不能访问 | 不能访问 | 不能访问 |
final关键字
- final修饰类体,该类不可被继承,主要防止滥用继承
public final class FinalTest{}
- final修饰成员方法体,该方法不可以被重写
public class FinalVoidTest{public final void show(){}
}
-
final修饰成员变量,要求成员变量必须初始化,且不可被改变
Thread类中MAX_PRIORITY
public class FinalMemberTest{private final int cnt = 1; //直接初始化{cnt = 2; //构造块中初始化}public FinalMemberTest(){cnt = 3; //构造方法中初始化}
}
- 设置常量
public static final double PI = 3.14
多态和特殊类
多态
- 子类未重写父类方法,调用的是父类的方法(父类方法不满足子类需求)
public class Father { //父类public void show() {System.out.println("我是爸爸");}}
public class Son extends Father { //子类@Overridepublic void show() {super.show();System.out.println("我是儿子");}public void goToSchool(){System.out.println("儿子上学");}public static void main(String[] args) {Father father = new Son(); //父类类型引用指向子类对象father.show(); //打印 我是儿子}
}
- 编译阶段调用父类show()方法,运行阶段调用子类show()方法
类型转换
- 父类类型引用调用子类独有方法,父类转子类–>大转小需要强转
public static void main(String[] args) {Father father = new Son(); //父类类型引用指向子类对象((Son)father).goToSchool(); //打印 儿子上学
}
- 非继承关系强制转换–>ClassCastException 类型转换异常(运行阶段异常)
- 使用instanceof关键字判断对象是否为类的实例
if(father instanceof Son){((Son)father).goToSchool();
}else{...
}
抽象类
- abstract 关键字
public abstract class AbstractTest{}
- 不能实例化,也就是不能创建对象
- 可以有成员方法、成员变量、构造方法
- 可以有抽象方法,也可以没有抽象方法
public abstract class AbstractTest{private int x;public AbstractTest(){}public int getX(){return x;}public static void main(String[] args){AbstractTest at = new AbstractTest(); //报错}
}
- 抽象类不能创建对象,由于抽象类中可能会有抽象方法,为了防止抽象方法被调用,禁止创建抽象类的对象
- 抽象类的意义不在于创建对象而在于被继承(模板)
- 继承抽象类必须重写抽象方法,否则该类也要变成抽象类
笔试考点
- private和abstract关键字不能共同修饰一个方法
//子类不能继承父类构造方法、私有方法,无法被子类重写,无意义
private abstract void show();
- final和abstract关键字不能共同修饰一个方法
//final不能被重写只能被继承;abstract只能被重写后使用,相互矛盾
public final abstract void show();
- static和abstract关键字不能共同修饰一个方法
//abstract类不能创建对象,使用static之后就可以使用类名.访问,不允许直接调用抽象方法
public static abstract void show();
接口
- 接口里面的成员变量只能是常量
public interface InterfaceTest1{/*public static final*/ int CNT = 1; //里面只能有常量
}
- 接口内方法必须是抽象方法(java1.9之后接口内允许有私有方法)
public interface InterfaceTest2{private void show1(); //允许/*public abstract*/ void show2(); //建议写上提高代码可读性 public void show3(); //不允许
}
- 无构造方法
- 可以多实现(implements)
public class InterfaceTest3 implements InterfaceTest2,InterfaceTest1{}
- 类实现接口内抽象方法,必须也实现该接口父类的抽象方法
类和接口的关系
名称 | 关键字 | 关系 |
---|---|---|
类和类 | extends | 单继承 |
接口和接口 | extends | 支持多继承 |
类和接口 | implements | 多实现 |
接口和抽象类的区别
- 定义方式:接口是使用interface关键字;抽象类是使用abstract class关键字
- 继承方式:实现接口使用implements关键字,而且可以多实现;抽象类使用extends关键字,只能单继承
- 方法:抽象类中可以有成员方法;接口中只能有抽象方法
- 子类:抽象类中的方法子类不是必须重写;接口中的方法子类必须重写(java8之前的版本)
- 从java8开始增加新特性,接口中可以出现非抽象方法和静态方法,单非抽象方法需要用default关键字修饰
public interface Hunter {public default void show(){} //仅仅是接口中的默认功能,子类可以自由选择是否重写public static void show1(){} //静态方法属于类层级,可以用类名.的方式调用
}
特殊类
内部类
普通内部类
- 将一个类定义在另一个类的类体内
- 和普通类一样可以拥有成员变量、成员方法、构造方法
- 和普通类一样可以使用final、abstract关键字修饰
- 创建内部类对象需要使用外部类对象来创建
- 可以使用private和protected关键字修饰
- 如果使用内部类访问外部类与本类
public class NormalOuter {private int cnt = 3;public class NormalInner {private int cnt = 2;public void show1(){System.out.println(this.cnt);}public void show2(){System.out.println(NormalOuter.this.cnt);}}public static void main(String[] args) {//声明NormalOuter类型引用指向Normal对象NormalOuter no = new NormalOuter();//使用NormalOuter类型引用指向内部类对象NormalOuter.NormalInner nn = no.new NormalInner();//输出2nn.show1();//输出3nn.show2();}
}
静态内部类
- 使用static关键字修饰的内部类,隶属于类层级
- 静态内部类不能访问外部类非静态的成员
public class StaticOuter {private int cnt = 1;private static int snt = 2;public static class StaticInner {private int cnt = 3;private static int snt = 4;public void show(int cnt) {System.out.println("cnt = " + cnt); //就近原则,打印的是形参System.out.println("内部类cnt = " + this.cnt); //打印3,内部类的成员变量System.out.println("内部类静态snt = " + snt); //打印4,内部类的静态成员变量System.out.println("外部类cnt = " + new StaticOuter().cnt); // 打印1,外部类的成员变量System.out.println("外部类cnt = " + cnt); //报错,无法直接访问System.out.println("外部类静态snt = " + StaticOuter.snt); //打印2,外部类的静态成员变量}}
}
局部内部类
- 将一个类定义在方法体内
- 只能在该方法的内部使用
- 可以在方法体的内部直接创建对象
- 不能使用访问控制符(public、private)
- 可以使用外部方法的局部变量,但必须是使用final修饰的
public class AreaOuter {private int cnt = 1;public void show() {// 定义一个局部变量进行测试,从Java8开始默认理解为final关键字修饰的变量// 虽然可以省略final关键字,但建议还是加上final int ic = 4;// 定义局部内部类,只在当前方法体的内部好使 拷贝一份class AreaInner {private int ia = 2;public AreaInner() {System.out.println("局部内部类的构造方法!");}public void test() {int ib = 3;System.out.println("ia = " + ia); // 2System.out.println("cnt = " + cnt); // 1//ic = 5; ErrorSystem.out.println("ic = " + ic); // 4}}// 声明局部内部类的引用指向局部内部类的对象AreaInner ai = new AreaInner();ai.test();}
}
回调模式
- 如果一个方法的形参是接口类型,在调用该方法时要创建一个实现了该接口类型的对象,并且会调用到实现了该接口定义的方法。
public interface AnonymousInterface {void show();
}
public class AnonymousInterfaceImpl implements AnonymousInterface {@Overridepublic void show() {System.out.println("这里是AnonymousInterfaceImpl");}
}
public class AnonymousInterfaceTest {public void show(AnonymousInterface ai){}public static void main(String[] args) {AnonymousInterfaceTest anonymousInterfaceTest = new AnonymousInterfaceTest();//传递实现类 多态的一种表现形式anonymousInterfaceTest.show(new AnonymousInterfaceImpl()); }
}
匿名内部类
public class AnonymousInterfaceTest {public void show(AnonymousInterface ai){}public static void main(String[] args) {AnonymousInterfaceTest anonymousInterfaceTest = new AnonymousInterfaceTest();//匿名内部类anonymousInterfaceTest.show(new AnonymousInterface() {@Overridepublic void show() {System.out.println("这里是匿名内部类!");}});//java8 Lambda表达式anonymousInterfaceTest.show(()->{System.out.println("这里是Lambda表达式替代匿名内部类");});}
}
枚举
- 使用public static final修饰常量比较繁琐,使用enum关键字来定义枚举类型取代常量
- 从java5开始,是一种引用数据类型
- 枚举值是当前类的引用类型,默认使用private static final修饰,因此采用==枚举类型.==的方式调用
- 枚举类型可以自定义构造方法,但访问控制符必须是private(可省略)
public enum DirectionEnum {UP("向上"),DOWN("向下"),LEFT("向左"),RIGHT("向右");private String desc;/*private*/ DirectionEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}public static void main(String[] args) {//输出 向下System.out.println(DirectionEnum.DOWN.getDesc());}
}
public class DirectionTest {public static void test(DirectionEnum directionEnum){switch (directionEnum){case UP:System.out.println("向上");break;case DOWN:System.out.println("向下");break;case LEFT:System.out.println("向左");break;case RIGHT:System.out.println("向右");break;default:System.out.println("你好");}}public static void main(String[] args) {DirectionTest.test(DirectionEnum.DOWN);}
}
- 所有的枚举类都继承自java.lang.Enum类,不能再继承其他类,但是可以实现接口
常用方法
方法 | 作用 |
---|---|
static T[] values() | 返回当前枚举类所有对象 |
String toString() | 返回当前枚举类对象的名称 |
int ordinal() | 返回枚举对象在枚举类中的索引值 |
static T valueOf(String str) | 将参数指定的字符串名转为当前枚举类的对象 |
int compareTo(E o) | 使用当前枚举对象和传入的枚举对象比较顺序(返回传入的枚举对象索引位置-当前枚举对象索引位置) |
public class DirectionTest {public static void test(DirectionEnum directionEnum){switch (directionEnum){case UP:System.out.println("向上");break;case DOWN:System.out.println("向下");break;case LEFT:System.out.println("向左");break;case RIGHT:System.out.println("向右");break;default:System.out.println("你好");}}public static void main(String[] args) {DirectionEnum[] values = DirectionEnum.values();//java.lang.IllegalArgumentException: No enum constant com.miaoxun.study01.DirectionEnum.STAND//DirectionEnum directionEnum = DirectionEnum.valueOf("STAND"); //Error//将字符串转换为枚举对象DirectionEnum directionEnum1 = DirectionEnum.valueOf("UP");for (int i = 0; i < values.length; i++) {//打印枚举值:UP 当前索引值为:0System.out.println("枚举值:" + values[i].toString() + " 当前索引值为:" + values[i].ordinal());//与DOWN比较结果为-1System.out.println("与" + directionEnum1.toString() + "比较结果为" + values[i].compareTo(directionEnum1));}System.out.println(Arrays.toString(values));//打印[UP, DOWN, LEFT, RIGHT]System.out.println(Arrays.toString(values));}
}
- 枚举类实现接口,实现接口定义的成员方法可以整个类重写一次,也可以每个枚举对象单独全部重写(匿名内部类写法)
public enum DirectionEnum implements DirectionInterface {UP("向上") {@Overridepublic void show() {System.out.println("专属于向上的show()");}},DOWN("向下"){@Overridepublic void show() {System.out.println("专属于向下的show()");}},LEFT("向左"){@Overridepublic void show() {System.out.println("专属于向左的show()");}},RIGHT("向右"){@Overridepublic void show() {System.out.println("专属于向右的show()");}};private String desc;DirectionEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}public static void main(String[] args) {System.out.println(DirectionEnum.DOWN.getDesc());}// @Override
// public void show() {
// System.out.println("整个枚举类公用一个show()方法");
// }
}
注解
- 从java5开始引入的引用数据类型(特殊的接口)
- 自定义注解默认继承java.lang.annotation.Annotation接口
- 通过**@+注解名称**的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等
- 若一个注解中没有任何的成员,则被成为标记注解/标识注解
public @interface AnnoTest {}
@AnnoTest
public class Test{}
- 注解中没有成员方法,只有成员变量,以无形参的形式来声明,方法名=成员变量名,返回值=成员变量类型
- 类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型
public @interface AnnoTest {public String value(); //声明一个String类型的成员变量,名字为valuepublic String name();public DirectionEnum direction();
}
//无默认值必须要给成员赋值
@AnnoTest(value = "123", name = "mm", direction = DirectionEnum.UP)
public class Test{}
- 注解内的成员使用default给定初始值,使用注解可以不给成员赋值
public @interface AnnoTest {public String value() default "321"; //声明一个String类型的成员变量,名字为valuepublic String name() default "ll";public DirectionEnum direction() default DirectionEnum.UP;
}@AnnoTest()
public class Test{}
元注解
- 元注解是可以注解到注解上的注解,是一种基本注解le
- 主要有==@Retetion、@Documented、@Target、@Inherited、@Repeatable==
@Retetion
- 主要作用于一个注解上,说明该注解的生命周期
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM 中,默认方式。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM 中,所以在程序运行时可以获取到它们。
- RetentionPolicy是一个enum类型,有SOURCE、CLASS、RUNTIME三个枚举值
//@Retention(RetentionPolicy.SOURCE) //表示下面的注解在源代码中有效
//@Retention(RetentionPolicy.CLASS) //表示下面的注解在编译时有效
//@Retention(RetentionPolicy.RUNTIME) //表示下面的注解在程序运行时有效
public @interface AnnoTest {public String value() default "321"; //声明一个String类型的成员变量,名字为valuepublic String name() default "ll";public DirectionEnum direction() default DirectionEnum.UP;
}
@Documented
- 主要作用于一个注解上,加上注解后文档内可以看到该注解,否则看不到
@Documented
public @interface AnnoTest {public String value() default "321"; //声明一个String类型的成员变量,名字为valuepublic String name() default "ll";public DirectionEnum direction() default DirectionEnum.UP;
}
@Target
- 用于指定被修饰的注解能用于哪些元素的修饰
- ElementType 枚举
枚举值 | 用处 |
---|---|
ElementType.ANNOTATION_TYPE | 可以给注解注解 |
ElementType.CONSTRUCTOR | 可以给构造方法注解 |
ElementType.FIELD | 可以给成员变量注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量注解 |
ElementType.METHOD | 可以给方法注解 |
ElementType.PACKAGE | 可以给包注解 |
ElementType.PARAMETER | 可以给方法内的参数注解 |
ElementType.TYPE | 可以给类型注解,如类、接口、枚举 |
@Target({ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.METHOD})
public @interface AnnoTest {public String value() default "321"; //声明一个String类型的成员变量,名字为valuepublic String name() default "ll";public DirectionEnum direction() default DirectionEnum.UP;
}
@Inherited
- 如果一个父类被使用@Inherited注解,且子类没有被注解,则继承父类的注解
@Repeatable
-
Repeatable(可重复的)
-
一个注解描述多个角色
@Target(ElementType.TYPE_USE)
public @interface ManTypes {ManType[] value();
}
@Repeatable(value = ManTypes.class)
public @interface ManType {String value();
}
@ManType(value = "职工")
@ManType(value = "超人")
//@ManTypes({@ManType(value = "职工"), @ManType(value = "超人")})
public class Test{}
作业
- 编程实现以下需求:
定义一个长度为[16][16]的整型二维数组并输入或指定所有位置的元素值,分别实现二维数组中所有行和所有列中所有元素的累加和并打印。
再分别实现二维数组中左上角到右下角和右上角到左下角所有元素的累加和并打印。
/*** @author miaoxun* 编程实现以下需求:* 定义一个长度为[16][16]的整型二维数组并输入或指定所有位置的元素值,* 分别实现二维数组中所有行和所有列中所有元素的累加和并打印。* 再分别实现二维数组中左上角到右下角和右上角到左下角所有元素的累加和并打印。*/
public class Homework1 {public static void main(String[] args) {//定义一个长度为[16][16]的整型二维数组int[][] arr = new int[16][16];//定义局部变量统计所有元素的累加和int sum = 0;//定义局部变量统计二维数组中左上角到右下角的元素累加和int leftToRightSum = 0;//定义局部变量统计二维数组中左上角到右下角的元素累加和int rightToLeftSum = 0;for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {//指定所有位置的元素值arr[i][j] = j;System.out.print(arr[i][j]);//计算所有元素的累加和sum += arr[i][j];//计算二维数组中左上角到右下角的元素累加和if (j == i) {leftToRightSum += arr[i][j];}//计算二维数组中右上角到左下角的元素累加和if (j == (arr.length - i)){rightToLeftSum += arr[i][j];}}System.out.println();}System.out.println("所有元素求和的值为:" + sum);System.out.println("二维数组中左上角到右下角的元素累加和为:" + leftToRightSum);System.out.println("二维数组中右上角到左下角的元素累加和为:" + rightToLeftSum);}
}
-
编程实现控制台版并支持两人对战的五子棋游戏。
(1)绘制棋盘 - 写一个成员方法实现
(2)提示黑方和白方分别下棋并重新绘制棋盘 - 写一个成员方法实现。
(3)每当一方下棋后判断是否获胜 - 写一个成员方法实现。
(4)提示: 采用二维数组来模拟并描述棋盘,棋盘如下:
import java.util.Scanner;/*** @author miaoxun* 编程实现控制台版并支持两人对战的五子棋游戏。*/
public class Homework2 {/*** 第一步:使用二维数组绘制五子棋盘** @return 返回五子棋盘二维数组*/public char[][] drawChessboard() {char[] arr = {' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};char[][] chessboard = new char[17][17];for (int i = 0; i < chessboard.length; i++) {for (int j = 0; j < chessboard[i].length; j++) {if (0 == i) {chessboard[i] = arr;} else if (0 == j) {chessboard[i][j] = arr[i];} else {chessboard[i][j] = '+';}}}return chessboard;}/*** 下棋方法*/public void playChess() {Scanner scanner = new Scanner(System.in);//定义退出循环标志位boolean flag = true;//定义数字达成两个人交替下棋int num = 0;char[][] chessboard = drawChessboard();char white = '⚪';char black = '⚫';while (flag) {if (num % 2 == 0) {System.out.println("请输入棋子的坐标,白棋:");//定义输入的横坐标int x = scanner.nextInt();//定义输入的纵坐标int y = scanner.nextInt();//判断是否走出棋盘外if (x <= 0 || x >= 17 || y <= 0 || y >= 17) {System.out.println("输入坐标不正确,请重新输入");continue;} else if (chessboard[x][y] != '+') { //判断是否已有棋子System.out.println("该处已有棋子,请重新下棋");continue;}chessboard[x][y] = white;//判断如果一方获胜则退出循环 程序结束if (isWin(chessboard)) {System.out.println("game over 白棋获胜!");flag = false;}} else {System.out.println("请输入棋子的坐标,黑棋:");//定义输入的横坐标int x = scanner.nextInt();//定义输入的纵坐标int y = scanner.nextInt();//判断是否走出棋盘外if (x <= 0 || x >= 17 || y <= 0 || y >= 17) {System.out.println("输入坐标不正确,请重新输入");continue;} else if (chessboard[x][y] != '+') { //判断是否已有棋子System.out.println("该处已有棋子,请重新下棋");continue;}chessboard[x][y] = black;//判断如果一方获胜则退出循环if (isWin(chessboard)) {System.out.println("game over 黑棋获胜!");flag = false;}}for (int i = 0; i < chessboard.length; i++) {for (int j = 0; j < chessboard[i].length; j++) {System.out.print(chessboard[i][j] + " ");}System.out.println();}num++;}}/*** 判断是否已经获胜** @param chessboard 棋局现状* @return true:已获胜 false:未获胜*/public boolean isWin(char[][] chessboard) {char empty = '+';//设置棋子连珠数量int count = 1;//全盘扫描是否有符合条件的for (int i = 1; i < chessboard.length; i++) {for (int j = 1; j < chessboard[i].length; j++) {//横向判断 x(纵坐标)不变y(横坐标)向左右查询各4个for (int k = 1; k <= 4; k++) {//判断是否已经出界,且元素不是空值if ((j + k) <= chessboard.length && (j - k) > 0 && (chessboard[i][j] != empty)) {//如果左右查询4个发现是相同的棋子则count++if (chessboard[i][j] == chessboard[i][j + k] || chessboard[i][j] == chessboard[i][j - k]) {count++;//判断如果5子连珠则return true 停止程序游戏结束if (count == 5) {return true;}}}}//不满足上一条件重新设置count的值count = 1;//纵向判断 y(横坐标)不变x(纵坐标)向左右查询各4个for (int k = 1; k <= 4; k++) {//判断是否已经出界,且元素不是空值if ((i + k) <= chessboard.length && (i - k) > 0 && (chessboard[i][j] != empty)) {//如果上下查询4个发现是相同的棋子则count++if (chessboard[i][j] == chessboard[i + k][j] || chessboard[i][j] == chessboard[i - k][j]) {count++;//判断如果5子连珠则return true 停止程序游戏结束if (count == 5) {return true;}}}}//不满足上一条件重新设置count的值count = 1;//撇方向判断 x(纵坐标)加1 y(横坐标)减1/ x(纵坐标)减1 y(横坐标)加1查询各4个for (int k = 1; k <= 4; k++) {//判断是否已经出界,且元素不是空值if ((i + k) <= chessboard.length && (j - k) > 0 && (i - k) > 0 && (j + k) <= chessboard.length && (chessboard[i][j] != empty)) {//如果撇方向查询4个发现是相同的棋子则count++if (chessboard[i][j] == chessboard[i + k][j - k] || chessboard[i][j] == chessboard[i - k][j + k]) {count++;//判断如果5子连珠则return true 停止程序游戏结束if (count == 5) {return true;}}}}//不满足上一条件重新设置count的值count = 1;//捺方向判断 x(纵坐标)加1 y(横坐标)加1/x(纵坐标)减1 y(横坐标)减1 查询各4个for (int k = 1; k <= 4; k++) {//判断是否已经出界,且元素不是空值if ((i + k) <= chessboard.length && (j + k) <= chessboard.length && (i - k) > 0 && (j - k) > 0 && (chessboard[i][j] != empty)) {//如果撇方向查询4个发现是相同的棋子则count++if (chessboard[i][j] == chessboard[i + k][j + k] || chessboard[i][j] == chessboard[i - k][j - k]) {count++;//判断如果5子连珠则return true 停止程序游戏结束if (count == 5) {return true;}}}}//不满足上一条件重新设置count的值count = 1;}}//全部不满足条件继续循环return false;}public static void main(String[] args) {Homework2 homework2 = new Homework2();homework2.playChess();}
}