มีวิธีการแทนที่ตัวแปรคลาสใน Java หรือไม่


161
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";
}

public void doIt()
{
    new Son().printMe();
}

ฟังก์ชั่น doIt จะพิมพ์ "พ่อ" มีวิธีที่จะทำให้มันพิมพ์ "ลูกชาย" หรือไม่?


111
หากคุณเคยเห็นการป้องกันแบบคงที่ให้เรียกใช้
Tom Hawtin - tackline

23
@ TomHawtin-tackline ให้อภัยความไม่รู้ของฉัน แต่ทำไมถึงมีการป้องกันคงขมวดคิ้วเมื่อ? ฉันลองใช้ Google แต่ไม่สามารถหาคำตอบที่ชัดเจน ขอบคุณ
Tony Chan

คำตอบ:


68

ใช่. แต่เนื่องจากตัวแปรเกี่ยวข้องมันถูกเขียนทับ (ให้ค่าใหม่กับตัวแปรการให้นิยามใหม่กับฟังก์ชันคือ Override)Just don't declare the variable but initialize (change) in the constructor or static block.

ค่าจะได้รับการสะท้อนเมื่อใช้ในบล็อกของชั้นผู้ปกครอง

ถ้าตัวแปรเป็นค่าคงที่แล้วเปลี่ยนค่าในระหว่างการเริ่มต้นตัวเองด้วยบล็อกคงที่

class Son extends Dad {
    static { 
       me = 'son'; 
    }
}

หรือการเปลี่ยนแปลงอื่น ๆ ในการสร้าง

นอกจากนี้คุณยังสามารถเปลี่ยนค่าในภายหลังในบล็อกใด ๆ มันจะได้รับการสะท้อนในระดับสุดยอด


10
ฉันคิดว่าคำตอบควรมีรหัสเพื่อให้อ่านง่ายขึ้น เพียงแค่เปลี่ยนการใช้ Son ไปเป็น class Son extends Dad { static { me = 'son' } }
Cléssio Mendes

97

ในระยะสั้นไม่มีไม่มีวิธีการแทนที่ตัวแปรคลาส

คุณไม่ได้แทนที่ตัวแปรคลาสใน Java ที่คุณซ่อนไว้ การเอาชนะเป็นวิธีการเช่น การซ่อนนั้นแตกต่างจากการเอาชนะ

ในตัวอย่างที่คุณได้รับโดยการประกาศตัวแปรคลาสด้วยชื่อ 'me' ในคลาส Son คุณซ่อนตัวแปรคลาสที่มันจะสืบทอดมาจาก superclass Dad ด้วยชื่อเดียวกันกับ 'me' การซ่อนตัวแปรด้วยวิธีนี้จะไม่มีผลกับค่าของตัวแปรคลาส 'me' ใน superclass Dad

สำหรับคำถามที่สองของคุณเกี่ยวกับวิธีทำให้พิมพ์ "ลูกชาย" ฉันจะตั้งค่าผ่านตัวสร้าง แม้ว่ารหัสด้านล่างจะแยกออกจากคำถามเดิมของคุณค่อนข้างมาก แต่ฉันจะเขียนบางอย่างเช่นนี้

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

JLS ให้รายละเอียดเพิ่มเติมเกี่ยวกับการซ่อนในส่วนที่8.3 - การประกาศภาคสนาม


1
การแทนที่สามารถทำได้โดยการบล็อกแบบคงที่ในคลาสที่ได้รับ
siddagrl

1
@siddagrl: คุณอธิบายได้ไหม?
Mr_and_Mrs_D

คลาสใดของ OP ที่Personควรจะแทนที่ในตัวอย่างนี้
n611x007

1
@naxa SonและDadควรจะสืบทอดจากPersonนั้นเรียกใช้super("Son or Dad");ใน constructors ของพวกเขา
Panzercrisis

ถือว่าดีหรือไม่ดีในการซ่อนตัวแปรเช่นนี้ใน Java หรือไม่?
Panzercrisis

