21.类之间的关系
is a:继承关系
has a : 关联关系,通常以属性的形式存在
like a:实现关系,通常是类与接口的关系;
22.抽象类与接口的区别
抽象类是半抽象的,接口是完全抽象的;
抽象类有构造方法,接口没有构造方法;
抽象类之间只支持单继承,接口与接口之间可以多继承;
普通类只能继承一个抽象类,可以实现多个接口;
接口中只能出现常量和抽象方法;
实际项目开发中,接口使用的多,抽象类使用的少,接口一般是对行为进行抽象;
23.Object基类
toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
建议所有子类覆盖此方法:
public class Movie {
public String toString() {
return "这是一个电影类";
}
}
System.out.println("引用"),会自动调用引用的toString方法;
equals()方法
public boolean equals(Object obj) {
return (this == obj);
}
因为Object源码中的equals比较的是两个实例的内存地址,并不能准确的表达我们需要比较的内容,所以建议所有类将equals方法重写;
java中基本数据类型用"=="来判断,引用数据类型用equals()判断是否相等;
符串也用equals()判断,因为字符串的创建方式有两种
String s="aaa"
//或者
String s=new String("aaa");
如果是用new的形式创建的话,那么用==判断是不准确的,并且String已经将equals方法重写了
准确的判断字符串相等:
public class Movie {
private String name;
private String code;
public Movie(String name, String code) {
this.name = name;
this.code = code;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Movie) {
Movie o = (Movie) obj;
return (o.name.equals(this.name) && o.code.equals(this.code))
}
return false;
}
}
finalize()方法
@Deprecated(since="9")
protected void finalize() throws Throwable { }
这个方法不需要程序员手动调用,jvm的垃圾回收器会自动调用这个方法;
finalize的调用时机,当一个对象被垃圾回收器(GC)回收的时候,垃圾回收器负者调用此方法;
finalize实际是上是sun公司为java程序员准备的一个时机,垃圾销毁时机,如果希望在对象销毁时执行一段代码的话,这段代码可以写在finalize当中;
如果垃圾比较少,垃圾回收器也有可能不启动;
Movie m = new Movie(new String("无极"), new Players("刘德华", "梁朝伟"));
m = null;
//建议垃圾回收器启动,只是建议,也有可能不启动,启动的概率高一些
System.gc();
hashCode()方法
hashCode()返回的是哈希码,实际上就是一个java对象的内存地址,经过哈希算法,得出一个值,所以hashCode()的执行结果可以等同看作一个java对象的内存地址;
24.内部类
内部类:在类的内部定义了一个新的类,被称为新的类;
内部类的分类:
静态内部类,类似于静态变量;
实例内部类,类似于实例变量;
局部内部类,类似于局部变量;
package movie;
public class Movie {
//静态内部类
static class Inner1 {}
//实例内部类
class Inner2 {}
public void name1() {
//局部内部类
class Inner3 {}
}
}
使用内部类编写的代码可读性很差,能不用尽量不用;
匿名内部类是局部内部类中的一种;
public class Movie {
public void doSome() {
SeeMovie s = new SeeMovie();
double pay = s.pay(new Cinema() {
//匿名内部类里面写类体
public double countPrice(int count, double price) {
return count * price;
}
}, 10, 32.2);
System.out.println(pay);
}
}
interface Cinema {
double countPrice(int count, double price);
}
// class DadiCinema implements Cinema {
// public double countPrice(int count, double price) {
// return count * price;
// }
// }
class SeeMovie {
public double pay(Cinema c, int count, double price) {
return c.countPrice(count, price);
}
}
匿名内部类缺点:因为一个类没有名字,没有办法重复使用,另外代码太乱,可读性差;
25.数组
java语言中的数组是一种引用数据类型,不属于基本数据类型,数组的父类是Object;
因为数组是引用数据类型,所以数组是存储在堆内存当中的;
数组中如果存储的是对象的话,实际上存的是对象的引用;
在java中,数组一旦创建,长度不可变;
java中的数组,要求元素类型统一,比如int类型的数组只能存int类型元素;
数组在内存中存储的时候,数组中元素的内存地址是连续的;
数组也是一种简单的数据结构;
所有数组都是拿第一个元素的内存地址作为数组的内存地址;
优点:在查询/查找/检索某个下标的元素时效率极高,可以说是查询效率最高的一种数据结构;
因为:
①每一个元素的内存地址在空间存储上是连续的;
②每一个元素类型相同,所以占用的空间大小是一样的;
③知道第一个元素的内存地址,知道每一个元素的占用空间大小,知道元素的下标,所以通过一个数学表达式就可以知道某个下标上元素的内存地址,直接通过内存地址就能锁定元素,所以数组的检索效率是最高的;
数组中查找第100个元素与查找第100w个元素的效率是相同的,因为数组元素的查找不是逐个查找的,是通过数学表达式计算出来的(算出一个内存地址,直接定位);
缺点:①为了保证数组中每个元素的内存地址是连续的,所以在数组中删除或者新增一个元素的时候效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作;
②数组不能存储大数据量,因为很难在内存空间中找到一块特别大的连续的内存空间;
对数组最后一个元素的增删是没有效率影响的;
2种方式初始化一维数组:
静态初始化:
当创建数组的时候,确定存储哪些数据的时候采用静态初始化方式;
int[] array1={1,5,3,6,9}
C++风格,不建议使用
int arr[]={1,2,3,4,5};
动态初始化:
当创建数组的时候,不确定存储哪些数据的时候采用动态初始化方式,预先分配内存空间;
int[] array2=new int[5]; //这里表示初始化包含5个元素的int类型数组,每个元素的默认值是0
String[] array2=new String[5]; //这里表示初始化包含5个元素的String类型数组,每个元素的默认值是null
往方法里面传数组参数
//直接传递静态数组语法
App.printArray(new int[] { 1, 2, 36, 9 });
App.printArray(new int[5]);
String[] arr4 = { "a", "bbb", "ccc" };
App.printArray(arr4);
main方法的数组参数 args:
jvm负责调用main方法,这个数组参数是留给用户的,用户可以在控制台上输入参数,这个参数会自动转换成String[] args,例如这样运行程序
java App aaa bbb ccc
那么这个时候jvm就会根据空格将字符串进行分离,存储到String[] args数组当中
所以main方法的String[] args主要是用来接收用户输入参数的,了解即可,实际开发一般都是有界面的。
此功能可以用来在程序入口判断登录
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println("请输入账号密码");
return;
}
//if (args[0].equals("admin") && args[1].equals("123")) {
//这样写可以避免空指针异常
if ("admin".equals(args[0]) && "123".equals(args[1])) {
System.out.println("进入系统");
} else {
System.out.println("账号密码错误");
return;
}
}
数组存引用数据类型,并完成多态
public static void main(String[] args) throws Exception {
Movie[] m = { new Movie(), new Movie(), new WarMovie(), new LoveMovie() };
for (int i = 0; i < m.length; i++) {
if (m[i] instanceof WarMovie) {
((WarMovie) m[i]).warMoveInfo();
} else if (m[i] instanceof LoveMovie) {
((LoveMovie) m[i]).loveMovieInfo();
} else {
m[i].seeMovie();
}
}
}
数组存引用数据类型实际上存的是引用地址
数组扩容
由于java中的数组长度一旦确定是不能改变的,所以java中的数组扩容是新建一个大容量数组,再将原数组的元素拷贝到新数组;
数组的扩容效率较低,因为涉及到拷贝的问题,所以在开发中尽量少用数组拷贝,可以在数组创建的时候预估准确的数组容量,这样可以减少数组的扩容次数,提高效率;
数组拷贝
public static void main(String[] args) throws Exception {
String[] arr1 = { "aa", "bb", "cc", "dd" };
String[] arr2 = new String[10];
System.arraycopy(arr1, 1, arr2, 3, 3);
App.printArray(arr2);
}
二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中,每一个元素是一个一维数组;
三维数组是一个特殊的二维数组,二维数组的每个元素的一维数组;
实际开发中基本都用一维数组,二维数组用的比较少,三维数组基本不用;
arr[2][3]表示第3个一维数组中的第4个元素;
二维数组demo:
public class App {
public static void main(String[] args) throws Exception {
//静态二维数组
String[][] arr1 = { { "aa", "bb", "cc", "dd" }, { "aaa", "bbb", "ccc" } };
//初始化同时传参
App.printArray2(new String[][]{{"a","b","c"},{"g","b","k"}});
App.printArray2(arr1);
//动态二维数组,5行3列
String[][] arr2 = new String[5][3];
App.printArray2(arr2);
}
public static void printArray2(String[][] array) {
System.out.println(array.length + "--长度");
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.println(array[i][j]);
}
}
}
}
数组工具类java.util.Arrays:排序:Arrays.sort;二分法查找:Arrays.binarySearch(arr,num),用来查找数组是否存在某个数字,不存在返回-1;
26.String
java中双引号内的都是String对象,因为字符串在实际开发中的频率非常高,所以jdk中双引号括起来的字符串都是存储在方法区里面的字符串常量池中的,因为是常量,所以已经创建的字符串不能更改;
String s1 = "abcde";
String s2 = "abcde" + "xy";
String s3 = new String("xy");
new User(111,"张三");
String s1 = "hello";
String s2 = "hello";
//hello是方法区常量池的字符串常量,创建的时候就有一个内存地址,再把内存地址赋值给s1和s2,所以s1与s2的内存地址一样,所以是true
System.out.println(s1 == s2); //true
String s1 = new String("abc");
String s2 = new String("abc");
//s1与s2指向堆内存的不同对象,所以是false
System.out.println(s1 == s2); //false
由上可知,由于字符串的创建方式有两种,new形式创建的字符串对象用==判断会导致不相等,所以判断字符串相等应该用equals方法,String对象已经重写了equals方法;
String的特殊构造方法:
byte构造:
byte[] b = { 97, 98, 99 };
String s1 = new String(b);
System.out.println(s1); //abc
byte[] b = { 97, 98, 99, 100 };
String s1 = new String(b, 1, 2);
System.out.println(s1); //bc
char[] ch = { '霸', '王', '别', '姬' };
String s2 = new String(ch, 1, 3);
System.out.println(s2);
compareTo:按照字典顺序比较
System.out.println("abc".compareTo("abc")); //0
System.out.println("ac".compareTo("ab")); //1
System.out.println("ad".compareTo("ae")); //-1
contains:是否包含
System.out.println("adsasa".contains("asss"));
endsWith:是否以xxx结尾
System.out.println("adsasa".endsWith("sssa"));
equalsIgnoreCase:判断是否相等并且忽略大小写
System.out.println("asD".equalsIgnoreCase("ASD")); //true
getBytes:字符串转换成字节码数组
App.printArr("assssD".getBytes());
indexOf:判断某个字符串在字符串中第一次出现的索引位置
System.out.println("assssD".indexOf("D")); //5
System.out.println("assssD".indexOf("r")); //-1
isEmpty():判断字符串是否为空
System.out.println(" ".isEmpty());
length():字符串长度
System.out.println("aaa".length());
lastIndexOf:子字符串在字符串中最后一次出现的索引
System.out.println("asdasda".lastIndexOf("d")); //5
replace:替换
System.out.println("asdasda".replace("as", "r")); //rdrda
split:分割字符串
printArr("as-das-da".split("-"));
startsWith:某字符串是否以子字符串开头
System.out.println("as-das-da".startsWith("as"));
substring:截取字符串
System.out.println("as-das-da".substring(3));
System.out.println("as-das-da".substring(3, 9));
toCharArray:将字符串转换成字节符数组
printArr("as-das-da".toCharArray());
toLowerCase:转换成小写
System.out.println("AsdGHJ".toLowerCase());
toUpperCase:转换成大写
System.out.println("AsdGHJ".toUpperCase());
trim:去除前后空格
System.out.println(" Asd GHJ ".trim());
valueOf:String中只有这个方法是静态的,不需要new对象,作用是将非字符串转换成字符串,如果参数是对象的话,会调用对象的toString方法,System.out.println源码就是调用这个方法;
String s1=String.valueOf(true);
//String s1 = String.valueOf(new Movie());
System.out.println(s1);
由于java的字符串是存在方法区内存当中的,字符串无法更改,每次拼接都会产生新的字符串,这样会占用大量方法区内存,造成内存空间的浪费;
如果需要进行大量的字符串拼接操作,建议使用java.lang.StringBuffer字符串缓冲区;
StringBuffer s = new StringBuffer();
// 拼接字符串
s.append("a");
s.append(33.5);
s.append(true);
System.out.println(s);
这样只占用一个内存地址,append方法底层在进行追加的时候,如果byte数组满了,会自动扩容;
StringBuffer性能优化:在StringBuffer创建的时候尽可能给定一个合适的初始化容量,最好减少底层数组的扩容次数;
StringBuilder与StringBuffer可以实现同样的功能,但是StringBuffer是synchronized修饰的(线程安全的),表示StringBuffer在多线程环境下是安全的;
27.8种包装类
java为8中数据类型又准备了8种包装类型,8种包装类型都是引用数据类型,其父类是Object类,用来将8种数据类型向上转型;
8种基本数据类型对应的包装类型:
byte -- java.lang.Byte
boolean -- java.lang.Boolean
char -- java.lang.Character
float -- java.lang.Float
double -- java.lang.Double
short -- java.lang.Short
int -- java.lang.Integer
long -- java.lang.Long
//基本数据类型转成引用数据类型-装箱
Integer i = new Integer(10);
//引用数据类型转成基本数据类型-拆箱
float f = i.floatValue();
System.out.println(f);
通过对基本数据类型的包装,达到由基本数据类型向引用数据类型转换的目的;
jdk1.5之后支持自动装箱与自动拆箱
//自动装箱
Integer x = 100;
//自动拆箱
float y = x;
System.out.println(y);
java中为了提高程序的执行效率,将-128到127之间所有的包装对象提前创建好,放到了方法区的整数型常量池当中,所以这个区间的数据不需要包装创建对象,直接从整数型常量池中提取出来;
Integer x = 128;
Integer x1 = 128;
System.out.println(x == x1); //false
Integer y = 127;
Integer y1 = 127;
System.out.println(y == y1); //true
//字符串转数字
int a = Integer.parseInt("123");
//数字转二进制
Integer.toBinaryString(100);
//十进制转16进制
Integer.toHexString(100);
//十进制转8进制
Integer.toOctalString(100);
28.Date库
import java.text.SimpleDateFormat;
import java.util.Date;
//标准日期转字符串格式
Date d = new java.util.Date();
System.out.println(d);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
String s = sdf.format(d);
System.out.println(s);
//字符串格式转标准格式
String s2 = "2008-08-08 13:13:13 333";
//字符串的日期格式与SimpleDateFormat的日期格式要一致,否则报ParseException异常
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss SSS");
Date d2 = sdf2.parse(s2);
System.out.println(d2);
//获取时间戳
long i = System.currentTimeMillis();
//时间戳转标准时间
long i1 = System.currentTimeMillis();
Date d = new Date(i1);
29.System
System.out //out是System的静态变量
System.out.println //println是PrintStream类的方法
System.gc(); //建议启动垃圾回收器
System.currentTimeMillis(); //获取时间戳
System.exit(0); //退出jvm
30.数字格式化
//#代表任意数字
//.代表小数点
//,代表千分位
//0代表补0
DecimalFormat d = new DecimalFormat("###,###.##");
String s = d.format(12345.45); //12,345.45
DecimalFormat d2 = new DecimalFormat("###,###.00000");
String s2 = d2.format(12345.45); //12,345.45000
//BigDecimal属于大数据,精度极高,不属于基本数据类型,属于java对象(引用类型)
//由于在财务软件中,double是不够用的,所以SUN提供了BigDecimal类用于财务软件业务
BigDecimal v1 = new BigDecimal(100);
BigDecimal v2 = new BigDecimal(3.14);
BigDecimal v3 = v1.add(v2);