算法网站:https://visualgo.net/
第5章 JAVA面向对象编程进阶
新建包的方法,按照域名倒着写可以保证包是唯一的
在包里可以新建类,目录树如下图
5.2
5.2.3 == 和equals 方法
package com.bjsxt.inherit;
import java.util.Objects;
public class User extends Object{
int id;
String name;
String pwd;
User(int id,String name,String pwd){
this.id = id;
this.name = name;
this.pwd = pwd;
}
public static void main(String[] args){
User u1 = new User(1001,"张三","123456");
User u2 = new User(1001,"李四","张三");
System.out.println(u1);
System.out.println(u2.toString());
System.out.println(u1 == u2);
System.out.println(u1.equals(u2));
}
//这里是对Object中的equals方法进行了重写 重写后 两个对象的id相同 则认为两个对象相同
//不重写,使用默认的equals时,只有两个对象的地址相同时,才认为两个对象相同。
public boolean equals(Object obj){
if(obj==null){
return false; //return两个作用,返回值,结束方法运行
}else {
if(obj instanceof User){
User u1 = (User)obj;
if(u1.id == this.id){
return true;
}
}
}
return false;
}
}
在重写equals时,也可采用快捷键ALT+insert的方式进行 在自动生成的代码里 重写了equals 也重写了hashcode
在String类中,可以看到equals也被重写了
5.2.4 super关键字(看书)
5.4.2 封装的实现
先看一个小问题,`运行下列程序 会报错
package cn.sxt.gao7;
import cn.sxt.gao6.Person;
public class Student {
Person p1 = new Person();
public static void main(String[] args){
System.out.println(p1.toString());
}
}
报错内容如图 ,分析:找不到p1变量 ,因为main函数中没有定义,没有对p1开辟地址 ,上文中虽然有p1但是属于 组合 的写法,并不是为p1分配地址
正确写法为
package cn.sxt.gao7;
import cn.sxt.gao6.Person;
public class Student {
public static void main(String[] args){
Person p1 = new Person(); //此为更改内容
System.out.println(p1.toString());
}
}
5.9 接口 interface
5.9.1 Java8以后 新增特性
接口中允许默认方法
Java8 及以上旧版本,允许给接口添加一个非抽象的方法实现,只需要使用default关键字(这里的default和封装里的default是两码事)即可,这个特征又叫做默认方法(也称为扩展方法)
默认方法和抽象方法的区别时抽象方法必须要被实现,默认方法不是。作为代替方式,接口可以提供默认方法的实现,所有这个接口的实现类都会通过继承得到这个方法。
基本上不会用,某些开源软件里可能需要
package com.bjsxt.testInterface2;
public class Test {
public static void main(String[] args){
A a = new Test_A();
a.moren();
}
}
interface A {
default void moren(){ //注意默认方法和抽象方法是不同的 这里的default 和封装 里的default是不同的
System.out.println("我是接口A中的默认方法!");
}
}
class Test_A implements A{
@Override
public void moren() {
System.out.println("我BU是接口A中的默认方法!");
}
}
}
接口中允许静态方法
Java8以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。
如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。
package com.bjsxt.testInterface2;
public class Test {
public static void main(String[] args){
// B b = new Test_B (); //这样写是错误的
// b.staticMethod();
Test_B b = new Test_B ();
b.staticMethod();
B.staticMethod();
}
}
interface B{
public static void staticMethod(){
System.out.println("B.staticMethod");
}
}
class Test_B implements B{
public static void staticMethod(){
System.out.println("Test_B.staticMethod");
}
}
静态方法和默认方法
静态方法不能调用默认方法
默认方法可以调用静态方法
5.10 内部类
如何创建内部类对象
如何在内部类里调用外部类的属性
package com.bjsxt.testinertclass;
public class Outer1 {
private int age =10;
private void show(){
System.out.println("你真好看");
}
//内部类
public class Inner1{
private String name ="time";
private int age =20;
public void showInner(){
System.out.println("Inner.showInner");
System.out.println(age); //这里调用的是内部类的age变量
System.out.println(Outer1.this.age); //这里调用的是外部类的age 当外部类属性
//和内部类属性同名时,可以通过Outer1.this.age的方式调用
// System.out.println(Outer1.age); //这么写是错的
show();//内部类可以直接使用外部类的成员
}
}
public static void main(String[] args){
Outer1.Inner1 inn01 = new Outer1().new Inner1(); //创建内部类的方式1
inn01.showInner();
Outer1 out02 = new Outer1();
Inner1 inn02 = out02.new Inner1(); //创建内部类的方式2
inn02.showInner();
}
}
匿名内部类的使用
package com.bjsxt.testinertclass;
public class TestAnonymousInnerClass {
public void test(A a) {
a.run();
}
public static void main(String[] args) {
TestAnonymousInnerClass tai = new TestAnonymousInnerClass();
// AA aa = new AA();
//tai.test(aa);
//他就是匿名内部类
tai.test(new A(){
@Override
public void run() {
System.out.println("窝嫩娘");
}
});
}
}
interface A{
void run();
}
//有名字的类 可以反复使用
class AA implements A {
@Override
public void run() {
System.out.println("窝嫩爹");
}
}
5.11.1 字符串比较
== 号 比较对象是否相同
equals 来比较两个字符串对象内容是否相同
7.1一维数组
所有的查找 需要按照新的规则 重新处理一遍
在进行索引时 包头不包尾 比如 索引2-6位置的元素 在下标上显示的就是a[2] a[3] a[4] a[5]
Arrays.fill(a,2,6,100); //数组填充 或者其他方法查找 包头不包尾 java 字符串进行索引时 一般都是包头不包尾
//即 2 3 4 5 包含2 不包含6
7.2 二维数组
二维数组可以看作是以数组为元素的数组,从内存分析过程就可以看出来
二维数组工作中不常用,后面会有容器相关方式等来代替
7.3 数组打印:Arrays.toString(数组名)
如下代码,为什么Arrays.toString(数组名) 不进行实例化后可以直接使用
可以按住alt键 在Arrays字母上左键单击,跳转到相应源码,可以发现toString()方法是静态方法
静态方法是可以直接用类调用的 其实就是类方法
package com.bjsxt.array;
import java.util.Arrays;
/**
* 二维数组的练习
* 使用Object[][]存储整个表格的数据
*/
public class Test09 {
public static void main(String[] args){
//每一个一维数组 代表一行数据
Object[] emp0 = {1001,"高琪",18,"讲师","2006-2-14"};
Object[] emp1 = {1002,"高小七",19,"助教","2007-10-10"};
Object[] emp2 = {1003,"高小琴",20,"班主任","2008-5-5"};
Object [][] emps = new Object[3][];
emps[0] = emp0;
emps[1] = emp1;
emps[2] = emp2;
//打印
System.out.println(Arrays.toString(emps[0])); //Arrays.toString 未进行实例化
System.out.println(Arrays.toString(emps[1]));
System.out.println(Arrays.toString(emps[2]));
Test10.print();
}
}
8 使用Javabean和数组存储表格的信息,这个方式非常重要,后面天天用(120节)
package com.bjsxt.array;
public class Test11 {
public static void main(String[] args) {
//在某行前 按 crtl+d可以将改行复制到下一行
Emp emp0 = new Emp(1001,"高效益",18,"程序员","2019-9-9");
Emp emp1 = new Emp(1002,"高度J",18,"程序员","2019-9-10");
Emp emp2 = new Emp(1003,"高反对",18,"程序员","2019-9-11");
// Emp[] emps = {emp0,emp1,emp2}; //静态初始化
Emp[] emps = new Emp[3]; //动态初始化
emps[0] = emp0;
emps[1] = emp1;
emps[2] = emp2;
for (int i = 0;i< emps.length;i++)
{
System.out.println(emps[i].toString());
}
}
}
class Emp {
private int id; //使用javabean封装
private String name;
private int age;
private String job;
private String hiredate;
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getJob() {
return job;
}
public String getHiredate() {
return hiredate;
}
public Emp(){} //一般情况下建议有 无参构造器
public Emp(int id, String name, int age, String job, String hiredate) {
this.id = id;
this.name = name;
this.age = age;
this.job = job;
this.hiredate = hiredate;
}
@Override
public String toString() {
//制表符 \t
//最好是用getName这种形式,方便改变,属性不方便改,但是getName等函数可以很方便的改
return getId()+"\t"+getName()+"\t"+getAge()+"\t"+getJob()+"\t"+getHiredate();
}
}
输出结果为
“C:\Program Files\Java\jdk-13.0.2\bin\java.exe” “-javaagent:E:\javasoft\idea2020\IntelliJ IDEA 2020.2.4\lib\idea_rt.jar=52780:E:\javasoft\idea2020\IntelliJ IDEA 2020.2.4\bin” -Dfile.encoding=UTF-8 -classpath C:\Users\gou\IdeaProjects\wang01\out\production\wang01 com.bjsxt.array.Test11
1001 高效益 18 程序员 2019-9-9
1002 高度J 18 程序员 2019-9-10
1003 高反对 18 程序员 2019-9-11
9 泛型
9.2.1 定义泛型
泛型字符可以是任何标识符,一般采用几个标记:E,T,K,V,N,?
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的JAVA类 |
K | Key | 表示键,例如:Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的JAVA类 |
9.2.1 泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如: `,<T,K,V>
语法结构
`public class 类名<泛型表示符号>{}`
例子
/*
创建一个泛型类 Generic
*/
public class Generic <T>{ //这里用T表示普通的泛型类
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
public T getFlag(){
return this.flag;
}
}
/*
测试这个泛型类
*/
public class Test {
public static void main(String[] args) {
Generic<String> generic = new Generic<>(); //这里对泛型类进行使用
//传递的是String 也就是说在 创建泛型类的时候,不用关心所传递的是什么参数 到调用的时候在考虑就可以。方便编程
generic.setFlag("admin");
//generic.setFlag(100); 出现编译报错,因为已经确定要传的类是String类 不能再传int 了。
String flag = generic.getFlag();
System.out.println(flag);
Generic<Integer> generic1 = new Generic<>(); //这里传递的是包装类
generic1.setFlag(100);
Integer flag1 = generic1.getFlag();
System.out.println(flag1);
}
}
9.2.2 泛型接口
泛型接口 泛型接口和泛型类的声明一致。泛型接口的具体类型需要在实现类中进行声明
语法结构public interface 接口名<泛型表示符号>{ }
例子
/**
* 定义一个泛型接口
*/
public interface Igeneric<T> {
T getName(T name); //
}
/**
* 实现定义的泛型接口
* 可以发现 原来的T变成了 String
* 重写getName方法时,返回的类型也是String
*/
public class IgenericImpl implements Igeneric<String>{
@Override //按下alt+insert键,插入getName键,可以发现方法的返回值就是String
public String getName(String name) {
return name;
}
}
/**
* 创建一个测试类,测试泛型接口
*/
public class Test22 {
public static void main(String[] args) {
IgenericImpl t1 = new IgenericImpl();
System.out.println(t1.getName("wonendie"));
Igeneric igeneric1 = new Igeneric() {
@Override //直接引用接口,没有给定泛型类型,默认返回的就是Object
public Object getName(Object name) {
return null;
}
};
Igeneric<String> igeneric2 = new Igeneric<String>() {
@Override //引用接口时定义泛型类型为String,重写方法时直接返回String
public String getName(String name) {
return null;
}
};
}
}
9.2.3 泛型方法
泛型类中所定义的泛型,在方法中也可以使用。 但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接受不同类型的参数。 类型参数可以有多个,用逗号隔开,如<K,V>。定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
9.2.3.4 非静态方法
语法结构
public <泛型表示符号> void getName(泛型表示符号 name){ }
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name)
{
}
示例
/**
* 定义一个泛型方法
*/
public class MethodGeneric {
/**
* 定义了一个泛型方法,无返回值
* @param name
* @param <T>
*/
public <T> void setName(T name){
System.out.println(name);
}
/**
* 定义了一个泛型方法,有返回值
* @param name
* @param <T>
* @return
*/
public <T> T getName(T name){
return name;
}
}
/**
*调用泛型方法
*/
public class Test33 {
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
/**
* 可以看到 传递什么类型都可以
*/
methodGeneric.setName("oldru");
methodGeneric.setName(1233333);
MethodGeneric methodGeneric11 = new MethodGeneric();
System.out.println(methodGeneric11.getName(12333));
System.out.println(methodGeneric11.getName("dsfdfd"));
}
}
9.2.3.5静态方法
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上
public class Generic <T>{
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
/**
* 定义静态方法 报错 报错原因:在静态方法中,泛型是不允许使用类中所定义的泛型的
* 这是泛型非静态方法和泛型静态方法的一个重要区别
* @param name
* @return
*/
public static T demo(T name){
return null; //报错 在静态方法当中,泛型是不允许使用类中所定义的泛型的
}
public T getFlag(){
return this.flag;
}
}
语法结构
public static <泛型表示符号> void getName(泛型表示符号 name){
}
public statci <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
示例
/**
* 定义一个静态泛型方法
*/
public class MethodGeneric {
public static <T> void setFlag(T flag){
System.out.println(flag);
}
public static <T> T getFlag(T flag){
return flag;
}
}
调用静态方法 静态方法调用不需要实例化,直接通过类名就可以
public class Test44 {
public static void main(String[] args) {
//静态方法的调用不需要实例化,直接通过类名就可以
MethodGeneric.setFlag("oldlu");
int a = MethodGeneric.getFlag(123);
System.out.println(a);
}
}
9.2.4 泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型
语法结构
public <泛型表示符号> void showMsg(泛型表示符号...agrs){
}
示例
/**
* 定义一个可变参数的泛型方法
*/
public class MethodGeneric {
public <T> void method(T...args){
//可变参数是一个数组,直接遍历
//增强for循环遍历数组
for(T t:args){
System.out.println(t);
}
}
}
调用泛型方法
public class Test5 {
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
String[] arr = new String[]{"a","b","c"};
methodGeneric.method(arr);
Integer[] arr2 = new Integer[]{1,2,3};
methodGeneric.method(arr2);
}
}
9.2.5 通配符和上下限定
9.2.5.1 无界通配符 (171集 6:10)不太懂 回头看
“?” 表示类型通配符,用于代替具体的类型。它只能在"<>"中使用。可以解决当具体类型不确定的问题
语法结构
public void showFlag(Generic<?> generic){
}
示例
/*
*用通配符创建方法
*/
public class ShowMsg {
public void showFlag(Generic<?> generic){
System.out.println(generic.getFlag());
}
}
调用
/**
* 调用方法
*/
public class Test6 {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic<Integer> generic = new Generic<>();
generic.setFlag1(20);
showMsg.showFlag(generic);
Generic<Number> generic1 = new Generic<Number>();
showMsg.showFlag(generic1);
Generic<Integer> generic2 = new Generic<Integer>();
showMsg.showFlag(generic2);
}
}
9.2.5.2 通配符的上限限定
上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口。
该方式同样适用于泛型的上限限定
语法结构
这个Generic是什么?
public void showFlag(Generic<? extends Number generic){
}
/**
*这里的?继承于Number 因此?只能是Number或者Number的子类
示例:
1.通配符加了上限限定,调用该方法时,传进去的参数只能是其子类
9.2.5.3通配符的下限限定
- 下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口
注意:该方法不适用于泛型类
- 语法结构
public void showFlag(Generic<? super Integer> generic){
}
示例
改成下限限定
发现报错
因为Integer不是Number的父类
9.2.6 泛型总结
-
自我总结1:泛型说白了就是 类 接口 方法 传参的时候,不需要确定参数的类型,实际使用的时候再传就行。和C语言对比,C语言中函数在传参时必须要定义参数的数据类型,但JAVA中的泛型就不需要,方便了程序员。
-
自我总结2:泛型和Object比较;泛型不需要显示类型转换,即在使用的时候不需要用instanceof判定,但Object中 转化成 具体类型时,需要用instanceof关键字判定,否则可能会编译出错。
-
自我总结3:和C语言中的预编译进行比较,泛型也是在运行时会被替换成其他类(Object类)。有点类似,但又所不同?
-
老师总结:
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。 **类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:
1.基本类型不能用于泛型。基本类型不能用于泛型。
Test<int> t; //这样写法是错误的,我们可以使用对应的包装类:Test<Inerger> t
;
不能通过类型参数创建对象
T elm = new T(); //运行时类型参数T会被替换成Object,无法创建T类型的对象,容易引起误解,所以在Java中不支持这种写法。