31

ใช่เพียงแทนที่printMe()วิธีการ:

class Son extends Dad {
        public static final String me = "son";

        @Override
        public void printMe() {
                System.out.println(me);
        }
}

เกิดอะไรขึ้นถ้าไลบรารี่ที่คุณกำลังใช้อยู่ไม่มีprintMe()เมธอดหรือแม้แต่มีเมธอดนั้นเป็นเมธอดแบบสแตติก
Venkat Sudheer Reddy Aedama

1
เพื่อให้เป็นตัวอย่างในชีวิตจริงมากขึ้นคุณอาจพิจารณาว่าวิธี "printMe" อาจเป็นฟังก์ชันที่ซับซ้อนมากพร้อมตรรกะที่คุณไม่ต้องการทำซ้ำ ในตัวอย่างนี้คุณเพียงแค่ต้องการเปลี่ยนชื่อของบุคคลไม่ใช่ตรรกะที่พิมพ์ คุณควรสร้างวิธีที่เรียกว่า "getName" ซึ่งคุณจะแทนที่เพื่อส่งกลับ "son" และเมธอด printMe จะเรียกเมธอด getName เป็นส่วนหนึ่งของการพิมพ์
แหวน

17

คุณสามารถสร้างทะเยอทะยานแล้วแทนที่แทนที่ทะลัก มันมีประโยชน์อย่างยิ่งหากตัวแปรที่คุณกำลังเขียนทับนั้นเป็นคลาสย่อยของตัวเอง ลองนึกภาพชั้นซุปเปอร์ของคุณมีObjectสมาชิก Integerแต่ในชั้นย่อยนี้ได้ในขณะนี้กำหนดให้เป็น

class Dad
{
        private static final String me = "dad";

        protected String getMe() {
            return me;
        }

        public void printMe()
        {
                System.out.println(getMe());
        }
}

class Son extends Dad
{
        private static final String me = "son";

        @Override
        protected String getMe() {
            return me;
        }
}

public void doIt()
{
        new Son().printMe(); //Prints "son"
}

11

หากคุณกำลังจะแทนที่มันฉันไม่เห็นเหตุผลที่ถูกต้องในการคงที่นี้ ฉันขอแนะนำให้ใช้นามธรรม (ดูตัวอย่างรหัส) :

     public interface Person {
        public abstract String getName();
       //this will be different for each person, so no need to make it concrete
        public abstract void setName(String name);
    }

ตอนนี้เราสามารถเพิ่มพ่อ:

public class Dad implements Person {

    private String name;

