面向对象程序设计-继承2
继承知识点汇总
-继承使用extends关键字
- 子类继承父类之后,子类可以直接使用父类中非私有的元素,并且子类中也能定义自己的元素
- 一个类只能继承一个父类
- 一个类可以存在多个子类
- 继承关系的构造中,创建子类对象时先调用父类的构造器
- Java中允许多重继承
模拟ArrayList实现(动态数组)
Java中ArrayList的实现是基于数组+数组拷贝完成
public class MyArrayList {
/**用于存储数据的真实数组*/
private Goods[] arr;
/**数组中元素目前的索引位置*/
private int index;
/**
* 构造一个初始容量为5的数组
*/
public MyArrayList() {
arr = new Goods[5];
}
/**
* 构造一个指定长度的数组
* @param capacity
*/
public MyArrayList(int capacity) {
arr = new Goods[capacity];
}
/**
* 添加元素
* @param g
*/
public void add(Goods g) {
//检测容量是否足够
ensureCapacity();
arr[index++] = g;
}
/**
* 确认容量是否足够
*/
private void ensureCapacity() {
if(index >= arr.length) {
//容量超出
grow();
}
}
/**
* 容量增长
*/
private void grow() {
//获取原数组的长度
int len = arr.length;
//创建临时的数长度为原始数组的1.5倍
Goods[] temp = new Goods[len + (len >> 1)];
//数组拷贝
System.arraycopy(arr, 0, temp, 0, len);
//将原数组的指针指向新数组地址
arr = temp;
}
/**
* 获取指定位置的元素
* @param i
* @return
*/
public Goods get(int i) {
return arr[i];
}
/**
* 返回元素的总个数
* @return
*/
public int size() {
return index;
}
/**
* 移除元素
* @param i
*/
public void remove(int i) {
System.arraycopy(arr, i+1, arr, i, arr.length - (i + 1));
index--;
}
}
重写(override)与重载(overload)
Java中允许方法的重写和重载,重写和重载是多态的一种实现策略:
- 重载(overload):一个类中存在多个同名的方法
- 重写(override):一个子类对父类的方法覆盖
重写
当父类中的方法实现满足不了子类需求时,此时子类中可以编写一个与父类方法一模一样的方法,对父类的方法进行覆盖,该过程称之为方法的重写(Override).
案例:
Animal类
public class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//动物咆哮(不同的动物发出的叫声一样,因此无法一概而论)
public void bark() {
System.out.println(name + "哇哇哇哇啊啊...");
}
}
Dog类
public class Dog extends Animal{
//对bark方法重写
public void bark() {
System.out.println(getName() + "汪汪汪。。。");
}
}
Cat类
public class Cat extends Animal{
//对bark方法重写
public void bark() {
System.out.println(getName() + "喵喵喵。。。");
}
}
f方法重写注意事项:
- 重写必须存在继承关系,一般是由子类重写父类方法
- 被重写的方法名称,返回值类型,参数列表必须跟父类方法一模一样
- 子类重写的方法访问权限不能低于父类
重载
重载指的是在同一个类中,或者存在继承关系的多个类中,存在同名的方法,这些方法的名称一模一样,但是方法的参数个数 ,类型,顺序任意有一项不一致,具备以上特点的方法统一称之为重载方法。
重载的条件:
- 方法名称必须一致
- 参数的个数,顺序和类型任意有一项不一致
- 重载与返回值无关
案例:
System.out.println("helloworld");
System.out.println(10);
System.out.println(true);
System.out.println('a');
System.out.println(3.14);
自定义重载方法:
public class Driver {
public void driven(Car c) {
}
public void driven(Bus b) {
}
public void driven(Bike b) {
}
public void driven(Plane p) {
}
}
关于方法的重载和重写的区别?
- 重写必须存在继承关系,由子类重写父类的方法,重载无要求
- 重写的方法名称,参数类型必须和父类一致;重载必须要保证方法的参数列表任意有一项不一致
- 重载与返回值无关
Object类(祖宗类)
Object类
Object类在Java中作为左右类的顶层父类,Java中的继承允许多重继承,即一个类可以存在多个间接的父类,最终会从java.lang.Object继承
toString
toString是Object类中的一个方法,默认的实现如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
通过对toString的重写可以实现对象的不同输出信息:
@Override
public String toString() {
return "User [name=" + name + ", pwd=" + pwd + "]";
}
== & equals
**在Java中一般用于对基本类型数据进行值的比较,如果两个值一致的结果为true,反之为false;**但是,在比较引用类型数据时比较的是对象的内存地址(不是对象中的具体数据),如下图:
通过以上的图形展示,明显能够发现,new的两个对象地址是不一样的,因此使用判断结果必然是false;另外Java中的Object类提供一个用于进行对象比较的方法equals,但是equals的默认实现是直接使用的判断;不过可以通过对equals方法进行重写,达到对象数据比较的目的,比如:
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof User) {
User u = (User)obj;
if(!this.name.equals(u.getName())) {
return false;
}
if(!this.pwd.equals(u.getPwd())) {
return false;
}
return true;
}else {
return false;
}
}
以上程序是对equals方法的重写,通过比较对象中的属性值来确定是否为同一个对象。
关于重写equals的注意事项
通常在 重写equals的时候也要求必须对hashcode重写;如果只是重写equals只能从逻辑上认为对象一致,实际上在内存中对象的地址依然是不同的,因此在后续比如集合排序,对象比较的时候可能造成错误结果。
instanceof运算符
instanceof运算符用于对目标对象判断,判断是否为指定的类型,用法如下:
引用变量 instanceof 引用数据类型
案例
//判断obj 是否是一个User类型
if(obj instanceof User) {
}
instanceof运算符的运算结果为boolean类型
多态
hashCode & equals
hashCode在Java中用于表示引用类型对象在堆内存的存储地址,如果是两个相同(比如对象的equals为true)的对象,必须要保证两个对象的hash地址是一致的,如果对象的equals方法执行为true时,hashcode却不一致,将会导致集合框架中(特别是Set集合)出现大量重复元素,从而违反hash表(散列表)的存储规则;因此,为了维护这一规则,必须要确保重写对象的equals时必须要同时重写hashcode。
public class Student {
private int sno;
private String sname;
// alt+/
public Student() {
// TODO Auto-generated constructor stub
}
public Student(int sno, String sname) {
super();
this.sno = sno;
this.sname = sname;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((sname == null) ? 0 : sname.hashCode());
result = prime * result + sno;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (sname == null) {
if (other.sname != null)
return false;
} else if (!sname.equals(other.sname))
return false;
if (sno != other.sno)
return false;
return true;
}
}
注意事项:
- 在比较引用数据类型的时候永远使用equals
- 重写equals方法时一定要同时重写hashCode
多态入门
多态是面向对象程序设计中最为重要的一种(没有之一)特性,java中的多态主要包含两种呈现方式:
- 行为的多态(方法多态:一种方法以多种不同的形态呈现)
- 方法的重写
- 方法的重载
- 变量的多态
- 父类的引用指向子类对象
- 子类引用指向父类对象(强制转换:前提是被转换的引用变量原本指向的就是目标对象)
public class Pet{
public void eat(){
System.out.println("宠物吃东西");
}
}
public class Dog extends Pet{
}
public class Test{
public static void main(String[] args){
//父类引用指向子类对象(p只能调用父类和子类共有的方法)
Pet p = new Dog();
//子类引用指向父类对象(实际还是Dog对象)
Dog d = (Dog) p;
}
}
多态的好处
多态性的优势体现在如下:
- 提高程序的可扩展性
- 提高代码的可复用性
- 提高了程序的可维护性
封装类型
由于java中提供基本数据类型只能用于一些简单运算(算术,关系,布尔逻辑等),但是无法像引用数据类型一样可以进行对象创建,方法或属性调用;因此对于一些特殊需求(比如:将字符串类型的数字转换整数,将十进制整数转成其他进制:2进制,8进制,16进制),基本数据类型显得功能不足;所以JDK提供了针对以上需求的基本类型封装类,用于进行相关类型的面向对象操作(方法调用)。
针对java中的8个基本类型数据,JDK也提供了对应的8个封装类型:
基本类型 | 封装类型 |
---|---|
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
boolean | java.lang.Boolean |
基本使用
int i = 10;
int j = 20;
//需求:
//将十进制整数类型转成二进制字符串 1010
//将字符串转成整数型
// int ---> java.lang.Integer
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
Integer inte = new Integer("123");
System.out.println(inte + i);
装箱拆箱
在JDK1.5之后,引入了装箱和拆箱机制,用于实现基本类型数据和对应的封装类型之间的自动转换。
- 装箱(boxing):将基本类型的数据值包装为对应的引用类型对象
- 拆箱(unboxing):将封装类型中的基本数据值拆成为基本数据类型
//装箱(boxing)
Integer i = 10;
//拆箱(unboxing)
int j = new Integer(10);
装箱和拆箱机制的实现:
1.5之后编译器对以下代码自动调用Integer.valueOf()完成装箱提供能
Integer i = 10
拆箱的实现使用的是如下方法
intValue()
笔试题
Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1 == i2); // true
Integer i3 = 130;
Integer i4 = 130;
System.out.println(i3 == i4); //false
Integer i5 = new Integer(10);
System.out.println(i1 == i5); //false
在对基本类型数据装箱时,valueOf方法内部会检测传入的数据值是否在缓存范围之内(-128~127),如果位于缓存区间,则直接返回缓存的对象(如果两个基本值是相同的,则缓存的对象地址为同一个);如果基本值超出了缓存范围,则重新创建一个Integer对象:
//jdk5之后新增(装箱时由编译器自动调用)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
注: IntegerCache是一个内部类,用于缓存位于-128~127之间的封装类型对象
String类
String是来自java.lang包中的一个常见字符串处理类,用于表示一个定长字符串,String的底层实现原理实际上就是一个字符序列(char数组);由于String是一个长度不可变的字符数组,因此对String进行修改实际就是在创建新的对象,在进行大量字符串拼接操作时内存和时间的开销会比较大,影响程序执行效率,因此String不适合做大量的字符串拼接操作。(如果有大量字符串拼接需求,建议使用:StringBuffer或StringBuilder)。
常见构造器
构造器 | 说明 |
---|---|
String(String s) | 根据提供的字符串直接量创建String对象 |
String(char[] c) | 根据提供的字符数组创建String对象 |
String(char[] c,int offset,int len) | 根据提供的字符数组,偏移量以及长度构建String对象 |
String(byte[] b) | 根据提供的字节数组,转换为String对象 |
String(byte[] b,int offset,int len) | 根据提供的字节数组,偏移量以及长度构建String对象 |
String s1 = new String("helloworld");
char[] c = {'s','o','f','t','e','e','m'};
s1 = new String(c);
System.out.println(s1);
//3:表示从数组中开始读取的位置;4:读取长度
s1 = new String(c,3,4);
System.out.println(s1);
byte[] b = {97,98,99,100};
s1 = new String(b);
System.out.println(s1);
s1 = new String(b,2,2);
System.out.println(s1);
String常用方法使用
//将ABCDEFGHIJKLMN字符串以16进制的方式显示到控制台中?
String str = "ABCDEFGHIJKLMN";
for(int i = 0;i < str.length();i++) {
char ch = str.charAt(i);
String hex = Integer.toHexString(ch);
System.out.println("0X"+hex);
}
//-15 比较两个字符串在字典的顺序
System.out.println("hello".compareTo("hOrld"));
System.out.println("hello".compareToIgnoreCase("hOrld"));
//字符串拼接
System.out.println("hello".concat("world"));
//判断目标字符串中是否包含指定的子串
System.out.println("helloworld".contains("wor"));
String path = "http://www.softeem.com/index/imgs/logo.png";
//判断目标字符串是否以指定的后缀结尾
System.out.println(path.endsWith(".png"));
//判断目标字符串是否以指定的子字符串开头
System.out.println(path.startsWith("http"));
//判断指定指定的字符串在目标字符串中第一次出现的位置(索引)
System.out.println(path.indexOf("www"));
//判断目标String对象的length()返回值是否为0
System.out.println("".isEmpty());
//jdk8新增用于使用指定的分割符将不同的字符串使用该分隔符连接到一起
System.out.println(String.join("-", "hello","world","softeem"));
动态数组参数
public class Test {
/**
* 动态数组参数(语法糖技术):
* 允许在定义方法的时候将参数定义为动态数组参数,
* 在调用时可以动态传入任意多个符合要求的数据类型值
*
* 动态数组参数只能位于方法参数的最后一位
* @param names
*/
public void showAllNames(String s,String... names) {
for(String n:names) {
System.out.println(n);
}
}
public static void main(String[] args) {
Test t = new Test();
t.showAllNames("james","rose","kobe","curry","wade");
}
}
作业
-
完成一个验证码生成器,要求能够随机生成09,az,A~Z之间长度为4位的验证码,并完成一个验证过程
-
String commons = “听说凤姐去了米国,找到了心仪男票,三天两头在微博上抨击单身狗。。。。”;
//屏蔽:凤姐,男票,单身狗 规则用*替换 -
//有如下请求路径
http://www.softeem.com/query/list.html
//请获取请求的资源名称,即list.html
public class TestString {
public void replace() {
String commons = "听说单身狗凤姐去了米国,找到了心仪男票,三天两头在微博上抨击单身狗。。。。";
String[] words = { "凤姐", "米国", "单身狗" };
for (String k : words) {
commons = commons.replace(k, placeholder(k));
}
System.out.println("替换后:" + commons);
}
// 根据字符串的字符个数返回对应长度的“*”
public String placeholder(String s) {
String p = "";
for (int i = 0; i < s.length(); i++) {
p += "*";
}
return p;
}
/**
* 手机号脱敏 将手机号的前三位后四位显示中间四位使用"*"替换
*
* @param phoneNum
* @return
*/
public String handler(String phoneNum) {
return phoneNum.substring(0,3)+"****"+phoneNum.substring(8);
}
public static void main(String[] args) {
String url = "http://www.softeem.com/query/list.html";
// 获取指定的子字符串在目标字符串中最后一次出现的索引
int i = url.lastIndexOf("/");
System.out.println(i);
System.out.println(url.substring(i + 1));
// 匹配给定的字符串是否符合指定的正则表达式规则(匹配,查找,替换)
System.out.println("13567898765".matches("1[3578]\\d{9}"));
// 将字符串中指定的字符替换成新的字符
System.out.println("无极剑圣剑客".replace('剑', '刀'));
String commons = "听说单身狗凤姐去了米国,找到了心仪男票,三天两头在微博上抨击单身狗。。。。";
System.out.println(commons.replace("单身狗", "***"));
new TestString().replace();
// 脱敏操作 134****7890
System.out.println("张三13456543212男".replaceAll("1[3578]\\d{9}", "*"));
String phone = new TestString().handler("13245674321");
System.out.println(phone);
String str = "10/张三/男/计算机科学与技术/68.8";
//使用特定的分隔符对字符串截取,并存储到数组中
String[] info = str.split("/");
// for (String s : info) {
// System.out.print(s + " ");
// }
Student stu = new Student();
stu.setSno(Integer.parseInt(info[0]));
stu.setSname(info[1]);
stu.setSex(info[2]);
stu.setMajor(info[3]);
stu.setScore(Double.parseDouble(info[4]));
System.out.println(stu);
String file = "美女.png";
System.out.println(file.split("\\.")[0]);
//将指定的字符串转换为大写字母
System.out.println("helloworld".toUpperCase());
//将字符串转换为小写
System.out.println("HelloWorld".toLowerCase());
//将字符串前后空格去除
System.out.println(" hello world 123 ".trim());
//有以下学生名称 smith scott allen james kobe
//要求将学生名字首字符大写输出?
String name = " smith scott allen james kobe ";
String[] names = name.trim().split(" ");
for (String n : names) {
n = n.substring(0,1).toUpperCase()+n.substring(1).toLowerCase();
System.out.println(n);
}
//将整数类型值转换为String
System.out.println(String.valueOf(100));
}
}
StringBuffer & StringBuilder
String类型由于是一个定长字符串,一旦为String赋值,则无法修改,修改只能重新为其指定新的地址;如果需要在原来字符串的基础上增加新的内容,则意味着每次都要重新创建对象,因此在进行大量字符串拼接时,会大量消耗内存空间;如果需要涉及到经常性的字符串拼接java中提供了两个类来完成:
- java.lang.StringBuffer
- java.lang.StringBuilder
以上两个类型拥有相同的API(提供的构造器和方法几乎一致),两个类都是可变长度的字符串实现,区别在于StringBuffer是线程安全的,StringBuilder是线程不安全;StringBuilder在多线程环境下效率要高于StringBuffer,因为StringBuffer的方法中都有同步锁,因此在多线程并发访问的时候,同一时间只能有一条线程访问方法,因此,效率较低但是数据的安全性一致性能得到保证。单线程环境下两者的效率一致
String、StringBuffer、StringBuilder字符串拼接效率对比
public class Test {
//17s
public void testString() {
String s = "";
for (int i = 0; i < 100000; i++) {
s += "hello";
}
}
//0.005s
public void testStringBuffer() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 100000; i++) {
sb.append("hello");
}
}
//0.005s
public void testStringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("hello");
}
}
public static void main(String[] args) {
// 记录开始时间毫秒数
long start = System.currentTimeMillis();
new Test().testString();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒");
}
}
StringBuffer 常用方法
StringBuffer sb = new StringBuffer();
//链式编程(函数式编程)
sb = sb.append("hello") // 追加内容
.append(true)
.append(100)
.append(3.14);
//.insert(5, "你好世界"); // 插入 366712642
//无论StringBuffer如何追加内容,hashCode始终不变
System.out.println(sb.hashCode());
String s = "123";
System.out.println(s.hashCode());
s = "123"+"456";
//一旦String类型变量重新值则hashCode将会改变
System.out.println(s.hashCode());
System.out.println(sb);
//删除指定索引区间的子字符串
sb = sb.delete(0, 5);
//字符串反转
System.out.println(sb.reverse());
System.out.println(sb.hashCode());
static
static是一个java中的关键字,同时也是一个修饰符;static主要用于修饰属性,方法,内部类;被static修饰的元素与对象无关(不需要通过对象访问),称之为类成员。
- 被static修饰的属性称之为类属性(静态属性)
- 被static修饰的方法称之为类方法(静态方法)
- 被static修饰的初始化块称之为静态块(静态游离块)
public class StaticDemo {
static String s = "hello";
int i = 10;
//静态方法
public static void m() {
System.out.println("do something.....");
//i++; //编译错误:不能在静态方法中调用非静态成员
//t(); //编译错误
}
public void t(){
System.out.println("hello";)
}
public static void main(String[] args) {
// StaticDemo sd = new StaticDemo();
// sd.m();
//静态方法无需创建对象调用
m();
//静态属性也无需使用对象调用
System.out.println(s);
}
}
注意事项:
- 被static修饰的元素不再与对象相关,即jvm的gc(垃圾回收)机制回收对象时与类成员无关,即不会回收static元素,static元素会常驻内存直到JVM结束
- static一般用于工具类的方法,比如:java.util.Arrays、java.lang.Math等
- 类加载时会对所有的静态成员进行验证,但是此时对象还未产生,因此不能在静态方法中调用非静态成员
- 针对以上的问题即:不允许在static方法中使用this关键字
初始化块与static块
java的类结构中还存在一个特殊的语句块:初始化块,又称之为游离块;游离块作用于构造器执行前,执行一些初始化操作;一般用于将多个不同构造器中要执行的重复代码进行统一编写。如:
public class Player {
private String name;
private int level;
//初始化块(游离块)
{
this.name = "德玛西亚";
System.out.println("加载画布。。。。");
}
public Player() {
}
public Player(String name) {
super();
this.name = name;
}
public Player(String name, int level) {
super();
this.name = name;
this.level = level;
System.out.println("构造器执行");
}
}
public class Mianshi {
String msg = "hello";
static {
Mianshi ms = new Mianshi();
ms.msg = "world";
System.out.println(ms.msg);
}
{
msg="softeem";
}
public Mianshi() {
msg = "你好世界";
}
public static void main(String[] args) {
//类加载时会自动执行静态初始化块中的内容,即便main方法中没有编写任何内容
}
}
static块,游离块,构造器的执行顺序
- 首先执行static块,并且只执行一次
- 其次对象创建时先执行游离块然后才执行构造器
- 游离块的执行次数取决于构造器的调用次数
综合练习
-
分析以下需求,并用代码实现:
(1)从键盘循环录入录入一个字符串,输入"end"表示结束
(2)将字符串中大写字母变成小写字母,小写字母变成大写字母,其它字符用"*"代替,并统计字母的个数
举例: 键盘录入:Hello12345World 输出结果:hELLO*****wORLD
public class ConvertChars {
public static String convert(String s) {
StringBuffer sb = new StringBuffer();
for(int i = 0;i<s.length();i++) {
//取得每一个字符
char c = s.charAt(i);
//判断目标字符是否是小写字符
if(Character.isLowerCase(c)) {
c = Character.toUpperCase(c);
}else if(Character.isUpperCase(c)) {
c = Character.toLowerCase(c);
}else {
c = '*';
}
//将转换后的字符追加到StringBuffer中
sb.append(c);
}
return sb.toString();
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
//当输入内容不为“end”时执行循环
while(!"end".equals(str)) {
//执行转换
String s = convert(str);
System.out.println("转换完成:" + s);
str = sc.nextLine(); //i++
}
System.out.println("结束!");
}
}
- 完成一个企业人事管理系统,该系统中包含两个实体类:员工(Emp),部门(Dept),两个类中分别包含以下属性:
员工(工号,姓名,性别,职位,年龄,月薪,部门)
部门(编号,部门名,部门介绍,分机号)
要求实现以下功能:
完成部门的创建,添加3个部门
向各个部门中添加若干员工
查询所有的员工信息和所在的部门信息
根据员工工号显示员工信息
修改指定工号员工的薪资
根据部门号查询出门中的所有员工
员工类
public class Emp {
private int eno;
private String ename;
private String sex;
private int age;
private String job;
private double sal;
private Dept dept;
//constractor
//setter/getter..
//toString
}
部门类
public class Dept {
private int dno;
private String dname;
private String summary;
private String tel;
//constractor
//setter/getter..
//toString
}
员工管理类
public class EmpManagement {
//声明为static元素 无论创建多少个EmpManagement对象,始终只存在一个ArrayList实例
static ArrayList<Emp> list = new ArrayList<>();
/**
* 添加员工
* @param e
* @return
*/
public boolean addEmp(Emp e) {
return list.add(e);
}
/**
* 查询所有员工
* @return
*/
public ArrayList<Emp> findAllEmp() {
return list;
}
/**
* 根据员工的工号查询员工对象
* @param eno
* @return
*/
public Emp findByEno(int eno) {
for(Emp e:list) {
if(e.getEno() == eno) {
return e;
}
}
return null;
}
/**
* 修改员工薪资
* @param eno
* @param sal
* @return
*/
public boolean updateSal(int eno,double sal) {
Emp e = findByEno(eno);
if(e == null) {
return false;
}
e.setSal(sal);
return true;
}
/**
* 根据部门号查询部门中的员工
* @param dno
* @return
*/
public ArrayList<Emp> findByDno(int dno){
ArrayList<Emp> emps = new ArrayList<Emp>();
for(Emp e:list) {
//判断员工是否存在部门,以及其部门号是否等于参数提供的部门号
if(e.getDept() != null && e.getDept().getDno() == dno) {
emps.add(e);
}
}
return emps;
}
}
测试类
public class Client {
public static void main(String[] args) {
Dept d1 = new Dept(10,"研发部","软件开发方向","996996");
Dept d2 = new Dept(20,"行政部","公司内务管理","123123");
Dept d3 = new Dept(30,"市场部","对外招投标","333333");
Emp e1 = new Emp(1001, "张三丰", "男", 110, "技术总监", 50000, d1);
Emp e2 = new Emp(1002, "张翠山", "男", 110, "部门经理", 20000, d1);
Emp e3 = new Emp(1003, "张无忌", "男", 110, "程序员", 17000, d1);
Emp e4 = new Emp(1004, "张远桥", "男", 110, "程序员", 15000, d1);
Emp e5 = new Emp(1005, "刘德华", "男", 110, "行政主管", 8000, d2);
Emp e6 = new Emp(1006, "马德华", "女", 110, "前台", 5000, d2);
Emp e7 = new Emp(1007, "王德华", "男", 110, "市场经理", 25000, d3);
Emp e8 = new Emp(1008, "李德华", "女", 110, "市场专员", 5000, d3);
EmpManagement em = new EmpManagement();
em.addEmp(e1);
em.addEmp(e2);
em.addEmp(e3);
em.addEmp(e4);
em.addEmp(e5);
em.addEmp(e6);
em.addEmp(e7);
em.addEmp(e8);
System.out.println("------所有员工信息和部门信息-------");
ArrayList<Emp> list = em.findAllEmp();
for (Emp emp : list) {
System.out.println(emp);
}
System.out.println("------显示指定员工信息-------");
Emp e = em.findByEno(1009);
if(e != null) {
System.out.println(e);
}else {
System.out.println("员工不存在");
}
System.out.println("-------根据工号修改工资-------");
boolean b = em.updateSal(1009, 15200);
if(b) {
System.out.println("修改成功");
System.out.println(em.findByEno(1004));
}else {
System.out.println("修改失败,员工不存在!");
}
System.out.println("-------根据部门号查询部门中所有员工------");
list = em.findByDno(40);
if(list.size() > 0) {
for (Emp emp : list) {
System.out.println(emp);
}
}else {
System.out.println("该部门中不存在员工!");
}
}
}
Math类&Array类
Math类
System.out.println(Math.E);
//获取圆周率
System.out.println(Math.PI);
System.out.println(Math.abs(-10));
System.out.println(5.0/2);
//分页技术 101 10
//向上取整 不论小数点之后是什么值始终向整数位舍入
System.out.println(Math.ceil(10.0001));
//向下取整
System.out.println(Math.floor(10.9999));
//四舍五入
System.out.println(Math.round(10.49));
//随机数(抽奖系统)
System.out.println(Math.random());
//取最大值
System.out.println(Math.max(15, 10));
//取最小值
System.out.println(Math.min(15, 10));
//BigDecimal
System.out.println(Math.nextUp(3.4));
//求一个数的n次幂
System.out.println(Math.pow(2, 4));
Arrays类
/**
* 使用Math和ArrayList完成一个摇奖机的功能
* 1. 将一些学生对象加入到ArrayList中(从控制台输入学生信息)
* 2. 输入2抽象
*
* =======双11大抽奖======
* ===1.添加抽奖学生======
* ===2.开始抽奖==========
*
* 输入1:
* 输入学生信息,添加学生
* 输入" end" 完成添加
* 输入2:
* 开始抽奖,对于被抽取到的学生提示:恭喜中奖,作业题10道!
* 已经中过奖的学生从集合中移除 remove
* 输入“back”可以返回上一级菜单
*
* 学生信息:学号,姓名,性别,年龄,专业
*
* @author mrchai
*/
public class ArraysDemo {
public static void main(String[] args) {
//将一个数组转换为List(集合)
List list = Arrays.asList("hello","word","softeem",100);
int[] arr = {10,20,30,40,50,60,70,90};
//二分查找(有序数组)
int i = Arrays.binarySearch(arr, 70);
System.out.println(i);
}
}
设计模式之-单例模式(Singleton)
设计模式即在软件开发历史中,经过不断迭代和演变形成的一套针对于某些特定问题,所提供的的专门的解决方案;在java中常见的设计模式包含23种,比如:
- 单例模式
- 模板方法
- 简单工厂
- 适配器模式
- 观察者模式
- 代理模式
- 装饰模式
- …
单例模式概述
单例模式也称之为单态模式,单子模式等;指的是在程序运行的过程中始终只存在一个对象(实例)。单例模式的表现形式又分为两种:
- 懒汉式(需要的时候才创建:以时间换空间的概念)
- 饿汉式(类加载即创建:以空间换时间的概念)
懒汉式
懒汉式即需要实例对象时才创建:
/**
* 单例模式 - 懒汉式(需要时才创建)
* @author mrchai
*/
public class Singleton {
private static Singleton instance; //15db9742
//构造器私有化,不允许外界随意访问(不允许随便创建对象)
private Singleton() {}
public static Singleton newInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
饿汉式即类加载时就创建实例对象
/**
* 单例模式-饿汉式(类加载即创建对象)
* @author mrchai
*
*/
public class Singleton2 {
private static Singleton2 instance = new Singleton2();
private Singleton2() {
public static Singleton2 newInstance() {
return instance;
}
}
应用场景
单例模式的使用场景十分广泛,比如:数据库连接池,字符串常量池,线程池等资源池,比如日历类,Runtime类等常见与系统环境交互相关的类。
Runtime类
Runtime类是java中提供的与本机系统交互的类,该类的实现使用了单例模式(饿汉式);
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
//...
}
通过Runtime类可以获取系统运行环境信息,以及操作系统命令和系统中的应用程序:
public class TestRuntime {
public static void main(String[] args) throws IOException {
//获取与当前系统环境交互的运行时对象
Runtime rt = Runtime.getRuntime();
//返回JVM可用内存空间
long t = rt.freeMemory();
//1024字节=1kb 1024kb=1mb 1024=1gb
System.out.println(t/(1024*1024));
//运行垃圾回收器,清理内存空间(标记清除,标记压缩,分代收集...)
rt.gc();
//系统退出(终止JVM)
// rt.exit(0);
// System.out.println("hello");
//定时关机 (300秒之后关机)shutdown -a
// rt.exec("shutdown -s -t 300");
//打开控制面板
// rt.exec("control");
//打开计算器
// rt.exec("calc");
//打开画图板
// rt.exec("mspaint");
for (int i = 0; i < 10; i++) {
//启动应用程序
rt.exec("C:\\Program Files (x86)\\Tencent\\QQ\\Bin\\QQScLauncher.exe");
}
}
}
final关键字
final表示最终的,final是一个关键字,可以用于修饰类,属性和方法,被final修饰的元素有以下特征:
- final修饰的类无法被继承(断子绝孙类)
- final修饰属性一旦赋值则无法修改(常量)
- final修饰的方法无法被重写
final在修饰类时一般表示该类就已经是最终的实现了,不能再进行扩展;final在修饰属性时一般会结合static共同使用,用于声明常量(constant)
常量使用
java中的常量分为两种:
- 直接量
- 自定义常量
自定义常量一般使用final和static结合使用,通常用于对某些固定不变的值进行声明,
语法:
[<访问修饰符>] static final 数据类型 类型名称 = 值;
例如:
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
public static final int EXIT_ON_CLOSE = 3;
/**游戏窗体标题*/
public static final String TITLE = "CXK 打篮球?or 打飞机?";
/** 窗体宽度*/
public static final int WIDTH = 450;
/** 窗体高度*/
public static final int HEIGHT = 700;
/** 隐藏窗口*/
public static final boolean HIDDEN = false;
/** 显示窗口*/
public static final boolean SHOW = true;
关于常量的命名规范:
- 名称全部使用大写的英文单词
- 如果由多个单词组合而成,每个单词之间使用下划线隔开
扩展:GUI编程
GUI(Graphic User Interface):图形用户接口,Java中提供的图形界面编程技术,内部主要有两个部分构成:
- java.awt.*
- javax.swing.*
- javaFx
Swing
- JFrame 窗体:界面的骨架
- JPanel 面板:容器,内部可以摆放各种控件
- 布局(layout):设置窗口中控件的摆放方式
- 控件:按钮,文本,输入框,文本域,下拉列表,单选按钮,表格,列表组件等
抽象类&接口
抽象类(abstract class)
java中凡是普通类都具备实例化对象的能力,因为一个类具备了实例化对象的一些必要信息,比如属性,比如行为;但是有些时候,当一个类中信息不足以描述一个具体对象时,此时该类就应该考虑定义为抽象类。
java中的抽象类中所包含的信息,不足以描述一个具体对象,抽象类的内部成分由以下信息构成:
- 属性
- 构造器
- 行为
- 已实现
- 未实现(抽象方法)
抽象类基本案例
语法
public abstract class 类名称{
//属性定义
//方法定义
//抽象方法定义
public abstract 返回值类型 方法名(参数列表);
}
案例:
/**
* 抽象类
* @author mrchai
*/
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//抽象方法
public abstract void eat();
public abstract void sleep();
}
Dog类:
public class Dog extends Animal{
//实现方法
public void eat() {
System.out.println("吃骨头");
}
//实现方法
public void sleep() {
System.out.println("睡狗屋");
}
}
特点
- 抽象类必须使用abstract修饰
- 抽象类存在构造器,但是无法实例化(可以使用匿名内部类创建子类对象)
- 抽象类中通常会包含抽象方法
- 抽象类的存在一般是需要由子类继承,子类一旦继承了抽象类,则子类必须实现父类中的抽象方法(除非子类也是抽象类)
- 抽象类允许继承其他抽象类,但是也只能单继承,可以通过多重继承的方式实现多继承
- abstract只能修饰类和方法
设计模式之模板方法(Template Method)
模板方法指的是对某些方法实现,另外对于一些方法定义为抽象,在实现方法中调用未实现的方法(定义好算法的骨架,具体实现由子类完成)
例如:银行计算利息,都是利率乘以本金和存款时间,但各种存款方式计算利率的方式不同,所以,在账户这个类的相关方法里,只搭出算法的骨架,但不具体实现。具体实现由各个子类来完成。
账户类(Account.java):
public abstract class Account {
//本金
private double money;
//利息
private double interest;
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//定义好算法的实现骨架
public double getInterest() {
return money * getInterestRate();
}
public void setInterest(double interest) {
this.interest = interest;
}
/**
* 返回利率
* @param time
* @return
*/
public abstract double getInterestRate() ;
}
活期账户类(ActiveAccount.java):
/**
* 活期账户
* @author mrchai
*
*/
public class ActiveAccount extends Account{
//总存款月数
private int month;
//基础利率
private double base;
public ActiveAccount(int month,double base) {
super();
this.month = month;
this.base = base;
}
@Override
public double getInterestRate() {
return month * base;
}
}
定期账户类(FixedAccount.java):
/**
* 定期账户
* @author mrchai
*/
public class FixedAccount extends Account{
private int year;
private double base;
public FixedAccount(int year, double base) {
super();
this.year = year;
this.base = base;
}
@Override
public double getInterestRate() {
return year * base * 2;
}
}
测试类:
public class TestAccount {
public static void main(String[] args) {
ActiveAccount a1 = new ActiveAccount(5, 0.0001);
a1.setMoney(50000);
FixedAccount a2 = new FixedAccount(5, 0.03);
a2.setMoney(50000);
System.out.println(a1.getInterest());
System.out.println(a2.getInterest());
}
}
接口(interface)
接口是一种比抽象类更纯粹的抽象;因为内部只能够存在常量以及未实现的方法(JDK8以前),接口通常用于定义一些未实现方法的集合,但是不对法方法具体实现,具体的实现通常是由实现类完成,接口具备以下特征:
- 接口不存在构造器,因此无法实例化
- 接口允许继承接口,可以同时继承多个接口
- 一个类可以实现多个接口,但是必须要同时实现所有接口的方法(除非抽象类)
- 抽象类也实现接口
- JDK8中对接口有其他新增特性(默认方法,静态方法,函数式接口)
语法
public interface 接口名称{
//常量定义
//方法的声明(不含实现)
}
/**
* 接口即标准
* USB接口:只是一种标准的定义,不包含具体实现
* 一流的公司定义标准 做架构
* 二流的公司卖服务 写框架
* 三流的公司卖产品 写CRUD
*
* 定义一个接口
* @author mrchai
*/
public interface Flyable {
//默认编译器会将以下代码完善成: public static final String msg = "hello";
String msg = "hello";
//编译器会将以下代码自动完善:public abstract void fly()
void fly();
}
抽象类和接口区别
- 抽象类是一种类结构,接口是一种行为准则
- 抽象类中包含类的所有特征,同时包含抽象方法;接口只能有常量和未实现的方法
- 抽象类由子类通过extends进行扩展;接口由实现类通过implements扩展
- 子类只能继承一个抽象类;一个类可以实现多个接口
- 抽象类能且只能继承一个父类;接口可以继承多个接口,接口不能继承任何类
- 抽象类是对类(名词)的一种抽象;接口是对行为(动词,形容词)的抽象,接口是一种特殊的抽象类
多态详解(polymorphism)
多态的体现包含两种层面:
- 方法层面(重写和重载)
- 属性层面
- 父类引用指向子类对象
- 子类引用指向父类对象(原本就是子类)
多态也称之为动态绑定:在运行期间动态为引用变量绑定具体的对象数据
内部类
通常一个java文件中只能包含一个public class,但是可以在一个java类的内部中嵌套其他的java类,这种类称之为内部类,也叫嵌套类,或者类属类,比如:
public class People {
public void m(){
}
public static void main(String[] args) {
}
//内部类
class Body{
}
}
//不是内部类,和People是平级的关系
//class Head{
//}
以上就是一个典型的内部类,生成的字节码文件如下:
People$Body.class
People.class
java中的内部类包含以下几种定义方式:
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
内部类的好处
- 提供了比方法更好的封装性
- 提高类多继承的可能性
成员内部类
成员内部类即在类结构中直接定义的与属性,方法,构造器同一级别的类。
public class People extends JFrame{
public void m1() {
System.out.println("外部类方法");
// new Body().m3();
}
//内部类
private class Body extends ArrayList{
String name;
public void m2() {
System.out.println("内部类方法");
}
public void m3() {
}
}
}
成员内部类的使用场景:在JDK中有很多类使用到了成员内部类,例如: ArrayList:
成员内部类注意事项:
成员内部类类似成员方法,允许被任意的访问修饰符修饰(default,public,private,protected)
局部内部类
所谓局部内部类即在一个类的方法或者其他成员的(构造器,游离块)语句块中定义,不太常见:
public class User {
public void login() {
int i = 10;
class Validate{
public void t() {
System.out.println("======="+i);
}
}
Validate v = new Validate();
v.t();
}
public void reg() {
//编译错误:找不到类
// Validate v = new Validate();
// v.t();
}
public static void main(String[] args) {
User u = new User();
u.login();
}
}
局部内部类注意事项:
- 不能使用任何的访问修饰符(private,protected,public)修饰,原理参考局部变量的定义
- 局部内部类中不能对方法中的局部变量进行修改,如果局部内部类中需要使用为外部方法的局部变量时,该变量必须定义为final(java8无需显式定义final,编译器默认添加)
- 使用final修饰局部变量的作用在于延长局部变量的生命周期,避免因为方法的结束而被清理(可能局部内部类在使用该变量)
静态内部类
静态内部类即使用static修饰的成员内部类;静态内部类与外部类的对象无关,一般用于数据缓存,比如Integer中的静态内部类:IntegerCache
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
自定义静态内部类:
public class StaticClass {
public void m() {
System.out.println("外部类方法");
}
public static class MyCache{
static String msg = "天干物燥,小心火烛";
public static void t() {
System.out.println("静态内部类的方法");
}
public void m2() {
System.out.println("静态内部类的普通方法");
}
}
}
静态内部类的对象创建与方法调用:
//创建静态内部类的对象
StaticClass.MyCache cache = new StaticClass.MyCache();
//调用静态内部类的静态方法
StaticClass.MyCache.t();
//调用静态内部类的成员方法
cache.m2();
匿名内部类
匿名内部类即没有名字的内部类,通常用于事件监听机制,回调机制中
public abstract class Animal {
public abstract void eat();
}
匿名内部类实例:
public static void main(String[] args) {
//匿名内部类
Animal a = new Animal(){
int i = 10;
public void eat() {
i = 20;
System.out.println("吃狗肉"+i);
m();
}
//由于匿名内部类没有名称
//则内部定义的方法外界无法访问
//通常方法使用private修饰
private void m() {
System.out.println("----");
}
};
a.eat();
}
Timer&TimerTask
Timer类是JDK中,java.util包中提供的用于执行定时任务的定时器类;Timer通常与TimerTask结合使用,以达到定时任务执行的功能,比如定时消息推送,闹钟应用的实现等。
TimerTask是一个抽象类,内部的run方法是一个抽象方法,因此通过对TimerTask扩展可以创建自定义的定时任务,只需要实现run方法即可:
定时任务类:
public class MyTask extends TimerTask{
@Override
public void run() {
System.out.println("任务执行。。。");
}
}
测试类:
public class TestTimer {
public static void main(String[] args) {
//1秒 = 1000毫秒
//创建一个定时器
Timer t = new Timer();
t.schedule(new MyTask(), 5000,1000);
}
}
另外如果定时任务只在某个固定的位置使用,可以无需创建具体类进行继承;此时可以考虑使用匿名内部类实现:
Timer t = new Timer();
t.schedule(new TimerTask() {
public void run() {
System.out.println(LocalDateTime.now());
}
}, 3000, 500);