前言:
对象=数据+操作 =属性(field)+方法(method)
组织起来的一组数据+围绕这组数据的相关方法
类需要定义这类对象——哪些属性、哪些方法
Java中类的作用:
(1)类是用来组织方法的组织单元
Java.util.Arrays/java.lang.Math
Arrays.copyOf(……)
类名.方法名(……)
(2)若对象比作一个产品,类是用来生成对象的一个图纸。
1、学会定义类:
修饰符 class 类名{
属性们(fields);
普通方法们(methods);
构造器/构造方法(constructors);
}
如:public class Hello{}
(1)属性(filed)/实例变量/对象变量:定义在类的内部,方法的外部。(类型不限制)
Teacher t=new Teacher();
注解:Teacher类在此处,当做数据类型来使用,是一种引用类型。
new构造一个对象;
Teacher()对象类型是Teacher这个类的对象——Teacher对象
最终表现为:首先构造一个Teacher类型的对象,使用一个Teacher类型的引用t指向该对象
public class Teacher {
/*目前所有属性的定义全部带上public,属性的数据类型不限,可以是基本类型或者引用类型
书写位置在类的内部,方法的外部
定义规则:修饰符 变量类型 变量名——这类变量被称为属性
*/
public String name;
public int age;
}
(2)方法:{普通方法,静态方法}
普通方法:不带static public void 讲课(){
……}
静态方法:带static public static void fill(){
……}
public class 老师 {
public String name;
public int age;
public String[] courseArray;//教的那些课程
public void 自我介绍(){
//在方法中可以直接使用属性,name读取该对象中,属性name的值
System.out.printf("我叫 %s,我今年 %d岁,我教以下课程:\n",name,age);
if (courseArray!=null){
for (String course:courseArray){
System.out.println(course);
}
}
}
}
静态属性的初始化:
书写方式:(1)定义时初始化(2)静态代码块
public class 静态属性的初始化 {
//执行顺序:按照代码的书写顺序进行执行
public static int a=init();
//定义时初始化
static {
System.out.println("我是静态代码块");
}
//静态代码块
public static int init(){
System.out.println("定义时初始化");
return 0;
}
public static void main(String[] args) {
}
}
静态属性初始化时机——发生在类被加载期间
类的加载(Load Class):
以hello.class类为例,JVM在程序执行期间,需要用到hello类的信息,但JVM只能读取内存中的数据,所以需要有一步操作:把关于hello的类的信息,从硬盘上的hello.class文件中读取出来,放入到内存中的合适位置中,此过程称为类的加载过程。程序的运行期间,用到的类,只会被加载一次。JVM在实现类的加载时,并不是一开始就把所有要用到的类全部加载进来,而是等真正某个类需要用到时,才加载——按需加载。
使用类的情况:
(1)拿着这个类,实例化一个对象时
(2)使用这个类的静态属性/静态方法时
在普通方法体中,可以直接访问对象的属性(哪个对象)
(1)this.属性名 肯定可以
(2)属性名 在没有歧义的情况下
Scanner sca=new Scanner(System.in);
//传入合适的参数,构造对象
Scanner sca=new Scanner();
sca.in=SyStem.in;
//构造对象和传参分离
构造方法:
长得有点接近方法,和普通方法有以下区别:
(1)没有返回值类型
(2)出现方法名的位置,必须使用类名
public class MyDate{
public MyDate(){……}
}
在构造方法中,可以调用其他构造方法(多个构造方法之间遵守重载规则)
如果类中一个构造方法都没有定义,则java添加一个隐含的无参构造方法,什么都不做。
当形参名和属性名相同时,如何访问属性?
——关键字:this
this关键字的一个引用,其作用:
(1)类型变化(看出现在哪个类中),this出现哪个类的方法下,this类型就是该类的引用。this一定不是null,指向当前对象。其指向对象变化(看通过哪个对象调用方法)。当形参名和属性名不冲突时可以用,冲突时要加this。
(2)this用在构造方法中,调用其他构造方法时,当成方法名使用。
this(name:null;age:0;courseArray:null);
如果要使用这种用法,这句话必须是构造方法的第一条语句
static关键字方法总结:
static出现的位置都是类的内部,方法的外部
(1)修饰属性——变成静态属性
静态属性不是保存在该类的对象中,而是保存在“类类型对象”中
(2)修饰方法——变成静态方法
静态方法中,没有this,没有该类的对象
(3)静态代码块
执行时机是类的加载时期,而不是对象的实例化时期
被static修饰,就表示被修饰的东西(属性/方法/代码块)都和类的对象无直接关联,即没有this。
static使用的时机:
(1)用到了this,一定不能用static修饰
(2)不用this,可以用static修饰,也可以不用,其中main方法是特例,必须用static进行修饰
2、如何使用定义好的类,实例化出对象
定义好的类,可以当做数据类型使用(通过这种类型定义的数据,都是引用类型)
如:Teacher a;定义了一个Teacher类型(引用类型)的变量。
public class 使用Teacher类实例化对象 {
public static void main(String[] args) {
//使用Teacher类,实用化对象。一共实例化出三个对象。
Teacher a=new Teacher();
Teacher b=new Teacher();
Teacher c=new Teacher();
//带有new的操作,就会实例化一个新的对象出来
System.out.println(a==b);//false
System.out.println(b==c);//false
//操作对象的属性的语法(读取/修改)——通过.解引用操作
System.out.println(a.name);
System.out.println(a.age);
//通过.解引用操作,修改对象的属性(通过引用)
a.name="hello";
a.age=18;
System.out.println(a.name);
System.out.println(a.age);
}
}
3、对象的初始化方式有两类:
(1)第一类分为两种
(a)定义时,初始化
public class Teacher {
public String name="米倾屿";
public int age=18;
(b)通过构造代码块(construct block)初始化
public class Teacher {
public String name;
public int age;
{
name="米倾屿";
age=18;
}
}
(2)第二类:构造方法
public class Teacher {
public String name;
public int age;
public Teacher(){
name="米倾屿";
age=18;
}
}
等级:
定义初始化和构造代码块初始化相同,谁出现在前面就表现谁,构造方法永远在第一类执行完后执行与方法定义在哪里无关。
4、使用对象(通过引用)
(1)实例化对象
new 类名(调用该类构造方法的参数——实参)
类里定义了哪些构造方法,实例化对象时,才可以调用哪些构造方法形式
实例化对象步骤:(Teacher a=new Teacher(“miqingyu”,35);
(a)new:在内存中,根据类(Teacher)的信息,计算出一个Teacher对象需要多少内存,并且申请内存,将申请下来的内存数据,全部初始化为0x0。
(b)执行类中关于对象属性的初始化操作——包括但不限于构造方法。(执行完成后,对象是一个完整的情况了)
(c)让a引用指向刚才实例化出的对象(完成后,程序员可以正常使用该对象)
(2)调用普通方法时,必须通过对象(实际上通过引用间接地通过对象)调用。
Teacher a=……;
a.方法名(); 如果a==null,同样是NPE。满足解引用规则
定义普通方法时,出现的this,则指向a目前指向的对象
5、方便打印对象——toString方法
Java中所有类,都继承自Object类,所以toString在Object类中有默认实现。
//重写继承自object的toString方法
public String toString(){
return "Teacher{name= "+ name+"}";
}
6、在静态方法中,不能使用非静态的属性和非静态的方法——通过this访问
在普通方法中,可以使用静态属性和静态方法——通过类名访问
例题1:时间差计算:计算两个日期之间的时间差(如:2020.1.20-2000.2.14)
public class MyDate {
public int year;
public int month;
public int day;
/*(1)必须校验传入参数的合法性,year:1900<=year<=3000;month:1<=month<=12;day:1<=day<=每个月的天数
(2)如果不符合,应该抛异常,通知对方出错/尝试修复参数
*/
public MyDate(int year, int month, int day) {
if (year<=1900||year>3000){
//完全就是一个实例化对象的代码
RuntimeException exception=new RuntimeException("year参数错误");
//通过throw关键字,抛出一个异常对象
throw exception;
}
if (month<1||month>12){
throw new RuntimeException("month参数错误");
}
//需要一个根据year,month计算出该月共有多少天的方法
if (day<1||day>getMonthDay(year,month)){
throw new RuntimeException("day参数错误");
}
this.year = year;
this.month = month;
this.day = day;
}
public MyDate(MyDate from) {
this.year= from.year;
this.month= from.month;
this.day= from.day;
}
public int getMonthDay(int year,int month) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return isLeapYear(year)?29:28;//方法判断是否是闰年
default:
return -1;
}
}
public boolean isLeapYear(int year){
return year % 400==0||(year%4==0&&year%100!=0);
}
/*@这种语法叫做注解(Annotation)主要用来声明(Declare)一些方式/属性/类具备的特征
Override的意思是重写/覆写,所以Override意思就是单纯声明toString方法是一个重写方法,注解没有执行含义(目前遇到的)
主要是方便开发者一眼看出哪些方法是重写方法,那些不是。
*/
@Override
public String toString() {
String s=String.format("%04d-%02d-%02d",year,month,day);
return s;
}
/*参数合法性校验:this代表的日期必须大于from代表的日期
*/
public int 计算相差天数(MyDate from){
//this指向的对象和from指向的对象之间相差的天数,要求this大于from
if (this.compareTo(from)<=0){
throw new RuntimeException("from的日期必须在当前日期之前");
}
//用from的复制计算,以免下面计算时将别人传入的from对象修改掉
MyDate fromCopy=new MyDate(from);
int count=0;
while (fromCopy.compareTo(this)<0){
//表示当from<this时,让from向后走一天,from.day++;是错误的,没有考虑到进位问题
System.out.println(fromCopy);
fromCopy.increment();
count++;
}
return count;
}
private void increment() {
day++;
if (day<=getMonthDay(year,month)){
//day不需要考虑进位
return;
}
//否则需要考虑日期的进位
month++;
day=1;
if (month<=12){
//month不需要考虑进位
return;
}
//否则需要考虑年份的进位
year++;
month=1;
}
/* if (from>=this){ 引用不能使用>=运算符,因此需要定义一个比较方法(this,from)
则认为规定compareTo方法,如果this>from返回任意正数,this==from返回0,this<from返回任意负数
*/
public int compareTo(MyDate from){
// this和from在进行比较
if (year!= from.year) {
return year - from.year; //year>from.year则为正数,否则为负数。
}
//说明year==from.year
if (month!= from.month){
return month= from.month;
}
//说明year==from.year&&month==from.month
return day- from.day;
}
}
测试:
public class MyDateTest {
public static void main(String[] args) {
MyDate from=new MyDate(2020,10,18);
MyDate to=new MyDate(2021,1,21);
// System.out.println(to.compareTo(from));//正数to>from
// System.out.println(from.compareTo(to));//负数to<from
// System.out.println(to.compareTo(to));//0 to==from
// System.out.println(from.compareTo(from));//0 from=from
System.out.printf("从 %s 到 %s 经过了 %d天\n",from,to,to.计算相差天数(from)
);
}
}
结果:
例题2:处理时分秒
public class MyTime {
private int hour;
private int minute;
private int second;
public MyTime(MyTime time){
this.hour= time.hour;
this.minute= time.minute;
this.second=time.second;
}
public MyTime(int hour,int minute,int second){
check(hour,minute,second);
this.hour=hour;
this.minute=minute;
this.second=second;
}
//向前走一天,返回值代表本次Time向后走一秒是否到了第二天,true代表有进位,false代表没有进位
public boolean next(){
second++;
if (second<60){
return false;
}
minute++;
second=0;
if (minute<60){
return false;
}
hour++;
minute=0;
if (hour<24){
return false;
}
hour=0;
return true;
}
//向后走一秒
public boolean previous(){
second--;
if (second>=0){
return false;
}
minute--;
second=59;
if (minute>=0){
return false;
}
hour--;
minute=59;
if (hour>=0){
return false;
}
hour=23;
return true;
}
@Override
public String toString() {
return String.format("%02d:%02d:%02d",hour,minute,second);
}
private static void check(int hour,int minute,int second){
if (hour<0||hour>=24){
throw new RuntimeException("hour的有效范围是[0,23]");
}
if (minute<0||minute>=60){
throw new RuntimeException("minute的有效范围是[0,59]");
}
if (second<0||second>=60){
throw new RuntimeException("second的有效范围是[0,59]");
}
}
}
年月日+时分秒
//既有年月日,又有时分秒
public class MyDateTime {
private MyDate date;
private MyTime time;
public MyDateTime(MyDateTime dateTime){
this.date= dateTime.date; //TODO:BUG fix
this.time=dateTime.time; //TODO:BUG fix
}
public MyDateTime(int year,int month,int day,int hour,int minute,int second){
this.date=new MyDate(year, month, day);
this.time=new MyTime(hour, minute, second);
}
//部分构造,只传年月日,时分秒默认设置成0时0分0秒
public MyDateTime(int year,int month,int day){
this(year,month,day,0,0,0);
}
public MyDateTime(MyDate date){
this.date=date; //TODO:BUG fix
}
//向后走一秒
public void next(){
if (time.next()){
date.next();
}
}
//向前走一秒
public void previous(){
if (time.previous()){
date.previous();
}
}
@Override
public String toString() {
return String.format("%s %s",date,time);
}
}
测试1:
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args)throws InterruptedException {
MyDateTime dateTime=new MyDateTime(2019,2,2,00,30,59);
while (true){
System.out.println(dateTime);
dateTime.next();
TimeUnit.SECONDS.sleep(1);//让代码停顿一秒
}
}
}
结果:
测试2:BUG-与预想值不同
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args)throws InterruptedException {
MyDateTime dt1=new MyDateTime(2019,2,2,00,30,59);
MyDateTime dt2=new MyDateTime(dt1);
System.out.println(dt1); //2019-2-2-00-30-59
System.out.println(dt2); //2019-2-2-00-30-59
dt1.next();
dt1.next();
dt1.next();
System.out.println(dt1); //2019-2-2-00-31-02
System.out.println(dt2); //2019-2-2-00-30-59
原因:
dt1和dt2指向的是不同对象,但两个对象中的data/time指向对象是同一个
修改为:
this.date=new MyDate(dateTime.date);
this.time=new MyTime(dateTime.time);
效果:
同理修改:
public MyDateTime(MyDate date){
// this.date=date; //TODO:BUG fix
this.date=new MyDate(date);
this.time=new MyTime(0,0,0);
例题3:面向对象的思维写二分查找
class Range{
//属性,区间的上限下标+区间的下限下标,这里选用左闭右闭
private final long[] array;
private int lowIndex;
private int highIndex;//区间array[lowIndex,highIndex]
public Range(long[] array){
this.array=array;
this.lowIndex=0;
this.highIndex=array.length-1;
}
public int size(){
return highIndex-lowIndex+1;
}
public long getMiddleValue(){
return array[getMiddleIndex()];
}
public int getMiddleIndex(){
return (lowIndex+highIndex)/2;
}
public int discardRightPart(){
highIndex=getMiddleIndex()-1;
return 0;
}
public int discardLeftPart(){
lowIndex=getMiddleIndex()+1;
return 0;
}
}
public class BinarySearch {
//二分查找,不需要一个BinarySearch对象,所以可以用也可以不用static
public static int binarySearch(long[] array,long target){
Range range=new Range(array);//把数组抽象成一个区间对象
while (range.size()>0){
long middleValue=range.getMiddleValue();
if (target==middleValue){
return range.getMiddleIndex();//返回当前值所在的下标位置
}else if (target<middleValue){
range.discardRightPart();//丢弃右边部分
}else {
range.discardLeftPart();//丢弃左边部分
}
}
return -1;
}
}
例题4:实现二维数组的二分查找 ——二维数组的区间表示
public class search {
//new int[] {0,3}
public static int[] search(long[][]array,int rows,int columns,long target){
Range range=new Range(array,rows,columns);
while (range.size()>0){
long middleValue=range.getMiddleValue();
if (target==middleValue){
return range.getMiddleIndex();
}else if (target<middleValue){
range.discardRightPart();
}else {
range.discardLeftPart();
}
}
return new int[]{-1,-1};
//也可以返回null只要是特殊值即可
}
}
public class Range {
private final long[][] array;
private final int columns;
private int lowRow;
private int lowColumn;
private int highRow;
private int highColumn;
public Range(long[][]array,int rows,int columns){
this.array=array;
this.columns=columns;
this.lowRow=0;
this.lowColumn=0;
this.highRow=rows-1;
this.highColumn=columns-1;
}
public int size() {
return (columns-lowColumn)+((highRow-lowRow-1)*columns)+(highColumn+1);//区间内所有元素的个数
}
public long getMiddleValue() {
int[] index=getMiddleIndex();
int row=index[0];
int column=index[1];
return array[row][column];
}
//得到中间位置的下标
public int[] getMiddleIndex() {
int halfSize=size()/2;
int middleRow=lowRow;
int middleColumn=lowColumn;
middleColumn+=halfSize;
//middleColumn还不是一个合法下标
while (middleColumn>=columns){
middleRow++;
middleColumn-=columns;
}
return new int[]{middleRow,middleColumn};
}
public void discardRightPart() {
//让high往左走,不包括middle位置
int[] index=getMiddleIndex();
int row=index[0];
int column=index[1];
highRow=row;
highColumn=column-1;//不要中间位置,可以进行合法性检查
if (highColumn<0){
highRow--;
highColumn=columns-1;
}
}
public void discardLeftPart() {
//让high往右走,不包括middle位置
int[] index=getMiddleIndex();
int row=index[0];
int column=index[1];
lowRow=row;
lowColumn=column+1;//不要中间位置,可以进行合法性检查
if (lowColumn>=columns){
lowRow++;
lowColumn=0;
}
}
}
import java.util.Arrays;
public class search_test {
public static void main(String[] args) {
long[][] array={
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15},
};
int rows=3;
int columns=5;
for (long target=0;target<=16;target++){
int[] index=search.search(array,rows,columns,target);
System.out.printf("查找 %d的结果是 %s\n",target, Arrays.toString(index));
}
}
}
结果显示:
例题5:猜数字/井字棋游戏
package game;
//提供一个放main方法的位置
public class Main {
public static void main(String[] args) {
Player player=new Player();
Game game=new Game(player);
while (true){
game.initialize();
game.play();
game.destroy();
boolean b=player.queryContinue();
if (!b){
System.out.println("欢迎下次继续游戏");
break;
}
}
}
}
package game;
/*实例化一个对象,Game对象代表的是一局游戏,核心功能:initialize,play,destroy
*/
public class Game {
private final Chessboard chessboard;
private final Player player;
private final AI ai;
public Game(Player player){
this.chessboard=new Chessboard();
this.player=player;
this.ai=new AI();
}
//游戏开始之前的初始化工作
public void initialize(){
System.out.println("欢迎进入《井字棋》游戏");
System.out.println(chessboard);
System.out.println("========================================");
}
//游戏主流程——回合制
public void play() {
while (true){
//一个循环=玩家回合+AI回合
if (playerTurn()){
break;
}
if (aiTurn()){
break;
}
}
}
private boolean playerTurn() {
System.out.println ("玩家回合:");
while (true){
int[] rc=player.getRowColumn();
int row=rc[0];
int column=rc[1];
if (chessboard.moveCrossAt(row,column)){
break;
}
System.out.println("该位置已有落子,请玩家重新选择位置");
}
System.out.println(chessboard);
return chessboard.getState()!=Chessboard.CONTINUE;
}
private boolean aiTurn(){
System.out.println("AI回合:");
while (true) {
int[] rc=ai.getRowColumn();
int row=rc[0];
int column=rc[1];
if (chessboard.moveCrossAt(row, column)) {
break;
}
}
System.out.println(chessboard);
return chessboard.getState()!=Chessboard.CONTINUE;
}
//游戏结束之前的收尾工作
public void destroy(){
chessboard.reset();
}
}
package game;
import java.util.Random;
//生成AI的落子位置
public class AI {
/*new int[]{第几行,第几列},第几行,第几列均从0开始
有效取值[0,2]
*/
private final Random random = new Random();
public int[] getRowColumn() {
int r = random.nextInt(3);
int c = random.nextInt(3);
return new int[]{r, c};
}
}
package game;
import java.util.Scanner;
//生成玩家的落子位置,询问玩家是否继续游戏
public class Player {
private final Scanner scanner=new Scanner(System.in);
/*new int[]{第几行,第几列},第几行,第几列均从0开始
有效取值[0,2]
*/
public int[] getRowColumn(){
System.out.println("请输入落子的位置,行 列 有效范围是[0,1,2] ");
System.out.print(">>>>>>>>");
int r,c;
while (true){
System.out.print(">>>>>>>>");
r=scanner.nextInt();
c=scanner.nextInt();
if (r>=0&&r<=2&&c>=0&&c<=2){
break;
}
System.out.println("有效范围是[0,1,2],请重新进行输入");
}
return new int[]{r,c};
}
//询问用户是否进行下一把游戏
public boolean queryContinue(){
System.out.println("是否进入下一轮游戏?true/false");
System.out.print(">>>>>>>>");
return scanner.nextBoolean();
}
}
package game;
import java.util.Arrays;
//棋盘类用来实例化对象,核心功能:落子;判断棋盘状态.
public class Chessboard {
private static final int 空白=0;
private static final int 画圈=1;
private static final int 画叉=2;
private final int[][] array={
{空白,空白,空白},
{空白,空白,空白},
{空白,空白,空白},
};
public boolean moveCircleAt(int row,int column){
if (array[row][column]!=空白){
return false;
}
array[row][column]=画圈;
return true;
}
public boolean moveCrossAt(int row,int column){
if (array[row][column]!=空白){
return false;
}
array[row][column]=画叉;
return true;
}
public static final int CIRCLE_WIN=0; //圈赢
public static final int CROSS_WIN=1; //叉赢
public static final int DRAW=2; //平局
public static final int CONTINUE=3; //继续游戏
public int getState() {
//按行判断
for (int i = 0; i < 3; i++) {
if (array[i][0] == array[i][1] && array[i][1] == array[i][2]) {
if (array[i][0] == 画圈) {
return CIRCLE_WIN;
} else if (array[i][0] == 画叉) {
return CROSS_WIN;
}
}
}
//按列判断
for (int i = 0; i < 3; i++) {
if (array[0][i] == array[1][i] && array[1][i] == array[2][i]) {
if (array[0][i] == 画圈) {
return CIRCLE_WIN;
} else if (array[0][i] == 画叉) {
return CROSS_WIN;
}
}
}
//对角线判断
if (array[0][0]==array[1][1]&&array[1][1]==array[2][2]){
if (array[1][1]==画圈){
return CIRCLE_WIN;
}else if (array[1][1]==画叉){
return CROSS_WIN;
}
}
if (array[0][2]==array[1][1]&&array[1][1]==array[2][0]){
if (array[1][1]==画圈){
return CIRCLE_WIN;
}else if (array[1][1]==画叉){
return CROSS_WIN;
}
}
//无人获胜
for (int i=0;i<3;i++){
for (int j=0;j<3;j++){
if (array[i][j]==空白){
return CONTINUE;
}
}
}
return DRAW;
}
public static String 显示(int i){
switch (i){
case 空白: return " ";
case 画圈: return "@";
case 画叉: return "*";
default:return " ";
}
}
@Override
public String toString() {
String s="+-+-+-+\n";
for (int i=0;i<2;i++){
s+=String.format("|%s|%s|%s|\n",显示(array[i][0]),显示(array[i][1]),显示(array[i][2]));
s+="+-+-+-+\n";
}
s+=String.format("|%s|%s|%s|\n",显示(array[2][0]),显示(array[2][1]),显示(array[2][2]));
s+="+-+-+-+";
return s;
}
public void reset() {
for (int i=0;i<3;i++){
Arrays.fill(array[i],空白);
}
}
}