    public Dad(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
    return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

ลูกชาย:

public class Son implements Person {

    private String name;

    public Son(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

และพ่อได้พบกับผู้หญิงที่ดี:

public class StepMom implements Person {

    private String name;

    public StepMom(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

ดูเหมือนว่าเรามีครอบครัวให้บอกชื่อโลกแก่พวกเขา:

public class ConsoleGUI {

    public static void main(String[] args) {
        List<Person> family = new ArrayList<Person>();
        family.add(new Son("Tommy"));
        family.add(new StepMom("Nancy"));
        family.add(new Dad("Dad"));
        for (Person person : family) {
            //using the getName vs printName lets the caller, in this case the
            //ConsoleGUI determine versus being forced to output through the console. 
            System.out.print(person.getName() + " ");
            System.err.print(person.getName() + " ");
            JOptionPane.showMessageDialog(null, person.getName());
    }
}

}

เอาต์พุตของ System.out: Tommy Nancy Dad
System.err เหมือนกับข้างบน (เพิ่งมีตัวอักษรสีแดง)
เอาต์พุต JOption:
Tommy จากนั้น
แนนซี่ก็จะเป็น
พ่อ


1
OP เกี่ยวข้องกับการเปลี่ยนการเข้าถึงสถิตยศาสตร์ ดังนั้น "ฉัน" เป็น "พ่อ" ทั่วไปหรือ "ลูกชาย" - สิ่งที่ไม่จำเป็นต้องสร้างสำหรับพ่อหรือลูกชายทุกคนและด้วยเหตุนี้จึงเป็นแบบคงที่
RichieHH

ใช่คุณถูกฉันไม่เห็นว่ามันคงที่ ฮ่าฮ่านั่นจะเปลี่ยนคำตอบของฉันไปเป็นรหัสน้อยกว่า 100 บรรทัด l ถ้าตอบเลย ขอบคุณสำหรับ heads up
nckbrz

6

ดูเหมือนว่าข้อบกพร่องในการออกแบบ

ลบคำหลักคงที่และตั้งค่าตัวแปรเช่นในตัวสร้าง ด้วยวิธีนี้ลูกชายเพียงแค่ตั้งค่าตัวแปรเป็นค่าที่แตกต่างกันในตัวสร้างของเขา


1
มีอะไรผิดปกติเกี่ยวกับเรื่องนี้? ถ้า 'ฉัน' ตัวแปรสมาชิกควรจะเป็น overridable ในการออกแบบของคุณแล้วแพทริกเป็นวิธีการแก้ปัญหาที่ถูกต้อง
Chii

ที่จริงแล้วถ้าค่าของฉันเหมือนกันสำหรับทุกอินสแตนซ์เพียงแค่ลบ 'คงที่' จะดี การเริ่มต้นไม่จำเป็นต้องอยู่ในคอนสตรัคเตอร์
เนทพาร์สันส์

ขวาแม้ในทางเทคนิค (ใน bytecode) ฉันคิดว่ามันเกือบจะเหมือนกัน ;-)
แพทริค Cornelissen

4

แม้ว่ามันจะเป็นจริงที่ตัวแปรคลาสอาจถูกซ่อนอยู่ในคลาสย่อยเท่านั้นและไม่ถูกแทนที่ แต่ก็ยังเป็นไปได้ที่จะทำสิ่งที่คุณต้องการโดยไม่ต้องลบล้างprintMe ()คลาสย่อยและการสะท้อนกลับเป็นเพื่อนของคุณ ในรหัสด้านล่างฉันละเว้นการจัดการเพื่อความชัดเจน โปรดทราบว่าการประกาศmeว่าprotectedดูเหมือนจะไม่สมเหตุสมผลในบริบทนี้เนื่องจากมันจะถูกซ่อนอยู่ในคลาสย่อย ...

class Dad
  {
    static String me = "dad";

    public void printMe ()
      {
        java.lang.reflect.Field field = this.getClass ().getDeclaredField ("me");
        System.out.println (field.get (null));
      }
  }

น่าสนใจมาก Java.lang.reflect huh? ... +1
nckbrz

3
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String _me = me = "son";
}

public void doIt()
{
    new Son().printMe();
}

... จะพิมพ์ "ลูกชาย"


นี่ไม่ใช่คำถามเดิมใช่ไหม
Corley Brigman

คุณอธิบายได้ว่าทำไม (ในคำตอบของคุณ)
Mr_and_Mrs_D

3

https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html

มันเรียกว่า Hiding Fields

จากลิงค์ด้านบน

ภายในคลาสฟิลด์ที่มีชื่อเดียวกันกับฟิลด์ในซูเปอร์คลาสจะซ่อนฟิลด์ของซูเปอร์คลาสแม้ว่าประเภทจะแตกต่างกัน ภายในคลาสย่อยฟิลด์ในซูเปอร์คลาสไม่สามารถอ้างอิงได้โดยใช้ชื่อแบบง่าย แต่จะต้องเข้าถึงฟิลด์ดังกล่าวผ่าน super ซึ่งครอบคลุมในส่วนถัดไป โดยทั่วไปเราไม่แนะนำให้ซ่อนเขตข้อมูลเนื่องจากทำให้รหัสอ่านยาก


1
ยินดีต้อนรับลิงก์ไปยังโซลูชันที่เป็นไปได้ แต่โปรดเพิ่มบริบทรอบลิงก์เพื่อให้ผู้ใช้เพื่อนของคุณมีความคิดว่ามันคืออะไรและเพราะเหตุใด อ้างถึงส่วนที่เกี่ยวข้องที่สุดของลิงค์สำคัญเสมอในกรณีที่เว็บไซต์เป้าหมายไม่สามารถเข้าถึงหรือออฟไลน์อย่างถาวร คำนึงถึงว่าการเชื่อมโยงไปยังเว็บไซต์ภายนอกเป็นเพียงเหตุผลที่เป็นไปได้ว่าเป็นเพราะเหตุใดและคำตอบบางคำตอบถูกลบไปอย่างไร .
FelixSFD

2

โดยการเอาชนะเท่านั้นprintMe():

class Son extends Dad 
{
    public void printMe() 
    {
        System.out.println("son");
    }
}

การอ้างอิงถึงmeในDad.printMeวิธีโดยปริยายชี้ไปยังเขตข้อมูลแบบคงที่Dad.meดังนั้นไม่ทางใดก็ทางหนึ่งคุณกำลังเปลี่ยนสิ่งที่printMeเกิดขึ้นในSon...


2

คุณไม่สามารถแทนที่ตัวแปรในคลาสได้ คุณสามารถแทนที่วิธีการเท่านั้น คุณควรเก็บตัวแปรไว้เป็นส่วนตัวมิฉะนั้นคุณจะได้รับปัญหามากมาย


คุณไม่สามารถแทนที่สถิตยศาสตร์ใด ๆ
Tom Hawtin - tackline

(หรืออย่างน้อยก็ไม่ได้ทำให้ความรู้สึก IFSWIM.)
ทอม Hawtin - tackline

ถูกต้องคุณไม่สามารถแทนที่สถิตยศาสตร์ คุณสามารถเงาพวกเขาหรือบางคนเรียกฉันว่ามาสก์
Dale

2

มันพิมพ์ 'พ่อ' เนื่องจากสนามไม่ถูกแทนที่ แต่ซ่อนอยู่ มีสามวิธีในการพิมพ์ 'ลูกชาย':

วิธีที่ 1: แทนที่ printMe

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";

    @override
    public void printMe()
    {
        System.out.println(me);
    }
}

public void doIt()
{
    new Son().printMe();
}

วิธีที่ 2: อย่าซ่อนเขตข้อมูลและเริ่มต้นในตัวสร้าง

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    public Son()
    {
        me = "son";
    }
}

public void doIt()
{
    new Son().printMe();
}

วิธีที่ 3: ใช้ค่าคงที่เพื่อเริ่มต้นเขตข้อมูลในตัวสร้าง

class Dad
{
    private static String meInit = "Dad";

    protected String me;

    public Dad() 
    {
       me = meInit;
    }

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    private static String meInit = "son";

    public Son()
    {
        me = meInit;
    }

}

public void doIt()
{
    new Son().printMe();
}

2

ตัวแปรไม่ได้มีส่วนร่วมในการ overrinding วิธีการทำเท่านั้น การเรียกเมธอดได้รับการแก้ไขที่รันไทม์นั่นคือการตัดสินใจที่จะเรียกเมธอดนั้นเกิดขึ้นที่รันไทม์ แต่ตัวแปรจะถูกตัดสินใจ ณ เวลาคอมไพล์เท่านั้น ดังนั้นตัวแปรที่ถูกเรียกใช้ซึ่งมีการอ้างอิงที่ใช้สำหรับการโทรและไม่ใช่ของวัตถุรันไทม์

ดูตัวอย่างต่อไปนี้:

package com.demo;

class Bike {
  int max_speed = 90;
  public void disp_speed() {
    System.out.println("Inside bike");
 }
}

public class Honda_bikes extends Bike {
  int max_speed = 150;
  public void disp_speed() {
    System.out.println("Inside Honda");
}

public static void main(String[] args) {
    Honda_bikes obj1 = new Honda_bikes();
    Bike obj2 = new Honda_bikes();
    Bike obj3 = new Bike();

    obj1.disp_speed();
    obj2.disp_speed();
    obj3.disp_speed();

    System.out.println("Max_Speed = " + obj1.max_speed);
    System.out.println("Max_Speed = " + obj2.max_speed);
    System.out.println("Max_Speed = " + obj3.max_speed);
  }

}

เมื่อคุณเรียกใช้รหัสคอนโซลจะแสดง:

Inside Honda
Inside Honda
Inside bike

Max_Speed = 150
Max_Speed = 90
Max_Speed = 90

0

แน่นอนว่าการใช้คุณลักษณะส่วนตัวและ getters และ setters จะเป็นสิ่งที่แนะนำให้ทำ แต่ฉันทดสอบต่อไปนี้และใช้งานได้ ... ดูความคิดเห็นในรหัส

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";

    /* 
    Adding Method printMe() to this class, outputs son 
    even though Attribute me from class Dad can apparently not be overridden
    */

    public void printMe()
    {
        System.out.println(me);
    }
}

class Tester
{
    public static void main(String[] arg)
    {
        new Son().printMe();
    }
}

Sooo ... ฉันเพิ่งกำหนดกฎการรับมรดกใหม่หรือฉันทำให้ Oracle กลายเป็นสถานการณ์ที่ยุ่งยากหรือไม่? สำหรับฉัน String Static me ที่ได้รับการป้องกันนั้นถูกเขียนทับอย่างชัดเจนดังที่คุณเห็นเมื่อคุณเรียกใช้โปรแกรมนี้ นอกจากนี้มันไม่สมเหตุสมผลกับฉันว่าทำไมคุณลักษณะไม่ควร overridable


1
ในกรณีนี้คุณกำลัง "ซ่อน" ตัวแปรจากคลาสสุด สิ่งนี้แตกต่างจากการเอาชนะและไม่มีอะไรเกี่ยวข้องกับมรดก คลาสอื่น ๆ จะเห็นตัวแปรหนึ่งตัวหรือตัวแปรอื่นขึ้นอยู่กับว่าพวกเขาอ้างถึงคลาสฐานหรือคลาสย่อยและตัวแปรที่พวกเขาเห็นได้รับการแก้ไข ณ เวลารวบรวม หากคุณเปลี่ยนชื่อ "ฉัน" ในคลาสย่อยเป็นอย่างอื่นคุณจะได้รับผลกระทบเดียวกัน เครื่องมือตรวจสอบ IDE และโค้ดส่วนใหญ่ (findbugs ฯลฯ ) จะเตือนคุณเมื่อคุณซ่อนตัวแปรเช่นนี้เนื่องจากโดยปกติจะไม่ใช่สิ่งที่คุณต้องการทำ
AutomatedMike

คุณกำลังดูการเขียนโปรแกรมจากมุมมองของการเข้ารหัสเพียงอย่างเดียว รหัสใดดี แต่คุณต้องการในกรณีนั้น ถ้าคุณดูที่การเข้ารหัสจากมุมมองของสมาชิกในทีมกฎจะชัดเจนเช่นทำไมเขตข้อมูลจึงไม่สามารถ overridable และอื่น ๆ ฉันสามารถพูดให้คุณฟังได้ว่าทำไม แต่ถ้าคุณไม่เห็นมันจากมุมมองของสมาชิกในทีมคุณจะเห็นว่าคะแนนของฉันไม่ถูกต้อง
nckbrz

0

เหตุใดคุณจึงต้องการแทนที่ตัวแปรเมื่อคุณสามารถกำหนดค่าเหล่านั้นในคลาสย่อยได้อย่างง่ายดาย

ฉันทำตามรูปแบบนี้เพื่อหลีกเลี่ยงการออกแบบภาษา สมมติกรณีที่คุณมีคลาสเซอร์วิสที่มีน้ำหนักมากในกรอบงานของคุณซึ่งจำเป็นต้องใช้ในรสชาติที่แตกต่างกันในหลาย ๆ แอพพลิเคชั่นที่ได้รับมาในกรณีนี้วิธีที่ดีที่สุดในการกำหนดค่าตรรกะคลาสซูเปอร์คือ

public interface ExtensibleService{
void init();
}

public class WeightyLogicService implements ExtensibleService{
    private String directoryPath="c:\hello";

