类和对象 ◼ 类:是一组 属性(成员变量/常量) 以及属性上的 方法 (函数/操作)的 封装体
◼ 对象:是类的实例(具体),类是对象的模板(抽象)
类的声明
Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package edu.wust.examples; public class Student { private String name; private int age; private String major; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public String getMajor () { return major; } public void setMajor (String major) { this .major = major; } @Override public String toString () { return "我叫" + name + "," + age + "岁,专业是" + major; } }
私有属性的好处
◼ 防止对封装数据的未授权访问
◼ 有助于保证数据完整性
◼ 当类的变量必须改变时,可以限制发生在整个应用程序中的“连锁反应”
对象的创建和成员访问
1 2 3 4 5 6 7 8 9 10 11 public class Test { public static void main (String args[]) { Student s = new Student (); s.setName("小花" ); s.setAge(19 ); s.setMajor("软件" ); System.out.println( s ); } }
了解:关于Java Bean的概念
◼ 对于遵循以下规范的Java类就称为Java Bean:
◼ JavaBean是一个公共的类(public class)
◼ JavaBean有一个不带参数的构造方法(或默认构造函数)
◼ JavaBean通过setXxx方法设置属性,通过getXxx方法获取属性
Java Bean 在 Java EE 开发中,通常用于 封装数据 ,是一种可重复使用的,跨平台的软件组件
构造函数 ◼ 新对象的创建和初始化通常是调用一个构造函数的来实现的。
◼ 例如:Student s = new Student();
对象 s
由 Student() 这个构造函数创建
◼ 每个类至少要有一个构造函数,如果没有编写构造函数,系统会提供一个默认构造函数 。
◼ 默认构造函数:不带参数,函数体是空的,形如:Student() { }
◼ 可自定义构造函数,并向构造函数传递创建对象所需参数。
注:一旦自定义构造函数,系统默认的构造函数将自动关闭。如果要使用默认构造函数,则必须显示声明。
自定义构造函数
好处:(1)给初始化带来多种形式;(2)为用户提供更大的灵活性
1 2 3 4 5 6 7 8 9 public class Student { … public Student (String name, int age, String major) { this .name = name; this .age = age; this .major = major; } … }
说明
◼ 构造函数的名称必须与类名相同
◼ 构造函数无任何返回值
◼ 构造函数不指定类型(包括void)
◼ 构造函数前面一般加public (也可用private)
◼ 一个类可以有多个构造函数(重载,见下节)
构造函数使用
1 2 3 4 5 6 7 public class Test { public static void main (String[] args) { Student s1 = new Student ("小花" , 19 , "软件" ); System.out.println( s1 ); } }
补充:匿名对象
◼ 匿名对象就是没有明确的给出名字的对象
◼ 如果一个对象只用一次,就可以使用匿名对象
◼ 例如: System.out.println( new Student(“小花”, 19, “软件”) ); // 此处为一个匿名对象
了解:Java没有析构函数
◼ Java没有析构函数,代之以 垃圾回收机制 (garbage collection,简称GC),但垃圾回收并不等同 于“析构”。
◼ 垃圾回收是Java虚拟机(JVM )提供的一种用于在 空闲时间 不定时 回收无用对象所占据内存空间的一种机制。
◼ 如果想提示垃圾回收器尽快执行垃圾回收操作,可在程序中使用:
◼ System.gc();
//注:该命令不保证会确实进行垃圾回收
方法重载 方法重载在 Java 中叫作 overload
1 2 3 4 public void indexOfInt (int ch) { ... }public void indexOfString (String str) { ... }public void indexOfFromIndex1 (int ch, int fromIndex) { … }public void indexOfFromIndex2 (String str, int fromIndex) { … }
背景:功能相似,但函数名不同会造成用户调用繁琐
用重载处理
↓
1 2 3 4 public int indexOf (int ch) { … }public int indexOf (String str) { … }public indexOf (int ch, int fromIndex) { … }public indexOf (String str, int fromIndex) { … }
重载好处:功能类似的方法使用同一名字,更容易记住,调用起来更简单
重载概念
◼ 方法重载:方法名相同,但各自的参数不同 (参数类型、参数个数、参数顺序至少有一项不同) // 参数顺序在这指的是不同类型的参数顺序发生改变
◼ 系统在编译时能够根据参数情况自动选择一个合适的方法
重载的条件 (关键在参数)
◼ 方法名必须相同
◼ 方法的 参数类型 、参数个数 、参数顺序 至少有一项不相同
注意:
◼ 不以 返回类型 的不同或 方法的修饰符 不同来进行重载。
注意:方法重载的返回值类型和修饰符通常都是相同的。
示例
哪些可以和①重载? 答案:②④
① public int fun( int, double )
② public int fun( int )
③ private double fun( int, double )
④ int fun( double, int )
⑤ public int funs( int )
程序阅读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Test { public void foo () { System.out.println("No parameters" ); } public void foo (int a) { System.out.println("a: " + a); } public void foo (int a, int b) { System.out.println("a and b: " + a + " " + b); } public void foo (double a) { System.out.println("double a: " + a * a); } public static void main (String[] args) { Test t = new Test (); t.foo(); t.foo(2 ); t.foo(2 , 3 ); t.foo(2.0 ); t.foo(2L ); t.foo(2.0f ); } }
构造函数重载
好处:(1)给初始化带来多种形式;(2)为用户提供更大的灵活性
定义3个构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Student { … public Student () { } public Student (String name, int age, String major) { this .name = name; this .age = age; this .major = major; } public Student (String name, int age) { this .name = name; this .age = age; this .major = "待定" ; } … }
构造函数使用
1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) { Student s1 = new Student (); Student s2 = new Student ("小明" , 19 , "软件" ); Student s3 = new Student ("小花" , 18 ); System.out.println( s1 ); System.out.println( s2 ); System.out.println( s3 ); } }
this关键字
this() 访问构造方法
◼ 一个类的构造函数之间可以相互调用
◼ 调用方法:this([参数])
◼ 特别注意:
◼ this() 仅在类的构造函数中使用 ,别的地方不能用
◼ this() 必须是整个构造函数的 第一个可执行语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Employee { private String name; private int salary; public Employee (String name, int salary) { this .name = name; this .salary = salary; } public Employee (String name) { this (name, 0 ); } public Employee () { this ( " unknown " ); } @Override public String toString () { return "Employee{name='" + name + "', salary=" + salary +"}" ; } }
测试
1 2 3 4 5 6 7 8 Employee e1=new Employee ("zz" ,100 ); => e1.name="zz" e1.salary=100 Employee e2=new Employee ("qq" ); => e2.name="qq" e2.salary=0 Employee e3=new Employee (); => e3.name="unknow" e3.salary=0
static关键字 ◼ 通常情况下,类成员必须通过类的对象来访问,但是可以创建这样的成员,它的 使用完全独立于该类的任何对象 。
◼ static 关键字用来 声明成员属于类 ,被类的所有实例 共享 ,而 不依赖于类的特定实例 。
◼ 如果一个成员被声明为 static,它就能够在它的类的任何对象创建之前被访问,而不必引用任何对象(只要这个类被加载,JVM 就可以根据类名找到它们)。
◼ 调用静态成员的语法:类名.静态成员
static用法 ◼ 静态变量
◼ 静态方法
◼ 静态代码块
1.静态变量
◼ 程序运行时,Java 虚拟机为 静态变量只分配一次内存 ,在 加载类的过程中完成 静态变量的内存分配
◼ 在类的内部,可以在 任何方法 内直接访问静态变量
◼ 在其他类中,可以通过 类名.静态变量 来访问(也可通过对象,但不推荐) // 注:静态变量通常加 public 修饰
静态变量作用
◼ 静态变量可被类的 所有实例共享 ,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
◼ 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而 节省内存空间 。
静态变量示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class StaticVar { public static String logo = "wust" ; public static void main (String[] args) { System.out.println("第1次访问静态变量,结果为:" + logo); System.out.println("第2次访问静态变量,结果为:" + StaticVar.logo); StaticVar ojb1 = new StaticVar (); ojb1.logo = ojb1.logo + " cs!" ; System.out.println("第3次访向静态变量,结果为:" + ojb1.logo); StaticVar ojb2 = new StaticVar (); System.out.println("第4次访问静态变量,结果为:" + ojb2.logo); } }
2.静态方法
◼ 静态方法只能调用静态成员 (static 方法或 static 属性) // 注:实例方法可以直接访问所属类的静态变量、静态方法
◼ 原因:静态方法在class装载时首先完成,比构造函数早,此时非静态属性和方法还没完成初始化, 所以静态方法不能调用非静态方法和属性。
◼ 静态方法不能以任何方式引用 this 或 super
◼ 原因:静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字。
◼ 和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字
静态方法示例
1 2 3 4 5 6 7 8 9 10 public class Test { private int x; public static int y; public static void main (String args[]) { x = 9 ; this .y = 10 ; y = 10 ; Test.y = 10 ; } }
示例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Simple { public void go1 () { Test t = new Test (); t.go3(); } public static void go2 () { System.out.println("Go2..." ); } } public class Test { public static void go3 () { System.out.println("Go3..." ); } public void go4 () { System.out.println("Go4..." ); } public static void main (String[] args) { new Simple ().go1(); go3(); go4(); Simple.go2(); } }
示例3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Value { public static int c = 0 ; public static void inc () { c++; } } public class Count { public static void prt (String s) { System.out.println(s); } public static void main (String[] args) { Value v1 = new Value (); Value v2 = new Value (); prt("v1.c=" + v1.c + " v2.c=" + v2.c); Value.inc(); prt("v1.c=" + v1.c + " v2.c=" + v2.c); } }
3.静态代码块
◼ 静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块
◼ 静态代码块 只会执行一次 ,且在 类被第一次装载时 // 通常将一次性的初始化操作放在静态代码块中进行
◼ 静态代码块按它们在类中出现的顺序被执行
静态代码块示例
1 2 3 4 5 6 7 8 9 10 11 class StaticCode { public static int i = 5 ; static { System.out.println("Static code i=" + i++ ); } } public class Test { public static void main (String args[]) { System.out.println("Main code: i=" + StaticCode.i ); } }
程序阅读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class StaticCode { public static int count = 0 ; { count++; System.out.println("非静态代码块 count=" + count); } static { count++; System.out.println("静态代码块1 count=" + count); } static { count++; System.out.println("静态代码块2 count=" + count); } } public class Test {public static void main (String args[]) {StaticCode sc1 = new StaticCode ();StaticCode sc2 = new StaticCode ();} }
final关键字 final类 ◼ 在设计类时候,如果这个类的实现细节不允许改变,类不需要有子类,并且确信这个类不会再被扩展,那么就设计为 final 类
1 2 3 public final class 类名 { … }
final类不能被继承 。因此 final 类的成员方法没有机会被覆盖
◼ 如 String、Math 等类都是 final 类
final方法 ◼ final方法可以被继承,但不能被重写(覆盖)
1 2 3 4 5 public class 类名 { public final void f2 () { System.out.println("f2" ); } }
◼ 使用final方法的原因有二:
◼ 把方法锁定,防止任何子类修改它的实现
◼ 高效。编译器在遇到调用 final 方法时候会转入内嵌机制,大大提高执行效率
final变量 ◼ final 修饰 基本类型 变量时即成为 常量 ,且只能赋值一次 (可先声明,不给初值,但在使用之前必须 被初始化一次)=>(被称为空白 final 变量)
例如
1 2 3 4 5 final double PI = 3.14159 ; PI = 3.14 ; final String LOGO; System.out.println( LOGO );
◼ final 修饰引用类型变量时,只保证这个引用类型变量所引用的地址不会改变,但其自身的成员 变量的值可以被改变
例如
1 2 3 4 5 6 final int [] arr = { 5 , 6 , 12 , 9 };arr[2 ] = -8 ; arr = null ; final Person p1 = new Person (45 ); p1.setAge(23 ); p1 = p2;
final参数 ◼ 当函数参数为final类型时,只可以读取使用该参数,但是无法改变该参数的值。
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { foo(2 ); } public static void foo ( final int n ) { n++; System.out.print(n); } }
编程练习 1.定义一个三角形Triangle类:
◼ 数据成员:a, b, c(double类型);
◼ 具有的操作:
(1) 构造函数
(2) 判断是否构成三角形 isTriangle()
(3) 计算三角形面积 getArea()
(4) 显示三角形信息 toString()
(5) getter/setter(本例略)
Triangle.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Triangle { private double a; private double b; private double c; public Triangle () { } public Triangle (double a, double b, double c) { this .a = a; this .b = b; this .c = c; } public Triangle (double a) { this (a, a, a); } public boolean isTriangle () { return a + b > c && a + c > b && b + c > a; } public double getArea () { double s = (a + b + c) / 2.0 ; if ( isTriangle() ) return Math.sqrt(s * (s - a) * (s - b) * (s - c)); else return -1 ; } @Override public String toString () { return "a=" + a + ", b=" + b + ", c=" + c; } }
测试类:
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Test { public static void main (String[] args) { Triangle t1 = new Triangle (); System.out.println( t1 ); System.out.println( "面积=" +t1.getArea() ); Triangle t2 = new Triangle (3 ,4 ,5 ); System.out.println( t2 ); System.out.println( "面积=" +t2.getArea() ); Triangle t3 = new Triangle (3 ); System.out.println( t3 ); System.out.println( "面积=" +t3.getArea() ); } }
编程练习 :
2、定义一个People类:
数据成员:name (String)、birthday (Date)
具有的操作:
(1) 构造函数
(2) 重载方法:eat()、eat(s)
(3) birthday修改操作:getBirthday()、getAge() – 计算年龄
说明:Date和Calendar类用法见附录1
◼ Date和Calendar类:
import java.util.Calendar;
import java.util.Date;
◼ 格式化Date数据的SimpleDateFormat类:
import java.text.SimpleDateFormat;
People.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class People { private String name; private Date birthday; public People () { } public People (String name, Date birthday) { this .name = name; this .birthday = birthday; } public void eat () { System.out.println("People can eat" ); } public void eat (String food) { System.out.println("People can eat " + food); } public Date getBirthday () { return birthday; } public int getAge () { Calendar now = Calendar.getInstance(); Calendar birth = Calendar.getInstance(); birth.setTime(birthday); int age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR); if (age <= 0 ) { return 0 ; } int currMonth = now.get(Calendar.MONTH); int currDay = now.get(Calendar.DAY_OF_MONTH); int birthMonth = birth.get(Calendar.MONTH); int birthDay = birth.get(Calendar.DAY_OF_MONTH); if ((currMonth < birthMonth) || (currMonth == birthMonth && currDay <= birthDay)) { age--; } return age < 0 ? 0 : age; } }
测试类
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test { public static void main (String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd" ); Date birthday = sdf.parse( "1975-03-03" ); People p=new People ( "zz" , birthday ); p.eat(); p.eat("apple" ); System.out.println("年龄:" + p.getAge() ); SimpleDateFormat sdf2 = new SimpleDateFormat ( "yyyy年M月d日" ); String birthStr = sdf2.format( p.getBirthday() ); System.out.println( "生日:" + birthStr ); } }
课后练习题
◼ 编写一个Point类:拥有x,y坐标值;
◼ 编写一个Line类:拥有两个点,能计算两点线段长度、斜率;
◼ 考虑特殊斜率情况:如0或者无穷大
◼ 编写测试类:增加判断两条线段是否正交功能。
Point.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Point { private double x; private double y; public Point () {} public Point (double x,double y) { this .x = x; this .y = y; } public Point (double a) { this (a,a); } public double getX () { return x; } public double getY () { return y; } }
Line.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Line { Point head; Point tail; public Line () {} public Line (int x1,int y1,int x2,int y2) { head = new Point (x1,y1); tail = new Point (x2,y2); } public Line (int a,int b) { head = new Point (a); tail = new Point (b); } public Line (Point a,Point b) { head = a; tail = b; } public static double min (double a,double b) { return a>b?b:a; } public static double max (double a,double b) { return a>b?a:b; } public static boolean isorthogonal (Line a,Line b) { if (min(a.head.getX(),a.tail.getX()) <= max(b.head.getX(),b.tail.getX()) && min(b.head.getX(),b.tail.getX()) <= max(a.head.getX(),a.tail.getX()) && min(a.head.getY(),a.tail.getY()) <= max(b.head.getY(),b.tail.getY()) &&min(b.head.getY(),b.tail.getY())<=max(a.head.getY(),a.tail.getY())) { double u,v,w,z; u=(b.head.getX()-a.head.getX())*(a.tail.getY()-a.head.getY())-(a.tail.getX()-a.head.getX())*(b.head.getY()-a.head.getY()); v=(b.tail.getX()-a.head.getX())*(a.tail.getY()-a.head.getY())-(a.tail.getX()-a.head.getX())*(b.tail.getY()-a.head.getY()); w=(a.head.getX()-b.head.getX())*(b.tail.getY()-b.head.getY())-(b.tail.getX()-b.head.getX())*(a.head.getY()-b.head.getY()); z=(a.tail.getX()-b.head.getX())*(b.tail.getY()-b.head.getY())-(b.tail.getX()-b.head.getX())*(a.tail.getY()-b.head.getY()); return (u*v<=0.00000001 && w*z<=0.00000001 ); } return false ; } }
Test.java
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { Point A = new Point (0 ,0 ); Point B = new Point (2 ); Line line1 = new Line (A,B); Line line2 = new Line (1 ,1 ,0 ,2 ); System.out.println(String.format("line1 和 line2 是否正交?%b" ,Line.isorthogonal(line1,line2))); } }
附录 附录1:Date 类和 Calendar 类
◼ Date 类表示的是特定的,瞬间的,它能精确毫秒 (比较晦涩)
◼ Calendar 是一种抽象类,它为 Date 类与年、月、日等字段之间的转换提供了一些方法
◼ 在大多数情况下,Date 要实现的功能都可以通过 Calendar 来实现的
Date基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Date date = new Date (); System.out.println(date); System.out.println("毫秒:" + date.getTime()); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss" );String dateStr = sdf.format(date);System.out.println("Date转String:" + dateStr); String dateString = "2021-09-25" ;SimpleDateFormat sdf2 = new SimpleDateFormat ("yyyy-MM-dd" );Date date2 = sdf2.parse(dateString);System.out.println("String转Date:" + date2);
SimpleDateFormat 两个重要用法: format 和 parse
Calendar基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Calendar cal = Calendar.getInstance(); System.out.println("年:" + cal.get( Calendar.YEAR ) ); System.out.println("月:" + ( cal.get( Calendar.MONTH ) + 1 ) ); System.out.println("日:" + cal.get( Calendar.DAY_OF_MONTH ) ); System.out.println("时:" + cal.get( Calendar.HOUR_OF_DAY ) ); System.out.println("分:" + cal.get( Calendar.MINUTE ) ); System.out.println("秒:" + cal.get( Calendar.SECOND ) ); Calendar cal02 = Calendar.getInstance();cal02.set(2020 ,9 ,1 ,12 ,0 ,0 ); System.out.println("新设置的Date:" + cal02.getTime() );
Date和Calendar互相转换:
1 2 3 4 5 6 7 8 Calendar cal = Calendar.getInstance();Date date = cal.getTime();Date date2 = new Date ();Calendar cal2 = Calendar.getInstance();cal2.setTime( ate);