Day08
对象类型转换 (Casting )
基本数据类型的Casting:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型。
如long g=20; double d=12.0f - 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0d; int a=(int)1200L - 对Java对象的强制类型转换称为造型
从子类到父类的类型转换可以自动进行
从父类到子类的类型转换必须通过造型(强制类型转换)实现
无继承关系的引用类型间的转换是非法的
举例1
//自动类型转换
Student s = new Student();
Person p = s;
Object obj = "hello";//String是引用类型,String类型自动转换成Object类型
String s = (String) Obj;
//强制类型转换
Person p = new Person();
Syudent s = (Student) p;
举例2
public class Test{
public void method(Person e) { //设Person类中没有getschool()方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if(e instanceof Student){
Student me = (Student)e; //将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(Stirng args[]){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
图解
==操作符与equals()方法
==操作符
- 基本类型比较值:只要两个变量的值相等,即为true.
int a=5;
System.out.println(a == 6) ; //false - 引用类型比较地址(是否指向同一个对象):只有指向同一个对象时(堆内存地址相同),= = 才返回true。
Person p1=new Person();
Person p2=new Person();
System.out.println(p1 == p2) ; //false - 用“= =”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错;
兼容指:数据兼容是指,相比较的两者必须为同一类型或可以自动转换的类型(如int与long、子类与父类等),两个不同类的对象之间无法用“= =”比较。
equals()方法
- equals():所有类都继承了Object,也就获得了equals()方法,并且可以重写。
- 只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
格式:obj1.equals(obj2); - 特例:equals()方法比较File类、String类、Data类或包装类对象时,比较的是对象的内容而不考虑对象的地址是否相同;
原因:在这些类中重写了Object类的equals()方法。 - 相比较的两者必须为同一类型或可以自动转换的类型(如int与long、子类与父类等),两个不同类的对象之间无法用equals()比较。
- 总结:
方法 | 比较内容 |
---|---|
== | 基本数据类型:值;引用数据类型:堆内存地址 |
equals() | 只能比较引用数据类型,比较值 |
String
String对象的创建
举例
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2);//true
String s3 = new String("123");
String s4 = new String("123");
System.out.println(s3 == s4);//false
new一定开辟两块堆内存空间,不管对象的内容是否相同,因此s3和s4是内容相同的不同引用(堆地址不同);不使用new时,当对象的内容相同时,系统不再分配新的空间,而是将已有的空间地址赋给新引用,因此s1和s2指向相同的内存空间(是同一内存空间的不同引用)。
- 字符串最大的特点:一旦被初始化就不能被改变。
- 字面量赋值String对象时,只在常量池创建一个对象。
练习
- 代码如下:
int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl)); //true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println(“12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//false
System.out.println("str1是否equals str2?"+(str1.equals(str2)));//true
System.out.println(“hello” == new java.sql.Date()); //编译不通过
Person p1 = new Person();
p1.name = "atguigu";//字面量赋值
Person p2 = new Person();
p2.name = "atguigu";//字面量赋值
System.out.println(p1.name .equals( p2.name));//true,比较内容
System.out.println(p1.name == p2.name);//true,字面量赋值,内容相同,地址相同
System.out.println(p1.name == "atguigu");//true,字面量赋值,内容相同,地址相同
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1==s2);//false,s1和s2new创建,不管内容是否相同,总是开辟两块不同的内存空间
- 编写Order类,有int型的orderoId,String型的ordername,相应的getter()和setter()方法,两个参数的构造器,重写父类的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。
package practice1.test;
class Order {
int orderold;
String ordername;
Order(int orderold, String ordername) {
this.orderold = orderold;
this.ordername = ordername;
}
@Override
public boolean equals(Object obj) {
boolean flag = false;
Order ord = (Order)obj;
if(this.orderold == ord.orderold && this.ordername.equals(ord.ordername)) {
flag = true;
}
return flag;
}
}
public class Test {
public static void main(String[] args) {
Order ord1 = new Order(22, "XiaoMing");
Order ord2 = new Order(23, "LiMing");
System.out.println(ord1.equals(ord2));
}
}
- 请根据以下代码自行定义能满足需要的MyDate类,在MyDate类中覆盖equals方法,使其判断当两个MyDate类型对象的年月日都相同时,结果为true,否则为false。 public boolean equals(Object obj)
package practice1.test;
class MyData {
int year;
int month;
int day;
public MyData(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public boolean equals(Object obj) {
boolean flag = false;
MyData md = (MyData)obj;
if(this.year == md.year && this.month == md.month && this.day == md.day) {
flag = true;
System.out.println("年月日均相同");
}
if(this.year != md.year ) {
System.out.println("年份不相同");
}
if(this.month != md.month) {
System.out.println("月份不相同");
}
if(this.day != md.day) {
System.out.println("日期不相同");
}
return flag;
}
}
public class Test {
public static void main(String[] args) {
MyData md1 = new MyData(2019, 9, 10);
MyData md2 = new MyData(2019, 9, 10);
System.out.println(md1.equals(md2));
MyData md3 = new MyData(2019, 9, 10);
MyData md4 = new MyData(2020, 9, 10);
System.out.println(md3.equals(md4));
}
}
- 判断字符串是否是一个空字符串。
package practice1.test;
public class StringIsEmpty {
public static boolean isEmpty(String str) {
boolean flag = false;
if(str != null && str.equals("")) {
flag = true;
}
return flag;
}
public static void main(String[] args) {
String s = "";
System.out.println(s.isEmpty());//String类自带的isEmpty方法,true
System.out.println(StringIsEmpty.isEmpty(s));//true
}
}
方法
获取
- int length():获取字符串长度。注意,数组是length属性,字符串是length()方法。
- char charAt(int index):获取index位置上的字符。
- int indexOf(char ch):获取字符ch在字符串中第一次出现的位置。若不存在则返回-1。
- int indexOf(char ch,int fromIndex):获取字符ch在指定位置之后中第一次出现的位置。
- int indexOf(String str):获取字符串str第一次出现的位置。
- int indexOf(String str,int fromIndex):获取字符串str在指定位置之后中第一次出现的位置。
- int lastIndexOf(char ch):获取字符ch从后向前查找第一次出现的位置。
- 其他lastIndexOf()方法同上。
判断
- boolean contains(String str):判断字符串是否包含字符ch或字符串str。注意,使用indexOf()也可以判断字符串中是否包含某个字符或字符串,即if(str.indexOf(“xxx”) != -1),并且该方法不仅可以判断是否存在,还可以返回位置。
- boolean isEmpty():判断字符串是否为空。原理是判断字符串长度是否为0。
- boolean startWith(String str):判断字符串是否以某一字符或字符串开始。
- boolean endWith(String str):判断字符串是否以某一字符或字符串结束。
- boolean equals(String str):判断两字符串内容是否相同。
- boolean equalsIgnoreCase(String str):判断两字符串内容是否相同(忽略大小写)。
转换
- 将字符数组转换为字符串:
1.构造方法:
String(char[ ] ch):将字符数组全部转换为字符串。
String(char[ ] ch,int offset,int count):将字符数组从offset开始,长度为count的部分转换为字符串。
2.静态方法:
static String copyValueOf(char[ ] ch)
static String copyValueOf(char[ ] ch,int offset,int count) - 将字符串转换为字符数组:
char[ ] toCharArray() - 将字节数组转换为字符串:
String(byte[ ] b)
String(byte[ ] b,int offset,int count) - 将字符串转换为字节数组:
byte[ ] getBytes() - 将基本数据类型转换为字符串:
static String valueOf(数据)
替换
- String replace(char oldChar,char newChar)
- String replace(String oldStr,String newStr)
切割
- String[ ] split(regex):以regex为切割标记,将字符串切割。
子串
- String subString(int begin):截取从begin开始到最后结束的子串。
- String subString(int begin,int end):截取 从begin开始到(end-1)结束 的子串。
其他方法
- String toUpperCase():将字符串转换为全大写。
- String toLowerCase():将字符串转换为全小写。
- String trim():去除字符串两端的空格。
- int compareTo(String str):以自然顺序比较两个字符串的大小。str1.compareTo(s2); 若s1<s2则返回小于0的数,若s1=s2则返回0,若s1>s2则返回小于0的数。返回的数是字符的ASCII码差值。
练习
- 模拟一个trim方法,去除字符串两端的空格。
- 将一个字符串中指定的部分进行反转。
- 获取一个字符串在另一个字符串中出现的次数。
- 获取两个字符串的最大相同子串。
package string;
class StringTool{
//去除字符串两端空格
public static String myTrim(String str) {
int start = 0;
int end = str.length()-1;
while(start<=end && str.charAt(start) == ' ') {
start++;
}
while(start<=end && str.charAt(end) == ' ') {
end--;
}
return str.substring(start, end+1);
}
//将一个字符串中指定的部分进行反转。
public static String myReverse(String str, int begin, int end) {
char[] ch = str.toCharArray();
for(int start=begin, last=end-1; start<last; start++, last--) {
swap(ch, start, last);
}
return String.copyValueOf(ch);
}
//将一个字符串中指定的全部进行反转。
public static String myReverse(String str) {
return myReverse(str, 0, str.length());
}
//换位操作
private static void swap(char[] ch, int x, int y) {
char temp = ch[x];
ch[x] = ch[y];
ch[y] = temp;
}
//获取一个字符串在另一个字符串中出现的次数。
public static int countSubStr(String str, String subStr) {
int count = 0;
int index = 0;
if(str.length()<subStr.length()) {
return -1;
}else {
while((index = str.indexOf(subStr)) != -1) {
str = str.substring(index + subStr.length());
count++;
// while((index = str.indexOf(subStr, index)) != -1) {
// index = index + subStr.length();
// count++;
// }
}
}
return count;
}
//获取两个字符串的最大相同子串。
public static String getMaxSubString(String str, String subStr) {
String maxStr = "";
String minStr = "";
maxStr = (str.length()>subStr.length())?str:subStr;
minStr = (maxStr==str)?subStr:str;
for(int x=0; x<minStr.length(); x++) {
for(int y=0, z=minStr.length()-x; z!=minStr.length()+1; y++, z++) {
String temp = minStr.substring(y, z);
if(maxStr.contains(temp)) {//if(maxStr.indexOf(temp) != -1)
return temp;
}
}
}
return "";
}
}
public class StringDemo {
public static void main(String[] args) {
String s1 = " 123Hello456Java789World ";
String s2 = "Java";
sop(StringTool.myTrim(s1));
sop(StringTool.myReverse(s1, 4, 12));
sop(StringTool.myReverse(s2));
sop(StringTool.countSubStr(s1, s2));
sop(StringTool.getMaxSubString(s1, s2));
}
//输出方法
public static void sop(Object obj) {
System.out.println(obj);
}
}
StringBuffer
- StringBuffer 是字符串缓冲区,是一个用于存储字符串的容器。
- 特点:
1.长度可以变化(数组长度固定,不可变化)。
2.可以操作多种数据类型。
3.通过toString() 方法转换为字符串。
方法
存储
- StringBuffer append(数据):将数据添加到结尾处。
- StringBuffer insert(int index,数据):将数据插入到指定位置。
删除
- StringBuffer delete(int start,int end):删除 从start到(end-1) 的数据。清空缓冲区:delete(0,s.length());
- StringBuffer deleteCharAt(int index):删除index位置的数据。
获取
- Char charAt(int index):获取index位置的数据
- int indexOf(String str):获取str的位置。
- int lastIndexOf(String str):从后向前查找,获取str的位置。
- int length():获取StringBuffer的长度。
- String subString(int start, int end):获取 从start开始到(end-1) 结束的子串。
修改
- StringBuffer replace(int start,int end,String str):将 从start到(end-1) 的子串修改为str。
- void setCharAt(int index,char ch):将index位置的数据修改为ch。
反转
- StringBuffer reverse():将缓冲区内的字符串反转。
其他方法
- String toString():将StringBuffer对象转换为字符串。
- void getChars(int srcBegin,int srcEnd,char[ ] dst,int dstBegin):将 从srcBegin开始到(srcEnd-1)结束 的数据存入到从dstBegin开始的dst数组中。
StringBuilder
JDK1.5之后出现了StringBuilder。
- StringBuffer是线程同步的(安全);
- StringBuilder是线程不同步的(不安全)。
包装类
- 针对八种基本定义相应的引用类型—包装类
- 有了类的特点,就可以调用类中的方法。
基本数据类型的包装类
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
基本数据类型与字符串之间的转换
1. 基本数据类型转换成字符串
- 基本数据类型 + ""
- static String valueOf(数据):如,String s = String.valueOf(2.34f);
- 包装类.toString(数据):如,Integer.toString(34);
2. 字符串转换成基本数据类型
- 包装类.parseXxx(String s) :以十进制将s转换成对应的基本数据类型。
int i = Integer.parseInt (“20”); - 包装类.parseXxx(String s,int radix) :以指定进制将s转换成对应的基本数据类型(s要符合对应进制的写法)。
int i = Integer.parseInt (“20”,2);//错误,二进制下不存在20这种写法
int i = Integer.parseInt (“110”,2);//i = 6
3.包装类的用法举例
- 装箱:包装类使得一个基本数据类型的数据变成了类。有了类的特点,可以调用类中的方法。
- 拆箱:将数字包装类中内容变为基本数据类型。
- 包装类在实际开发中用的最多的在于字符串变为基本数据类型。
String str1 = "18" ;
String str2 = "21.2" ;
int x = Integer.parseInt(str1); // 将字符串变为int型,也可写成Integer x = Integer.parseInt(str1);
float f = Float.parseFloat(str2) ; // 将字符串变为float型
装箱和拆箱
概念
1. 装箱
- 将基本数据类型包装成包装类的实例。
- 自动装箱:省略new的书写。
Integer i = 20; 其实是Integer i = new Integer(20);
2. 拆箱
- 将基本数据类型的包装类实例转换为基本数据类型。
- 调用包装类的 变量名.xxxValue() 方法:
float f = fl.floatValue(); - 自动拆箱:
float f = fl;
Integer x = 127;
Integer y = 127;
INteger m = 128;
Integer n = 128;
System.out.println(x == y);//true
System.out.println(m == n);//false
System.out.println(x.equals(y));//true
System.out.println(m.equals(n));//true
原因:因为自自动装箱其实是省略了new的书写,但实质上还是在new对象,因此尽管m和n的值相同,但地址是不同的,因此返回false。但是对于在byte范围内的数值,如果数值相同,在自动装箱下,不开辟新的内存空间, 因此返回true。equals()方法对应包装类来说比较的是内容,因此均返回true。
static关键字
情景
考虑如下情况,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。此时需要定义一个可供所有对象共用的属性。
- 创建两个Circle对象
Circle c1=new Circle(2.0); //c1.radius=2.0
Circle c2=new Circle(3.0); //c2.radius=3.0
c1的radius独立于c2的radius,存储在不同的空间。c1中的radius变化不会影响c2的radius。
类属性、类方法
1. 类属性
- 类属性(类变量、静态变量)是指在各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
- 访问方式:类名.属性
2. 类方法
- 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于类方法不需要创建对象就可以调用,从而简化了方法的调用。
- 调用方法:类名.方法()
3. 注意
- 一般static修饰的属性和方法只用public修饰。
static关键字
1. 使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类,只要是static修饰的,就是共用的,全局唯一。
2. 被修饰后的成员具备以下特点:
- 随着类的加载而加载;
- 优先于对象存在;
- 修饰的成员,被所有对象所共享(类属性);
- 访问权限允许时,可不创建对象,直接被类调用(类方法)。
public class Student{
String name;//实例变量
int age;//实例变量
static int school;//类变量
}
实例变量:只有在对象实例化之后才可使用;
类变量:不需要实例化对象,可以直接通过类名.属性访问。
类方法
- 没有对象的实例时,可以用 类名.方法名() 的形式访问由static标记的类方法。
- static方法内可以直接访问static属性,不能直接访问非static属性。
- 在同一个类内,通过直接使用方法名的方式调用类内其他方法时,static方法只能直接调用static方法,不能直接调用非static方法。
- 因为类变量和类方法随着类的加载而加载,并且优先于对象存在,因此当类变量和类方法加载完成后,类中的其他成员变量和方法还没有被加载,即访问不到其他成员变量和成员方法,所以static方法内不能有this,也不能有super。
- 重载的方法需要同时为static的或者非static的。
- 因为不需要实例化对象就可以调用类方法,因此类方法用于开发工具类比较多。
- 上述约束对于非static方法不起作用。
- static方法若非要访问非static属性,可以通过传入对象实参或在static方法内实例化对象的方式调用成员属性或成员方法。
举例说明
package test;
public class test1 {
String name;
static String country;
public static void main(String[] args) {
show();//静态方法可以直接调用静态方法
// showInfo();//不能直接调用非静态方法,可以通过实例化对象的方式调用
new test1().showInfo();
}
public static void show() {
System.out.println(country);//静态方法可以直接访问静态变量
// System.out.println(name);/不能直接访问非静态变量,可以通过实例化对象或传入对象实参调用
}
public void showInfo() {
System.out.println();
}
}
练习
1. 统计CountNew类一共实例化了多少个对象。
package practice2;
public class TestCount {
public static void main(String[] args) {
new CountNew();
new CountNew();
new CountNew();
CountNew.showCount();//已经生成了 3 个对象
}
}
class CountNew{
static int count = 0;
public CountNew() {
CountNew.count++;
}
public static void showCount() {
System.out.println("已经生成了 " + CountNew.count + " 个对象");
}
}
2. 对数组操作的工具类。
package test;
/**
* 对数组操作的工具类
* @author 14251
*
*/
public class ArrayTool {
//构造函数私有化,防止实例化对象
private ArrayTool() {}
//遍历数组
public static void printArray(int[] arr) {
for(int i=0; i<arr.length; i++) {
if(i != arr.length-1) {
System.out.print(arr[i] + ",");
}else {
System.out.println(arr[i]);
}
}
}
//获取最大值
public static int getMax(int[] arr) {
int max = 0;
for(int i=1; i<arr.length; i++) {
if(arr[i] > arr[max]) {
max = i;
}
}
return max;
}
//获取最小值
public static int getMin(int[] arr) {
int min = 0;
for(int i=1; i<arr.length; i++) {
if(arr[i] < arr[min]) {
min = i;
}
}
return min;
}
//选择排序
public static void selectSort(int[] arr) {
for(int i=0; i<arr.length-1; i++) {
for(int j=i+1; j<arr.length; j++) {
if(arr[i] > arr[j]) {
swap(arr, i, j);
}
}
}
}
//冒泡排序
public static void bubbleSort(int[] arr) {
for(int i=0; i<arr.length-1; i++) {
for(int j=0; j<arr.length-i-1; j++) {
if(arr[j] > arr[j+1]) {
swap(arr, j, j+1);
}
}
}
}
//置换
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//折半查找
public static int searchNum(int[] arr, int key) {
bubbleSort(arr);
int min = 0;
int max = arr.length-1;
int mid = (min + max)/2;
while(arr[mid] != key) {
if(arr[mid] < key) {
min = mid + 1;
}else if(arr[mid] > key) {
max = mid - 1;
}
mid = (min + max)/2;
if(min > max) {
return -1;
}
}
return mid;
}
}
package test;
public class Test {
public static void main(String[] args) {
int[] arr = new int[]{6, 25, 8, 13, 2, 7, 34, 1, 9, 0};
System.out.println(arr[ArrayTool.getMin(arr)]);
System.out.println(arr[ArrayTool.getMax(arr)]);
System.out.println(ArrayTool.searchNum(arr, 7));
ArrayTool.bubbleSort(arr);
ArrayTool.printArray(arr);
}
}
单例 (Singleton)设计模式
概念
-
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
-
单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
实现方法
- 将类的构造方法的访问权限设置为private —— 不能用new操作符在类的外部产生类的对象,保证类在一个虚拟机中只能产生一个对象。
- 在类内部产生该类的对象。
- 使用某个静态方法来返回类内部创建的对象 —— 不能new对象,无法创建该类的对象,因此不能访问类内方法,设置成static方法就可以使用类名来调用类内方法了。
- 接收实例对象的引用必须定义成静态的 —— 静态方法只能访问类中的静态成员变量。
使用场景
实例化对象耗费大量的时间和资源。实例化对象之后,调用equals()方法返回的均为true。
public class TestSingle{
public static void main(String args[]) {
Single s1 = Single.getSingle(); //访问静态方法
Single s2 = Single.getSingle();
if (s1==s2){
System.out.println("s1 is equals to s2!");//true
}
}
}
代码实现
饿汉式
- 类内创建一个对象,只要有人调用静态方法,就返回同一个对象。
package practice1.test;
public class SingletonHungry {
private SingletonHungry() {}//构造方法私有化,外部不能使用new实例化对象
private static SingletonHungry single = new SingletonHungry();
public static SingletonHungry getNewInstance() {
return single;
}
}
package practice1.test;
public class Test {
public static void main(String[] args) {
SingletonHungry single = SingletonHungry.getNewInstance();
}
懒汉式
- 最开始类内部不创建对象,为null,当有人实例化第一个对象之后,以后再调用静态方法返回的都是第一个人创建的对象。
package practice1.test;
public class SingletonLazy {
private SingletonLazy() {}//构造方法私有化,外部不能使用new实例化对象
private static SingletonLazy single = null;
public static SingletonLazy getNewInstance() {
if(single == null) {
single = new SingletonLazy();
}
return single;
}
}
package practice1.test;
public class Test {
public static void main(String[] args) {
SingletonLazy single = SingletonLazy.getNewInstance();
}
main方法
由于java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。