    public void doLogic(){
         //never forget to call init() before invocation or build safeguards
         init();
       //some logic goes here
   }

   public void init(){}    

}

public class WeightyLogicService_myAdaptation extends WeightyLogicService {
   @Override
   public void init(){
    directoryPath="c:\my_hello";
   }

}

0

ไม่ตัวแปรคลาส (ใช้ได้กับตัวแปรอินสแตนซ์) จะไม่แสดงคุณสมบัติการเอาชนะใน Java เนื่องจากมีการเรียกใช้ตัวแปรคลาสตามประเภทของการเรียกออบเจ็กต์ เพิ่มอีกหนึ่งคลาส (มนุษย์) ในลำดับชั้นเพื่อให้ชัดเจนยิ่งขึ้น ดังนั้นตอนนี้เรามี

ลูกชายขยายพ่อขยายมนุษย์

ในรหัสด้านล่างเราพยายามทำซ้ำกับอาเรย์ของวัตถุมนุษย์พ่อและลูก แต่มันพิมพ์ค่าของคลาสมนุษย์ในทุกกรณีเนื่องจากประเภทของการโทรเป็นมนุษย์

    class Human
{
    static String me = "human";

    public void printMe()
    {
        System.out.println(me);
    }
}
class Dad extends Human
{
    static String me = "dad";

}

class Son extends Dad
{
    static String me = "son";
}


public class ClassVariables {
    public static void main(String[] abc)   {
        Human[] humans = new Human[3];
        humans[0] = new Human();
        humans[1] = new Dad();
        humans[2] = new Son();
        for(Human human: humans)   {
            System.out.println(human.me);        // prints human for all objects
        }
    }
}

จะพิมพ์

  • เป็นมนุษย์
  • เป็นมนุษย์
  • เป็นมนุษย์

ดังนั้นจึงไม่มีการแทนที่ตัวแปรคลาส

ถ้าเราต้องการเข้าถึงตัวแปรคลาสของวัตถุจริงจากตัวแปรอ้างอิงของคลาสพาเรนต์เราจำเป็นต้องบอกสิ่งนี้กับคอมไพเลอร์อย่างชัดเจนโดยการส่งการอ้างอิงพาเรนต์หลัก (Human object) ไปเป็นประเภทของมัน

    System.out.println(((Dad)humans[1]).me);        // prints dad

    System.out.println(((Son)humans[2]).me);        // prints son

จะพิมพ์

  • พ่อ
  • ลูกชาย

ในส่วนของคำถามนี้: - ตามที่แนะนำแล้วแทนที่เมธอด printMe () ในคลาส Son จากนั้นโทร

Son().printMe();

ตัวแปรคลาสของพ่อ "ฉัน" จะถูกซ่อนไว้เนื่องจากการประกาศที่ใกล้ที่สุด (จากวิธี Son class printme ()) ของ "ฉัน" (ในคลาส Son) จะได้รับความสำคัญกว่า


0

เพียงแค่เรียก super.variable ในตัวสร้างคลาสย่อย

public abstract class Beverage {

int cost;


int getCost() {

    return cost;

}

}`

public class Coffee extends Beverage {


int cost = 10;
Coffee(){
    super.cost = cost;
}


}`

public class Driver {

public static void main(String[] args) {

    Beverage coffee = new Coffee();

    System.out.println(coffee.getCost());

}

}

ผลผลิตคือ 10

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.